From 2e371fcc6cee07b784c91c8f106ef0af5fe8626b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 09:44:57 +0100 Subject: [PATCH 001/396] Setup widget for prototype. --- src/ascii_dialog/main.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/ascii_dialog/main.py diff --git a/src/ascii_dialog/main.py b/src/ascii_dialog/main.py new file mode 100644 index 0000000000..2b2dab4935 --- /dev/null +++ b/src/ascii_dialog/main.py @@ -0,0 +1,16 @@ +from PySide6.QtWidgets import QPushButton, QWidget, QApplication + +class AsciiDialog(QWidget): + def __init__(self): + super().__init__() + + + +if __name__ == "__main__": + app = QApplication([]) + + widget = AsciiDialog() + widget.show() + + + exit(app.exec()) From fba256afced79df16ea590251436cf83a802a983 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 09:45:39 +0100 Subject: [PATCH 002/396] Renamed file. --- src/ascii_dialog/{main.py => dialog.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/ascii_dialog/{main.py => dialog.py} (100%) diff --git a/src/ascii_dialog/main.py b/src/ascii_dialog/dialog.py similarity index 100% rename from src/ascii_dialog/main.py rename to src/ascii_dialog/dialog.py From 3a6b6d133f8c0f1defc4925a357f4d379fda46e0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:12:41 +0100 Subject: [PATCH 003/396] Loading file dialog. --- src/ascii_dialog/dialog.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2b2dab4935..ee6811c26b 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,9 +1,20 @@ -from PySide6.QtWidgets import QPushButton, QWidget, QApplication +from PySide6.QtWidgets import QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget, QApplication +from PySide6.QtCore import Slot class AsciiDialog(QWidget): def __init__(self): super().__init__() + self.load_button = QPushButton("Load File") + self.load_button.clicked.connect(self.load) + + self.layout = QVBoxLayout(self) + self.layout.addWidget(self.load_button) + + @Slot() + def load(self): + filename = QFileDialog.getOpenFileName(self) + print(filename) if __name__ == "__main__": From f5e18e7bd7e7538a0ef217e62c95afbb77543af5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:16:36 +0100 Subject: [PATCH 004/396] Set label to filename. --- src/ascii_dialog/dialog.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ee6811c26b..1b71f49400 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,19 +1,25 @@ from PySide6.QtWidgets import QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot +from os import path class AsciiDialog(QWidget): def __init__(self): super().__init__() + self.filename_label = QLabel("Click the button below to load a file.") + self.load_button = QPushButton("Load File") self.load_button.clicked.connect(self.load) self.layout = QVBoxLayout(self) + + self.layout.addWidget(self.filename_label) self.layout.addWidget(self.load_button) @Slot() def load(self): - filename = QFileDialog.getOpenFileName(self) + filename = QFileDialog.getOpenFileName(self)[0] + self.filename_label.setText(path.basename(filename)) print(filename) From 022e630dcda7c679825d2f879050094344b57582 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:19:32 +0100 Subject: [PATCH 005/396] Load the csv file. --- src/ascii_dialog/dialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 1b71f49400..1c6649c616 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -6,6 +6,8 @@ class AsciiDialog(QWidget): def __init__(self): super().__init__() + self.raw_csv = None + self.filename_label = QLabel("Click the button below to load a file.") self.load_button = QPushButton("Load File") @@ -20,7 +22,10 @@ def __init__(self): def load(self): filename = QFileDialog.getOpenFileName(self)[0] self.filename_label.setText(path.basename(filename)) - print(filename) + + # TODO: Add error handling + with open(filename) as file: + self.raw_csv = file.read() if __name__ == "__main__": From 1ef0ed289e1fdafe7055b0cf9c9f513e174a8a95 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:23:36 +0100 Subject: [PATCH 006/396] Reading in lines will probably be easier. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 1c6649c616..b2c1ae5ae5 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -25,7 +25,7 @@ def load(self): # TODO: Add error handling with open(filename) as file: - self.raw_csv = file.read() + self.raw_csv = file.readlines() if __name__ == "__main__": From 3a16bd20d0c4d9d04cba22f1a417dac65d4e8748 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:36:48 +0100 Subject: [PATCH 007/396] Guess seperator function. --- src/ascii_dialog/guess.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/ascii_dialog/guess.py diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py new file mode 100644 index 0000000000..5fd62842ee --- /dev/null +++ b/src/ascii_dialog/guess.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +def guess_seperator(raw_csv: list[str]) -> str | None: + """Try to guess what the seperator is in raw_csv, and return it. Will return + None if a seperator cannot be guessed, and thus will likely require manual + intervention from the user.""" + + candidates = [",", ";", ":", "\t" " "] + + for sep in candidates: + if all([sep in line for line in raw_csv]): + return sep + + # If none of the candidate appear to be the seperator, then the seperator is + # potentially a number of whitespaces (n). + # + # Try to determine what n is. + + # Maximum whitespace seperation is 15 to stop this from going into an + # infinite loop. This might not be needed later. + for candidate_n in range(1, 15): + candidate_sep = " " * candidate_n + attempted_split = raw_csv[0].split(candidate_sep) + if '' not in attempted_split: + return candidate_sep + + # No seperator found. + return None From 21df57721c1fec82889909f4b1a4785e565987c1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:39:01 +0100 Subject: [PATCH 008/396] Fixed the candidates list. --- src/ascii_dialog/guess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 5fd62842ee..5d19e20539 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -5,7 +5,7 @@ def guess_seperator(raw_csv: list[str]) -> str | None: None if a seperator cannot be guessed, and thus will likely require manual intervention from the user.""" - candidates = [",", ";", ":", "\t" " "] + candidates = [",", ";", ":", "\t"] for sep in candidates: if all([sep in line for line in raw_csv]): From e0bb8efcabdbb48fb850229d81953504cffd009e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:55:09 +0100 Subject: [PATCH 009/396] Added param setting for seperator. --- src/ascii_dialog/dialog.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index b2c1ae5ae5..e31dc1d806 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,6 @@ -from PySide6.QtWidgets import QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot +from guess import guess_seperator from os import path class AsciiDialog(QWidget): @@ -13,10 +14,26 @@ def __init__(self): self.load_button = QPushButton("Load File") self.load_button.clicked.connect(self.load) + # Data parameters + self.sep_layout = QHBoxLayout() + self.sep_label = QLabel('Seperator') + self.sep_entry = QLineEdit() + self.sep_layout.addWidget(self.sep_label) + self.sep_layout.addWidget(self.sep_entry) + self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) self.layout.addWidget(self.load_button) + self.layout.addLayout(self.sep_layout) + + def attempt_guesses(self): + guessed_seperator = guess_seperator(self.raw_csv) + if guessed_seperator == None: + # Seperator couldn't be guessed; just let the user fill that in. + guessed_seperator = '' + + self.sep_entry.setText(guessed_seperator) @Slot() def load(self): @@ -27,6 +44,8 @@ def load(self): with open(filename) as file: self.raw_csv = file.readlines() + self.attempt_guesses() + if __name__ == "__main__": app = QApplication([]) From 9280c1d8a948a8a73442aa6086ef2eab5ce053ae Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:06:43 +0100 Subject: [PATCH 010/396] Add spin box for starting line. --- src/ascii_dialog/dialog.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index e31dc1d806..38a32ba403 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,4 +1,5 @@ -from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayout, QWidget, QApplication +from PySide6.QtGui import QIntValidator +from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from guess import guess_seperator from os import path @@ -15,17 +16,27 @@ def __init__(self): self.load_button.clicked.connect(self.load) # Data parameters + + ## Seperator self.sep_layout = QHBoxLayout() self.sep_label = QLabel('Seperator') self.sep_entry = QLineEdit() self.sep_layout.addWidget(self.sep_label) self.sep_layout.addWidget(self.sep_entry) + ## Starting Line + self.startline_layout = QHBoxLayout() + self.startline_label = QLabel('Starting Line') + self.startline_entry = QSpinBox() + self.startline_layout.addWidget(self.startline_label) + self.startline_layout.addWidget(self.startline_entry) + self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) self.layout.addWidget(self.load_button) self.layout.addLayout(self.sep_layout) + self.layout.addLayout(self.startline_layout) def attempt_guesses(self): guessed_seperator = guess_seperator(self.raw_csv) From e8b1c1eca88620ebc81aa83a15f0893726b1482f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:17:53 +0100 Subject: [PATCH 011/396] Guess column count function. --- src/ascii_dialog/guess.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 5d19e20539..020e3c3c5c 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -26,3 +26,7 @@ def guess_seperator(raw_csv: list[str]) -> str | None: # No seperator found. return None + +def guess_column_count(raw_csv: list[str], sep: str, starting_pos: int) -> int: + """Guess the amount of columns present in the data.""" + return len(raw_csv[starting_pos].split(sep)) From 8247d6af9661393bc77b06e3b7e4078cd65da190 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:21:16 +0100 Subject: [PATCH 012/396] Added col count entry. --- src/ascii_dialog/dialog.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 38a32ba403..2657d67ae4 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -31,12 +31,20 @@ def __init__(self): self.startline_layout.addWidget(self.startline_label) self.startline_layout.addWidget(self.startline_entry) + ## Column Count + self.colcount_layout = QHBoxLayout() + self.colcount_label = QLabel('Number of Columns') + self.colcount_entry = QSpinBox() + self.colcount_layout.addWidget(self.colcount_label) + self.colcount_layout.addWidget(self.colcount_entry) + self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) self.layout.addWidget(self.load_button) self.layout.addLayout(self.sep_layout) self.layout.addLayout(self.startline_layout) + self.layout.addLayout(self.colcount_layout) def attempt_guesses(self): guessed_seperator = guess_seperator(self.raw_csv) From b326eb2f5ec9ccd7d70722c1e6f9a6e1b77c36c6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:39:00 +0100 Subject: [PATCH 013/396] Set col count based on guess. --- src/ascii_dialog/dialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2657d67ae4..e45a8ccc9d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,7 +1,7 @@ from PySide6.QtGui import QIntValidator from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot -from guess import guess_seperator +from guess import guess_column_count, guess_seperator from os import path class AsciiDialog(QWidget): @@ -54,6 +54,9 @@ def attempt_guesses(self): self.sep_entry.setText(guessed_seperator) + guessed_colcount = guess_column_count(self.raw_csv, guessed_seperator, self.startline_entry.value()) + self.colcount_entry.setValue(guessed_colcount) + @Slot() def load(self): filename = QFileDialog.getOpenFileName(self)[0] From 813ec6c614beede88feaa8b969cca4d3fbeb33eb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:39:14 +0100 Subject: [PATCH 014/396] New column editor widget --- src/ascii_dialog/col_editor.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/ascii_dialog/col_editor.py diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py new file mode 100644 index 0000000000..8d9ef2a0cd --- /dev/null +++ b/src/ascii_dialog/col_editor.py @@ -0,0 +1,15 @@ +from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget + + +class ColEditor(QWidget): + def __init__(self, cols: int): + super().__init__() + + self.layout = QHBoxLayout() + self.option_widgets = [] + for _ in range(cols): + # TODO: This is placeholder data. + placeholder_options = ["First", "Second", "Third"] + new_combo_box = QComboBox() + for option in placeholder_options: + new_combo_box.addItem(option) From 4b5e3efac6ca5c0e68e65d5d9b870b125eb8cf98 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:42:47 +0100 Subject: [PATCH 015/396] Use col editor in main widget. --- src/ascii_dialog/dialog.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index e45a8ccc9d..8f6b9aa272 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,6 +1,7 @@ from PySide6.QtGui import QIntValidator from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot +from col_editor import ColEditor from guess import guess_column_count, guess_seperator from os import path @@ -38,6 +39,9 @@ def __init__(self): self.colcount_layout.addWidget(self.colcount_label) self.colcount_layout.addWidget(self.colcount_entry) + ## Column Editor + self.col_editor = ColEditor(4) ## TODO: 4 is just a placeholder. Use value from colcount + self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) @@ -45,6 +49,7 @@ def __init__(self): self.layout.addLayout(self.sep_layout) self.layout.addLayout(self.startline_layout) self.layout.addLayout(self.colcount_layout) + self.layout.addWidget(self.col_editor) def attempt_guesses(self): guessed_seperator = guess_seperator(self.raw_csv) From 9ba43b75a7bf63887781dc16c55044db5f5a6c2b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:44:36 +0100 Subject: [PATCH 016/396] Add to lists. --- src/ascii_dialog/col_editor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 8d9ef2a0cd..6b123be534 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -13,3 +13,5 @@ def __init__(self, cols: int): new_combo_box = QComboBox() for option in placeholder_options: new_combo_box.addItem(option) + self.option_widgets.append(new_combo_box) + self.layout.addWidget(new_combo_box) From d8477506e7ad4d6e392dbdaccb151a42c521e416 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:47:53 +0100 Subject: [PATCH 017/396] Missing self param. --- src/ascii_dialog/col_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 6b123be534..00da71ed52 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -5,7 +5,7 @@ class ColEditor(QWidget): def __init__(self, cols: int): super().__init__() - self.layout = QHBoxLayout() + self.layout = QHBoxLayout(self) self.option_widgets = [] for _ in range(cols): # TODO: This is placeholder data. From 6dc679be56dbb5bdcf4a8551c2e1ecc5d439b380 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:50:35 +0100 Subject: [PATCH 018/396] Can't have zero cols. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8f6b9aa272..bd5a59d89d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -36,6 +36,7 @@ def __init__(self): self.colcount_layout = QHBoxLayout() self.colcount_label = QLabel('Number of Columns') self.colcount_entry = QSpinBox() + self.colcount_entry.setMinimum(1) self.colcount_layout.addWidget(self.colcount_label) self.colcount_layout.addWidget(self.colcount_entry) From 06bf45e69fff24423784d8f98bcdf37e4dbccfe0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:37:22 +0100 Subject: [PATCH 019/396] Allow the amount of cols to change. --- src/ascii_dialog/col_editor.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 00da71ed52..7da9eb549e 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,17 +1,37 @@ from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget +def create_col_combo_box() -> QComboBox: + placeholder_options = ["First", "Second", "Third"] + new_combo_box = QComboBox() + for option in placeholder_options: + new_combo_box.addItem(option) + return new_combo_box class ColEditor(QWidget): def __init__(self, cols: int): super().__init__() + self.cols = cols self.layout = QHBoxLayout(self) self.option_widgets = [] for _ in range(cols): # TODO: This is placeholder data. - placeholder_options = ["First", "Second", "Third"] - new_combo_box = QComboBox() - for option in placeholder_options: - new_combo_box.addItem(option) + new_combo_box = create_col_combo_box() self.option_widgets.append(new_combo_box) self.layout.addWidget(new_combo_box) + + def set_cols(self, new_cols: int): + # Decides whether we need to extend the current set of combo boxes, or + # remove some. + if self.cols < new_cols: + for _ in range(new_cols - self.cols): + new_combo_box = create_col_combo_box() + self.option_widgets.append(new_combo_box) + self.cols = new_cols + if self.cols > new_cols: + excess_cols = new_cols - self.cols + length = len(self.option_widgets) + excess_combo_boxes = self.option_widgets[length - excess_cols:length] + for box in excess_combo_boxes: + self.layout.removeWidget(box) + self.option_widgets = self.option_widgets[0:length - excess_cols] From 376ed010f14ee1df013ed1c90a844447a6dffadf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:47:03 +0100 Subject: [PATCH 020/396] Add cols in response to change on entry. --- src/ascii_dialog/col_editor.py | 1 + src/ascii_dialog/dialog.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 7da9eb549e..aed67852c3 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -27,6 +27,7 @@ def set_cols(self, new_cols: int): for _ in range(new_cols - self.cols): new_combo_box = create_col_combo_box() self.option_widgets.append(new_combo_box) + self.layout.addWidget(new_combo_box) self.cols = new_cols if self.cols > new_cols: excess_cols = new_cols - self.cols diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index bd5a59d89d..d9e6a21954 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -37,11 +37,12 @@ def __init__(self): self.colcount_label = QLabel('Number of Columns') self.colcount_entry = QSpinBox() self.colcount_entry.setMinimum(1) + self.colcount_entry.valueChanged.connect(self.update_colcount) self.colcount_layout.addWidget(self.colcount_label) self.colcount_layout.addWidget(self.colcount_entry) ## Column Editor - self.col_editor = ColEditor(4) ## TODO: 4 is just a placeholder. Use value from colcount + self.col_editor = ColEditor(self.colcount_entry.value()) self.layout = QVBoxLayout(self) @@ -74,6 +75,10 @@ def load(self): self.attempt_guesses() + @Slot() + def update_colcount(self): + self.col_editor.set_cols(self.colcount_entry.value()) + print(self.colcount_entry.value()) if __name__ == "__main__": app = QApplication([]) From 47f4a356b7134ac869de7177ef9f9a1156802ef1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:49:33 +0100 Subject: [PATCH 021/396] Calculation wrong way round. --- src/ascii_dialog/col_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index aed67852c3..07ac3b63c4 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -30,7 +30,7 @@ def set_cols(self, new_cols: int): self.layout.addWidget(new_combo_box) self.cols = new_cols if self.cols > new_cols: - excess_cols = new_cols - self.cols + excess_cols = self.cols - new_cols length = len(self.option_widgets) excess_combo_boxes = self.option_widgets[length - excess_cols:length] for box in excess_combo_boxes: From 70b54f9c68b37ecab43e1cadc4f5f8b93ff5446c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:53:02 +0100 Subject: [PATCH 022/396] Set box's parent to None. --- src/ascii_dialog/col_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 07ac3b63c4..f9b48d74cf 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -35,4 +35,5 @@ def set_cols(self, new_cols: int): excess_combo_boxes = self.option_widgets[length - excess_cols:length] for box in excess_combo_boxes: self.layout.removeWidget(box) + box.setParent(None) self.option_widgets = self.option_widgets[0:length - excess_cols] From 3fd0cfe6f1deecb525444d330fc122b452c48e73 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:56:47 +0100 Subject: [PATCH 023/396] Forgot to update self.cols --- src/ascii_dialog/col_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index f9b48d74cf..80a9395b4f 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -37,3 +37,4 @@ def set_cols(self, new_cols: int): self.layout.removeWidget(box) box.setParent(None) self.option_widgets = self.option_widgets[0:length - excess_cols] + self.cols = new_cols From 04161a3d39285103bd61d9c2f3e7d4908c6f2a36 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 14:10:38 +0100 Subject: [PATCH 024/396] Add a function to get the col names. --- src/ascii_dialog/col_editor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 80a9395b4f..d02d8b3473 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -38,3 +38,6 @@ def set_cols(self, new_cols: int): box.setParent(None) self.option_widgets = self.option_widgets[0:length - excess_cols] self.cols = new_cols + + def col_names(self) -> list[str]: + return [col.value() for col in self.option_widgets] From cd21229bfc91603c814d3f9d2a0f4c337186d6f4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:32:43 +0100 Subject: [PATCH 025/396] Code for setting up the table. --- src/ascii_dialog/dialog.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d9e6a21954..2f98ee332f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,5 @@ from PySide6.QtGui import QIntValidator -from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QTableWidget, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor from guess import guess_column_count, guess_seperator @@ -44,6 +44,11 @@ def __init__(self): ## Column Editor self.col_editor = ColEditor(self.colcount_entry.value()) + ## Data Table + + self.table = QTableWidget() + self.table.show() + self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) @@ -52,6 +57,8 @@ def __init__(self): self.layout.addLayout(self.startline_layout) self.layout.addLayout(self.colcount_layout) self.layout.addWidget(self.col_editor) + self.layout.addWidget(self.table) + def attempt_guesses(self): guessed_seperator = guess_seperator(self.raw_csv) @@ -64,6 +71,29 @@ def attempt_guesses(self): guessed_colcount = guess_column_count(self.raw_csv, guessed_seperator, self.startline_entry.value()) self.colcount_entry.setValue(guessed_colcount) + def fill_table(self): + # At the moment, we're just going to start making the table from where + # the user told us to start. Just trying this for now. We might want to + # draw the full table later. + + # Don't try to fill the table if there's no data. + if self.raw_csv is not None: + return + + starting_pos = self.startline_entry.value() + + self.table.setRowCount(len(self.raw_csv) - starting_pos) + self.table.setColumnCount(self.colcount_entry.value()) + self.table.setHorizontalHeaderLabels(self.col_editor.col_names()) + + # Now fill the table with data + for i, row in enumerate(self.raw_csv[starting_pos::]): + for j, col_value in enumerate(row): + self.table.setItem(i, j, col_value) + + self.table.show() + + @Slot() def load(self): filename = QFileDialog.getOpenFileName(self)[0] From f1c413ae2665bfa993ebbe087ac2e5e8b765f12d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:34:50 +0100 Subject: [PATCH 026/396] Show table on loading data. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2f98ee332f..7e096fe0e6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -104,6 +104,7 @@ def load(self): self.raw_csv = file.readlines() self.attempt_guesses() + self.fill_table() @Slot() def update_colcount(self): From 3e9e8ff7c90593e29f54a59b2bf1909044893aa2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:38:05 +0100 Subject: [PATCH 027/396] Fixed if statement. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 7e096fe0e6..d4d3240e5d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -77,7 +77,7 @@ def fill_table(self): # draw the full table later. # Don't try to fill the table if there's no data. - if self.raw_csv is not None: + if self.raw_csv is None: return starting_pos = self.startline_entry.value() From 5aee359b90f1514f15685d0cd1dff0ef30353dc4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:40:51 +0100 Subject: [PATCH 028/396] Wrong method call. --- src/ascii_dialog/col_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index d02d8b3473..f0c16ae643 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -40,4 +40,4 @@ def set_cols(self, new_cols: int): self.cols = new_cols def col_names(self) -> list[str]: - return [col.value() for col in self.option_widgets] + return [col.currentText() for col in self.option_widgets] From 667dfc0773db8ff1a0edeefda6eae10b802f99c8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:47:48 +0100 Subject: [PATCH 029/396] Forgot to make the items in QT. --- src/ascii_dialog/dialog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d4d3240e5d..092c01bb91 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,5 @@ from PySide6.QtGui import QIntValidator -from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QTableWidget, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor from guess import guess_column_count, guess_seperator @@ -88,8 +88,9 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv[starting_pos::]): - for j, col_value in enumerate(row): - self.table.setItem(i, j, col_value) + row_split = row.split(self.sep_entry.text()) + for j, col_value in enumerate(row_split): + self.table.setItem(i, j, QTableWidgetItem(col_value)) self.table.show() From bfd592aa1d2ca10aa8769a61f47e20446e316b94 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:55:53 +0100 Subject: [PATCH 030/396] Make table readonly. --- src/ascii_dialog/dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 092c01bb91..a93ccb9809 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,3 +1,4 @@ +from PySide6 import QtGui from PySide6.QtGui import QIntValidator from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot @@ -48,6 +49,8 @@ def __init__(self): self.table = QTableWidget() self.table.show() + # Make the table readonly + self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) self.layout = QVBoxLayout(self) From ad72cbbf00c04c717b7878b14b177fc02a998964 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:56:00 +0100 Subject: [PATCH 031/396] Removed old print statement. --- src/ascii_dialog/dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a93ccb9809..9341c195e2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -113,7 +113,6 @@ def load(self): @Slot() def update_colcount(self): self.col_editor.set_cols(self.colcount_entry.value()) - print(self.colcount_entry.value()) if __name__ == "__main__": app = QApplication([]) From e0a3709fb729bcf580c9f67d9837e6048efcfb0c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:02:33 +0100 Subject: [PATCH 032/396] Update table on startpos change. --- src/ascii_dialog/dialog.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9341c195e2..30a3e9130a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -30,6 +30,7 @@ def __init__(self): self.startline_layout = QHBoxLayout() self.startline_label = QLabel('Starting Line') self.startline_entry = QSpinBox() + self.startline_entry.valueChanged.connect(self.update_startpos) self.startline_layout.addWidget(self.startline_label) self.startline_layout.addWidget(self.startline_entry) @@ -114,6 +115,10 @@ def load(self): def update_colcount(self): self.col_editor.set_cols(self.colcount_entry.value()) + @Slot() + def update_startpos(self): + self.fill_table() + if __name__ == "__main__": app = QApplication([]) From 308b8d13659a7da267801a7274f5feba2357b7a6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:23:19 +0100 Subject: [PATCH 033/396] Tried to make the table resize on its own. It didn't work :( --- src/ascii_dialog/dialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 30a3e9130a..a827c6f589 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,6 +1,6 @@ from PySide6 import QtGui from PySide6.QtGui import QIntValidator -from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QAbstractScrollArea, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor from guess import guess_column_count, guess_seperator @@ -52,6 +52,7 @@ def __init__(self): self.table.show() # Make the table readonly self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) + self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) self.layout = QVBoxLayout(self) @@ -97,6 +98,7 @@ def fill_table(self): self.table.setItem(i, j, QTableWidgetItem(col_value)) self.table.show() + self.table.resizeColumnsToContents() @Slot() From d4b1b413326ed4ee72222cd774677dca7e39dd84 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:28:44 +0100 Subject: [PATCH 034/396] Re render table when seperator changes. --- src/ascii_dialog/dialog.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a827c6f589..a9ab4bd2b9 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -23,6 +23,7 @@ def __init__(self): self.sep_layout = QHBoxLayout() self.sep_label = QLabel('Seperator') self.sep_entry = QLineEdit() + self.sep_entry.textChanged.connect(self.update_seperator) self.sep_layout.addWidget(self.sep_label) self.sep_layout.addWidget(self.sep_entry) @@ -121,6 +122,10 @@ def update_colcount(self): def update_startpos(self): self.fill_table() + @Slot() + def update_seperator(self): + self.fill_table() + if __name__ == "__main__": app = QApplication([]) From 936f2bf17d8e82a5bc4d3cdc17710b9c44b0f52d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:31:03 +0100 Subject: [PATCH 035/396] Only show the first 100 rows. --- src/ascii_dialog/dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a9ab4bd2b9..a5bb862cfb 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -6,6 +6,8 @@ from guess import guess_column_count, guess_seperator from os import path +TABLE_MAX_ROWS = 100 + class AsciiDialog(QWidget): def __init__(self): super().__init__() @@ -88,7 +90,7 @@ def fill_table(self): starting_pos = self.startline_entry.value() - self.table.setRowCount(len(self.raw_csv) - starting_pos) + self.table.setRowCount(min(len(self.raw_csv) - starting_pos, TABLE_MAX_ROWS)) self.table.setColumnCount(self.colcount_entry.value()) self.table.setHorizontalHeaderLabels(self.col_editor.col_names()) @@ -97,6 +99,8 @@ def fill_table(self): row_split = row.split(self.sep_entry.text()) for j, col_value in enumerate(row_split): self.table.setItem(i, j, QTableWidgetItem(col_value)) + if i == TABLE_MAX_ROWS: + break self.table.show() self.table.resizeColumnsToContents() From faaefcb39df74e8c255863173d94835ec3a6a800 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 08:10:05 +0100 Subject: [PATCH 036/396] Clear the table before filling it. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a5bb862cfb..c3bafa6d5f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -88,6 +88,8 @@ def fill_table(self): if self.raw_csv is None: return + self.table.clear() + starting_pos = self.startline_entry.value() self.table.setRowCount(min(len(self.raw_csv) - starting_pos, TABLE_MAX_ROWS)) From 9a807d738a11d1bb54c7083fa21f5edb5b4438a5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 08:10:24 +0100 Subject: [PATCH 037/396] Coped over Lucas' dataset types. --- src/ascii_dialog/dataset_types.py | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/ascii_dialog/dataset_types.py diff --git a/src/ascii_dialog/dataset_types.py b/src/ascii_dialog/dataset_types.py new file mode 100644 index 0000000000..c8a362f79f --- /dev/null +++ b/src/ascii_dialog/dataset_types.py @@ -0,0 +1,73 @@ +""" Information used for providing guesses about what text based files contain """ + +from dataclasses import dataclass + +# +# VERY ROUGH DRAFT - FOR PROTOTYPING PURPOSES +# + +@dataclass +class DatasetType: + name: str + required: list[str] + optional: list[str] + expected_orders: list[list[str]] + + +one_dim = DatasetType( + name="1D I vs Q", + required=["Q", "I"], + optional=["dI", "dQ", "shadow"], + expected_orders=[ + ["Q", "I", "dI"], + ["Q", "dQ", "I", "dI"]]) + +two_dim = DatasetType( + name="2D I vs Q", + required=["Qx", "Qy", "I"], + optional=["dQx", "dQy", "dI", "Qz", "shadow"], + expected_orders=[ + ["Qx", "Qy", "I"], + ["Qx", "Qy", "I", "dI"], + ["Qx", "Qy", "dQx", "dQy", "I", "dI"]]) + +sesans = DatasetType( + name="SESANS", + required=["z", "G"], + optional=["stuff", "other stuff", "more stuff"], + expected_orders=[["z", "G"]]) + +dataset_types = {dataset.name for dataset in [one_dim, two_dim, sesans]} + + +# +# Some default units, this is not how they should be represented, some might not be correct +# +# The unit options should only be those compatible with the field +# +default_units = { + "Q": "1/A", + "I": "1/cm", + "Qx": "1/A", + "Qy": "1/A", + "Qz": "1/A", + "dI": "1/A", + "dQ": "1/A", + "dQx": "1/A", + "dQy": "1/A", + "dQz": "1/A", + "z": "A", + "G": "", + "shaddow": "", + "temperature": "K", + "magnetic field": "T" +} + +# +# Other possible fields. Ultimately, these should come out of the metadata structure +# + +metadata_fields = [ + "temperature", + "magnetic field", +] From bd6f810dfe9ff87ebf702b1bd94722ad05ea790d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:01:33 +0100 Subject: [PATCH 038/396] Add a combo box for the datatype. --- src/ascii_dialog/dialog.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c3bafa6d5f..ce82783eff 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,10 +1,11 @@ from PySide6 import QtGui from PySide6.QtGui import QIntValidator -from PySide6.QtWidgets import QAbstractScrollArea, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QAbstractScrollArea, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor from guess import guess_column_count, guess_seperator from os import path +from dataset_types import dataset_types TABLE_MAX_ROWS = 100 @@ -21,6 +22,11 @@ def __init__(self): # Data parameters + ## Dataset type selection + self.dataset_combobox = QComboBox() + for name in dataset_types: + self.dataset_combobox.addItem(name) + ## Seperator self.sep_layout = QHBoxLayout() self.sep_label = QLabel('Seperator') @@ -61,6 +67,7 @@ def __init__(self): self.layout.addWidget(self.filename_label) self.layout.addWidget(self.load_button) + self.layout.addWidget(self.dataset_combobox) self.layout.addLayout(self.sep_layout) self.layout.addLayout(self.startline_layout) self.layout.addLayout(self.colcount_layout) From 6cb2ac8015c41e62b61743759faa5ac42fe5ccde Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:26:09 +0100 Subject: [PATCH 039/396] Show a label for the dataset type. --- src/ascii_dialog/dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ce82783eff..58147aa6bc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -23,9 +23,13 @@ def __init__(self): # Data parameters ## Dataset type selection + self.dataset_layout = QHBoxLayout() + self.dataset_label = QLabel("Dataset Type") self.dataset_combobox = QComboBox() for name in dataset_types: self.dataset_combobox.addItem(name) + self.dataset_layout.addWidget(self.dataset_label) + self.dataset_layout.addWidget(self.dataset_combobox) ## Seperator self.sep_layout = QHBoxLayout() @@ -67,7 +71,7 @@ def __init__(self): self.layout.addWidget(self.filename_label) self.layout.addWidget(self.load_button) - self.layout.addWidget(self.dataset_combobox) + self.layout.addLayout(self.dataset_layout) self.layout.addLayout(self.sep_layout) self.layout.addLayout(self.startline_layout) self.layout.addLayout(self.colcount_layout) From b95e0ce940591ba7fc9d433615e8c09fb64f8ed0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:39:38 +0100 Subject: [PATCH 040/396] Guess columns function. --- src/ascii_dialog/guess.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 020e3c3c5c..145810c9d5 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +from ascii_dialog.dataset_types import DatasetType + + def guess_seperator(raw_csv: list[str]) -> str | None: """Try to guess what the seperator is in raw_csv, and return it. Will return None if a seperator cannot be guessed, and thus will likely require manual @@ -30,3 +33,10 @@ def guess_seperator(raw_csv: list[str]) -> str | None: def guess_column_count(raw_csv: list[str], sep: str, starting_pos: int) -> int: """Guess the amount of columns present in the data.""" return len(raw_csv[starting_pos].split(sep)) + +def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: + # Ideally we want an exact match but if the ordering is bigger than the col + # count then we can accept that as well. + for order_list in dataset_type.expected_orders: + if len(order_list) >= col_count: + return order_list From 1d500432b8fafeea65986be46a81c7409395e3bf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:41:28 +0100 Subject: [PATCH 041/396] Make sure the function always returns a list. --- src/ascii_dialog/guess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 145810c9d5..72515d1aa5 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -40,3 +40,5 @@ def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: for order_list in dataset_type.expected_orders: if len(order_list) >= col_count: return order_list + + return dataset_type.expected_orders[-1] From ea79a61c66ad3438598ce7b4aa22cefc0878e5f9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:44:33 +0100 Subject: [PATCH 042/396] Fixed import. --- src/ascii_dialog/guess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 72515d1aa5..4c2a9fa9b8 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from ascii_dialog.dataset_types import DatasetType +from dataset_types import DatasetType def guess_seperator(raw_csv: list[str]) -> str | None: From 5575290a3f0ec31eb435f4bf28df9336e077316c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 10:23:46 +0100 Subject: [PATCH 043/396] Use checkboxes for selecting seperators. --- src/ascii_dialog/dialog.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 58147aa6bc..6456ce5ac2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,11 +1,12 @@ from PySide6 import QtGui from PySide6.QtGui import QIntValidator -from PySide6.QtWidgets import QAbstractScrollArea, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor from guess import guess_column_count, guess_seperator from os import path from dataset_types import dataset_types +import re TABLE_MAX_ROWS = 100 @@ -15,6 +16,12 @@ def __init__(self): self.raw_csv = None + self.seperators = { + 'Comma': True, + 'Whitespace': True, + 'Tab': True + } + self.filename_label = QLabel("Click the button below to load a file.") self.load_button = QPushButton("Load File") @@ -33,11 +40,16 @@ def __init__(self): ## Seperator self.sep_layout = QHBoxLayout() - self.sep_label = QLabel('Seperator') - self.sep_entry = QLineEdit() - self.sep_entry.textChanged.connect(self.update_seperator) + + self.sep_widgets = [] + self.sep_label = QLabel('Seperators:') self.sep_layout.addWidget(self.sep_label) - self.sep_layout.addWidget(self.sep_entry) + for seperator_name, value in self.seperators.items(): + check_box = QCheckBox(seperator_name) + check_box.setChecked(value) + check_box.clicked.connect(self.seperator_toggle) + self.sep_widgets.append(check_box) + self.sep_layout.addWidget(check_box) ## Starting Line self.startline_layout = QHBoxLayout() @@ -143,6 +155,11 @@ def update_startpos(self): def update_seperator(self): self.fill_table() + @Slot() + def seperator_toggle(self): + check_box = self.sender() + self.seperators[check_box.text()] = check_box.isChecked() + if __name__ == "__main__": app = QApplication([]) From 01ea9ac34998657f7cffa8bd9fd8949fda33705d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:07:36 +0100 Subject: [PATCH 044/396] Function to split with new seperators. --- src/ascii_dialog/dialog.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 6456ce5ac2..80a72721a0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -90,6 +90,22 @@ def __init__(self): self.layout.addWidget(self.col_editor) self.layout.addWidget(self.table) + def splt_line(self, line: str) -> list[str]: + expr = '' + for seperator, isenabled in self.seperators.items(): + if expr != r'': + expr += r'|' + if isenabled: + match seperator: + case 'Comma': + seperator_text = r',' + case 'Whitespace': + seperator_text = r'␣*' + case 'Tab': + seperator_text = r'\t' + expr += seperator_text + + return re.split(expr, line) def attempt_guesses(self): guessed_seperator = guess_seperator(self.raw_csv) From a63e8b9c2c56182392eeb32222390e5563c6e36f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:08:18 +0100 Subject: [PATCH 045/396] Use new split line function. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 80a72721a0..5b650576b0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -137,7 +137,7 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv[starting_pos::]): - row_split = row.split(self.sep_entry.text()) + row_split = self.splt_line(row) for j, col_value in enumerate(row_split): self.table.setItem(i, j, QTableWidgetItem(col_value)) if i == TABLE_MAX_ROWS: From 3ba88f20c9ec21c1efa6996fc2404e78011b2ea3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:16:16 +0100 Subject: [PATCH 046/396] Change guesses with new seperator. --- src/ascii_dialog/dialog.py | 20 ++++++++++++-------- src/ascii_dialog/guess.py | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 5b650576b0..b18da2e420 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -108,14 +108,18 @@ def splt_line(self, line: str) -> list[str]: return re.split(expr, line) def attempt_guesses(self): - guessed_seperator = guess_seperator(self.raw_csv) - if guessed_seperator == None: - # Seperator couldn't be guessed; just let the user fill that in. - guessed_seperator = '' - - self.sep_entry.setText(guessed_seperator) - - guessed_colcount = guess_column_count(self.raw_csv, guessed_seperator, self.startline_entry.value()) + # TODO: We're not guessing seperators anymore (just presuming that they + # are all enabled). Can probably delete this code later. + # + # guessed_seperator = guess_seperator(self.raw_csv) + # if guessed_seperator == None: + # # Seperator couldn't be guessed; just let the user fill that in. + # guessed_seperator = '' + + # self.sep_entry.setText(guessed_seperator) + + guessed_colcount = guess_column_count([self.splt_line(line) for line in self.raw_csv], + self.startline_entry.value()) self.colcount_entry.setValue(guessed_colcount) def fill_table(self): diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 4c2a9fa9b8..e2bf22dc3f 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -30,9 +30,9 @@ def guess_seperator(raw_csv: list[str]) -> str | None: # No seperator found. return None -def guess_column_count(raw_csv: list[str], sep: str, starting_pos: int) -> int: +def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: """Guess the amount of columns present in the data.""" - return len(raw_csv[starting_pos].split(sep)) + return len(split_csv[starting_pos]) def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: # Ideally we want an exact match but if the ordering is bigger than the col From 0dcf738f949380af476dcb20bdbca2900f57f324 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:26:13 +0100 Subject: [PATCH 047/396] + not * for 1 or more. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index b18da2e420..07cbcf99a2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -100,7 +100,7 @@ def splt_line(self, line: str) -> list[str]: case 'Comma': seperator_text = r',' case 'Whitespace': - seperator_text = r'␣*' + seperator_text = r'␣+' case 'Tab': seperator_text = r'\t' expr += seperator_text From 18323fe4d59da9471c4a5f57e1f06fc801aa6d79 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:32:40 +0100 Subject: [PATCH 048/396] Function to guess starting position. --- src/ascii_dialog/guess.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index e2bf22dc3f..ac26c15300 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -42,3 +42,13 @@ def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: return order_list return dataset_type.expected_orders[-1] + +def guess_starting_position(split_csv: list[list[str]]) -> int: + """Try to look for a line where the first item in the row can be converted + to a number. If such a line doesn't existTry to look for a line where the + first item in the row can be converted to a number. If such a line doesn't + exist, then just return 0 as the starting position.""" + for i, row in enumerate(split_csv): + if row[0].replace('.', '').isdigit(): + return i + return 0 From 1f0ebbccdc874f263b1e8e969cdb9bbfd1340f32 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:32:49 +0100 Subject: [PATCH 049/396] Fill table on some events. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 07cbcf99a2..bccbf30d01 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -166,6 +166,7 @@ def load(self): @Slot() def update_colcount(self): self.col_editor.set_cols(self.colcount_entry.value()) + self.fill_table() @Slot() def update_startpos(self): @@ -179,6 +180,7 @@ def update_seperator(self): def seperator_toggle(self): check_box = self.sender() self.seperators[check_box.text()] = check_box.isChecked() + self.fill_table() if __name__ == "__main__": app = QApplication([]) From 3953928931aba0258ca148504bc1ced2b2e1ca5a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:34:51 +0100 Subject: [PATCH 050/396] Use new function for guessing starting pos. --- src/ascii_dialog/dialog.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index bccbf30d01..db991f0117 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -3,7 +3,7 @@ from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor -from guess import guess_column_count, guess_seperator +from guess import guess_column_count, guess_seperator, guess_starting_position from os import path from dataset_types import dataset_types import re @@ -118,9 +118,14 @@ def attempt_guesses(self): # self.sep_entry.setText(guessed_seperator) - guessed_colcount = guess_column_count([self.splt_line(line) for line in self.raw_csv], - self.startline_entry.value()) + split_csv = [self.splt_line(line) for line in self.raw_csv] + + starting_pos = guess_starting_position(split_csv) + + guessed_colcount = guess_column_count(split_csv, + starting_pos) self.colcount_entry.setValue(guessed_colcount) + self.startline_entry.setValue(starting_pos) def fill_table(self): # At the moment, we're just going to start making the table from where From b0ad0779d0cb5d3b44ec440e216623e6cada7659 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:38:22 +0100 Subject: [PATCH 051/396] Fixed typo. --- src/ascii_dialog/dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index db991f0117..059768fce4 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -90,7 +90,7 @@ def __init__(self): self.layout.addWidget(self.col_editor) self.layout.addWidget(self.table) - def splt_line(self, line: str) -> list[str]: + def split_line(self, line: str) -> list[str]: expr = '' for seperator, isenabled in self.seperators.items(): if expr != r'': @@ -118,7 +118,7 @@ def attempt_guesses(self): # self.sep_entry.setText(guessed_seperator) - split_csv = [self.splt_line(line) for line in self.raw_csv] + split_csv = [self.split_line(line) for line in self.raw_csv] starting_pos = guess_starting_position(split_csv) @@ -146,7 +146,7 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv[starting_pos::]): - row_split = self.splt_line(row) + row_split = self.split_line(row) for j, col_value in enumerate(row_split): self.table.setItem(i, j, QTableWidgetItem(col_value)) if i == TABLE_MAX_ROWS: From a2914a52c47326c09d1ac0b19ac4f5b946f54a2a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:46:56 +0100 Subject: [PATCH 052/396] Correct expression. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 059768fce4..0edb3b7944 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -100,7 +100,7 @@ def split_line(self, line: str) -> list[str]: case 'Comma': seperator_text = r',' case 'Whitespace': - seperator_text = r'␣+' + seperator_text = r'\s+' case 'Tab': seperator_text = r'\t' expr += seperator_text From 31ef0f1e50e596253bb50fdf3f08fe3c08a2379b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:50:21 +0100 Subject: [PATCH 053/396] Ignore minus sign as well. --- src/ascii_dialog/guess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index ac26c15300..ae3aeb3a57 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -49,6 +49,6 @@ def guess_starting_position(split_csv: list[list[str]]) -> int: first item in the row can be converted to a number. If such a line doesn't exist, then just return 0 as the starting position.""" for i, row in enumerate(split_csv): - if row[0].replace('.', '').isdigit(): + if row[0].replace('.', '').replace('-', '').isdigit(): return i return 0 From 39f77cc79e2e5ad67c35f546ffbb9b9eec72d5ca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:50:26 +0100 Subject: [PATCH 054/396] Strip before splitting. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 0edb3b7944..bc4490bcbd 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -118,7 +118,7 @@ def attempt_guesses(self): # self.sep_entry.setText(guessed_seperator) - split_csv = [self.split_line(line) for line in self.raw_csv] + split_csv = [self.split_line(line.strip()) for line in self.raw_csv] starting_pos = guess_starting_position(split_csv) From 74d306314c0585b8b0e81699d93ed8ce5904d78e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 12:03:04 +0100 Subject: [PATCH 055/396] Still show hidden items but grey them out. --- src/ascii_dialog/dialog.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index bc4490bcbd..d2d8fbcd56 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,5 @@ from PySide6 import QtGui -from PySide6.QtGui import QIntValidator +from PySide6.QtGui import QColor, QIntValidator from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor @@ -145,10 +145,13 @@ def fill_table(self): self.table.setHorizontalHeaderLabels(self.col_editor.col_names()) # Now fill the table with data - for i, row in enumerate(self.raw_csv[starting_pos::]): + for i, row in enumerate(self.raw_csv): row_split = self.split_line(row) for j, col_value in enumerate(row_split): - self.table.setItem(i, j, QTableWidgetItem(col_value)) + item = QTableWidgetItem(col_value) + if i < starting_pos: + item.setForeground(QColor.fromString('grey')) + self.table.setItem(i, j, item) if i == TABLE_MAX_ROWS: break From 4f7a484a334bea9d3a23d16057f77d13a313f5aa Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:19:43 +0100 Subject: [PATCH 056/396] Get the current dataset type. --- src/ascii_dialog/dialog.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d2d8fbcd56..992ad3a2ba 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -5,7 +5,7 @@ from col_editor import ColEditor from guess import guess_column_count, guess_seperator, guess_starting_position from os import path -from dataset_types import dataset_types +from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans import re TABLE_MAX_ROWS = 100 @@ -158,6 +158,13 @@ def fill_table(self): self.table.show() self.table.resizeColumnsToContents() + def current_dataset_type(self) -> DatasetType: + # TODO: Using linear search but should probably just use a dictionary + # later. + for type in [one_dim, two_dim, sesans]: + if type.name == self.dataset_combobox.currentText(): + return type + return one_dim @Slot() def load(self): From bfc48295342737a40b238acb02ddbf09d1019ef9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:25:49 +0100 Subject: [PATCH 057/396] Use the options for the dataset type. --- src/ascii_dialog/col_editor.py | 18 +++++++++--------- src/ascii_dialog/dialog.py | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index f0c16ae643..d31e36982e 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,22 +1,22 @@ from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget -def create_col_combo_box() -> QComboBox: - placeholder_options = ["First", "Second", "Third"] - new_combo_box = QComboBox() - for option in placeholder_options: - new_combo_box.addItem(option) - return new_combo_box class ColEditor(QWidget): - def __init__(self, cols: int): + def create_col_combo_box(self) -> QComboBox: + new_combo_box = QComboBox() + for option in self.options: + new_combo_box.addItem(option) + return new_combo_box + def __init__(self, cols: int, options: list[str]): super().__init__() self.cols = cols + self.options = options self.layout = QHBoxLayout(self) self.option_widgets = [] for _ in range(cols): # TODO: This is placeholder data. - new_combo_box = create_col_combo_box() + new_combo_box = self.create_col_combo_box() self.option_widgets.append(new_combo_box) self.layout.addWidget(new_combo_box) @@ -25,7 +25,7 @@ def set_cols(self, new_cols: int): # remove some. if self.cols < new_cols: for _ in range(new_cols - self.cols): - new_combo_box = create_col_combo_box() + new_combo_box = self.create_col_combo_box() self.option_widgets.append(new_combo_box) self.layout.addWidget(new_combo_box) self.cols = new_cols diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 992ad3a2ba..dcf999a339 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -69,7 +69,9 @@ def __init__(self): self.colcount_layout.addWidget(self.colcount_entry) ## Column Editor - self.col_editor = ColEditor(self.colcount_entry.value()) + current_dataset_type = self.current_dataset_type() + options = current_dataset_type.required + current_dataset_type.optional + self.col_editor = ColEditor(self.colcount_entry.value(), options) ## Data Table From 3ad48dffb287d56bd11f1e5eb93de6e217ceeed3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:32:52 +0100 Subject: [PATCH 058/396] Method to replace options. --- src/ascii_dialog/col_editor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index d31e36982e..3f66ca4a14 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -41,3 +41,8 @@ def set_cols(self, new_cols: int): def col_names(self) -> list[str]: return [col.currentText() for col in self.option_widgets] + + def replace_options(self, new_options: list[str]) -> None: + for box in self.option_widgets: + box.clear() + box.addItems(new_options) From d52200454d38b5ad760a684767713620e6b8a60f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:47:07 +0100 Subject: [PATCH 059/396] Change options when dataset changes. --- src/ascii_dialog/dialog.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index dcf999a339..459d3448f0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -33,6 +33,7 @@ def __init__(self): self.dataset_layout = QHBoxLayout() self.dataset_label = QLabel("Dataset Type") self.dataset_combobox = QComboBox() + self.dataset_combobox.currentTextChanged.connect(self.change_dataset_type) for name in dataset_types: self.dataset_combobox.addItem(name) self.dataset_layout.addWidget(self.dataset_label) @@ -199,6 +200,11 @@ def seperator_toggle(self): self.seperators[check_box.text()] = check_box.isChecked() self.fill_table() + @Slot() + def change_dataset_type(self): + new_dataset = self.current_dataset_type() + self.col_editor.replace_options(new_dataset.required + new_dataset.optional) + if __name__ == "__main__": app = QApplication([]) From ad3d7160b035473d5d5ed378887dcfc758f5e7c4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:51:24 +0100 Subject: [PATCH 060/396] Automagically set an assumed col order. --- src/ascii_dialog/col_editor.py | 4 ++++ src/ascii_dialog/dialog.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 3f66ca4a14..d345db40fb 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -39,6 +39,10 @@ def set_cols(self, new_cols: int): self.option_widgets = self.option_widgets[0:length - excess_cols] self.cols = new_cols + def set_col_order(self, cols: list[str]): + for i, col_name in enumerate(cols): + self.option_widgets[i].setCurrentText(col_name) + def col_names(self) -> list[str]: return [col.currentText() for col in self.option_widgets] diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 459d3448f0..73ec5499c0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -3,7 +3,7 @@ from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor -from guess import guess_column_count, guess_seperator, guess_starting_position +from guess import guess_column_count, guess_columns, guess_seperator, guess_starting_position from os import path from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans import re @@ -127,6 +127,9 @@ def attempt_guesses(self): guessed_colcount = guess_column_count(split_csv, starting_pos) + + columns = guess_columns(guessed_colcount, self.current_dataset_type()) + self.col_editor.set_col_order(columns) self.colcount_entry.setValue(guessed_colcount) self.startline_entry.setValue(starting_pos) From 80a211cf72a7c1d91798e3c87ef5cbaa917d5876 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:53:15 +0100 Subject: [PATCH 061/396] Ignore index errors. --- src/ascii_dialog/col_editor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index d345db40fb..ba5b5337a5 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -47,6 +47,9 @@ def col_names(self) -> list[str]: return [col.currentText() for col in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: - for box in self.option_widgets: - box.clear() - box.addItems(new_options) + try: + for box in self.option_widgets: + box.clear() + box.addItems(new_options) + except IndexError: + pass # Can ignore because it means we've run out of widgets. From ef2d4d1adfc6739817fcd449f93bf8162ab6e3fa Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:54:24 +0100 Subject: [PATCH 062/396] Whoops exception handling in wrong place. --- src/ascii_dialog/col_editor.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index ba5b5337a5..62269b5ba2 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -40,16 +40,16 @@ def set_cols(self, new_cols: int): self.cols = new_cols def set_col_order(self, cols: list[str]): - for i, col_name in enumerate(cols): - self.option_widgets[i].setCurrentText(col_name) + try: + for i, col_name in enumerate(cols): + self.option_widgets[i].setCurrentText(col_name) + except IndexError: + pass # Can ignore because it means we've run out of widgets. def col_names(self) -> list[str]: return [col.currentText() for col in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: - try: - for box in self.option_widgets: - box.clear() - box.addItems(new_options) - except IndexError: - pass # Can ignore because it means we've run out of widgets. + for box in self.option_widgets: + box.clear() + box.addItems(new_options) From 30755948e314049cdc88c8c585c376b43995ab82 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:35:46 +0100 Subject: [PATCH 063/396] Change when the event handler has happened. Stops it from triggering early. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 73ec5499c0..cc5d7182c7 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -33,7 +33,6 @@ def __init__(self): self.dataset_layout = QHBoxLayout() self.dataset_label = QLabel("Dataset Type") self.dataset_combobox = QComboBox() - self.dataset_combobox.currentTextChanged.connect(self.change_dataset_type) for name in dataset_types: self.dataset_combobox.addItem(name) self.dataset_layout.addWidget(self.dataset_label) @@ -73,6 +72,7 @@ def __init__(self): current_dataset_type = self.current_dataset_type() options = current_dataset_type.required + current_dataset_type.optional self.col_editor = ColEditor(self.colcount_entry.value(), options) + self.dataset_combobox.currentTextChanged.connect(self.change_dataset_type) ## Data Table From 1307eec95f4842555a23b66416a0259c047c675d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:36:22 +0100 Subject: [PATCH 064/396] Update columns. --- src/ascii_dialog/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index cc5d7182c7..1fda58e2f5 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -208,6 +208,10 @@ def change_dataset_type(self): new_dataset = self.current_dataset_type() self.col_editor.replace_options(new_dataset.required + new_dataset.optional) + # Update columns as they'll be different now. + columns = guess_columns(self.colcount_entry.value(), self.current_dataset_type()) + self.col_editor.set_col_order(columns) + if __name__ == "__main__": app = QApplication([]) From d92a870f6a43c9012f8e38f3fa62ad10569e0aeb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:37:32 +0100 Subject: [PATCH 065/396] Fixed bug when user doesn't select a file. --- src/ascii_dialog/dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 1fda58e2f5..778c25473c 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -174,7 +174,11 @@ def current_dataset_type(self) -> DatasetType: @Slot() def load(self): - filename = QFileDialog.getOpenFileName(self)[0] + result = QFileDialog.getOpenFileName(self) + # Happens when the user cancels without selecting a file. + if result[1] == '': + return + filename = result[1] self.filename_label.setText(path.basename(filename)) # TODO: Add error handling From 35757f0ca632e6488d31351ddfbe6683ac7f92d8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:38:33 +0100 Subject: [PATCH 066/396] Filename wrong part of the result. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 778c25473c..cdba34e9ca 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -178,7 +178,7 @@ def load(self): # Happens when the user cancels without selecting a file. if result[1] == '': return - filename = result[1] + filename = result[0] self.filename_label.setText(path.basename(filename)) # TODO: Add error handling From e911bc25457f2d1d6f031e21f0be419ac9d021e2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 16:37:58 +0100 Subject: [PATCH 067/396] Comment is more descriptive. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index cdba34e9ca..c128f88ee8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -175,7 +175,8 @@ def current_dataset_type(self) -> DatasetType: @Slot() def load(self): result = QFileDialog.getOpenFileName(self) - # Happens when the user cancels without selecting a file. + # Happens when the user cancels without selecting a file. There isn't a + # file to load in this case. if result[1] == '': return filename = result[0] From 4b04b132760137acd335c0ab7b2e15deb15d1d40 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 09:30:14 +0100 Subject: [PATCH 068/396] Fixed options updates. --- src/ascii_dialog/col_editor.py | 1 + src/ascii_dialog/dialog.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 62269b5ba2..850b3bc3fb 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -50,6 +50,7 @@ def col_names(self) -> list[str]: return [col.currentText() for col in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: + self.options = new_options for box in self.option_widgets: box.clear() box.addItems(new_options) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c128f88ee8..199fa64fe9 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -127,6 +127,7 @@ def attempt_guesses(self): guessed_colcount = guess_column_count(split_csv, starting_pos) + self.col_editor.set_cols(guessed_colcount) columns = guess_columns(guessed_colcount, self.current_dataset_type()) self.col_editor.set_col_order(columns) From 2af64d420c9039b03c89c7189b24fa175508f0a9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 10:27:35 +0100 Subject: [PATCH 069/396] Set editable to true for all boxes. --- src/ascii_dialog/col_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 850b3bc3fb..f338b90c34 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -6,6 +6,7 @@ def create_col_combo_box(self) -> QComboBox: new_combo_box = QComboBox() for option in self.options: new_combo_box.addItem(option) + new_combo_box.setEditable(True) return new_combo_box def __init__(self, cols: int, options: list[str]): super().__init__() From 3b9068b12d5041623e46576dcfaef5da8e4243ef Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 10:27:45 +0100 Subject: [PATCH 070/396] Removed todo comment. --- src/ascii_dialog/col_editor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index f338b90c34..68036c2173 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -16,7 +16,6 @@ def __init__(self, cols: int, options: list[str]): self.layout = QHBoxLayout(self) self.option_widgets = [] for _ in range(cols): - # TODO: This is placeholder data. new_combo_box = self.create_col_combo_box() self.option_widgets.append(new_combo_box) self.layout.addWidget(new_combo_box) From 745e8fba7a26d55c7212fe1fa0def8291d3a1960 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 11:55:05 +0100 Subject: [PATCH 071/396] Validator for the columns. --- src/ascii_dialog/col_editor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 68036c2173..145ec1b91c 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,3 +1,4 @@ +from PySide6.QtGui import QRegularExpressionValidator from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget @@ -7,6 +8,8 @@ def create_col_combo_box(self) -> QComboBox: for option in self.options: new_combo_box.addItem(option) new_combo_box.setEditable(True) + validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") + new_combo_box.setValidator(validator) return new_combo_box def __init__(self, cols: int, options: list[str]): super().__init__() From db9d44fd3fed825c32ff827c9e9e1692563d4173 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 10:44:03 +0100 Subject: [PATCH 072/396] Get the unit widgit to show up next to the col. --- src/ascii_dialog/col_editor.py | 39 ++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 145ec1b91c..3b0c90ebda 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,5 +1,6 @@ from PySide6.QtGui import QRegularExpressionValidator from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget +from dataset_types import default_units class ColEditor(QWidget): @@ -11,6 +12,20 @@ def create_col_combo_box(self) -> QComboBox: validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") new_combo_box.setValidator(validator) return new_combo_box + + def create_unit_combo_box(self, selected_option: str) -> QComboBox: + new_combo_box = QComboBox() + default_unit = default_units[selected_option] + new_combo_box.addItem(default_unit) + return new_combo_box + + def create_col_unit_box(self) -> tuple[QComboBox, QComboBox]: + new_col_combo_box = self.create_col_combo_box() + new_unit_combo_box = self.create_unit_combo_box(new_col_combo_box.currentText()) + # self.option_widgets.append(new_col_combo_box) + return new_col_combo_box, new_unit_combo_box + + def __init__(self, cols: int, options: list[str]): super().__init__() @@ -19,26 +34,32 @@ def __init__(self, cols: int, options: list[str]): self.layout = QHBoxLayout(self) self.option_widgets = [] for _ in range(cols): - new_combo_box = self.create_col_combo_box() - self.option_widgets.append(new_combo_box) - self.layout.addWidget(new_combo_box) + new_col_combo_box, new_unit_combo_box = self.create_col_unit_box() + self.layout.addWidget(new_col_combo_box) + self.layout.addWidget(new_unit_combo_box) def set_cols(self, new_cols: int): # Decides whether we need to extend the current set of combo boxes, or # remove some. if self.cols < new_cols: for _ in range(new_cols - self.cols): - new_combo_box = self.create_col_combo_box() - self.option_widgets.append(new_combo_box) - self.layout.addWidget(new_combo_box) + # new_combo_box = self.create_col_combo_box() + # self.option_widgets.append(new_combo_box) + # self.layout.addWidget(new_combo_box) + col_box, unit_box = self.create_col_unit_box() + self.layout.addWidget(col_box) + self.layout.addWidget(unit_box) + self.option_widgets.append((col_box, unit_box)) + self.cols = new_cols if self.cols > new_cols: excess_cols = self.cols - new_cols length = len(self.option_widgets) excess_combo_boxes = self.option_widgets[length - excess_cols:length] - for box in excess_combo_boxes: - self.layout.removeWidget(box) - box.setParent(None) + for boxes in excess_combo_boxes: + for box in boxes: + self.layout.removeWidget(box) + box.setParent(None) self.option_widgets = self.option_widgets[0:length - excess_cols] self.cols = new_cols From b1c07143d780307fe6cad89077162de1ff7e9e20 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 10:51:27 +0100 Subject: [PATCH 073/396] Changed replacing options. --- src/ascii_dialog/col_editor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 3b0c90ebda..6da46a9931 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -75,6 +75,9 @@ def col_names(self) -> list[str]: def replace_options(self, new_options: list[str]) -> None: self.options = new_options - for box in self.option_widgets: - box.clear() - box.addItems(new_options) + for col_box, unit_box in self.option_widgets: + col_box.clear() + col_box.addItems(new_options) + new_unit = default_units[col_box.currentText()] + unit_box.clear() + unit_box.addItem(new_unit) From 2d68063befc1e0669d3d9f66952a0358e8b32648 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 10:59:15 +0100 Subject: [PATCH 074/396] Fix set_col_order. --- src/ascii_dialog/col_editor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 6da46a9931..b0c00db87b 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -66,7 +66,10 @@ def set_cols(self, new_cols: int): def set_col_order(self, cols: list[str]): try: for i, col_name in enumerate(cols): - self.option_widgets[i].setCurrentText(col_name) + self.option_widgets[i][0].setCurrentText(col_name) + new_unit = default_units[col_name] + self.option_widgets[i][1].clear() + self.option_widgets[i][1].addItem(new_unit) except IndexError: pass # Can ignore because it means we've run out of widgets. From 72b8141af3cda1fc6b2eadaa04123cfa5daf7685 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 11:05:41 +0100 Subject: [PATCH 075/396] Fixerd col_names function. --- src/ascii_dialog/col_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index b0c00db87b..787eb9a715 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -74,7 +74,7 @@ def set_col_order(self, cols: list[str]): pass # Can ignore because it means we've run out of widgets. def col_names(self) -> list[str]: - return [col.currentText() for col in self.option_widgets] + return [col[0].currentText() for col in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: self.options = new_options From ff5e293410a588c88b59c97c459f0c63a604274c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 11:49:22 +0100 Subject: [PATCH 076/396] Created the ColumnUnit widgit. --- src/ascii_dialog/column_unit.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/ascii_dialog/column_unit.py diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py new file mode 100644 index 0000000000..fcd078760b --- /dev/null +++ b/src/ascii_dialog/column_unit.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from PySide6.QtWidgets import QComboBox, QWidget +from PySide6.QtGui import QRegularExpressionValidator +from dataset_types import default_units + + +class ColumnUnit(QWidget): + def create_col_combo_box(self, options) -> QComboBox: + new_combo_box = QComboBox() + for option in options: + new_combo_box.addItem(option) + new_combo_box.setEditable(True) + validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") + new_combo_box.setValidator(validator) + return new_combo_box + + def create_unit_combo_box(self, selected_option: str) -> QComboBox: + new_combo_box = QComboBox() + default_unit = default_units[selected_option] + new_combo_box.addItem(default_unit) + return new_combo_box + + def __init__(self, options) -> None: + super().__init__() + self.col_widget = self.create_col_combo_box(options) + self.unit_widget = self.create_unit_combo_box(self.col_widget.currentText()) From 4f82ff4393069a14654b05b2b3da242180efd60c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:13:18 +0100 Subject: [PATCH 077/396] Function for replace options. --- src/ascii_dialog/column_unit.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index fcd078760b..e435caf182 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -21,6 +21,13 @@ def create_unit_combo_box(self, selected_option: str) -> QComboBox: new_combo_box.addItem(default_unit) return new_combo_box + def replace_options(self, new_options): + self.col_widget.clear() + self.col_widget.addItems(new_options) + new_unit = default_units[self.col_widget.currentText()] + self.unit_widget.clear() + self.unit_widget.addItem(new_unit) + def __init__(self, options) -> None: super().__init__() self.col_widget = self.create_col_combo_box(options) From 49d8218589b826499eee36c03ceabb952bbc8d1d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:22:06 +0100 Subject: [PATCH 078/396] Use the new widget. --- src/ascii_dialog/col_editor.py | 80 +++++++++++++++++---------------- src/ascii_dialog/column_unit.py | 7 +++ 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 787eb9a715..b7771e918c 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,29 +1,35 @@ from PySide6.QtGui import QRegularExpressionValidator from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget +from PySide6.QtCore import Slot +from ascii_dialog.column_unit import ColumnUnit from dataset_types import default_units class ColEditor(QWidget): - def create_col_combo_box(self) -> QComboBox: - new_combo_box = QComboBox() - for option in self.options: - new_combo_box.addItem(option) - new_combo_box.setEditable(True) - validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") - new_combo_box.setValidator(validator) - return new_combo_box + # def create_col_combo_box(self) -> QComboBox: + # new_combo_box = QComboBox() + # for option in self.options: + # new_combo_box.addItem(option) + # new_combo_box.setEditable(True) + # validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") + # new_combo_box.setValidator(validator) + # return new_combo_box - def create_unit_combo_box(self, selected_option: str) -> QComboBox: - new_combo_box = QComboBox() - default_unit = default_units[selected_option] - new_combo_box.addItem(default_unit) - return new_combo_box + # def create_unit_combo_box(self, selected_option: str) -> QComboBox: + # new_combo_box = QComboBox() + # default_unit = default_units[selected_option] + # new_combo_box.addItem(default_unit) + # return new_combo_box - def create_col_unit_box(self) -> tuple[QComboBox, QComboBox]: - new_col_combo_box = self.create_col_combo_box() - new_unit_combo_box = self.create_unit_combo_box(new_col_combo_box.currentText()) - # self.option_widgets.append(new_col_combo_box) - return new_col_combo_box, new_unit_combo_box + # def create_col_unit_box(self) -> tuple[QComboBox, QComboBox]: + # new_col_combo_box = self.create_col_combo_box() + # new_unit_combo_box = self.create_unit_combo_box(new_col_combo_box.currentText()) + # # self.option_widgets.append(new_col_combo_box) + # return new_col_combo_box, new_unit_combo_box + + @Slot() + def on_column_update(self): + pass def __init__(self, cols: int, options: list[str]): @@ -34,9 +40,9 @@ def __init__(self, cols: int, options: list[str]): self.layout = QHBoxLayout(self) self.option_widgets = [] for _ in range(cols): - new_col_combo_box, new_unit_combo_box = self.create_col_unit_box() - self.layout.addWidget(new_col_combo_box) - self.layout.addWidget(new_unit_combo_box) + new_widget = ColumnUnit(self.options) + self.layout.addWidget(new_widget) + self.option_widgets.append(new_widget) def set_cols(self, new_cols: int): # Decides whether we need to extend the current set of combo boxes, or @@ -46,30 +52,25 @@ def set_cols(self, new_cols: int): # new_combo_box = self.create_col_combo_box() # self.option_widgets.append(new_combo_box) # self.layout.addWidget(new_combo_box) - col_box, unit_box = self.create_col_unit_box() - self.layout.addWidget(col_box) - self.layout.addWidget(unit_box) - self.option_widgets.append((col_box, unit_box)) + new_widget = ColumnUnit(self.options) + self.layout.addWidget(new_widget) + self.option_widgets.append(new_widget) self.cols = new_cols if self.cols > new_cols: excess_cols = self.cols - new_cols length = len(self.option_widgets) excess_combo_boxes = self.option_widgets[length - excess_cols:length] - for boxes in excess_combo_boxes: - for box in boxes: - self.layout.removeWidget(box) - box.setParent(None) + for box in excess_combo_boxes: + self.layout.removeWidget(box) + box.setParent(None) self.option_widgets = self.option_widgets[0:length - excess_cols] self.cols = new_cols def set_col_order(self, cols: list[str]): try: for i, col_name in enumerate(cols): - self.option_widgets[i][0].setCurrentText(col_name) - new_unit = default_units[col_name] - self.option_widgets[i][1].clear() - self.option_widgets[i][1].addItem(new_unit) + self.option_widgets[i].set_current_column(col_name) except IndexError: pass # Can ignore because it means we've run out of widgets. @@ -78,9 +79,10 @@ def col_names(self) -> list[str]: def replace_options(self, new_options: list[str]) -> None: self.options = new_options - for col_box, unit_box in self.option_widgets: - col_box.clear() - col_box.addItems(new_options) - new_unit = default_units[col_box.currentText()] - unit_box.clear() - unit_box.addItem(new_unit) + for widget in self.option_widgets: + # col_box.clear() + # col_box.addItems(new_options) + # new_unit = default_units[col_box.currentText()] + # unit_box.clear() + # unit_box.addItem(new_unit) + widget.replace_options(new_options) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index e435caf182..a5d46ba529 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -28,6 +28,13 @@ def replace_options(self, new_options): self.unit_widget.clear() self.unit_widget.addItem(new_unit) + def set_current_column(self, new_column_value: str): + self.col_widget.setCurrentText(new_column_value) + new_unit = default_units[new_column_value] + self.unit_widget.clear() + self.unit_widget.addItem(new_unit) + + def __init__(self, options) -> None: super().__init__() self.col_widget = self.create_col_combo_box(options) From 59e3d1f5870091134ad8688b8610a65f69e9d848 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:26:08 +0100 Subject: [PATCH 079/396] Fixed import. --- src/ascii_dialog/col_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index b7771e918c..2f9a758fc1 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,7 +1,7 @@ from PySide6.QtGui import QRegularExpressionValidator from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtCore import Slot -from ascii_dialog.column_unit import ColumnUnit +from column_unit import ColumnUnit from dataset_types import default_units From 8530b06dc7006601315cc2bd19fb04f1400c8f40 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:26:16 +0100 Subject: [PATCH 080/396] Use property for getting current column. --- src/ascii_dialog/col_editor.py | 2 +- src/ascii_dialog/column_unit.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 2f9a758fc1..d5006f9bd5 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -75,7 +75,7 @@ def set_col_order(self, cols: list[str]): pass # Can ignore because it means we've run out of widgets. def col_names(self) -> list[str]: - return [col[0].currentText() for col in self.option_widgets] + return [widget.current_column() for widget in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: self.options = new_options diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index a5d46ba529..a4c6cf2af8 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -34,6 +34,10 @@ def set_current_column(self, new_column_value: str): self.unit_widget.clear() self.unit_widget.addItem(new_unit) + @property + def current_column(self): + self.col_widget.currentText() + def __init__(self, options) -> None: super().__init__() From d06879cfb69cda6cec674da6cfeea08c40217b9c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:30:50 +0100 Subject: [PATCH 081/396] Don't need brackets for property. --- src/ascii_dialog/col_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index d5006f9bd5..279edb8826 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -75,7 +75,7 @@ def set_col_order(self, cols: list[str]): pass # Can ignore because it means we've run out of widgets. def col_names(self) -> list[str]: - return [widget.current_column() for widget in self.option_widgets] + return [widget.current_column for widget in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: self.options = new_options From 1595c8df261ee94129996188b5e0811f2b76ddda Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:31:33 +0100 Subject: [PATCH 082/396] Forgot to add to layout. --- src/ascii_dialog/column_unit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index a4c6cf2af8..240cf140e0 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from PySide6.QtWidgets import QComboBox, QWidget +from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtGui import QRegularExpressionValidator from dataset_types import default_units @@ -43,3 +43,6 @@ def __init__(self, options) -> None: super().__init__() self.col_widget = self.create_col_combo_box(options) self.unit_widget = self.create_unit_combo_box(self.col_widget.currentText()) + self.layout = QHBoxLayout(self) + self.layout.addWidget(self.col_widget) + self.layout.addWidget(self.unit_widget) From 02a0a0b95f162079e72e34e4ea9e6f6e18ac4cc6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:40:07 +0100 Subject: [PATCH 083/396] Change units when we change the column. --- src/ascii_dialog/column_unit.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 240cf140e0..a7396d218b 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from PySide6.QtCore import Slot from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtGui import QRegularExpressionValidator from dataset_types import default_units @@ -13,6 +14,7 @@ def create_col_combo_box(self, options) -> QComboBox: new_combo_box.setEditable(True) validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") new_combo_box.setValidator(validator) + new_combo_box.currentTextChanged.connect(self.on_option_change) return new_combo_box def create_unit_combo_box(self, selected_option: str) -> QComboBox: @@ -34,6 +36,14 @@ def set_current_column(self, new_column_value: str): self.unit_widget.clear() self.unit_widget.addItem(new_unit) + @Slot() + def on_option_change(self): + # Need to update units. + new_option = self.col_widget.currentText() + new_unit = default_units[new_option] + self.unit_widget.clear() + self.unit_widget.addItem(new_unit) + @property def current_column(self): self.col_widget.currentText() From 4bc1cfa1e1319a1e6415dc768e14a0ad7de0b837 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:43:00 +0100 Subject: [PATCH 084/396] Theoeretically don't need this anymore. --- src/ascii_dialog/column_unit.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index a7396d218b..e9fa050aa5 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -26,9 +26,6 @@ def create_unit_combo_box(self, selected_option: str) -> QComboBox: def replace_options(self, new_options): self.col_widget.clear() self.col_widget.addItems(new_options) - new_unit = default_units[self.col_widget.currentText()] - self.unit_widget.clear() - self.unit_widget.addItem(new_unit) def set_current_column(self, new_column_value: str): self.col_widget.setCurrentText(new_column_value) From 6da9dddb3508db3f58e52335c3116e16c836de9f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:46:12 +0100 Subject: [PATCH 085/396] Ignore if empty string. --- src/ascii_dialog/column_unit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index e9fa050aa5..13695ec931 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -36,7 +36,12 @@ def set_current_column(self, new_column_value: str): @Slot() def on_option_change(self): # Need to update units. + # + # If the new option is empty string, its probably because the current + # options have been removed. Can safely ignore this. new_option = self.col_widget.currentText() + if new_option == '': + return new_unit = default_units[new_option] self.unit_widget.clear() self.unit_widget.addItem(new_unit) From 6f094053a3105f3a7a3e46234a2c85fae28b899a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 14:10:10 +0100 Subject: [PATCH 086/396] Don't resize to contents. --- src/ascii_dialog/dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 199fa64fe9..5c97fc2879 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -163,7 +163,6 @@ def fill_table(self): break self.table.show() - self.table.resizeColumnsToContents() def current_dataset_type(self) -> DatasetType: # TODO: Using linear search but should probably just use a dictionary From f11e4fd3decac9f0760465063fa5044623620e2e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 14:51:01 +0100 Subject: [PATCH 087/396] Missing return statement. --- src/ascii_dialog/column_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 13695ec931..ef9fc0faae 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -48,7 +48,7 @@ def on_option_change(self): @property def current_column(self): - self.col_widget.currentText() + return self.col_widget.currentText() def __init__(self, options) -> None: From 019492f0bb044a13e668f0fc6b092e9043eb5ca4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 15:42:33 +0100 Subject: [PATCH 088/396] Column headers on table automatically update. --- src/ascii_dialog/col_editor.py | 7 +++++-- src/ascii_dialog/column_unit.py | 6 ++++-- src/ascii_dialog/dialog.py | 5 +++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 279edb8826..f83d5747a6 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,6 +1,6 @@ from PySide6.QtGui import QRegularExpressionValidator from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget -from PySide6.QtCore import Slot +from PySide6.QtCore import Slot, Signal from column_unit import ColumnUnit from dataset_types import default_units @@ -27,9 +27,11 @@ class ColEditor(QWidget): # # self.option_widgets.append(new_col_combo_box) # return new_col_combo_box, new_unit_combo_box + column_changed = Signal() + @Slot() def on_column_update(self): - pass + self.column_changed.emit() def __init__(self, cols: int, options: list[str]): @@ -53,6 +55,7 @@ def set_cols(self, new_cols: int): # self.option_widgets.append(new_combo_box) # self.layout.addWidget(new_combo_box) new_widget = ColumnUnit(self.options) + new_widget.column_changed.connect(self.on_column_update) self.layout.addWidget(new_widget) self.option_widgets.append(new_widget) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index ef9fc0faae..a22b8eb06f 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 -from PySide6.QtCore import Slot +from PySide6.QtCore import Signal, Slot from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtGui import QRegularExpressionValidator from dataset_types import default_units - class ColumnUnit(QWidget): + column_changed = Signal() + def create_col_combo_box(self, options) -> QComboBox: new_combo_box = QComboBox() for option in options: @@ -45,6 +46,7 @@ def on_option_change(self): new_unit = default_units[new_option] self.unit_widget.clear() self.unit_widget.addItem(new_unit) + self.column_changed.emit() @property def current_column(self): diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 5c97fc2879..52c93ef338 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -131,6 +131,7 @@ def attempt_guesses(self): columns = guess_columns(guessed_colcount, self.current_dataset_type()) self.col_editor.set_col_order(columns) + self.col_editor.column_changed.connect(self.update_column) self.colcount_entry.setValue(guessed_colcount) self.startline_entry.setValue(starting_pos) @@ -202,6 +203,10 @@ def update_startpos(self): def update_seperator(self): self.fill_table() + @Slot() + def update_column(self): + self.fill_table() + @Slot() def seperator_toggle(self): check_box = self.sender() From 14e7f257519205add9de1895e9c7122d49b0cf93 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 15:50:43 +0100 Subject: [PATCH 089/396] Determine if we have all the required columns. --- src/ascii_dialog/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 52c93ef338..98e00c24b7 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -222,6 +222,10 @@ def change_dataset_type(self): columns = guess_columns(self.colcount_entry.value(), self.current_dataset_type()) self.col_editor.set_col_order(columns) + def is_required_met(self): + dataset = self.current_dataset_type() + return [col in dataset.required for col in self.col_editor.col_names()] + if __name__ == "__main__": app = QApplication([]) From 52dd1b3927b0b97d3c15b1cf94fa17724516f7f6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 15:56:19 +0100 Subject: [PATCH 090/396] Added a label for warnings. --- src/ascii_dialog/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 98e00c24b7..0e77382af6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -82,6 +82,9 @@ def __init__(self): self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) + # Warning Label + self.warning_label = QLabel('All is good') + self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) @@ -92,6 +95,7 @@ def __init__(self): self.layout.addLayout(self.colcount_layout) self.layout.addWidget(self.col_editor) self.layout.addWidget(self.table) + self.layout.addWidget(self.warning_label) def split_line(self, line: str) -> list[str]: expr = '' From 53830febf3142cba5b37b5009147b5ece894bbaf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 16:05:17 +0100 Subject: [PATCH 091/396] Try creating an error message. --- src/ascii_dialog/dialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 0e77382af6..aeffb522d8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,5 @@ from PySide6 import QtGui -from PySide6.QtGui import QColor, QIntValidator +from PySide6.QtGui import QColor, QIntValidator, QPalette from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from col_editor import ColEditor @@ -230,6 +230,11 @@ def is_required_met(self): dataset = self.current_dataset_type() return [col in dataset.required for col in self.col_editor.col_names()] + def set_required_error(self): + self.warning_label.setText('Required columns are missing.') + palette = self.warning_label.palette() + palette.setColor(QPalette.ColorRole.WindowText, QColor.fromString('Red')) + if __name__ == "__main__": app = QApplication([]) From 60e1011dfb2192a6487087087994919eefb96f20 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 07:43:23 +0100 Subject: [PATCH 092/396] Set the error message if required cols are missing --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index aeffb522d8..cb2aa4d8b1 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -210,6 +210,8 @@ def update_seperator(self): @Slot() def update_column(self): self.fill_table() + if not self.is_required_met(): + self.set_required_error() @Slot() def seperator_toggle(self): From 70252dc1b3cefc8b1af876ef3ba3a16598c96a22 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 07:54:21 +0100 Subject: [PATCH 093/396] Fixed required label (a bit). --- src/ascii_dialog/dialog.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index cb2aa4d8b1..9890f7e359 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -230,12 +230,11 @@ def change_dataset_type(self): def is_required_met(self): dataset = self.current_dataset_type() - return [col in dataset.required for col in self.col_editor.col_names()] + return all([col in dataset.required for col in self.col_editor.col_names()]) def set_required_error(self): self.warning_label.setText('Required columns are missing.') - palette = self.warning_label.palette() - palette.setColor(QPalette.ColorRole.WindowText, QColor.fromString('Red')) + self.warning_label.setStyleSheet("QLabel { color: red}") if __name__ == "__main__": app = QApplication([]) From 197e28d0b8ebfe156c842659a343fc7b7c2ae8f0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:19:32 +0100 Subject: [PATCH 094/396] Improved the error message shown. --- src/ascii_dialog/dialog.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9890f7e359..3aa076bef3 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -210,8 +210,9 @@ def update_seperator(self): @Slot() def update_column(self): self.fill_table() - if not self.is_required_met(): - self.set_required_error() + required_missing = self.required_missing() + if len(required_missing) != 0: + self.set_required_error(required_missing) @Slot() def seperator_toggle(self): @@ -228,12 +229,13 @@ def change_dataset_type(self): columns = guess_columns(self.colcount_entry.value(), self.current_dataset_type()) self.col_editor.set_col_order(columns) - def is_required_met(self): + def required_missing(self) -> list[str]: dataset = self.current_dataset_type() - return all([col in dataset.required for col in self.col_editor.col_names()]) + missing_columns = [col for col in self.col_editor.col_names() if col not in dataset.required] + return missing_columns - def set_required_error(self): - self.warning_label.setText('Required columns are missing.') + def set_required_error(self, required_missing): + self.warning_label.setText(f'The following columns are missing: {required_missing}') self.warning_label.setStyleSheet("QLabel { color: red}") if __name__ == "__main__": From 77008e4eb0214c8081e40d8cac9b4c855f8bcd0b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:23:42 +0100 Subject: [PATCH 095/396] Wrong way round. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 3aa076bef3..9d9db939d9 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -231,7 +231,7 @@ def change_dataset_type(self): def required_missing(self) -> list[str]: dataset = self.current_dataset_type() - missing_columns = [col for col in self.col_editor.col_names() if col not in dataset.required] + missing_columns = [col for col in dataset.required if col not in self.col_editor.col_names()] return missing_columns def set_required_error(self, required_missing): From 660ff9494ac028c655b1a7012c8a91c08b3e480c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:40:39 +0100 Subject: [PATCH 096/396] Add a try statement to avoid key error. --- src/ascii_dialog/column_unit.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index a22b8eb06f..3b8cad2872 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -34,6 +34,7 @@ def set_current_column(self, new_column_value: str): self.unit_widget.clear() self.unit_widget.addItem(new_unit) + @Slot() def on_option_change(self): # Need to update units. @@ -43,9 +44,15 @@ def on_option_change(self): new_option = self.col_widget.currentText() if new_option == '': return - new_unit = default_units[new_option] - self.unit_widget.clear() - self.unit_widget.addItem(new_unit) + try: + new_unit = default_units[new_option] + self.unit_widget.clear() + self.unit_widget.addItem(new_unit) + except KeyError: + # Means the units for this column aren't known. This shouldn't be + # the case in the real version so for now we'll just clear the unit + # widget. + self.unit_widget.clear() self.column_changed.emit() @property From 90d5932fd9d33514cd45a5778d6fad0d201811b2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:55:57 +0100 Subject: [PATCH 097/396] Hook the event here as well. --- src/ascii_dialog/col_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index f83d5747a6..e19770fb3b 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -43,6 +43,7 @@ def __init__(self, cols: int, options: list[str]): self.option_widgets = [] for _ in range(cols): new_widget = ColumnUnit(self.options) + new_widget.column_changed.connect(self.on_column_update) self.layout.addWidget(new_widget) self.option_widgets.append(new_widget) From a55c269e4d1ec9ee29d1cf41e58175c28fd61e80 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 10:38:17 +0100 Subject: [PATCH 098/396] Changed around events to fix bugs. --- src/ascii_dialog/column_unit.py | 2 +- src/ascii_dialog/dialog.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 3b8cad2872..cd20387af4 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -41,6 +41,7 @@ def on_option_change(self): # # If the new option is empty string, its probably because the current # options have been removed. Can safely ignore this. + self.column_changed.emit() new_option = self.col_widget.currentText() if new_option == '': return @@ -53,7 +54,6 @@ def on_option_change(self): # the case in the real version so for now we'll just clear the unit # widget. self.unit_widget.clear() - self.column_changed.emit() @property def current_column(self): diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9d9db939d9..f6e71f80ab 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -73,6 +73,7 @@ def __init__(self): options = current_dataset_type.required + current_dataset_type.optional self.col_editor = ColEditor(self.colcount_entry.value(), options) self.dataset_combobox.currentTextChanged.connect(self.change_dataset_type) + self.col_editor.column_changed.connect(self.update_column) ## Data Table @@ -135,7 +136,6 @@ def attempt_guesses(self): columns = guess_columns(guessed_colcount, self.current_dataset_type()) self.col_editor.set_col_order(columns) - self.col_editor.column_changed.connect(self.update_column) self.colcount_entry.setValue(guessed_colcount) self.startline_entry.setValue(starting_pos) From ca7daa42b74caacd4cef27d6447ac385fd25b7e2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:04:05 +0100 Subject: [PATCH 099/396] Create a separate warning label class. --- src/ascii_dialog/warning_label.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/ascii_dialog/warning_label.py diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py new file mode 100644 index 0000000000..8fe1b79a9d --- /dev/null +++ b/src/ascii_dialog/warning_label.py @@ -0,0 +1,17 @@ + + +from PySide6.QtWidgets import QLabel + + +class WarningLabel(QLabel): + def update(self, missing_columns): + if len(missing_columns) == 0: + pass # TODO: Set normal message + else: + self.setText(f'The following columns are missing: {missing_columns}') + self.setStyleSheet("QLabel { color: red}") + + + def __init__(self, initial_missing_columns): + super().__init__() + self.update(initial_missing_columns) From 30d94e2c6297bba4d9bc79b8c44a04d5b1cb8e91 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:05:35 +0100 Subject: [PATCH 100/396] If no missing, all is fine. --- src/ascii_dialog/warning_label.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 8fe1b79a9d..bb14667671 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -6,7 +6,8 @@ class WarningLabel(QLabel): def update(self, missing_columns): if len(missing_columns) == 0: - pass # TODO: Set normal message + self.setText('All is fine') # TODO: Probably want to find a more appropriate message. + self.setStyleSheet('') else: self.setText(f'The following columns are missing: {missing_columns}') self.setStyleSheet("QLabel { color: red}") From 6758b3ecb2475a48390b896d4fecda1dc46f24b3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:20:20 +0100 Subject: [PATCH 101/396] Use the new warning label. --- src/ascii_dialog/dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f6e71f80ab..3457243cf3 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -2,6 +2,7 @@ from PySide6.QtGui import QColor, QIntValidator, QPalette from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot +from warning_label import WarningLabel from col_editor import ColEditor from guess import guess_column_count, guess_columns, guess_seperator, guess_starting_position from os import path @@ -84,7 +85,7 @@ def __init__(self): self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) # Warning Label - self.warning_label = QLabel('All is good') + self.warning_label = WarningLabel(self.required_missing()) self.layout = QVBoxLayout(self) @@ -211,8 +212,7 @@ def update_seperator(self): def update_column(self): self.fill_table() required_missing = self.required_missing() - if len(required_missing) != 0: - self.set_required_error(required_missing) + self.warning_label.update(required_missing) @Slot() def seperator_toggle(self): From ce428adf65620bff45f92fc688f2b8168a466cf5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:26:10 +0100 Subject: [PATCH 102/396] Add functionality for duplicate columns. --- src/ascii_dialog/warning_label.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index bb14667671..5025c9d4f4 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -4,15 +4,18 @@ class WarningLabel(QLabel): - def update(self, missing_columns): + def update(self, missing_columns, duplicate_classes): if len(missing_columns) == 0: self.setText('All is fine') # TODO: Probably want to find a more appropriate message. self.setStyleSheet('') + elif len(duplicate_classes) > 0: + self.setText(f'There are duplicate columns.') + self.setStyleSheet("QLabel { color: red}") else: self.setText(f'The following columns are missing: {missing_columns}') self.setStyleSheet("QLabel { color: red}") - def __init__(self, initial_missing_columns): + def __init__(self, initial_missing_columns, initial_duplicate_classes): super().__init__() - self.update(initial_missing_columns) + self.update(initial_missing_columns, initial_duplicate_classes) From 394656ded73c7b3678e9a8a3192b8a74020e72fb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:35:08 +0100 Subject: [PATCH 103/396] Function for getting duplicate columns. --- src/ascii_dialog/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 3457243cf3..a8391abda9 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -234,6 +234,10 @@ def required_missing(self) -> list[str]: missing_columns = [col for col in dataset.required if col not in self.col_editor.col_names()] return missing_columns + def duplicate_columns(self) -> list[str]: + col_names = self.col_editor.col_names() + [col for col in col_names if col_names.count(col) > 1] + def set_required_error(self, required_missing): self.warning_label.setText(f'The following columns are missing: {required_missing}') self.warning_label.setStyleSheet("QLabel { color: red}") From e78ec75f7470b60ac79159fd13d309ae92b718f4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:36:58 +0100 Subject: [PATCH 104/396] Pass duplicates in. --- src/ascii_dialog/dialog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a8391abda9..ce9e5e76b8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -85,7 +85,7 @@ def __init__(self): self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) # Warning Label - self.warning_label = WarningLabel(self.required_missing()) + self.warning_label = WarningLabel(self.required_missing(), self.duplicate_columns()) self.layout = QVBoxLayout(self) @@ -212,7 +212,8 @@ def update_seperator(self): def update_column(self): self.fill_table() required_missing = self.required_missing() - self.warning_label.update(required_missing) + duplicates = self.duplicate_columns() + self.warning_label.update(required_missing, duplicates) @Slot() def seperator_toggle(self): @@ -236,7 +237,7 @@ def required_missing(self) -> list[str]: def duplicate_columns(self) -> list[str]: col_names = self.col_editor.col_names() - [col for col in col_names if col_names.count(col) > 1] + return [col for col in col_names if col_names.count(col) > 1] def set_required_error(self, required_missing): self.warning_label.setText(f'The following columns are missing: {required_missing}') From f64778c1b70f158e12b0370ae39d68542a74190e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:59:20 +0100 Subject: [PATCH 105/396] Columns have changed when the number changes. --- src/ascii_dialog/col_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index e19770fb3b..9f67d3e8f1 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -70,6 +70,7 @@ def set_cols(self, new_cols: int): box.setParent(None) self.option_widgets = self.option_widgets[0:length - excess_cols] self.cols = new_cols + self.column_changed.emit() def set_col_order(self, cols: list[str]): try: From a8b59cc95759915a2a37431db56ae9284ed32ed1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 12:01:31 +0100 Subject: [PATCH 106/396] Changed orderings to fix bug. --- src/ascii_dialog/warning_label.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 5025c9d4f4..b64e5d9e2c 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -5,15 +5,15 @@ class WarningLabel(QLabel): def update(self, missing_columns, duplicate_classes): - if len(missing_columns) == 0: - self.setText('All is fine') # TODO: Probably want to find a more appropriate message. - self.setStyleSheet('') + if len(missing_columns) != 0: + self.setText(f'The following columns are missing: {missing_columns}') + self.setStyleSheet("QLabel { color: red}") elif len(duplicate_classes) > 0: self.setText(f'There are duplicate columns.') self.setStyleSheet("QLabel { color: red}") else: - self.setText(f'The following columns are missing: {missing_columns}') - self.setStyleSheet("QLabel { color: red}") + self.setText('All is fine') # TODO: Probably want to find a more appropriate message. + self.setStyleSheet('') def __init__(self, initial_missing_columns, initial_duplicate_classes): From e77f0e708f8dd81cd3edf9b5e4f0f6622b83b995 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 12:17:09 +0100 Subject: [PATCH 107/396] Abstract the font setting. --- src/ascii_dialog/warning_label.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index b64e5d9e2c..caafabc3f9 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -4,17 +4,22 @@ class WarningLabel(QLabel): + def set_font_red(self): + self.setStyleSheet("QLabel { color: red}") + + def set_font_normal(self): + self.setStyleSheet('') + def update(self, missing_columns, duplicate_classes): if len(missing_columns) != 0: self.setText(f'The following columns are missing: {missing_columns}') - self.setStyleSheet("QLabel { color: red}") + self.set_font_red() elif len(duplicate_classes) > 0: self.setText(f'There are duplicate columns.') - self.setStyleSheet("QLabel { color: red}") + self.set_font_red() else: self.setText('All is fine') # TODO: Probably want to find a more appropriate message. - self.setStyleSheet('') - + self.set_font_normal() def __init__(self, initial_missing_columns, initial_duplicate_classes): super().__init__() From 785e3dea7500b05f2ae1c39a6f306fff7fa81c0b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 09:42:41 +0100 Subject: [PATCH 108/396] Create a class for the row status. --- src/ascii_dialog/row_status_widget.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/ascii_dialog/row_status_widget.py diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py new file mode 100644 index 0000000000..9fed287d3c --- /dev/null +++ b/src/ascii_dialog/row_status_widget.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +from PySide6.QtWidgets import QCheckBox + + +class RowStatusWidget(QCheckBox): + def __init__(self): + super().__init__() + self.setTristate(True) From 02ce670394d9325526ff5ebdb135612a41ab7266 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 09:54:35 +0100 Subject: [PATCH 109/396] Update label based on state. --- src/ascii_dialog/row_status_widget.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 9fed287d3c..6ce77ba09a 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -1,9 +1,20 @@ #!/usr/bin/env python3 +from PySide6.QtGui import Qt from PySide6.QtWidgets import QCheckBox class RowStatusWidget(QCheckBox): + def update_label(self): + current_status = self.checkState() + match current_status: + case Qt.CheckState.Unchecked: + self.setText('Not included') + case Qt.CheckState.PartiallyChecked: + self.setText('Included as metadata') + case Qt.CheckState.Checked: + self.setText('Included as data') + def __init__(self): super().__init__() self.setTristate(True) From 726c42badad9737783979285884d1217f76b954d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:28:03 +0100 Subject: [PATCH 110/396] Update label when needed. --- src/ascii_dialog/row_status_widget.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 6ce77ba09a..231135f130 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from PySide6.QtCore import Slot from PySide6.QtGui import Qt from PySide6.QtWidgets import QCheckBox @@ -15,6 +16,12 @@ def update_label(self): case Qt.CheckState.Checked: self.setText('Included as data') + @Slot() + def on_state_change(self): + self.update_label() + def __init__(self): super().__init__() self.setTristate(True) + self.update_label() + self.stateChanged.connect(self.on_state_change) From 027678f69de06d0288b73ecaf17a36df8c5009ed Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:28:47 +0100 Subject: [PATCH 111/396] Show row status on table. --- src/ascii_dialog/dialog.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ce9e5e76b8..8e339c6cff 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -4,6 +4,7 @@ from PySide6.QtCore import Slot from warning_label import WarningLabel from col_editor import ColEditor +from row_status_widget import RowStatusWidget from guess import guess_column_count, guess_columns, guess_seperator, guess_starting_position from os import path from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans @@ -154,17 +155,19 @@ def fill_table(self): starting_pos = self.startline_entry.value() self.table.setRowCount(min(len(self.raw_csv) - starting_pos, TABLE_MAX_ROWS)) - self.table.setColumnCount(self.colcount_entry.value()) - self.table.setHorizontalHeaderLabels(self.col_editor.col_names()) + self.table.setColumnCount(self.colcount_entry.value() + 1) + self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) # Now fill the table with data for i, row in enumerate(self.raw_csv): + row_status = RowStatusWidget() + self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) for j, col_value in enumerate(row_split): item = QTableWidgetItem(col_value) if i < starting_pos: item.setForeground(QColor.fromString('grey')) - self.table.setItem(i, j, item) + self.table.setItem(i, j + 1, item) if i == TABLE_MAX_ROWS: break From bb9db7011211e81eba9189491de9d4ea1634b687 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:35:52 +0100 Subject: [PATCH 112/396] Don't include extra columns. --- src/ascii_dialog/dialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8e339c6cff..ea27a17a1d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -153,9 +153,10 @@ def fill_table(self): self.table.clear() starting_pos = self.startline_entry.value() + col_count = self.colcount_entry.value() self.table.setRowCount(min(len(self.raw_csv) - starting_pos, TABLE_MAX_ROWS)) - self.table.setColumnCount(self.colcount_entry.value() + 1) + self.table.setColumnCount(col_count + 1) self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) # Now fill the table with data @@ -164,6 +165,8 @@ def fill_table(self): self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) for j, col_value in enumerate(row_split): + if j >= col_count: + continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) if i < starting_pos: item.setForeground(QColor.fromString('grey')) From d5f72326f390e52ed35081c7b9f98deaa01ff9f5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:52:57 +0100 Subject: [PATCH 113/396] Maintain a list of row status widgets. --- src/ascii_dialog/dialog.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ea27a17a1d..85e03bf6cd 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -100,6 +100,8 @@ def __init__(self): self.layout.addWidget(self.table) self.layout.addWidget(self.warning_label) + self.row_status_widgets: list[RowStatusWidget] = [] + def split_line(self, line: str) -> list[str]: expr = '' for seperator, isenabled in self.seperators.items(): @@ -161,7 +163,11 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv): - row_status = RowStatusWidget() + if i <= len(self.row_status_widgets): + row_status = self.row_status_widgets[0] + else: + row_status = RowStatusWidget() + self.row_status_widgets.append(row_status) self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) for j, col_value in enumerate(row_split): From d897c056d1846a64ff5452da849a929a581fa3f9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:02:44 +0100 Subject: [PATCH 114/396] Fixed comparison. --- src/ascii_dialog/dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 85e03bf6cd..c4dba37a1a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -163,8 +163,8 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv): - if i <= len(self.row_status_widgets): - row_status = self.row_status_widgets[0] + if i < len(self.row_status_widgets): + row_status = self.row_status_widgets[i] else: row_status = RowStatusWidget() self.row_status_widgets.append(row_status) From 478db267aea6323b14f3273cf65a5c2903ede3f3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:29:33 +0100 Subject: [PATCH 115/396] Keep state outside of widgets. They get deleted when the table is cleared. This was leading to segfaults. --- src/ascii_dialog/dialog.py | 11 ++++++----- src/ascii_dialog/row_status_widget.py | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c4dba37a1a..17dfe45040 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,5 @@ from PySide6 import QtGui -from PySide6.QtGui import QColor, QIntValidator, QPalette +from PySide6.QtGui import QColor, QIntValidator, QPalette, Qt from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from warning_label import WarningLabel @@ -100,7 +100,7 @@ def __init__(self): self.layout.addWidget(self.table) self.layout.addWidget(self.warning_label) - self.row_status_widgets: list[RowStatusWidget] = [] + self.row_status_widgets: list[Qt.CheckState] = [] def split_line(self, line: str) -> list[str]: expr = '' @@ -164,10 +164,11 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv): if i < len(self.row_status_widgets): - row_status = self.row_status_widgets[i] + initial_state = self.row_status_widgets[i] else: - row_status = RowStatusWidget() - self.row_status_widgets.append(row_status) + # TODO: Change default + initial_state = Qt.CheckState.Checked + row_status = RowStatusWidget(initial_state, i) self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) for j, col_value in enumerate(row_split): diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 231135f130..97c8f7d688 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -20,8 +20,10 @@ def update_label(self): def on_state_change(self): self.update_label() - def __init__(self): + def __init__(self, initial_value: Qt.CheckState, row: int): super().__init__() + self.row = row self.setTristate(True) + self.setCheckState(initial_value) self.update_label() self.stateChanged.connect(self.on_state_change) From 3f1e74ecef017242f8ccfeda44a6a8420a5be3c6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:38:45 +0100 Subject: [PATCH 116/396] Set the initial state based on guessed starting ppos. --- src/ascii_dialog/dialog.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 17dfe45040..b336a8dcfc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -132,16 +132,22 @@ def attempt_guesses(self): split_csv = [self.split_line(line.strip()) for line in self.raw_csv] - starting_pos = guess_starting_position(split_csv) + self.initial_starting_pos = guess_starting_position(split_csv) guessed_colcount = guess_column_count(split_csv, - starting_pos) + self.initial_starting_pos) self.col_editor.set_cols(guessed_colcount) columns = guess_columns(guessed_colcount, self.current_dataset_type()) self.col_editor.set_col_order(columns) self.colcount_entry.setValue(guessed_colcount) - self.startline_entry.setValue(starting_pos) + self.startline_entry.setValue(self.initial_starting_pos) + + def guess_row_status(self, row) -> Qt.CheckState: + if row < self.initial_starting_pos: + return Qt.CheckState.PartiallyChecked + else: + return Qt.CheckState.Checked def fill_table(self): # At the moment, we're just going to start making the table from where @@ -166,8 +172,7 @@ def fill_table(self): if i < len(self.row_status_widgets): initial_state = self.row_status_widgets[i] else: - # TODO: Change default - initial_state = Qt.CheckState.Checked + initial_state = self.guess_row_status(i) row_status = RowStatusWidget(initial_state, i) self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) From 38eae3f17126b3579710fdeb3f9704276fdf7a7d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:54:06 +0100 Subject: [PATCH 117/396] Created a status changed signal. --- src/ascii_dialog/row_status_widget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 97c8f7d688..a001b9bcd2 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 -from PySide6.QtCore import Slot +from PySide6.QtCore import Signal, Slot from PySide6.QtGui import Qt from PySide6.QtWidgets import QCheckBox class RowStatusWidget(QCheckBox): + status_changed = Signal(int) def update_label(self): current_status = self.checkState() match current_status: @@ -19,6 +20,7 @@ def update_label(self): @Slot() def on_state_change(self): self.update_label() + self.status_changed.emit() def __init__(self, initial_value: Qt.CheckState, row: int): super().__init__() From 1b3d1bb3cc5ac19ac041b73218c5ac5590633192 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:00:49 +0100 Subject: [PATCH 118/396] Hook into row status change event. --- src/ascii_dialog/dialog.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index b336a8dcfc..ebeece1bd4 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -174,6 +174,7 @@ def fill_table(self): else: initial_state = self.guess_row_status(i) row_status = RowStatusWidget(initial_state, i) + row_status.status_changed.connect(self.update_row_status) self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) for j, col_value in enumerate(row_split): @@ -248,6 +249,10 @@ def change_dataset_type(self): columns = guess_columns(self.colcount_entry.value(), self.current_dataset_type()) self.col_editor.set_col_order(columns) + @Slot() + def update_row_status(self, row): + self.row_status_widgets[row] = self.table.cellWidget(row, 0).checkState() + def required_missing(self) -> list[str]: dataset = self.current_dataset_type() missing_columns = [col for col in dataset.required if col not in self.col_editor.col_names()] From 1eddbf439579891b685b2d1566bbf5fa7129be6a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:01:05 +0100 Subject: [PATCH 119/396] Pass in the row. --- src/ascii_dialog/row_status_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index a001b9bcd2..ff8ca6f04d 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -20,7 +20,7 @@ def update_label(self): @Slot() def on_state_change(self): self.update_label() - self.status_changed.emit() + self.status_changed.emit(self.row) def __init__(self, initial_value: Qt.CheckState, row: int): super().__init__() From e58b8c464789302c98f7ff82c206fe9d26c710a4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:04:15 +0100 Subject: [PATCH 120/396] Forgot to add to the list. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ebeece1bd4..a0a84ba970 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -173,6 +173,7 @@ def fill_table(self): initial_state = self.row_status_widgets[i] else: initial_state = self.guess_row_status(i) + self.row_status_widgets.append(initial_state) row_status = RowStatusWidget(initial_state, i) row_status.status_changed.connect(self.update_row_status) self.table.setCellWidget(i, 0, row_status) From 3c09829ebd3a477046e4696c4f63dcf4f020950d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:06:37 +0100 Subject: [PATCH 121/396] Stretch to full width. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a0a84ba970..fe6aff9b0f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -166,6 +166,7 @@ def fill_table(self): self.table.setRowCount(min(len(self.raw_csv) - starting_pos, TABLE_MAX_ROWS)) self.table.setColumnCount(col_count + 1) self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # Now fill the table with data for i, row in enumerate(self.raw_csv): From 30b182685f33544cc20864d2e97423d28c74a878 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:14:03 +0100 Subject: [PATCH 122/396] Change font and colour depending on check state. --- src/ascii_dialog/dialog.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index fe6aff9b0f..270782070d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -183,8 +183,14 @@ def fill_table(self): if j >= col_count: continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) - if i < starting_pos: + if initial_state == Qt.CheckState.PartiallyChecked: item.setForeground(QColor.fromString('grey')) + elif initial_state == Qt.CheckState.Unchecked: + item.setForeground(QColor.fromString('grey')) + item_font = item.font() + item_font.setStrikeOut(True) + item.setFont(item_font) + item.font().setStrikeOut(True) self.table.setItem(i, j + 1, item) if i == TABLE_MAX_ROWS: break From 3096f7511dc066dde1f4dd0237a92e7c4eec9073 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:15:09 +0100 Subject: [PATCH 123/396] Redraw the table when row status has changed. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 270782070d..44a734dd4d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -260,6 +260,7 @@ def change_dataset_type(self): @Slot() def update_row_status(self, row): self.row_status_widgets[row] = self.table.cellWidget(row, 0).checkState() + self.fill_table() def required_missing(self) -> list[str]: dataset = self.current_dataset_type() From 07e8b090928d1700c76dc3c887b498b46e41899d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:00:51 +0100 Subject: [PATCH 124/396] Set item props elsewhere so they can update. --- src/ascii_dialog/dialog.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 44a734dd4d..f67558bbd6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -183,19 +183,21 @@ def fill_table(self): if j >= col_count: continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) - if initial_state == Qt.CheckState.PartiallyChecked: - item.setForeground(QColor.fromString('grey')) - elif initial_state == Qt.CheckState.Unchecked: - item.setForeground(QColor.fromString('grey')) - item_font = item.font() - item_font.setStrikeOut(True) - item.setFont(item_font) - item.font().setStrikeOut(True) + # if initial_state == Qt.CheckState.PartiallyChecked: + # item.setForeground(QColor.fromString('grey')) + # elif initial_state == Qt.CheckState.Unchecked: + # item.setForeground(QColor.fromString('grey')) + # item_font = item.font() + # item_font.setStrikeOut(True) + # item.setFont(item_font) + # item.font().setStrikeOut(True) self.table.setItem(i, j + 1, item) if i == TABLE_MAX_ROWS: break self.table.show() + for row in range(self.table.rowCount()): + self.set_row_typesetting(row, self.row_status_widgets[row]) def current_dataset_type(self) -> DatasetType: # TODO: Using linear search but should probably just use a dictionary @@ -205,6 +207,21 @@ def current_dataset_type(self) -> DatasetType: return type return one_dim + def set_row_typesetting(self, row, row_status: Qt.CheckState): + for column in range(1, self.table.columnCount() + 1): + item = self.table.item(row, column) + if item is None: + continue + match row_status: + case Qt.CheckState.PartiallyChecked: + item.setForeground(QColor.fromString('grey')) + case Qt.CheckState.Unchecked: + item.setForeground(QColor.fromString('grey')) + item_font = item.font() + item_font.setStrikeOut(True) + item.setFont(item_font) + + @Slot() def load(self): result = QFileDialog.getOpenFileName(self) @@ -259,8 +276,9 @@ def change_dataset_type(self): @Slot() def update_row_status(self, row): - self.row_status_widgets[row] = self.table.cellWidget(row, 0).checkState() - self.fill_table() + new_status = self.table.cellWidget(row, 0).checkState() + self.row_status_widgets[row] = new_status + self.set_row_typesetting(row, new_status) def required_missing(self) -> list[str]: dataset = self.current_dataset_type() From 3a988bdd3e90fd9f61ec72af75cad1d4722fd8ae Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:31:34 +0100 Subject: [PATCH 125/396] Set the font so it'll go back. --- src/ascii_dialog/dialog.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f67558bbd6..355efc48ae 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -212,14 +212,18 @@ def set_row_typesetting(self, row, row_status: Qt.CheckState): item = self.table.item(row, column) if item is None: continue + item_font = item.font() match row_status: case Qt.CheckState.PartiallyChecked: item.setForeground(QColor.fromString('grey')) + item_font.setStrikeOut(False) case Qt.CheckState.Unchecked: item.setForeground(QColor.fromString('grey')) - item_font = item.font() item_font.setStrikeOut(True) - item.setFont(item_font) + case _: + item.setForeground(QColor.fromString('black')) + item_font.setStrikeOut(False) + item.setFont(item_font) @Slot() From 8155c64cfb1e884ccd1203d4291a92ade6e3ac92 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:31:54 +0100 Subject: [PATCH 126/396] Removed commented out code. --- src/ascii_dialog/dialog.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 355efc48ae..2d8065e5eb 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -183,14 +183,6 @@ def fill_table(self): if j >= col_count: continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) - # if initial_state == Qt.CheckState.PartiallyChecked: - # item.setForeground(QColor.fromString('grey')) - # elif initial_state == Qt.CheckState.Unchecked: - # item.setForeground(QColor.fromString('grey')) - # item_font = item.font() - # item_font.setStrikeOut(True) - # item.setFont(item_font) - # item.font().setStrikeOut(True) self.table.setItem(i, j + 1, item) if i == TABLE_MAX_ROWS: break From 4fe627aaf16b6a6d6d3f74a6d67c77b0d96dca16 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:55:36 +0100 Subject: [PATCH 127/396] Don't do tristate anymore. --- src/ascii_dialog/row_status_widget.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index ff8ca6f04d..3e88e7823a 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -8,14 +8,11 @@ class RowStatusWidget(QCheckBox): status_changed = Signal(int) def update_label(self): - current_status = self.checkState() - match current_status: - case Qt.CheckState.Unchecked: - self.setText('Not included') - case Qt.CheckState.PartiallyChecked: - self.setText('Included as metadata') - case Qt.CheckState.Checked: - self.setText('Included as data') + if self.isChecked(): + self.setText('Included') + else: + self.setText('Not Included') + @Slot() def on_state_change(self): @@ -25,7 +22,6 @@ def on_state_change(self): def __init__(self, initial_value: Qt.CheckState, row: int): super().__init__() self.row = row - self.setTristate(True) self.setCheckState(initial_value) self.update_label() self.stateChanged.connect(self.on_state_change) From e91368e43b175d0ee9fed65b8e56d65efb946d98 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:03:35 +0100 Subject: [PATCH 128/396] Make checkbox binary. --- src/ascii_dialog/dialog.py | 34 ++++++++++++--------------- src/ascii_dialog/row_status_widget.py | 4 ++-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2d8065e5eb..ecd24d666f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -100,7 +100,7 @@ def __init__(self): self.layout.addWidget(self.table) self.layout.addWidget(self.warning_label) - self.row_status_widgets: list[Qt.CheckState] = [] + self.rows_is_included: list[bool] = [] def split_line(self, line: str) -> list[str]: expr = '' @@ -170,11 +170,11 @@ def fill_table(self): # Now fill the table with data for i, row in enumerate(self.raw_csv): - if i < len(self.row_status_widgets): - initial_state = self.row_status_widgets[i] + if i < len(self.rows_is_included): + initial_state = self.rows_is_included[i] else: - initial_state = self.guess_row_status(i) - self.row_status_widgets.append(initial_state) + initial_state = True + self.rows_is_included.append(initial_state) row_status = RowStatusWidget(initial_state, i) row_status.status_changed.connect(self.update_row_status) self.table.setCellWidget(i, 0, row_status) @@ -189,7 +189,7 @@ def fill_table(self): self.table.show() for row in range(self.table.rowCount()): - self.set_row_typesetting(row, self.row_status_widgets[row]) + self.set_row_typesetting(row, self.rows_is_included[row]) def current_dataset_type(self) -> DatasetType: # TODO: Using linear search but should probably just use a dictionary @@ -199,22 +199,18 @@ def current_dataset_type(self) -> DatasetType: return type return one_dim - def set_row_typesetting(self, row, row_status: Qt.CheckState): + def set_row_typesetting(self, row, row_status: bool): for column in range(1, self.table.columnCount() + 1): item = self.table.item(row, column) if item is None: continue item_font = item.font() - match row_status: - case Qt.CheckState.PartiallyChecked: - item.setForeground(QColor.fromString('grey')) - item_font.setStrikeOut(False) - case Qt.CheckState.Unchecked: - item.setForeground(QColor.fromString('grey')) - item_font.setStrikeOut(True) - case _: - item.setForeground(QColor.fromString('black')) - item_font.setStrikeOut(False) + if row_status: + item.setForeground(QColor.fromString('black')) + item_font.setStrikeOut(False) + else: + item.setForeground(QColor.fromString('grey')) + item_font.setStrikeOut(True) item.setFont(item_font) @@ -272,8 +268,8 @@ def change_dataset_type(self): @Slot() def update_row_status(self, row): - new_status = self.table.cellWidget(row, 0).checkState() - self.row_status_widgets[row] = new_status + new_status = self.table.cellWidget(row, 0).isChecked() + self.rows_is_included[row] = new_status self.set_row_typesetting(row, new_status) def required_missing(self) -> list[str]: diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 3e88e7823a..dfab832e2f 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -19,9 +19,9 @@ def on_state_change(self): self.update_label() self.status_changed.emit(self.row) - def __init__(self, initial_value: Qt.CheckState, row: int): + def __init__(self, initial_value: bool, row: int): super().__init__() self.row = row - self.setCheckState(initial_value) + self.setChecked(initial_value) self.update_label() self.stateChanged.connect(self.on_state_change) From 03ad6ad2bba1e878d691a487b3a9bad46efe915f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:13:00 +0100 Subject: [PATCH 129/396] Bring back the starting pos. --- src/ascii_dialog/dialog.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ecd24d666f..9a58a50c36 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -175,9 +175,10 @@ def fill_table(self): else: initial_state = True self.rows_is_included.append(initial_state) - row_status = RowStatusWidget(initial_state, i) - row_status.status_changed.connect(self.update_row_status) - self.table.setCellWidget(i, 0, row_status) + if i >= starting_pos: + row_status = RowStatusWidget(initial_state, i) + row_status.status_changed.connect(self.update_row_status) + self.table.setCellWidget(i, 0, row_status) row_split = self.split_line(row) for j, col_value in enumerate(row_split): if j >= col_count: @@ -199,18 +200,19 @@ def current_dataset_type(self) -> DatasetType: return type return one_dim - def set_row_typesetting(self, row, row_status: bool): + def set_row_typesetting(self, row, item_checked: bool): + starting_pos = self.startline_entry.value() for column in range(1, self.table.columnCount() + 1): item = self.table.item(row, column) if item is None: continue item_font = item.font() - if row_status: - item.setForeground(QColor.fromString('black')) - item_font.setStrikeOut(False) - else: + if not item_checked or row < starting_pos: item.setForeground(QColor.fromString('grey')) item_font.setStrikeOut(True) + else: + item.setForeground(QColor.fromString('black')) + item_font.setStrikeOut(False) item.setFont(item_font) From b4ca3d555fafc0cef71b5a6b161401305c77c034 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:33:21 +0100 Subject: [PATCH 130/396] Display all the data if its below max. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9a58a50c36..998636d953 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -163,7 +163,7 @@ def fill_table(self): starting_pos = self.startline_entry.value() col_count = self.colcount_entry.value() - self.table.setRowCount(min(len(self.raw_csv) - starting_pos, TABLE_MAX_ROWS)) + self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS)) self.table.setColumnCount(col_count + 1) self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) From 2b6f09e3041bcc4903b3b4e77671cbd10bb823e7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:33:29 +0100 Subject: [PATCH 131/396] Reset rows is included on load. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 998636d953..5b775c201c 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -230,6 +230,8 @@ def load(self): with open(filename) as file: self.raw_csv = file.readlines() + # Reset checkboxes + self.rows_is_included = [] self.attempt_guesses() self.fill_table() From 89710d6d1dbed5960d68b140ad0d5ef38f699f27 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 17:14:45 +0100 Subject: [PATCH 132/396] Increased limit to 1000 --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 5b775c201c..2a960f1a73 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -10,7 +10,7 @@ from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans import re -TABLE_MAX_ROWS = 100 +TABLE_MAX_ROWS = 1000 class AsciiDialog(QWidget): def __init__(self): From d74e2a23bc14c6c1b1ea2f8d6f48ec79290e4c19 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:05:43 +0100 Subject: [PATCH 133/396] Type hinting and tidying. --- src/ascii_dialog/dialog.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2a960f1a73..26f5329e24 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -16,9 +16,9 @@ class AsciiDialog(QWidget): def __init__(self): super().__init__() - self.raw_csv = None + self.raw_csv: str | None = None - self.seperators = { + self.seperators: dict[str, bool] = { 'Comma': True, 'Whitespace': True, 'Tab': True @@ -29,8 +29,6 @@ def __init__(self): self.load_button = QPushButton("Load File") self.load_button.clicked.connect(self.load) - # Data parameters - ## Dataset type selection self.dataset_layout = QHBoxLayout() self.dataset_label = QLabel("Dataset Type") @@ -43,7 +41,7 @@ def __init__(self): ## Seperator self.sep_layout = QHBoxLayout() - self.sep_widgets = [] + self.sep_widgets: list[QWidget] = [] self.sep_label = QLabel('Seperators:') self.sep_layout.addWidget(self.sep_label) for seperator_name, value in self.seperators.items(): @@ -83,6 +81,7 @@ def __init__(self): self.table.show() # Make the table readonly self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) + # The table's width will always resize to fit the amount of space it has. self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) # Warning Label From 8336ef512500b69dcfff8552738edfaf94eb00dd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:06:17 +0100 Subject: [PATCH 134/396] Removed old comment. --- src/ascii_dialog/dialog.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 26f5329e24..8bcfde27f2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -119,16 +119,6 @@ def split_line(self, line: str) -> list[str]: return re.split(expr, line) def attempt_guesses(self): - # TODO: We're not guessing seperators anymore (just presuming that they - # are all enabled). Can probably delete this code later. - # - # guessed_seperator = guess_seperator(self.raw_csv) - # if guessed_seperator == None: - # # Seperator couldn't be guessed; just let the user fill that in. - # guessed_seperator = '' - - # self.sep_entry.setText(guessed_seperator) - split_csv = [self.split_line(line.strip()) for line in self.raw_csv] self.initial_starting_pos = guess_starting_position(split_csv) From dd9ca1b9b926b768ca3dd117d61c86c1165f7137 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:07:30 +0100 Subject: [PATCH 135/396] Removed old comment. --- src/ascii_dialog/dialog.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8bcfde27f2..3c2db7ba1f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -139,10 +139,6 @@ def guess_row_status(self, row) -> Qt.CheckState: return Qt.CheckState.Checked def fill_table(self): - # At the moment, we're just going to start making the table from where - # the user told us to start. Just trying this for now. We might want to - # draw the full table later. - # Don't try to fill the table if there's no data. if self.raw_csv is None: return From 7481fa07773d635e888303459e10cce54159bcca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:07:56 +0100 Subject: [PATCH 136/396] Removed unused function. --- src/ascii_dialog/dialog.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 3c2db7ba1f..8788020700 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -270,10 +270,6 @@ def duplicate_columns(self) -> list[str]: col_names = self.col_editor.col_names() return [col for col in col_names if col_names.count(col) > 1] - def set_required_error(self, required_missing): - self.warning_label.setText(f'The following columns are missing: {required_missing}') - self.warning_label.setStyleSheet("QLabel { color: red}") - if __name__ == "__main__": app = QApplication([]) From 3a5a7fbd12b37c26c21d3e735926670e7a6cbb4e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:10:59 +0100 Subject: [PATCH 137/396] Added more type hinting. --- src/ascii_dialog/dialog.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8788020700..a276ebc466 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -118,7 +118,7 @@ def split_line(self, line: str) -> list[str]: return re.split(expr, line) - def attempt_guesses(self): + def attempt_guesses(self) -> None: split_csv = [self.split_line(line.strip()) for line in self.raw_csv] self.initial_starting_pos = guess_starting_position(split_csv) @@ -132,13 +132,13 @@ def attempt_guesses(self): self.colcount_entry.setValue(guessed_colcount) self.startline_entry.setValue(self.initial_starting_pos) - def guess_row_status(self, row) -> Qt.CheckState: + def guess_row_status(self, row: int) -> Qt.CheckState: if row < self.initial_starting_pos: return Qt.CheckState.PartiallyChecked else: return Qt.CheckState.Checked - def fill_table(self): + def fill_table(self) -> None: # Don't try to fill the table if there's no data. if self.raw_csv is None: return @@ -185,7 +185,7 @@ def current_dataset_type(self) -> DatasetType: return type return one_dim - def set_row_typesetting(self, row, item_checked: bool): + def set_row_typesetting(self, row: int, item_checked: bool) -> None: starting_pos = self.startline_entry.value() for column in range(1, self.table.columnCount() + 1): item = self.table.item(row, column) @@ -202,7 +202,7 @@ def set_row_typesetting(self, row, item_checked: bool): @Slot() - def load(self): + def load(self) -> None: result = QFileDialog.getOpenFileName(self) # Happens when the user cancels without selecting a file. There isn't a # file to load in this case. @@ -221,33 +221,33 @@ def load(self): self.fill_table() @Slot() - def update_colcount(self): + def update_colcount(self) -> None: self.col_editor.set_cols(self.colcount_entry.value()) self.fill_table() @Slot() - def update_startpos(self): + def update_startpos(self) -> None: self.fill_table() @Slot() - def update_seperator(self): + def update_seperator(self) -> None: self.fill_table() @Slot() - def update_column(self): + def update_column(self) -> None: self.fill_table() required_missing = self.required_missing() duplicates = self.duplicate_columns() self.warning_label.update(required_missing, duplicates) @Slot() - def seperator_toggle(self): + def seperator_toggle(self) -> None: check_box = self.sender() self.seperators[check_box.text()] = check_box.isChecked() self.fill_table() @Slot() - def change_dataset_type(self): + def change_dataset_type(self) -> None: new_dataset = self.current_dataset_type() self.col_editor.replace_options(new_dataset.required + new_dataset.optional) @@ -256,7 +256,7 @@ def change_dataset_type(self): self.col_editor.set_col_order(columns) @Slot() - def update_row_status(self, row): + def update_row_status(self, row: int) -> None: new_status = self.table.cellWidget(row, 0).isChecked() self.rows_is_included[row] = new_status self.set_row_typesetting(row, new_status) From 8f7e1759a49b874b4c3f225ed302c0572db72dde Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:11:26 +0100 Subject: [PATCH 138/396] Removed code for guessing the seperator. --- src/ascii_dialog/guess.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index ae3aeb3a57..5645e9099f 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -2,34 +2,6 @@ from dataset_types import DatasetType - -def guess_seperator(raw_csv: list[str]) -> str | None: - """Try to guess what the seperator is in raw_csv, and return it. Will return - None if a seperator cannot be guessed, and thus will likely require manual - intervention from the user.""" - - candidates = [",", ";", ":", "\t"] - - for sep in candidates: - if all([sep in line for line in raw_csv]): - return sep - - # If none of the candidate appear to be the seperator, then the seperator is - # potentially a number of whitespaces (n). - # - # Try to determine what n is. - - # Maximum whitespace seperation is 15 to stop this from going into an - # infinite loop. This might not be needed later. - for candidate_n in range(1, 15): - candidate_sep = " " * candidate_n - attempted_split = raw_csv[0].split(candidate_sep) - if '' not in attempted_split: - return candidate_sep - - # No seperator found. - return None - def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: """Guess the amount of columns present in the data.""" return len(split_csv[starting_pos]) From 5a0e578e9afc9e0f528f6553af824a85564ae015 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:12:07 +0100 Subject: [PATCH 139/396] Fixed typo. --- src/ascii_dialog/guess.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 5645e9099f..5e52604bc7 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -17,9 +17,11 @@ def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: def guess_starting_position(split_csv: list[list[str]]) -> int: """Try to look for a line where the first item in the row can be converted - to a number. If such a line doesn't existTry to look for a line where the + to a number. If such a line doesn't exist, try to look for a line where the first item in the row can be converted to a number. If such a line doesn't - exist, then just return 0 as the starting position.""" + exist, then just return 0 as the starting position. + + """ for i, row in enumerate(split_csv): if row[0].replace('.', '').replace('-', '').isdigit(): return i From 02653ee0516ec61b9723d002371352d4b865696e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:14:38 +0100 Subject: [PATCH 140/396] Type hinting, and comment editing. --- src/ascii_dialog/column_unit.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index cd20387af4..ded3b3f682 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -8,7 +8,7 @@ class ColumnUnit(QWidget): column_changed = Signal() - def create_col_combo_box(self, options) -> QComboBox: + def create_col_combo_box(self, options: list[str]) -> QComboBox: new_combo_box = QComboBox() for option in options: new_combo_box.addItem(option) @@ -24,11 +24,11 @@ def create_unit_combo_box(self, selected_option: str) -> QComboBox: new_combo_box.addItem(default_unit) return new_combo_box - def replace_options(self, new_options): + def replace_options(self, new_options) -> None: self.col_widget.clear() self.col_widget.addItems(new_options) - def set_current_column(self, new_column_value: str): + def set_current_column(self, new_column_value: str) -> None: self.col_widget.setCurrentText(new_column_value) new_unit = default_units[new_column_value] self.unit_widget.clear() @@ -37,8 +37,6 @@ def set_current_column(self, new_column_value: str): @Slot() def on_option_change(self): - # Need to update units. - # # If the new option is empty string, its probably because the current # options have been removed. Can safely ignore this. self.column_changed.emit() From 9cf5aa7a569866d054f9a31759978ca0566b2362 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:59:01 +0100 Subject: [PATCH 141/396] Added some docstrings. --- src/ascii_dialog/column_unit.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index ded3b3f682..ddaa8c7b92 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -6,9 +6,13 @@ from dataset_types import default_units class ColumnUnit(QWidget): + """Widget with 2 combo boxes: one allowing the user to pick a column, and + another to specify the units for that column.""" column_changed = Signal() def create_col_combo_box(self, options: list[str]) -> QComboBox: + """Create the combo box for specifying the column based on the given + options.""" new_combo_box = QComboBox() for option in options: new_combo_box.addItem(option) @@ -19,16 +23,19 @@ def create_col_combo_box(self, options: list[str]) -> QComboBox: return new_combo_box def create_unit_combo_box(self, selected_option: str) -> QComboBox: + """Create the combo box for specifying the unit for selected_option""" new_combo_box = QComboBox() default_unit = default_units[selected_option] new_combo_box.addItem(default_unit) return new_combo_box def replace_options(self, new_options) -> None: + """Replace the old options for the column with new_options""" self.col_widget.clear() self.col_widget.addItems(new_options) def set_current_column(self, new_column_value: str) -> None: + """Change the current selected column to new_column_value""" self.col_widget.setCurrentText(new_column_value) new_unit = default_units[new_column_value] self.unit_widget.clear() @@ -55,6 +62,7 @@ def on_option_change(self): @property def current_column(self): + """The currently selected column.""" return self.col_widget.currentText() From 96f9596c230559ccc435148824627c067116aa13 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:01:40 +0100 Subject: [PATCH 142/396] Removed commented out code. --- src/ascii_dialog/col_editor.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 9f67d3e8f1..353abc06a3 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -6,27 +6,6 @@ class ColEditor(QWidget): - # def create_col_combo_box(self) -> QComboBox: - # new_combo_box = QComboBox() - # for option in self.options: - # new_combo_box.addItem(option) - # new_combo_box.setEditable(True) - # validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") - # new_combo_box.setValidator(validator) - # return new_combo_box - - # def create_unit_combo_box(self, selected_option: str) -> QComboBox: - # new_combo_box = QComboBox() - # default_unit = default_units[selected_option] - # new_combo_box.addItem(default_unit) - # return new_combo_box - - # def create_col_unit_box(self) -> tuple[QComboBox, QComboBox]: - # new_col_combo_box = self.create_col_combo_box() - # new_unit_combo_box = self.create_unit_combo_box(new_col_combo_box.currentText()) - # # self.option_widgets.append(new_col_combo_box) - # return new_col_combo_box, new_unit_combo_box - column_changed = Signal() @Slot() From eb975c8d8eb707efc08a74014245cf11d6a7b774 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:01:46 +0100 Subject: [PATCH 143/396] Added some docstrings. --- src/ascii_dialog/row_status_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index dfab832e2f..40ebdd8c50 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -6,8 +6,11 @@ class RowStatusWidget(QCheckBox): + """Widget to toggle whether the row is to be included as part of the data.""" status_changed = Signal(int) def update_label(self): + """Update the label of the check box depending on whether it is checked, + or not.""" if self.isChecked(): self.setText('Included') else: From f0947010c115c6e151d108105861057a9bc87ca8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:02:56 +0100 Subject: [PATCH 144/396] Added docstring. --- src/ascii_dialog/guess.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 5e52604bc7..9c01dd783b 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -7,6 +7,10 @@ def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: return len(split_csv[starting_pos]) def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: + """Based on the amount of columns specified in col_count, try to find a set + of columns that best matchs the dataset_type. + + """ # Ideally we want an exact match but if the ordering is bigger than the col # count then we can accept that as well. for order_list in dataset_type.expected_orders: From 4d31523270dea346b5ab70191d23b430528f1989 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:41:33 +0100 Subject: [PATCH 145/396] Documented warning label class. --- src/ascii_dialog/warning_label.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index caafabc3f9..7f2b4d9007 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -4,6 +4,8 @@ class WarningLabel(QLabel): + """Widget to display an appropriate warning message based whether there + exists columns that are missing, or there are columns that are duplicated.""" def set_font_red(self): self.setStyleSheet("QLabel { color: red}") @@ -11,6 +13,8 @@ def set_font_normal(self): self.setStyleSheet('') def update(self, missing_columns, duplicate_classes): + """Determine, and set the appropriate warning messages given how many + columns are missing, and how many columns are duplicated.""" if len(missing_columns) != 0: self.setText(f'The following columns are missing: {missing_columns}') self.set_font_red() From f57aaca90c9e8ec1d89eb75a22e0fda44110eea4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:47:37 +0100 Subject: [PATCH 146/396] Remove unneeeded import. --- src/ascii_dialog/row_status_widget.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 40ebdd8c50..cd95391d50 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from PySide6.QtCore import Signal, Slot -from PySide6.QtGui import Qt from PySide6.QtWidgets import QCheckBox From 7c23af1ce837fbe9c75ab7be3dd1882bafa916a0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:51:08 +0100 Subject: [PATCH 147/396] Removed some old comments. --- src/ascii_dialog/col_editor.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 353abc06a3..600248e4b0 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -31,9 +31,6 @@ def set_cols(self, new_cols: int): # remove some. if self.cols < new_cols: for _ in range(new_cols - self.cols): - # new_combo_box = self.create_col_combo_box() - # self.option_widgets.append(new_combo_box) - # self.layout.addWidget(new_combo_box) new_widget = ColumnUnit(self.options) new_widget.column_changed.connect(self.on_column_update) self.layout.addWidget(new_widget) @@ -64,9 +61,4 @@ def col_names(self) -> list[str]: def replace_options(self, new_options: list[str]) -> None: self.options = new_options for widget in self.option_widgets: - # col_box.clear() - # col_box.addItems(new_options) - # new_unit = default_units[col_box.currentText()] - # unit_box.clear() - # unit_box.addItem(new_unit) widget.replace_options(new_options) From 01c20b667bf6ba9bfcfb6b12c22c488e830aef02 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:56:38 +0100 Subject: [PATCH 148/396] Document col editor. --- src/ascii_dialog/col_editor.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 600248e4b0..10e443a7a1 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -6,6 +6,8 @@ class ColEditor(QWidget): + """An editor widget which allows the user to specify the columns of the data + from a set of options based on which dataset type has been selected.""" column_changed = Signal() @Slot() @@ -27,6 +29,8 @@ def __init__(self, cols: int, options: list[str]): self.option_widgets.append(new_widget) def set_cols(self, new_cols: int): + """Set the amount of columns for the user to edit.""" + # Decides whether we need to extend the current set of combo boxes, or # remove some. if self.cols < new_cols: @@ -49,6 +53,11 @@ def set_cols(self, new_cols: int): self.column_changed.emit() def set_col_order(self, cols: list[str]): + """Sets the series of currently selected columns to be cols, in that + order. If there are not enough column widgets include as many of the + columns in cols as possible. + + """ try: for i, col_name in enumerate(cols): self.option_widgets[i].set_current_column(col_name) @@ -56,9 +65,11 @@ def set_col_order(self, cols: list[str]): pass # Can ignore because it means we've run out of widgets. def col_names(self) -> list[str]: + """Get a list of all of the currently selected columns.""" return [widget.current_column for widget in self.option_widgets] def replace_options(self, new_options: list[str]) -> None: + """Replace options from which the user can choose for each column.""" self.options = new_options for widget in self.option_widgets: widget.replace_options(new_options) From 1e57d3e914213613db28ddf4bfd0c3cb965a3c81 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:00:40 +0100 Subject: [PATCH 149/396] I don't think this function is being used anymore. --- src/ascii_dialog/dialog.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a276ebc466..386944507b 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -132,12 +132,6 @@ def attempt_guesses(self) -> None: self.colcount_entry.setValue(guessed_colcount) self.startline_entry.setValue(self.initial_starting_pos) - def guess_row_status(self, row: int) -> Qt.CheckState: - if row < self.initial_starting_pos: - return Qt.CheckState.PartiallyChecked - else: - return Qt.CheckState.Checked - def fill_table(self) -> None: # Don't try to fill the table if there's no data. if self.raw_csv is None: From 1ffe53bffaa0393a156e254311cb294dab03212e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:02:13 +0100 Subject: [PATCH 150/396] Remove import. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 386944507b..950ef46ce6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -5,7 +5,7 @@ from warning_label import WarningLabel from col_editor import ColEditor from row_status_widget import RowStatusWidget -from guess import guess_column_count, guess_columns, guess_seperator, guess_starting_position +from guess import guess_column_count, guess_columns, guess_starting_position from os import path from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans import re From ae3b7d6d859f10560e313eeca9671370a004eac1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:08:18 +0100 Subject: [PATCH 151/396] Fixed bug with the regex. --- src/ascii_dialog/dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 950ef46ce6..cb3a924d7a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -104,9 +104,9 @@ def __init__(self): def split_line(self, line: str) -> list[str]: expr = '' for seperator, isenabled in self.seperators.items(): - if expr != r'': - expr += r'|' if isenabled: + if expr != r'': + expr += r'|' match seperator: case 'Comma': seperator_text = r',' From cc78b3cbf8275ed2a27279526779dc83ffc63218 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:14:37 +0100 Subject: [PATCH 152/396] Move init to top. --- src/ascii_dialog/row_status_widget.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index cd95391d50..077d7c1f77 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -6,6 +6,13 @@ class RowStatusWidget(QCheckBox): """Widget to toggle whether the row is to be included as part of the data.""" + def __init__(self, initial_value: bool, row: int): + super().__init__() + self.row = row + self.setChecked(initial_value) + self.update_label() + self.stateChanged.connect(self.on_state_change) + status_changed = Signal(int) def update_label(self): """Update the label of the check box depending on whether it is checked, @@ -20,10 +27,3 @@ def update_label(self): def on_state_change(self): self.update_label() self.status_changed.emit(self.row) - - def __init__(self, initial_value: bool, row: int): - super().__init__() - self.row = row - self.setChecked(initial_value) - self.update_label() - self.stateChanged.connect(self.on_state_change) From 0a448df74526253dea56425c22a9a50674edd527 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:15:21 +0100 Subject: [PATCH 153/396] Move init to top. --- src/ascii_dialog/column_unit.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index ddaa8c7b92..54f7f38f20 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -8,6 +8,14 @@ class ColumnUnit(QWidget): """Widget with 2 combo boxes: one allowing the user to pick a column, and another to specify the units for that column.""" + def __init__(self, options) -> None: + super().__init__() + self.col_widget = self.create_col_combo_box(options) + self.unit_widget = self.create_unit_combo_box(self.col_widget.currentText()) + self.layout = QHBoxLayout(self) + self.layout.addWidget(self.col_widget) + self.layout.addWidget(self.unit_widget) + column_changed = Signal() def create_col_combo_box(self, options: list[str]) -> QComboBox: @@ -64,12 +72,3 @@ def on_option_change(self): def current_column(self): """The currently selected column.""" return self.col_widget.currentText() - - - def __init__(self, options) -> None: - super().__init__() - self.col_widget = self.create_col_combo_box(options) - self.unit_widget = self.create_unit_combo_box(self.col_widget.currentText()) - self.layout = QHBoxLayout(self) - self.layout.addWidget(self.col_widget) - self.layout.addWidget(self.unit_widget) From afed9f537db7d94db24179aeb7830dece9ac32d1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:15:57 +0100 Subject: [PATCH 154/396] Fixed typo. --- src/ascii_dialog/warning_label.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 7f2b4d9007..1bb702acd8 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -4,8 +4,10 @@ class WarningLabel(QLabel): - """Widget to display an appropriate warning message based whether there - exists columns that are missing, or there are columns that are duplicated.""" + """Widget to display an appropriate warning message based on whether there + exists columns that are missing, or there are columns that are duplicated. + + """ def set_font_red(self): self.setStyleSheet("QLabel { color: red}") From 2b6d22442a5f338fc0d0f37c0636f75cbd73eff1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:22:45 +0100 Subject: [PATCH 155/396] Some more docstrings. --- src/ascii_dialog/dialog.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index cb3a924d7a..177d6ba2b4 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -13,6 +13,12 @@ TABLE_MAX_ROWS = 1000 class AsciiDialog(QWidget): + """A dialog window allowing the user to adjust various properties regarding + how an ASCII file should be interpreted. This widget allows the user to + visualise what the data will look like with the parameter the user has + selected. + + """ def __init__(self): super().__init__() @@ -102,6 +108,10 @@ def __init__(self): self.rows_is_included: list[bool] = [] def split_line(self, line: str) -> list[str]: + """Split a line in a CSV file based on which seperators the user has + selected on the widget. + + """ expr = '' for seperator, isenabled in self.seperators.items(): if isenabled: @@ -119,6 +129,10 @@ def split_line(self, line: str) -> list[str]: return re.split(expr, line) def attempt_guesses(self) -> None: + """Attempt to guess various parameters of the data to provide some + default values. Uses the guess.py module + + """ split_csv = [self.split_line(line.strip()) for line in self.raw_csv] self.initial_starting_pos = guess_starting_position(split_csv) @@ -133,6 +147,11 @@ def attempt_guesses(self) -> None: self.startline_entry.setValue(self.initial_starting_pos) def fill_table(self) -> None: + """Write the data to the table based on the parameters the user has + selected. + + """ + # Don't try to fill the table if there's no data. if self.raw_csv is None: return @@ -168,6 +187,8 @@ def fill_table(self) -> None: break self.table.show() + + # Apply typesetting to each row. for row in range(self.table.rowCount()): self.set_row_typesetting(row, self.rows_is_included[row]) From c35ab0bff7de767426dc149e7dc1b38f79fdcd95 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:29:28 +0100 Subject: [PATCH 156/396] Use dictionary for looking up dataset. --- src/ascii_dialog/dataset_types.py | 2 ++ src/ascii_dialog/dialog.py | 9 ++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/ascii_dialog/dataset_types.py b/src/ascii_dialog/dataset_types.py index c8a362f79f..7066228943 100644 --- a/src/ascii_dialog/dataset_types.py +++ b/src/ascii_dialog/dataset_types.py @@ -39,6 +39,8 @@ class DatasetType: dataset_types = {dataset.name for dataset in [one_dim, two_dim, sesans]} +dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) + # # Some default units, this is not how they should be represented, some might not be correct diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 177d6ba2b4..dbd2402eaf 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -7,7 +7,7 @@ from row_status_widget import RowStatusWidget from guess import guess_column_count, guess_columns, guess_starting_position from os import path -from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans +from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans, dataset_dictionary import re TABLE_MAX_ROWS = 1000 @@ -193,12 +193,7 @@ def fill_table(self) -> None: self.set_row_typesetting(row, self.rows_is_included[row]) def current_dataset_type(self) -> DatasetType: - # TODO: Using linear search but should probably just use a dictionary - # later. - for type in [one_dim, two_dim, sesans]: - if type.name == self.dataset_combobox.currentText(): - return type - return one_dim + return dataset_dictionary[self.dataset_combobox.currentText()] def set_row_typesetting(self, row: int, item_checked: bool) -> None: starting_pos = self.startline_entry.value() From 269f63a398339ef77318817cc4e17b3915203ec7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:37:52 +0100 Subject: [PATCH 157/396] More docstrings. --- src/ascii_dialog/dialog.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index dbd2402eaf..4121bacf36 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -193,9 +193,14 @@ def fill_table(self) -> None: self.set_row_typesetting(row, self.rows_is_included[row]) def current_dataset_type(self) -> DatasetType: + """Get the dataset type that the user has currently selected.""" return dataset_dictionary[self.dataset_combobox.currentText()] def set_row_typesetting(self, row: int, item_checked: bool) -> None: + """Set the typesetting for the given role depending on whether it is to + be included in the data being loaded, or not. + + """ starting_pos = self.startline_entry.value() for column in range(1, self.table.columnCount() + 1): item = self.table.item(row, column) @@ -213,6 +218,7 @@ def set_row_typesetting(self, row: int, item_checked: bool) -> None: @Slot() def load(self) -> None: + """Open the file loading dialog, and load the file the user selects.""" result = QFileDialog.getOpenFileName(self) # Happens when the user cancels without selecting a file. There isn't a # file to load in this case. @@ -232,19 +238,26 @@ def load(self) -> None: @Slot() def update_colcount(self) -> None: + """Triggered when the amount of columns the user has selected has + changed. + + """ self.col_editor.set_cols(self.colcount_entry.value()) self.fill_table() @Slot() def update_startpos(self) -> None: + """Triggered when the starting position of the data has changed.""" self.fill_table() @Slot() def update_seperator(self) -> None: + """Changed when the user modifies the set of seperators being used.""" self.fill_table() @Slot() def update_column(self) -> None: + """Triggered when any of the columns has been changed.""" self.fill_table() required_missing = self.required_missing() duplicates = self.duplicate_columns() @@ -252,12 +265,14 @@ def update_column(self) -> None: @Slot() def seperator_toggle(self) -> None: + """Triggered when one of the seperator check boxes has been toggled.""" check_box = self.sender() self.seperators[check_box.text()] = check_box.isChecked() self.fill_table() @Slot() def change_dataset_type(self) -> None: + """Triggered when the selected dataset type has changed.""" new_dataset = self.current_dataset_type() self.col_editor.replace_options(new_dataset.required + new_dataset.optional) @@ -267,16 +282,22 @@ def change_dataset_type(self) -> None: @Slot() def update_row_status(self, row: int) -> None: + """Triggered when the status of row has changed.""" new_status = self.table.cellWidget(row, 0).isChecked() self.rows_is_included[row] = new_status self.set_row_typesetting(row, new_status) def required_missing(self) -> list[str]: + """Returns all the columns that are required by the dataset type but + have not currently been selected. + + """ dataset = self.current_dataset_type() missing_columns = [col for col in dataset.required if col not in self.col_editor.col_names()] return missing_columns def duplicate_columns(self) -> list[str]: + """Returns all of the columns which have been sselected multiple times.""" col_names = self.col_editor.col_names() return [col for col in col_names if col_names.count(col) > 1] From 94846acd0677f2b629bcbfbf959ba4d21bf7d7fb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:38:21 +0100 Subject: [PATCH 158/396] Return a set instead of a list. --- src/ascii_dialog/dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 4121bacf36..92884e1678 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -296,10 +296,10 @@ def required_missing(self) -> list[str]: missing_columns = [col for col in dataset.required if col not in self.col_editor.col_names()] return missing_columns - def duplicate_columns(self) -> list[str]: + def duplicate_columns(self) -> set[str]: """Returns all of the columns which have been sselected multiple times.""" col_names = self.col_editor.col_names() - return [col for col in col_names if col_names.count(col) > 1] + return set([col for col in col_names if col_names.count(col) > 1]) if __name__ == "__main__": app = QApplication([]) From 19d8bf4611af7d7a4c8d8ce9f74ed76461ce41ee Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:15:12 +0100 Subject: [PATCH 159/396] Use elipsis to indicate there is more data. --- src/ascii_dialog/dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 92884e1678..f7adde7cdf 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -161,7 +161,7 @@ def fill_table(self) -> None: starting_pos = self.startline_entry.value() col_count = self.colcount_entry.value() - self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS)) + self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS + 2)) self.table.setColumnCount(col_count + 1) self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) @@ -184,6 +184,10 @@ def fill_table(self) -> None: item = QTableWidgetItem(col_value) self.table.setItem(i, j + 1, item) if i == TABLE_MAX_ROWS: + # Fill with elipsis to indicate there is more data. + for j in range(len(row_split)): + elipsis_item = QTableWidgetItem("...") + self.table.setItem(i + 1, j, elipsis_item) break self.table.show() From 2444fbc244925a1daa72f0abe407ecced7914b43 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:19:12 +0100 Subject: [PATCH 160/396] Allign the elipsis. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f7adde7cdf..c88ad7c9c8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -187,6 +187,7 @@ def fill_table(self) -> None: # Fill with elipsis to indicate there is more data. for j in range(len(row_split)): elipsis_item = QTableWidgetItem("...") + elipsis_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) self.table.setItem(i + 1, j, elipsis_item) break From d1a8e022e36aaf11c9fb9f244d2302ac2db32542 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:25:20 +0100 Subject: [PATCH 161/396] Don't need a seperate for loop for typesetting. --- src/ascii_dialog/dialog.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c88ad7c9c8..8a58e408d0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -183,6 +183,7 @@ def fill_table(self) -> None: continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) self.table.setItem(i, j + 1, item) + self.set_row_typesetting(i, self.rows_is_included[i]) if i == TABLE_MAX_ROWS: # Fill with elipsis to indicate there is more data. for j in range(len(row_split)): @@ -193,10 +194,6 @@ def fill_table(self) -> None: self.table.show() - # Apply typesetting to each row. - for row in range(self.table.rowCount()): - self.set_row_typesetting(row, self.rows_is_included[row]) - def current_dataset_type(self) -> DatasetType: """Get the dataset type that the user has currently selected.""" return dataset_dictionary[self.dataset_combobox.currentText()] From 441372fa63ed70845800a6cf3677a7f133873b3d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:27:10 +0100 Subject: [PATCH 162/396] Moved where the check for max row happens. --- src/ascii_dialog/dialog.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8a58e408d0..ac67808d1f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -161,13 +161,21 @@ def fill_table(self) -> None: starting_pos = self.startline_entry.value() col_count = self.colcount_entry.value() - self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS + 2)) + self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS + 1)) self.table.setColumnCount(col_count + 1) self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # Now fill the table with data for i, row in enumerate(self.raw_csv): + if i == TABLE_MAX_ROWS: + # Fill with elipsis to indicate there is more data. + for j in range(len(row_split)): + elipsis_item = QTableWidgetItem("...") + elipsis_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.table.setItem(i, j, elipsis_item) + break + if i < len(self.rows_is_included): initial_state = self.rows_is_included[i] else: @@ -184,13 +192,6 @@ def fill_table(self) -> None: item = QTableWidgetItem(col_value) self.table.setItem(i, j + 1, item) self.set_row_typesetting(i, self.rows_is_included[i]) - if i == TABLE_MAX_ROWS: - # Fill with elipsis to indicate there is more data. - for j in range(len(row_split)): - elipsis_item = QTableWidgetItem("...") - elipsis_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) - self.table.setItem(i + 1, j, elipsis_item) - break self.table.show() From 99d059f180a573f007e56173196eb2acf6c93959 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:44:18 +0100 Subject: [PATCH 163/396] Rename parameter. --- src/ascii_dialog/warning_label.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 1bb702acd8..fa8ed189cf 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -14,13 +14,13 @@ def set_font_red(self): def set_font_normal(self): self.setStyleSheet('') - def update(self, missing_columns, duplicate_classes): + def update(self, missing_columns, duplicate_columns): """Determine, and set the appropriate warning messages given how many columns are missing, and how many columns are duplicated.""" if len(missing_columns) != 0: self.setText(f'The following columns are missing: {missing_columns}') self.set_font_red() - elif len(duplicate_classes) > 0: + elif len(duplicate_columns) > 0: self.setText(f'There are duplicate columns.') self.set_font_red() else: From 58ffb24a57de6c4fc34e72d6404f730b1aa7db8f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:46:23 +0100 Subject: [PATCH 164/396] Don't count multiple ignores as duplicates. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ac67808d1f..e5b9310ff2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -302,7 +302,7 @@ def required_missing(self) -> list[str]: def duplicate_columns(self) -> set[str]: """Returns all of the columns which have been sselected multiple times.""" col_names = self.col_editor.col_names() - return set([col for col in col_names if col_names.count(col) > 1]) + return set([col for col in col_names if not col == '' and col_names.count(col) > 1]) if __name__ == "__main__": app = QApplication([]) From 6df877ebcb6f5f1359ee3bcd075bc09cd7efba81 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:51:11 +0100 Subject: [PATCH 165/396] Method for determining options. --- src/ascii_dialog/dialog.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index e5b9310ff2..dea2d7df73 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -75,8 +75,7 @@ def __init__(self): self.colcount_layout.addWidget(self.colcount_entry) ## Column Editor - current_dataset_type = self.current_dataset_type() - options = current_dataset_type.required + current_dataset_type.optional + options = self.dataset_options() self.col_editor = ColEditor(self.colcount_entry.value(), options) self.dataset_combobox.currentTextChanged.connect(self.change_dataset_type) self.col_editor.column_changed.connect(self.update_column) @@ -277,7 +276,8 @@ def seperator_toggle(self) -> None: def change_dataset_type(self) -> None: """Triggered when the selected dataset type has changed.""" new_dataset = self.current_dataset_type() - self.col_editor.replace_options(new_dataset.required + new_dataset.optional) + options = self.dataset_options() + self.col_editor.replace_options(options) # Update columns as they'll be different now. columns = guess_columns(self.colcount_entry.value(), self.current_dataset_type()) @@ -304,6 +304,10 @@ def duplicate_columns(self) -> set[str]: col_names = self.col_editor.col_names() return set([col for col in col_names if not col == '' and col_names.count(col) > 1]) + def dataset_options(self) -> list[str]: + current_dataset_type = self.current_dataset_type() + return current_dataset_type.required + current_dataset_type.optional + [''] + if __name__ == "__main__": app = QApplication([]) From 4b524acc4c8d8780c570b81f7d38acac3159ea21 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:54:48 +0100 Subject: [PATCH 166/396] Removed redundant var. --- src/ascii_dialog/dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index dea2d7df73..593cca3979 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -275,7 +275,6 @@ def seperator_toggle(self) -> None: @Slot() def change_dataset_type(self) -> None: """Triggered when the selected dataset type has changed.""" - new_dataset = self.current_dataset_type() options = self.dataset_options() self.col_editor.replace_options(options) From a2ddad8f4f0a67e5e443a6c5b019c7461cf15e58 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:55:29 +0100 Subject: [PATCH 167/396] Removed imports not being used anymore. --- src/ascii_dialog/dialog.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 593cca3979..ef2208213b 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,13 +1,12 @@ -from PySide6 import QtGui -from PySide6.QtGui import QColor, QIntValidator, QPalette, Qt -from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSizePolicy, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication +from PySide6.QtGui import QColor, Qt +from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from warning_label import WarningLabel from col_editor import ColEditor from row_status_widget import RowStatusWidget from guess import guess_column_count, guess_columns, guess_starting_position from os import path -from dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans, dataset_dictionary +from dataset_types import DatasetType, dataset_types, dataset_dictionary import re TABLE_MAX_ROWS = 1000 From 6af85cc85579d554503e12ad67512afc56398694 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 13:22:00 +0100 Subject: [PATCH 168/396] Added exception handling for reading the file. --- src/ascii_dialog/dialog.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ef2208213b..340aadad63 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,5 @@ from PySide6.QtGui import QColor, Qt -from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import Slot from warning_label import WarningLabel from col_editor import ColEditor @@ -228,14 +228,15 @@ def load(self) -> None: filename = result[0] self.filename_label.setText(path.basename(filename)) - # TODO: Add error handling - with open(filename) as file: - self.raw_csv = file.readlines() - - # Reset checkboxes - self.rows_is_included = [] - self.attempt_guesses() - self.fill_table() + try: + with open(filename) as file: + self.raw_csv = file.readlines() + # Reset checkboxes + self.rows_is_included = [] + self.attempt_guesses() + self.fill_table() + except OSError: + QMessageBox.critical(self, 'File Read Error', ' There was an error reading that file.') @Slot() def update_colcount(self) -> None: From b5a38367ccdf38c870d59f7dc69aada8768f5003 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 8 Aug 2024 13:22:13 +0100 Subject: [PATCH 169/396] Show units in unit box. --- src/ascii_dialog/column_unit.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 54f7f38f20..d07caeb4e2 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 from PySide6.QtCore import Signal, Slot -from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget +from PySide6.QtWidgets import QComboBox, QCompleter, QHBoxLayout, QWidget from PySide6.QtGui import QRegularExpressionValidator -from dataset_types import default_units +from sasdata.dataset_types import unit_kinds class ColumnUnit(QWidget): """Widget with 2 combo boxes: one allowing the user to pick a column, and @@ -33,10 +33,20 @@ def create_col_combo_box(self, options: list[str]) -> QComboBox: def create_unit_combo_box(self, selected_option: str) -> QComboBox: """Create the combo box for specifying the unit for selected_option""" new_combo_box = QComboBox() - default_unit = default_units[selected_option] - new_combo_box.addItem(default_unit) + new_combo_box.setEditable(True) + # word_list = ['alpha', 'omega', 'omicron', 'zeta'] + # completer = QCompleter(word_list, self) + # new_combo_box.setCompleter(completer) + self.update_units(new_combo_box, selected_option) return new_combo_box + def update_units(self, unit_box: QComboBox, selected_option: str): + unit_box.clear() + options = [unit.ascii_symbol for unit in unit_kinds[selected_option].units] + for option in options: + unit_box.addItem(option) + + def replace_options(self, new_options) -> None: """Replace the old options for the column with new_options""" self.col_widget.clear() @@ -45,9 +55,7 @@ def replace_options(self, new_options) -> None: def set_current_column(self, new_column_value: str) -> None: """Change the current selected column to new_column_value""" self.col_widget.setCurrentText(new_column_value) - new_unit = default_units[new_column_value] - self.unit_widget.clear() - self.unit_widget.addItem(new_unit) + self.update_units(self.unit_widget, new_column_value) @Slot() @@ -59,9 +67,7 @@ def on_option_change(self): if new_option == '': return try: - new_unit = default_units[new_option] - self.unit_widget.clear() - self.unit_widget.addItem(new_unit) + self.update_units(self.unit_widget, new_option) except KeyError: # Means the units for this column aren't known. This shouldn't be # the case in the real version so for now we'll just clear the unit From fbb62053a8f00ba6d79fd26e0d83b9d1861cc7f1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 8 Aug 2024 14:19:25 +0100 Subject: [PATCH 170/396] Use sasdata for the dataset types. --- src/ascii_dialog/col_editor.py | 1 - src/ascii_dialog/dataset_types.py | 75 ------------------------------- src/ascii_dialog/dialog.py | 4 +- src/ascii_dialog/guess.py | 2 +- 4 files changed, 4 insertions(+), 78 deletions(-) delete mode 100644 src/ascii_dialog/dataset_types.py diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 10e443a7a1..32d0e0a2b3 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -2,7 +2,6 @@ from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtCore import Slot, Signal from column_unit import ColumnUnit -from dataset_types import default_units class ColEditor(QWidget): diff --git a/src/ascii_dialog/dataset_types.py b/src/ascii_dialog/dataset_types.py deleted file mode 100644 index 7066228943..0000000000 --- a/src/ascii_dialog/dataset_types.py +++ /dev/null @@ -1,75 +0,0 @@ -""" Information used for providing guesses about what text based files contain """ - -from dataclasses import dataclass - -# -# VERY ROUGH DRAFT - FOR PROTOTYPING PURPOSES -# - -@dataclass -class DatasetType: - name: str - required: list[str] - optional: list[str] - expected_orders: list[list[str]] - - -one_dim = DatasetType( - name="1D I vs Q", - required=["Q", "I"], - optional=["dI", "dQ", "shadow"], - expected_orders=[ - ["Q", "I", "dI"], - ["Q", "dQ", "I", "dI"]]) - -two_dim = DatasetType( - name="2D I vs Q", - required=["Qx", "Qy", "I"], - optional=["dQx", "dQy", "dI", "Qz", "shadow"], - expected_orders=[ - ["Qx", "Qy", "I"], - ["Qx", "Qy", "I", "dI"], - ["Qx", "Qy", "dQx", "dQy", "I", "dI"]]) - -sesans = DatasetType( - name="SESANS", - required=["z", "G"], - optional=["stuff", "other stuff", "more stuff"], - expected_orders=[["z", "G"]]) - -dataset_types = {dataset.name for dataset in [one_dim, two_dim, sesans]} - -dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) - - -# -# Some default units, this is not how they should be represented, some might not be correct -# -# The unit options should only be those compatible with the field -# -default_units = { - "Q": "1/A", - "I": "1/cm", - "Qx": "1/A", - "Qy": "1/A", - "Qz": "1/A", - "dI": "1/A", - "dQ": "1/A", - "dQx": "1/A", - "dQy": "1/A", - "dQz": "1/A", - "z": "A", - "G": "", - "shaddow": "", - "temperature": "K", - "magnetic field": "T" -} - -# -# Other possible fields. Ultimately, these should come out of the metadata structure -# - -metadata_fields = [ - "temperature", - "magnetic field", -] diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 340aadad63..fa9ea26f34 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -6,11 +6,13 @@ from row_status_widget import RowStatusWidget from guess import guess_column_count, guess_columns, guess_starting_position from os import path -from dataset_types import DatasetType, dataset_types, dataset_dictionary +from sasdata.dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans import re TABLE_MAX_ROWS = 1000 +dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) + class AsciiDialog(QWidget): """A dialog window allowing the user to adjust various properties regarding how an ASCII file should be interpreted. This widget allows the user to diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 9c01dd783b..f9acae26f3 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from dataset_types import DatasetType +from sasdata.dataset_types import DatasetType def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: """Guess the amount of columns present in the data.""" From 3ac79a61bdcc50e4157a04a34d21c9b4cbdbc50f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 09:57:57 +0100 Subject: [PATCH 171/396] Beginning of unit selector widget. --- src/ascii_dialog/unit_selector.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/ascii_dialog/unit_selector.py diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py new file mode 100644 index 0000000000..8f6dfda619 --- /dev/null +++ b/src/ascii_dialog/unit_selector.py @@ -0,0 +1,28 @@ +from PySide6.QtWidgets import QApplication, QComboBox, QVBoxLayout, QWidget +from sasdata.quantities.units import length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance + +# TODO: Ask Lucas if this list can be in his code (or if it already is and I +# can't find it). I am lazy so only doing a subsection for now. + +all_unit_groups = [ + length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance +] + +class UnitSelector(QWidget): + def __init__(self): + super().__init__() + + self.unit_type_selector = QComboBox() + unit_group_names = [group.name for group in all_unit_groups] + self.unit_type_selector.addItems(unit_group_names) + + self.layout = QVBoxLayout(self) + self.layout.addWidget(self.unit_type_selector) + +if __name__ == "__main__": + app = QApplication([]) + + widget = UnitSelector() + widget.show() + + exit(app.exec()) From 0b95ae8e5b0f8d08adb75b08af3734b532b93ca1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 10:16:49 +0100 Subject: [PATCH 172/396] Create a widget for listing units. --- src/ascii_dialog/unit_list_widget.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/ascii_dialog/unit_list_widget.py diff --git a/src/ascii_dialog/unit_list_widget.py b/src/ascii_dialog/unit_list_widget.py new file mode 100644 index 0000000000..84efea58c9 --- /dev/null +++ b/src/ascii_dialog/unit_list_widget.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +from PySide6.QtWidgets import QListWidget, QListWidgetItem +from sasdata.quantities.units import NamedUnit + + +class UnitListWidget(QListWidget): + def populate_list(self, units: list[NamedUnit]) -> None: + self.clear() + for unit in units: + item = QListWidgetItem(unit.name) + self.addItem(item) + + def __init__(self): + super().__init__() From 4be7cb98d8b3482765621937ee37e730e519b0a4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:15:11 +0100 Subject: [PATCH 173/396] Integrate the new list widget. --- src/ascii_dialog/unit_selector.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 8f6dfda619..49991e8a8f 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,5 +1,7 @@ -from PySide6.QtWidgets import QApplication, QComboBox, QVBoxLayout, QWidget -from sasdata.quantities.units import length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance +from PySide6.QtWidgets import QApplication, QComboBox, QListWidget, QVBoxLayout, QWidget +from sasdata.quantities.units import UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance + +from unit_list_widget import UnitListWidget # TODO: Ask Lucas if this list can be in his code (or if it already is and I # can't find it). I am lazy so only doing a subsection for now. @@ -9,6 +11,10 @@ ] class UnitSelector(QWidget): + def current_unit_group(self) -> UnitGroup: + index = self.unit_type_selector.currentIndex() + return all_unit_groups[index] + def __init__(self): super().__init__() @@ -16,8 +22,13 @@ def __init__(self): unit_group_names = [group.name for group in all_unit_groups] self.unit_type_selector.addItems(unit_group_names) + self.unit_list_widget = UnitListWidget() + # TODO: Are they all named units? + self.unit_list_widget.populate_list(self.current_unit_group().units) + self.layout = QVBoxLayout(self) self.layout.addWidget(self.unit_type_selector) + self.layout.addWidget(self.unit_list_widget) if __name__ == "__main__": app = QApplication([]) From e57ae1767775186f25589c6631ad5ede1efaf609 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:23:03 +0100 Subject: [PATCH 174/396] Update when the selected group changes. --- src/ascii_dialog/unit_selector.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 49991e8a8f..a971f0f9c0 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,3 +1,4 @@ +from PySide6.QtCore import Slot from PySide6.QtWidgets import QApplication, QComboBox, QListWidget, QVBoxLayout, QWidget from sasdata.quantities.units import UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance @@ -15,12 +16,18 @@ def current_unit_group(self) -> UnitGroup: index = self.unit_type_selector.currentIndex() return all_unit_groups[index] + @Slot() + def unit_group_changed(self): + new_group = self.current_unit_group() + self.unit_list_widget.populate_list(new_group.units) + def __init__(self): super().__init__() self.unit_type_selector = QComboBox() unit_group_names = [group.name for group in all_unit_groups] self.unit_type_selector.addItems(unit_group_names) + self.unit_type_selector.currentTextChanged.connect(self.unit_group_changed) self.unit_list_widget = UnitListWidget() # TODO: Are they all named units? From 4f3fb36ca55983ce07b240b9520105a6134c4089 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:27:20 +0100 Subject: [PATCH 175/396] Include symbol in the representation on the list. --- src/ascii_dialog/unit_list_widget.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_list_widget.py b/src/ascii_dialog/unit_list_widget.py index 84efea58c9..373d968d8e 100644 --- a/src/ascii_dialog/unit_list_widget.py +++ b/src/ascii_dialog/unit_list_widget.py @@ -5,10 +5,13 @@ class UnitListWidget(QListWidget): + def repr_unit(self, unit: NamedUnit) -> str: + return f"{unit.symbol} ({unit.name})" + def populate_list(self, units: list[NamedUnit]) -> None: self.clear() for unit in units: - item = QListWidgetItem(unit.name) + item = QListWidgetItem(self.repr_unit(unit)) self.addItem(item) def __init__(self): From cb6dfc235ab1902f7c8cc0227a935a3da5418051 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:39:57 +0100 Subject: [PATCH 176/396] Added a search box. --- src/ascii_dialog/unit_selector.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index a971f0f9c0..ad909d98d9 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,5 +1,5 @@ from PySide6.QtCore import Slot -from PySide6.QtWidgets import QApplication, QComboBox, QListWidget, QVBoxLayout, QWidget +from PySide6.QtWidgets import QApplication, QComboBox, QLineEdit, QListWidget, QVBoxLayout, QWidget from sasdata.quantities.units import UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance from unit_list_widget import UnitListWidget @@ -16,9 +16,20 @@ def current_unit_group(self) -> UnitGroup: index = self.unit_type_selector.currentIndex() return all_unit_groups[index] + @Slot() + def on_search_changed(self): + search_input = self.search_box.text() + current_group = self.current_unit_group() + units = current_group.units + if search_input != '': + units = [unit for unit in units if search_input in unit.name] + self.unit_list_widget.populate_list(units) + + @Slot() def unit_group_changed(self): new_group = self.current_unit_group() + self.search_box.setText('') self.unit_list_widget.populate_list(new_group.units) def __init__(self): @@ -29,12 +40,16 @@ def __init__(self): self.unit_type_selector.addItems(unit_group_names) self.unit_type_selector.currentTextChanged.connect(self.unit_group_changed) + self.search_box = QLineEdit() + self.search_box.textChanged.connect(self.on_search_changed) + self.unit_list_widget = UnitListWidget() # TODO: Are they all named units? self.unit_list_widget.populate_list(self.current_unit_group().units) self.layout = QVBoxLayout(self) self.layout.addWidget(self.unit_type_selector) + self.layout.addWidget(self.search_box) self.layout.addWidget(self.unit_list_widget) if __name__ == "__main__": From cfac52887cf24c7a621db703d7de388e16e57988 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:43:37 +0100 Subject: [PATCH 177/396] Use lowercase for comparisons. --- src/ascii_dialog/unit_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index ad909d98d9..ba5db4dbee 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -22,7 +22,7 @@ def on_search_changed(self): current_group = self.current_unit_group() units = current_group.units if search_input != '': - units = [unit for unit in units if search_input in unit.name] + units = [unit for unit in units if search_input.lower() in unit.name] self.unit_list_widget.populate_list(units) From d001320faba4c9574f5a2727f8b58913fdb11596 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:45:58 +0100 Subject: [PATCH 178/396] Base on QDialog. --- src/ascii_dialog/unit_selector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index ba5db4dbee..c3b479a359 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,5 +1,5 @@ from PySide6.QtCore import Slot -from PySide6.QtWidgets import QApplication, QComboBox, QLineEdit, QListWidget, QVBoxLayout, QWidget +from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QListWidget, QVBoxLayout, QWidget from sasdata.quantities.units import UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance from unit_list_widget import UnitListWidget @@ -11,7 +11,7 @@ length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance ] -class UnitSelector(QWidget): +class UnitSelector(QDialog): def current_unit_group(self) -> UnitGroup: index = self.unit_type_selector.currentIndex() return all_unit_groups[index] From ce8d6904afc6f84a936dd15f94d14bd1870b16b1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 15:21:40 +0100 Subject: [PATCH 179/396] Have a way of getting the selected unit. --- src/ascii_dialog/unit_list_widget.py | 6 ++++++ src/ascii_dialog/unit_selector.py | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_list_widget.py b/src/ascii_dialog/unit_list_widget.py index 373d968d8e..6ccd05bd8a 100644 --- a/src/ascii_dialog/unit_list_widget.py +++ b/src/ascii_dialog/unit_list_widget.py @@ -10,9 +10,15 @@ def repr_unit(self, unit: NamedUnit) -> str: def populate_list(self, units: list[NamedUnit]) -> None: self.clear() + self.units = units for unit in units: item = QListWidgetItem(self.repr_unit(unit)) self.addItem(item) + @property + def selected_unit(self) -> NamedUnit | None: + return self.units[self.currentRow()] + def __init__(self): super().__init__() + self.units: list[NamedUnit] = [] diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index c3b479a359..e452b6bd69 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,6 +1,6 @@ from PySide6.QtCore import Slot from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QListWidget, QVBoxLayout, QWidget -from sasdata.quantities.units import UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance +from sasdata.quantities.units import NamedUnit, UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance from unit_list_widget import UnitListWidget @@ -16,6 +16,10 @@ def current_unit_group(self) -> UnitGroup: index = self.unit_type_selector.currentIndex() return all_unit_groups[index] + @property + def selected_unit(self) -> NamedUnit | None: + return self.unit_list_widget.selected_unit + @Slot() def on_search_changed(self): search_input = self.search_box.text() From a4be0dc33e4b76282d586007ba62fc1c02ab13e9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 15:33:49 +0100 Subject: [PATCH 180/396] Have a way of selecting, and closing the dialog. --- src/ascii_dialog/unit_selector.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index e452b6bd69..064d5013cf 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,5 +1,5 @@ from PySide6.QtCore import Slot -from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QListWidget, QVBoxLayout, QWidget +from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QListWidget, QPushButton, QVBoxLayout, QWidget from sasdata.quantities.units import NamedUnit, UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance from unit_list_widget import UnitListWidget @@ -36,6 +36,10 @@ def unit_group_changed(self): self.search_box.setText('') self.unit_list_widget.populate_list(new_group.units) + @Slot() + def select_unit(self): + self.accept() + def __init__(self): super().__init__() @@ -51,15 +55,20 @@ def __init__(self): # TODO: Are they all named units? self.unit_list_widget.populate_list(self.current_unit_group().units) + self.select_button = QPushButton('Select Unit') + self.select_button.pressed.connect(self.select_unit) + self.layout = QVBoxLayout(self) self.layout.addWidget(self.unit_type_selector) self.layout.addWidget(self.search_box) self.layout.addWidget(self.unit_list_widget) + self.layout.addWidget(self.select_button) if __name__ == "__main__": app = QApplication([]) widget = UnitSelector() - widget.show() + widget.exec() + print(widget.selected_unit) - exit(app.exec()) + exit() From ce67bc891572755649b14bd2e4810efc446bb5f4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:06:12 +0100 Subject: [PATCH 181/396] Use unicode symbols instead of ascii. --- src/ascii_dialog/column_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index d07caeb4e2..63cb10ccb7 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -42,7 +42,7 @@ def create_unit_combo_box(self, selected_option: str) -> QComboBox: def update_units(self, unit_box: QComboBox, selected_option: str): unit_box.clear() - options = [unit.ascii_symbol for unit in unit_kinds[selected_option].units] + options = [unit.symbol for unit in unit_kinds[selected_option].units] for option in options: unit_box.addItem(option) From 09c0b21292d944b6e44d0bbbe5360e3edad25a4d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:16:18 +0100 Subject: [PATCH 182/396] Integrate the unit selector with the ascii dialog. --- src/ascii_dialog/column_unit.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 63cb10ccb7..5507e25eec 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -5,6 +5,8 @@ from PySide6.QtGui import QRegularExpressionValidator from sasdata.dataset_types import unit_kinds +from unit_selector import UnitSelector + class ColumnUnit(QWidget): """Widget with 2 combo boxes: one allowing the user to pick a column, and another to specify the units for that column.""" @@ -38,6 +40,7 @@ def create_unit_combo_box(self, selected_option: str) -> QComboBox: # completer = QCompleter(word_list, self) # new_combo_box.setCompleter(completer) self.update_units(new_combo_box, selected_option) + new_combo_box.currentTextChanged.connect(self.on_unit_change) return new_combo_box def update_units(self, unit_box: QComboBox, selected_option: str): @@ -45,6 +48,7 @@ def update_units(self, unit_box: QComboBox, selected_option: str): options = [unit.symbol for unit in unit_kinds[selected_option].units] for option in options: unit_box.addItem(option) + unit_box.addItem('Select More') def replace_options(self, new_options) -> None: @@ -74,6 +78,13 @@ def on_option_change(self): # widget. self.unit_widget.clear() + @Slot() + def on_unit_change(self): + if self.unit_widget.currentText() == 'Select More': + selector = UnitSelector() + selector.exec() + print(selector.selected_unit) + @property def current_column(self): """The currently selected column.""" From c392f1834ac375ab782c2980f7f89ff584cbef6f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:18:32 +0100 Subject: [PATCH 183/396] Change the selected unit when using. --- src/ascii_dialog/column_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 5507e25eec..2c3c3ca399 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -83,7 +83,7 @@ def on_unit_change(self): if self.unit_widget.currentText() == 'Select More': selector = UnitSelector() selector.exec() - print(selector.selected_unit) + self.unit_widget.setCurrentText(selector.selected_unit.symbol) @property def current_column(self): From 91e2738ecd60e1d7488eaa3730161ceccce41dec Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:23:25 +0100 Subject: [PATCH 184/396] Property for current unit. --- src/ascii_dialog/column_unit.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 2c3c3ca399..ca96d62bf3 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -4,6 +4,7 @@ from PySide6.QtWidgets import QComboBox, QCompleter, QHBoxLayout, QWidget from PySide6.QtGui import QRegularExpressionValidator from sasdata.dataset_types import unit_kinds +from sasdata.quantities.units import symbol_lookup from unit_selector import UnitSelector @@ -89,3 +90,8 @@ def on_unit_change(self): def current_column(self): """The currently selected column.""" return self.col_widget.currentText() + + @property + def current_unit(self): + """The currently selected unit.""" + return symbol_lookup[self.unit_widget.currentText()] From e51d6b6ea33d539d9c482ae2a19bb629322e7667 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 17:05:24 +0100 Subject: [PATCH 185/396] Provide default group, and disable the option. --- src/ascii_dialog/column_unit.py | 2 +- src/ascii_dialog/unit_selector.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index ca96d62bf3..0cb60f0c09 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -82,7 +82,7 @@ def on_option_change(self): @Slot() def on_unit_change(self): if self.unit_widget.currentText() == 'Select More': - selector = UnitSelector() + selector = UnitSelector(unit_kinds[self.col_widget.currentText()].name, False) selector.exec() self.unit_widget.setCurrentText(selector.selected_unit.symbol) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 064d5013cf..755390404a 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -40,12 +40,15 @@ def unit_group_changed(self): def select_unit(self): self.accept() - def __init__(self): + def __init__(self, default_group='length', allow_group_edit=True): super().__init__() self.unit_type_selector = QComboBox() unit_group_names = [group.name for group in all_unit_groups] self.unit_type_selector.addItems(unit_group_names) + self.unit_type_selector.setCurrentText(default_group) + if not allow_group_edit: + self.unit_type_selector.setDisabled(True) self.unit_type_selector.currentTextChanged.connect(self.unit_group_changed) self.search_box = QLineEdit() From ce8f035f946a904ef06488bc76ff3ef2fd341f7f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 17:07:01 +0100 Subject: [PATCH 186/396] Added a placeholder for the search box. --- src/ascii_dialog/unit_selector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 755390404a..2360f555cd 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -53,6 +53,7 @@ def __init__(self, default_group='length', allow_group_edit=True): self.search_box = QLineEdit() self.search_box.textChanged.connect(self.on_search_changed) + self.search_box.setPlaceholderText('Search for a unit...') self.unit_list_widget = UnitListWidget() # TODO: Are they all named units? From 2529120ffd9b5c40ecd2f676a927f38be1eea441 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 17:12:33 +0100 Subject: [PATCH 187/396] Simulate having preferred options. --- src/ascii_dialog/column_unit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 0cb60f0c09..c400ca3372 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -47,7 +47,9 @@ def create_unit_combo_box(self, selected_option: str) -> QComboBox: def update_units(self, unit_box: QComboBox, selected_option: str): unit_box.clear() options = [unit.symbol for unit in unit_kinds[selected_option].units] - for option in options: + # We don't have preferred units yet. In order to simulate this, just + # take the first 5 options to display. + for option in options[:5]: unit_box.addItem(option) unit_box.addItem('Select More') From a6c3d4f16bb3d5541ae2defc6a092bf4d04f6ec7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:09:46 +0100 Subject: [PATCH 188/396] Allow double clicking to select a unit. --- src/ascii_dialog/unit_selector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 2360f555cd..ea1cd3bc90 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -58,6 +58,7 @@ def __init__(self, default_group='length', allow_group_edit=True): self.unit_list_widget = UnitListWidget() # TODO: Are they all named units? self.unit_list_widget.populate_list(self.current_unit_group().units) + self.unit_list_widget.itemDoubleClicked.connect(self.select_unit) self.select_button = QPushButton('Select Unit') self.select_button.pressed.connect(self.select_unit) From a2de5615007202d1064a6f4d815f631a41ad26d2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:24:16 +0100 Subject: [PATCH 189/396] Have a dictionary for the files loaded. --- src/ascii_dialog/dialog.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index fa9ea26f34..b66c0dea29 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -23,7 +23,9 @@ class AsciiDialog(QWidget): def __init__(self): super().__init__() - self.raw_csv: str | None = None + self.raw_csv: list[str] | None = None + + self.files: dict[str, list[str]] = {} self.seperators: dict[str, bool] = { 'Comma': True, @@ -232,7 +234,11 @@ def load(self) -> None: try: with open(filename) as file: - self.raw_csv = file.readlines() + file_csv = file.readlines() + self.raw_csv = file_csv + # TODO: This assumes that no two files will be loaded with the same + # name. This might not be a reasonable assumption. + self.files[filename] = file_csv # Reset checkboxes self.rows_is_included = [] self.attempt_guesses() From 852e3b18674e854570ebabfa007c91000720e156 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:28:28 +0100 Subject: [PATCH 190/396] Get raw_csv based on filename. --- src/ascii_dialog/dialog.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index b66c0dea29..9cf4e9ce2f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -23,9 +23,8 @@ class AsciiDialog(QWidget): def __init__(self): super().__init__() - self.raw_csv: list[str] | None = None - self.files: dict[str, list[str]] = {} + self.current_filename: str | None = None self.seperators: dict[str, bool] = { 'Comma': True, @@ -109,6 +108,12 @@ def __init__(self): self.rows_is_included: list[bool] = [] + @property + def raw_csv(self) -> list[str] | None: + if self.current_filename is None: + return None + return self.files[self.current_filename] + def split_line(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has selected on the widget. @@ -235,10 +240,10 @@ def load(self) -> None: try: with open(filename) as file: file_csv = file.readlines() - self.raw_csv = file_csv # TODO: This assumes that no two files will be loaded with the same # name. This might not be a reasonable assumption. self.files[filename] = file_csv + self.current_filename = filename # Reset checkboxes self.rows_is_included = [] self.attempt_guesses() From d8fad4ae3248b7858d85505d43a498079e128243 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:54:09 +0100 Subject: [PATCH 191/396] Added a filename chooser widget. --- src/ascii_dialog/dialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9cf4e9ce2f..2a391f4b41 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -33,6 +33,7 @@ def __init__(self): } self.filename_label = QLabel("Click the button below to load a file.") + self.filename_chooser = QComboBox() self.load_button = QPushButton("Load File") self.load_button.clicked.connect(self.load) @@ -97,6 +98,7 @@ def __init__(self): self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) + self.layout.addWidget(self.filename_chooser) self.layout.addWidget(self.load_button) self.layout.addLayout(self.dataset_layout) self.layout.addLayout(self.sep_layout) @@ -235,7 +237,10 @@ def load(self) -> None: if result[1] == '': return filename = result[0] - self.filename_label.setText(path.basename(filename)) + basename = path.basename(filename) + self.filename_label.setText(basename) + self.filename_chooser.addItem(basename) + self.filename_chooser.setCurrentText(basename) try: with open(filename) as file: From ce7f305334e91705ec07ddaf261b021f341354fc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 09:30:31 +0100 Subject: [PATCH 192/396] Add an event for when the current file changes. --- src/ascii_dialog/dialog.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2a391f4b41..ce16847a82 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -34,6 +34,7 @@ def __init__(self): self.filename_label = QLabel("Click the button below to load a file.") self.filename_chooser = QComboBox() + self.filename_chooser.currentTextChanged.connect(self.update_current_file) self.load_button = QPushButton("Load File") self.load_button.clicked.connect(self.load) @@ -239,20 +240,22 @@ def load(self) -> None: filename = result[0] basename = path.basename(filename) self.filename_label.setText(basename) - self.filename_chooser.addItem(basename) - self.filename_chooser.setCurrentText(basename) try: with open(filename) as file: file_csv = file.readlines() # TODO: This assumes that no two files will be loaded with the same # name. This might not be a reasonable assumption. - self.files[filename] = file_csv - self.current_filename = filename + self.files[basename] = file_csv + self.current_filename = basename # Reset checkboxes self.rows_is_included = [] self.attempt_guesses() - self.fill_table() + # This will trigger the update current file event which will cause + # the table to be drawn. + self.filename_chooser.addItem(basename) + self.filename_chooser.setCurrentText(basename) + except OSError: QMessageBox.critical(self, 'File Read Error', ' There was an error reading that file.') @@ -283,6 +286,15 @@ def update_column(self) -> None: duplicates = self.duplicate_columns() self.warning_label.update(required_missing, duplicates) + @Slot() + def update_current_file(self) -> None: + """Triggered when the current file (choosen from the file chooser + ComboBox) changes. + + """ + self.current_filename = self.filename_chooser.currentText() + self.fill_table() + @Slot() def seperator_toggle(self) -> None: """Triggered when one of the seperator check boxes has been toggled.""" From 6cccf578c8fefb7d2f9266e29753cab8d53c7d5b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 09:38:09 +0100 Subject: [PATCH 193/396] Changed when guesses are attempted. --- src/ascii_dialog/dialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ce16847a82..73d7d657e5 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -250,7 +250,9 @@ def load(self) -> None: self.current_filename = basename # Reset checkboxes self.rows_is_included = [] - self.attempt_guesses() + # Attempt guesses when this is the first file that has been loaded. + if len(self.files) == 1: + self.attempt_guesses() # This will trigger the update current file event which will cause # the table to be drawn. self.filename_chooser.addItem(basename) From 588c470fe1cb3a85d782f6f0dcbddc0f6f844a35 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 09:48:11 +0100 Subject: [PATCH 194/396] Track rows is included seperately for each file. --- src/ascii_dialog/dialog.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 73d7d657e5..f780378a80 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -24,6 +24,7 @@ def __init__(self): super().__init__() self.files: dict[str, list[str]] = {} + self.files_is_included: dict[str, list[bool]] = {} self.current_filename: str | None = None self.seperators: dict[str, bool] = { @@ -109,14 +110,19 @@ def __init__(self): self.layout.addWidget(self.table) self.layout.addWidget(self.warning_label) - self.rows_is_included: list[bool] = [] - @property def raw_csv(self) -> list[str] | None: if self.current_filename is None: return None return self.files[self.current_filename] + @property + def rows_is_included(self) -> list[bool] | None: + if self.current_filename is None: + return None + return self.files_is_included[self.current_filename] + + def split_line(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has selected on the widget. @@ -249,7 +255,7 @@ def load(self) -> None: self.files[basename] = file_csv self.current_filename = basename # Reset checkboxes - self.rows_is_included = [] + self.files_is_included[basename] = [] # Attempt guesses when this is the first file that has been loaded. if len(self.files) == 1: self.attempt_guesses() From 215e0f7e56d8b67bb3d641c6324237fc91106775 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 10:07:50 +0100 Subject: [PATCH 195/396] New widget for unit preferences. --- src/ascii_dialog/unit_preferences.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/ascii_dialog/unit_preferences.py diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py new file mode 100644 index 0000000000..bc65be358d --- /dev/null +++ b/src/ascii_dialog/unit_preferences.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +from PySide6.QtWidgets import QApplication, QWidget + +class UnitPreferences(QWidget): + def __init__(self): + super().__init__() + +if __name__ == "__main__": + app = QApplication([]) + + widget = UnitPreferences() + widget.show() + + exit(app.exec()) From 977a2f8143f7c7ade69aea7f5b60156bbe51e545 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 10:52:50 +0100 Subject: [PATCH 196/396] Created a preference line widget. --- src/ascii_dialog/unit_preference_line.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/ascii_dialog/unit_preference_line.py diff --git a/src/ascii_dialog/unit_preference_line.py b/src/ascii_dialog/unit_preference_line.py new file mode 100644 index 0000000000..29af4f75bb --- /dev/null +++ b/src/ascii_dialog/unit_preference_line.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +from PySide6.QtCore import Slot +from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QWidget +from sasdata.quantities.units import NamedUnit, UnitGroup + +from unit_selector import UnitSelector + +class UnitPreferenceLine(QWidget): + def __init__(self, column_name: str, initial_unit: NamedUnit, group: UnitGroup): + super().__init__() + + self.group = group + self.current_unit = initial_unit + + self.column_label = QLabel(column_name) + self.unit_button = QPushButton(initial_unit.symbol) + self.unit_button.clicked.connect(self.on_unit_press) + + self.layout = QHBoxLayout() + self.layout.addWidget(self.column_label) + self.layout.addWidget(self.unit_button) + + @Slot() + def on_unit_press(self): + picker = UnitSelector(self.group.name, False) + picker.exec() + self.current_unit = picker.selected_unit + self.unit_button.setText(self.current_unit.symbol) From 13434b5ad399a62422dbda072223787a84ec172b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 10:57:58 +0100 Subject: [PATCH 197/396] Add a line for each column. --- src/ascii_dialog/unit_preferences.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py index bc65be358d..c9584f4b82 100644 --- a/src/ascii_dialog/unit_preferences.py +++ b/src/ascii_dialog/unit_preferences.py @@ -1,11 +1,29 @@ #!/usr/bin/env python3 -from PySide6.QtWidgets import QApplication, QWidget +from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget +from sasdata.quantities.units import NamedUnit +from sasdata.dataset_types import unit_kinds +from unit_preference_line import UnitPreferenceLine +import random class UnitPreferences(QWidget): def __init__(self): super().__init__() + # TODO: Presumably this will be loaded from some config from somewhere. + # For now just fill it with some placeholder values. + column_names = unit_kinds.keys() + self.columns: dict[str, NamedUnit] = {} + for name in column_names: + self.columns[name] = random.choice(unit_kinds[name].units) + + self.layout = QVBoxLayout() + for column_name, unit in self.columns.items(): + line = UnitPreferenceLine(column_name, unit, unit_kinds[column_name]) + self.layout.addWidget(line) + + + if __name__ == "__main__": app = QApplication([]) From 8e0d0c669b622dc4469a76037de41b5bb653b74a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:10:36 +0100 Subject: [PATCH 198/396] Forgot to set layout parent. --- src/ascii_dialog/unit_preferences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py index c9584f4b82..6ad556719e 100644 --- a/src/ascii_dialog/unit_preferences.py +++ b/src/ascii_dialog/unit_preferences.py @@ -17,7 +17,7 @@ def __init__(self): for name in column_names: self.columns[name] = random.choice(unit_kinds[name].units) - self.layout = QVBoxLayout() + self.layout = QVBoxLayout(self) for column_name, unit in self.columns.items(): line = UnitPreferenceLine(column_name, unit, unit_kinds[column_name]) self.layout.addWidget(line) From 0d33b8cbb20379dcd60d059689ef1276dfc0169f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:12:34 +0100 Subject: [PATCH 199/396] Again forgot parent :P --- src/ascii_dialog/unit_preference_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_preference_line.py b/src/ascii_dialog/unit_preference_line.py index 29af4f75bb..888db3a51d 100644 --- a/src/ascii_dialog/unit_preference_line.py +++ b/src/ascii_dialog/unit_preference_line.py @@ -17,7 +17,7 @@ def __init__(self, column_name: str, initial_unit: NamedUnit, group: UnitGroup): self.unit_button = QPushButton(initial_unit.symbol) self.unit_button.clicked.connect(self.on_unit_press) - self.layout = QHBoxLayout() + self.layout = QHBoxLayout(self) self.layout.addWidget(self.column_label) self.layout.addWidget(self.unit_button) From 3f235429c8b339d932c746bf630d0a67d19f3252 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:20:27 +0100 Subject: [PATCH 200/396] Put the preferences in a scroll area. --- src/ascii_dialog/unit_preferences.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py index 6ad556719e..19aaa7e5ed 100644 --- a/src/ascii_dialog/unit_preferences.py +++ b/src/ascii_dialog/unit_preferences.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget +from PySide6.QtWidgets import QApplication, QScrollArea, QVBoxLayout, QWidget from sasdata.quantities.units import NamedUnit from sasdata.dataset_types import unit_kinds from unit_preference_line import UnitPreferenceLine @@ -18,10 +18,15 @@ def __init__(self): self.columns[name] = random.choice(unit_kinds[name].units) self.layout = QVBoxLayout(self) + preference_lines = QWidget() + scroll_area = QScrollArea() + scroll_layout = QVBoxLayout(preference_lines) for column_name, unit in self.columns.items(): line = UnitPreferenceLine(column_name, unit, unit_kinds[column_name]) - self.layout.addWidget(line) + scroll_layout.addWidget(line) + scroll_area.setWidget(preference_lines) + self.layout.addWidget(scroll_area) if __name__ == "__main__": From c8ff2f7531687771b6d498629c88c192ba8a6f44 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:33:07 +0100 Subject: [PATCH 201/396] Turn off the horizontal scroll. --- src/ascii_dialog/unit_preferences.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py index 19aaa7e5ed..fb49fc8879 100644 --- a/src/ascii_dialog/unit_preferences.py +++ b/src/ascii_dialog/unit_preferences.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from PySide6.QtGui import Qt from PySide6.QtWidgets import QApplication, QScrollArea, QVBoxLayout, QWidget from sasdata.quantities.units import NamedUnit from sasdata.dataset_types import unit_kinds @@ -20,6 +21,7 @@ def __init__(self): self.layout = QVBoxLayout(self) preference_lines = QWidget() scroll_area = QScrollArea() + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) scroll_layout = QVBoxLayout(preference_lines) for column_name, unit in self.columns.items(): line = UnitPreferenceLine(column_name, unit, unit_kinds[column_name]) From 0eab8780c740e167a6dbf40619f49e7f958ca102 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 16:01:00 +0100 Subject: [PATCH 202/396] Added a selection menu widget. --- src/ascii_dialog/selection_menu.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/ascii_dialog/selection_menu.py diff --git a/src/ascii_dialog/selection_menu.py b/src/ascii_dialog/selection_menu.py new file mode 100644 index 0000000000..cfbb8fcaba --- /dev/null +++ b/src/ascii_dialog/selection_menu.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + + +from PySide6.QtCore import Signal +from PySide6.QtGui import QAction +from PySide6.QtWidgets import QMenu + +class SelectionMenu(QMenu): + select_all_event = Signal() + deselect_all_event = Signal() + + def __init__(self): + super().__init__() + + select_all = QAction("Select All") + select_all.triggered.connect(self.select_all_event) + + deselect_all = QAction("Deselect All") + deselect_all.triggered.connect(self.deselect_all_event) + + self.addAction(select_all) + self.addAction(deselect_all) From b916ebd8e098f2da79fc2893016a27f8a4221382 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 08:34:11 +0100 Subject: [PATCH 203/396] Got the context menu to work. --- src/ascii_dialog/dialog.py | 31 ++++++++++++++++++++++++++++-- src/ascii_dialog/selection_menu.py | 10 +++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f780378a80..adb57e60fd 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,6 +1,7 @@ -from PySide6.QtGui import QColor, Qt +from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, Qt from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication -from PySide6.QtCore import Slot +from PySide6.QtCore import QModelIndex, QPoint, Slot +from selection_menu import SelectionMenu from warning_label import WarningLabel from col_editor import ColEditor from row_status_widget import RowStatusWidget @@ -93,6 +94,9 @@ def __init__(self): self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) # The table's width will always resize to fit the amount of space it has. self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) + # Add the context menu + self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.table.customContextMenuRequested.connect(self.show_context_menu) # Warning Label self.warning_label = WarningLabel(self.required_missing(), self.duplicate_columns()) @@ -327,6 +331,29 @@ def update_row_status(self, row: int) -> None: self.rows_is_included[row] = new_status self.set_row_typesetting(row, new_status) + @Slot() + def show_context_menu(self, point: QPoint) -> None: + """Show the context menu for the table.""" + context_menu = SelectionMenu(self) + context_menu.select_all_event.connect(self.select_items) + context_menu.deselect_all_event.connect(self.deselect_items) + context_menu.exec(QCursor.pos()) + + def change_inclusion(self, indexes: list[QModelIndex], new_value: bool): + for index in indexes: + row = index.row() + self.rows_is_included[row] = new_value + + @Slot() + def select_items(self) -> None: + """Include all of the items that have been selected in the table.""" + self.change_inclusion(self.table.selectedIndexes(), True) + + @Slot() + def deselect_items(self) -> None: + """Don't include all of the items that have been selected in the table.""" + self.change_inclusion(self.table.selectedIndexes(), False) + def required_missing(self) -> list[str]: """Returns all the columns that are required by the dataset type but have not currently been selected. diff --git a/src/ascii_dialog/selection_menu.py b/src/ascii_dialog/selection_menu.py index cfbb8fcaba..ef95ac04d5 100644 --- a/src/ascii_dialog/selection_menu.py +++ b/src/ascii_dialog/selection_menu.py @@ -3,19 +3,19 @@ from PySide6.QtCore import Signal from PySide6.QtGui import QAction -from PySide6.QtWidgets import QMenu +from PySide6.QtWidgets import QMenu, QWidget class SelectionMenu(QMenu): select_all_event = Signal() deselect_all_event = Signal() - def __init__(self): - super().__init__() + def __init__(self, parent: QWidget): + super().__init__(parent) - select_all = QAction("Select All") + select_all = QAction("Select All", parent) select_all.triggered.connect(self.select_all_event) - deselect_all = QAction("Deselect All") + deselect_all = QAction("Deselect All", parent) deselect_all.triggered.connect(self.deselect_all_event) self.addAction(select_all) From a0170dce80e38f92079a205bbd5260e6da2ab33b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 09:36:05 +0100 Subject: [PATCH 204/396] Set the checkbox first then update the rest. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index adb57e60fd..70fcb5118b 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -342,7 +342,8 @@ def show_context_menu(self, point: QPoint) -> None: def change_inclusion(self, indexes: list[QModelIndex], new_value: bool): for index in indexes: row = index.row() - self.rows_is_included[row] = new_value + self.table.cellWidget(row, 0).setChecked(new_value) + self.update_row_status(row) @Slot() def select_items(self) -> None: From b86fe90a0b8e688705d9fa238c1cc156b03fbebc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 10:36:19 +0100 Subject: [PATCH 205/396] Use the new list in sasview rather than hardcoding --- src/ascii_dialog/unit_selector.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index ea1cd3bc90..9c0f4bb1c6 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,15 +1,10 @@ from PySide6.QtCore import Slot from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QListWidget, QPushButton, QVBoxLayout, QWidget -from sasdata.quantities.units import NamedUnit, UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance +from sasdata.quantities.units import NamedUnit, UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance, unit_group_names, unit_groups from unit_list_widget import UnitListWidget -# TODO: Ask Lucas if this list can be in his code (or if it already is and I -# can't find it). I am lazy so only doing a subsection for now. - -all_unit_groups = [ - length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance -] +all_unit_groups = list(unit_groups.values()) class UnitSelector(QDialog): def current_unit_group(self) -> UnitGroup: @@ -44,7 +39,6 @@ def __init__(self, default_group='length', allow_group_edit=True): super().__init__() self.unit_type_selector = QComboBox() - unit_group_names = [group.name for group in all_unit_groups] self.unit_type_selector.addItems(unit_group_names) self.unit_type_selector.setCurrentText(default_group) if not allow_group_edit: From 666b0769dc26159475a00b87c7e0916bc00f4526 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 13:30:06 +0100 Subject: [PATCH 206/396] Removed some unused imports. --- src/ascii_dialog/unit_selector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 9c0f4bb1c6..a9c7504805 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,6 +1,6 @@ from PySide6.QtCore import Slot -from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QListWidget, QPushButton, QVBoxLayout, QWidget -from sasdata.quantities.units import NamedUnit, UnitGroup, length, area, volume, inverse_length, inverse_area, inverse_volume, time, rate, speed, density, force, pressure, energy, power, charge, potential, resistance, unit_group_names, unit_groups +from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QPushButton, QVBoxLayout +from sasdata.quantities.units import NamedUnit, UnitGroup, unit_group_names, unit_groups from unit_list_widget import UnitListWidget From a29595c388a873ae6f45a1dd8cb1a2f8680232ed Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 13:33:55 +0100 Subject: [PATCH 207/396] Don't enable the select button immediately. --- src/ascii_dialog/unit_selector.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index a9c7504805..fbe55e2470 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -35,6 +35,10 @@ def unit_group_changed(self): def select_unit(self): self.accept() + @Slot() + def selection_changed(self): + self.select_button.setDisabled(False) + def __init__(self, default_group='length', allow_group_edit=True): super().__init__() @@ -52,10 +56,11 @@ def __init__(self, default_group='length', allow_group_edit=True): self.unit_list_widget = UnitListWidget() # TODO: Are they all named units? self.unit_list_widget.populate_list(self.current_unit_group().units) - self.unit_list_widget.itemDoubleClicked.connect(self.select_unit) + self.unit_list_widget.itemSelectionChanged.connect(self.selection_changed) self.select_button = QPushButton('Select Unit') self.select_button.pressed.connect(self.select_unit) + self.select_button.setDisabled(True) self.layout = QVBoxLayout(self) self.layout.addWidget(self.unit_type_selector) From d83fadfb5a00daccfff5221868635e57c9ae07bb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 14 Aug 2024 13:22:24 +0100 Subject: [PATCH 208/396] Unpinned pyside6. Probably will need to change this later. I only did this because this specific version doesn't work with Python 3.12 which Lucas' unit system needs. --- build_tools/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_tools/requirements.txt b/build_tools/requirements.txt index 91f85fd0d0..d4b7f93d15 100644 --- a/build_tools/requirements.txt +++ b/build_tools/requirements.txt @@ -40,4 +40,4 @@ zope pywin32; platform_system == "Windows" # Alphabetized list of version-pinned packages -PySide6==6.4.3 # Later versions do not mesh well with pyinstaller < 6.0 +PySide6 From 78910b4033303dbdf9c245540a532cedd03034ee Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Sep 2024 09:09:25 +0100 Subject: [PATCH 209/396] Fixed typo. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 70fcb5118b..c60e5967bf 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -365,7 +365,7 @@ def required_missing(self) -> list[str]: return missing_columns def duplicate_columns(self) -> set[str]: - """Returns all of the columns which have been sselected multiple times.""" + """Returns all of the columns which have been selected multiple times.""" col_names = self.col_editor.col_names() return set([col for col in col_names if not col == '' and col_names.count(col) > 1]) From 1bc1421b34a64b7601b1060f9a56b66c18432f63 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Sep 2024 09:12:12 +0100 Subject: [PATCH 210/396] Remvoed shabang. --- src/ascii_dialog/guess.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index f9acae26f3..96ba9bc210 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - from sasdata.dataset_types import DatasetType def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: From 76555d6a7a6a01ea0e1a67c0ca25ffc32fab9605 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Sep 2024 09:23:56 +0100 Subject: [PATCH 211/396] Fixed casing. --- src/ascii_dialog/col_editor.py | 14 +-- src/ascii_dialog/column_unit.py | 32 +++--- src/ascii_dialog/dialog.py | 122 +++++++++++------------ src/ascii_dialog/row_status_widget.py | 10 +- src/ascii_dialog/unit_list_widget.py | 8 +- src/ascii_dialog/unit_preference_line.py | 4 +- src/ascii_dialog/unit_selector.py | 30 +++--- src/ascii_dialog/warning_label.py | 10 +- 8 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 32d0e0a2b3..064e8d079f 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -10,7 +10,7 @@ class ColEditor(QWidget): column_changed = Signal() @Slot() - def on_column_update(self): + def onColumnUpdate(self): self.column_changed.emit() @@ -23,11 +23,11 @@ def __init__(self, cols: int, options: list[str]): self.option_widgets = [] for _ in range(cols): new_widget = ColumnUnit(self.options) - new_widget.column_changed.connect(self.on_column_update) + new_widget.column_changed.connect(self.onColumnUpdate) self.layout.addWidget(new_widget) self.option_widgets.append(new_widget) - def set_cols(self, new_cols: int): + def setCols(self, new_cols: int): """Set the amount of columns for the user to edit.""" # Decides whether we need to extend the current set of combo boxes, or @@ -35,7 +35,7 @@ def set_cols(self, new_cols: int): if self.cols < new_cols: for _ in range(new_cols - self.cols): new_widget = ColumnUnit(self.options) - new_widget.column_changed.connect(self.on_column_update) + new_widget.column_changed.connect(self.onColumnUpdate) self.layout.addWidget(new_widget) self.option_widgets.append(new_widget) @@ -51,7 +51,7 @@ def set_cols(self, new_cols: int): self.cols = new_cols self.column_changed.emit() - def set_col_order(self, cols: list[str]): + def setColOrder(self, cols: list[str]): """Sets the series of currently selected columns to be cols, in that order. If there are not enough column widgets include as many of the columns in cols as possible. @@ -63,11 +63,11 @@ def set_col_order(self, cols: list[str]): except IndexError: pass # Can ignore because it means we've run out of widgets. - def col_names(self) -> list[str]: + def colNames(self) -> list[str]: """Get a list of all of the currently selected columns.""" return [widget.current_column for widget in self.option_widgets] - def replace_options(self, new_options: list[str]) -> None: + def replaceOptions(self, new_options: list[str]) -> None: """Replace options from which the user can choose for each column.""" self.options = new_options for widget in self.option_widgets: diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index c400ca3372..e8e7d1a162 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -13,15 +13,15 @@ class ColumnUnit(QWidget): another to specify the units for that column.""" def __init__(self, options) -> None: super().__init__() - self.col_widget = self.create_col_combo_box(options) - self.unit_widget = self.create_unit_combo_box(self.col_widget.currentText()) + self.col_widget = self.createColComboBox(options) + self.unit_widget = self.createUnitComboBox(self.col_widget.currentText()) self.layout = QHBoxLayout(self) self.layout.addWidget(self.col_widget) self.layout.addWidget(self.unit_widget) column_changed = Signal() - def create_col_combo_box(self, options: list[str]) -> QComboBox: + def createColComboBox(self, options: list[str]) -> QComboBox: """Create the combo box for specifying the column based on the given options.""" new_combo_box = QComboBox() @@ -30,21 +30,21 @@ def create_col_combo_box(self, options: list[str]) -> QComboBox: new_combo_box.setEditable(True) validator = QRegularExpressionValidator(r"[a-zA-Z0-9]+") new_combo_box.setValidator(validator) - new_combo_box.currentTextChanged.connect(self.on_option_change) + new_combo_box.currentTextChanged.connect(self.onOptionChange) return new_combo_box - def create_unit_combo_box(self, selected_option: str) -> QComboBox: + def createUnitComboBox(self, selected_option: str) -> QComboBox: """Create the combo box for specifying the unit for selected_option""" new_combo_box = QComboBox() new_combo_box.setEditable(True) # word_list = ['alpha', 'omega', 'omicron', 'zeta'] # completer = QCompleter(word_list, self) # new_combo_box.setCompleter(completer) - self.update_units(new_combo_box, selected_option) - new_combo_box.currentTextChanged.connect(self.on_unit_change) + self.updateUnits(new_combo_box, selected_option) + new_combo_box.currentTextChanged.connect(self.onUnitChange) return new_combo_box - def update_units(self, unit_box: QComboBox, selected_option: str): + def updateUnits(self, unit_box: QComboBox, selected_option: str): unit_box.clear() options = [unit.symbol for unit in unit_kinds[selected_option].units] # We don't have preferred units yet. In order to simulate this, just @@ -54,19 +54,19 @@ def update_units(self, unit_box: QComboBox, selected_option: str): unit_box.addItem('Select More') - def replace_options(self, new_options) -> None: + def replaceOptions(self, new_options) -> None: """Replace the old options for the column with new_options""" self.col_widget.clear() self.col_widget.addItems(new_options) - def set_current_column(self, new_column_value: str) -> None: + def setCurrentColumn(self, new_column_value: str) -> None: """Change the current selected column to new_column_value""" self.col_widget.setCurrentText(new_column_value) - self.update_units(self.unit_widget, new_column_value) + self.updateUnits(self.unit_widget, new_column_value) @Slot() - def on_option_change(self): + def onOptionChange(self): # If the new option is empty string, its probably because the current # options have been removed. Can safely ignore this. self.column_changed.emit() @@ -74,7 +74,7 @@ def on_option_change(self): if new_option == '': return try: - self.update_units(self.unit_widget, new_option) + self.updateUnits(self.unit_widget, new_option) except KeyError: # Means the units for this column aren't known. This shouldn't be # the case in the real version so for now we'll just clear the unit @@ -82,18 +82,18 @@ def on_option_change(self): self.unit_widget.clear() @Slot() - def on_unit_change(self): + def onUnitChange(self): if self.unit_widget.currentText() == 'Select More': selector = UnitSelector(unit_kinds[self.col_widget.currentText()].name, False) selector.exec() self.unit_widget.setCurrentText(selector.selected_unit.symbol) @property - def current_column(self): + def currentColumn(self): """The currently selected column.""" return self.col_widget.currentText() @property - def current_unit(self): + def currentUnit(self): """The currently selected unit.""" return symbol_lookup[self.unit_widget.currentText()] diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c60e5967bf..d2aa0da070 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -36,7 +36,7 @@ def __init__(self): self.filename_label = QLabel("Click the button below to load a file.") self.filename_chooser = QComboBox() - self.filename_chooser.currentTextChanged.connect(self.update_current_file) + self.filename_chooser.currentTextChanged.connect(self.updateCurrentFile) self.load_button = QPushButton("Load File") self.load_button.clicked.connect(self.load) @@ -59,7 +59,7 @@ def __init__(self): for seperator_name, value in self.seperators.items(): check_box = QCheckBox(seperator_name) check_box.setChecked(value) - check_box.clicked.connect(self.seperator_toggle) + check_box.clicked.connect(self.seperatorToggle) self.sep_widgets.append(check_box) self.sep_layout.addWidget(check_box) @@ -67,7 +67,7 @@ def __init__(self): self.startline_layout = QHBoxLayout() self.startline_label = QLabel('Starting Line') self.startline_entry = QSpinBox() - self.startline_entry.valueChanged.connect(self.update_startpos) + self.startline_entry.valueChanged.connect(self.updateStartpos) self.startline_layout.addWidget(self.startline_label) self.startline_layout.addWidget(self.startline_entry) @@ -76,15 +76,15 @@ def __init__(self): self.colcount_label = QLabel('Number of Columns') self.colcount_entry = QSpinBox() self.colcount_entry.setMinimum(1) - self.colcount_entry.valueChanged.connect(self.update_colcount) + self.colcount_entry.valueChanged.connect(self.updateColcount) self.colcount_layout.addWidget(self.colcount_label) self.colcount_layout.addWidget(self.colcount_entry) ## Column Editor - options = self.dataset_options() + options = self.datasetOptions() self.col_editor = ColEditor(self.colcount_entry.value(), options) - self.dataset_combobox.currentTextChanged.connect(self.change_dataset_type) - self.col_editor.column_changed.connect(self.update_column) + self.dataset_combobox.currentTextChanged.connect(self.changeDatasetType) + self.col_editor.column_changed.connect(self.updateColumn) ## Data Table @@ -96,10 +96,10 @@ def __init__(self): self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) # Add the context menu self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) - self.table.customContextMenuRequested.connect(self.show_context_menu) + self.table.customContextMenuRequested.connect(self.showContextMenu) # Warning Label - self.warning_label = WarningLabel(self.required_missing(), self.duplicate_columns()) + self.warning_label = WarningLabel(self.requiredMissing(), self.duplicateColumns()) self.layout = QVBoxLayout(self) @@ -127,7 +127,7 @@ def rows_is_included(self) -> list[bool] | None: return self.files_is_included[self.current_filename] - def split_line(self, line: str) -> list[str]: + def splitLine(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has selected on the widget. @@ -148,25 +148,25 @@ def split_line(self, line: str) -> list[str]: return re.split(expr, line) - def attempt_guesses(self) -> None: + def attemptGuesses(self) -> None: """Attempt to guess various parameters of the data to provide some default values. Uses the guess.py module """ - split_csv = [self.split_line(line.strip()) for line in self.raw_csv] + split_csv = [self.splitLine(line.strip()) for line in self.raw_csv] self.initial_starting_pos = guess_starting_position(split_csv) guessed_colcount = guess_column_count(split_csv, self.initial_starting_pos) - self.col_editor.set_cols(guessed_colcount) + self.col_editor.setCols(guessed_colcount) - columns = guess_columns(guessed_colcount, self.current_dataset_type()) - self.col_editor.set_col_order(columns) + columns = guess_columns(guessed_colcount, self.currentDatasetType()) + self.col_editor.setColOrder(columns) self.colcount_entry.setValue(guessed_colcount) self.startline_entry.setValue(self.initial_starting_pos) - def fill_table(self) -> None: + def fillTable(self) -> None: """Write the data to the table based on the parameters the user has selected. @@ -183,7 +183,7 @@ def fill_table(self) -> None: self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS + 1)) self.table.setColumnCount(col_count + 1) - self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.col_names()) + self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.colNames()) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # Now fill the table with data @@ -203,23 +203,23 @@ def fill_table(self) -> None: self.rows_is_included.append(initial_state) if i >= starting_pos: row_status = RowStatusWidget(initial_state, i) - row_status.status_changed.connect(self.update_row_status) + row_status.status_changed.connect(self.updateRowStatus) self.table.setCellWidget(i, 0, row_status) - row_split = self.split_line(row) + row_split = self.splitLine(row) for j, col_value in enumerate(row_split): if j >= col_count: continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) self.table.setItem(i, j + 1, item) - self.set_row_typesetting(i, self.rows_is_included[i]) + self.setRowTypesetting(i, self.rows_is_included[i]) self.table.show() - def current_dataset_type(self) -> DatasetType: + def currentDatasetType(self) -> DatasetType: """Get the dataset type that the user has currently selected.""" return dataset_dictionary[self.dataset_combobox.currentText()] - def set_row_typesetting(self, row: int, item_checked: bool) -> None: + def setRowTypesetting(self, row: int, item_checked: bool) -> None: """Set the typesetting for the given role depending on whether it is to be included in the data being loaded, or not. @@ -262,7 +262,7 @@ def load(self) -> None: self.files_is_included[basename] = [] # Attempt guesses when this is the first file that has been loaded. if len(self.files) == 1: - self.attempt_guesses() + self.attemptGuesses() # This will trigger the update current file event which will cause # the table to be drawn. self.filename_chooser.addItem(basename) @@ -272,105 +272,105 @@ def load(self) -> None: QMessageBox.critical(self, 'File Read Error', ' There was an error reading that file.') @Slot() - def update_colcount(self) -> None: + def updateColcount(self) -> None: """Triggered when the amount of columns the user has selected has changed. """ - self.col_editor.set_cols(self.colcount_entry.value()) - self.fill_table() + self.col_editor.setCols(self.colcount_entry.value()) + self.fillTable() @Slot() - def update_startpos(self) -> None: + def updateStartpos(self) -> None: """Triggered when the starting position of the data has changed.""" - self.fill_table() + self.fillTable() @Slot() - def update_seperator(self) -> None: + def updateSeperator(self) -> None: """Changed when the user modifies the set of seperators being used.""" - self.fill_table() + self.fillTable() @Slot() - def update_column(self) -> None: + def updateColumn(self) -> None: """Triggered when any of the columns has been changed.""" - self.fill_table() - required_missing = self.required_missing() - duplicates = self.duplicate_columns() + self.fillTable() + required_missing = self.requiredMissing() + duplicates = self.duplicateColumns() self.warning_label.update(required_missing, duplicates) @Slot() - def update_current_file(self) -> None: + def updateCurrentFile(self) -> None: """Triggered when the current file (choosen from the file chooser ComboBox) changes. """ self.current_filename = self.filename_chooser.currentText() - self.fill_table() + self.fillTable() @Slot() - def seperator_toggle(self) -> None: + def seperatorToggle(self) -> None: """Triggered when one of the seperator check boxes has been toggled.""" check_box = self.sender() self.seperators[check_box.text()] = check_box.isChecked() - self.fill_table() + self.fillTable() @Slot() - def change_dataset_type(self) -> None: + def changeDatasetType(self) -> None: """Triggered when the selected dataset type has changed.""" - options = self.dataset_options() - self.col_editor.replace_options(options) + options = self.datasetOptions() + self.col_editor.replaceOptions(options) # Update columns as they'll be different now. - columns = guess_columns(self.colcount_entry.value(), self.current_dataset_type()) - self.col_editor.set_col_order(columns) + columns = guess_columns(self.colcount_entry.value(), self.currentDatasetType()) + self.col_editor.setColOrder(columns) @Slot() - def update_row_status(self, row: int) -> None: + def updateRowStatus(self, row: int) -> None: """Triggered when the status of row has changed.""" new_status = self.table.cellWidget(row, 0).isChecked() self.rows_is_included[row] = new_status - self.set_row_typesetting(row, new_status) + self.setRowTypesetting(row, new_status) @Slot() - def show_context_menu(self, point: QPoint) -> None: + def showContextMenu(self, point: QPoint) -> None: """Show the context menu for the table.""" context_menu = SelectionMenu(self) - context_menu.select_all_event.connect(self.select_items) - context_menu.deselect_all_event.connect(self.deselect_items) + context_menu.select_all_event.connect(self.selectItems) + context_menu.deselect_all_event.connect(self.deselectItems) context_menu.exec(QCursor.pos()) - def change_inclusion(self, indexes: list[QModelIndex], new_value: bool): + def changeInclusion(self, indexes: list[QModelIndex], new_value: bool): for index in indexes: row = index.row() self.table.cellWidget(row, 0).setChecked(new_value) - self.update_row_status(row) + self.updateRowStatus(row) @Slot() - def select_items(self) -> None: + def selectItems(self) -> None: """Include all of the items that have been selected in the table.""" - self.change_inclusion(self.table.selectedIndexes(), True) + self.changeInclusion(self.table.selectedIndexes(), True) @Slot() - def deselect_items(self) -> None: + def deselectItems(self) -> None: """Don't include all of the items that have been selected in the table.""" - self.change_inclusion(self.table.selectedIndexes(), False) + self.changeInclusion(self.table.selectedIndexes(), False) - def required_missing(self) -> list[str]: + def requiredMissing(self) -> list[str]: """Returns all the columns that are required by the dataset type but have not currently been selected. """ - dataset = self.current_dataset_type() - missing_columns = [col for col in dataset.required if col not in self.col_editor.col_names()] + dataset = self.currentDatasetType() + missing_columns = [col for col in dataset.required if col not in self.col_editor.colNames()] return missing_columns - def duplicate_columns(self) -> set[str]: + def duplicateColumns(self) -> set[str]: """Returns all of the columns which have been selected multiple times.""" - col_names = self.col_editor.col_names() + col_names = self.col_editor.colNames() return set([col for col in col_names if not col == '' and col_names.count(col) > 1]) - def dataset_options(self) -> list[str]: - current_dataset_type = self.current_dataset_type() + def datasetOptions(self) -> list[str]: + current_dataset_type = self.currentDatasetType() return current_dataset_type.required + current_dataset_type.optional + [''] if __name__ == "__main__": diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 077d7c1f77..eade62b67f 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -10,11 +10,11 @@ def __init__(self, initial_value: bool, row: int): super().__init__() self.row = row self.setChecked(initial_value) - self.update_label() - self.stateChanged.connect(self.on_state_change) + self.updateLabel() + self.stateChanged.connect(self.onStateChange) status_changed = Signal(int) - def update_label(self): + def updateLabel(self): """Update the label of the check box depending on whether it is checked, or not.""" if self.isChecked(): @@ -24,6 +24,6 @@ def update_label(self): @Slot() - def on_state_change(self): - self.update_label() + def onStateChange(self): + self.updateLabel() self.status_changed.emit(self.row) diff --git a/src/ascii_dialog/unit_list_widget.py b/src/ascii_dialog/unit_list_widget.py index 6ccd05bd8a..1bd64c70bf 100644 --- a/src/ascii_dialog/unit_list_widget.py +++ b/src/ascii_dialog/unit_list_widget.py @@ -5,18 +5,18 @@ class UnitListWidget(QListWidget): - def repr_unit(self, unit: NamedUnit) -> str: + def reprUnit(self, unit: NamedUnit) -> str: return f"{unit.symbol} ({unit.name})" - def populate_list(self, units: list[NamedUnit]) -> None: + def populateList(self, units: list[NamedUnit]) -> None: self.clear() self.units = units for unit in units: - item = QListWidgetItem(self.repr_unit(unit)) + item = QListWidgetItem(self.reprUnit(unit)) self.addItem(item) @property - def selected_unit(self) -> NamedUnit | None: + def selectedUnit(self) -> NamedUnit | None: return self.units[self.currentRow()] def __init__(self): diff --git a/src/ascii_dialog/unit_preference_line.py b/src/ascii_dialog/unit_preference_line.py index 888db3a51d..5e00d0f571 100644 --- a/src/ascii_dialog/unit_preference_line.py +++ b/src/ascii_dialog/unit_preference_line.py @@ -15,14 +15,14 @@ def __init__(self, column_name: str, initial_unit: NamedUnit, group: UnitGroup): self.column_label = QLabel(column_name) self.unit_button = QPushButton(initial_unit.symbol) - self.unit_button.clicked.connect(self.on_unit_press) + self.unit_button.clicked.connect(self.onUnitPress) self.layout = QHBoxLayout(self) self.layout.addWidget(self.column_label) self.layout.addWidget(self.unit_button) @Slot() - def on_unit_press(self): + def onUnitPress(self): picker = UnitSelector(self.group.name, False) picker.exec() self.current_unit = picker.selected_unit diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index fbe55e2470..32be62a1dc 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -7,36 +7,36 @@ all_unit_groups = list(unit_groups.values()) class UnitSelector(QDialog): - def current_unit_group(self) -> UnitGroup: + def currentUnitGroup(self) -> UnitGroup: index = self.unit_type_selector.currentIndex() return all_unit_groups[index] @property def selected_unit(self) -> NamedUnit | None: - return self.unit_list_widget.selected_unit + return self.unit_list_widget.selectedUnit @Slot() - def on_search_changed(self): + def onSearchChanged(self): search_input = self.search_box.text() - current_group = self.current_unit_group() + current_group = self.currentUnitGroup() units = current_group.units if search_input != '': units = [unit for unit in units if search_input.lower() in unit.name] - self.unit_list_widget.populate_list(units) + self.unit_list_widget.populateList(units) @Slot() - def unit_group_changed(self): - new_group = self.current_unit_group() + def unitGroupChanged(self): + new_group = self.currentUnitGroup() self.search_box.setText('') - self.unit_list_widget.populate_list(new_group.units) + self.unit_list_widget.populateList(new_group.units) @Slot() - def select_unit(self): + def selectUnit(self): self.accept() @Slot() - def selection_changed(self): + def selectionChanged(self): self.select_button.setDisabled(False) def __init__(self, default_group='length', allow_group_edit=True): @@ -47,19 +47,19 @@ def __init__(self, default_group='length', allow_group_edit=True): self.unit_type_selector.setCurrentText(default_group) if not allow_group_edit: self.unit_type_selector.setDisabled(True) - self.unit_type_selector.currentTextChanged.connect(self.unit_group_changed) + self.unit_type_selector.currentTextChanged.connect(self.unitGroupChanged) self.search_box = QLineEdit() - self.search_box.textChanged.connect(self.on_search_changed) + self.search_box.textChanged.connect(self.onSearchChanged) self.search_box.setPlaceholderText('Search for a unit...') self.unit_list_widget = UnitListWidget() # TODO: Are they all named units? - self.unit_list_widget.populate_list(self.current_unit_group().units) - self.unit_list_widget.itemSelectionChanged.connect(self.selection_changed) + self.unit_list_widget.populateList(self.currentUnitGroup().units) + self.unit_list_widget.itemSelectionChanged.connect(self.selectionChanged) self.select_button = QPushButton('Select Unit') - self.select_button.pressed.connect(self.select_unit) + self.select_button.pressed.connect(self.selectUnit) self.select_button.setDisabled(True) self.layout = QVBoxLayout(self) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index fa8ed189cf..0d6eb0ecbb 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -8,10 +8,10 @@ class WarningLabel(QLabel): exists columns that are missing, or there are columns that are duplicated. """ - def set_font_red(self): + def setFontRed(self): self.setStyleSheet("QLabel { color: red}") - def set_font_normal(self): + def setFontNormal(self): self.setStyleSheet('') def update(self, missing_columns, duplicate_columns): @@ -19,13 +19,13 @@ def update(self, missing_columns, duplicate_columns): columns are missing, and how many columns are duplicated.""" if len(missing_columns) != 0: self.setText(f'The following columns are missing: {missing_columns}') - self.set_font_red() + self.setFontRed() elif len(duplicate_columns) > 0: self.setText(f'There are duplicate columns.') - self.set_font_red() + self.setFontRed() else: self.setText('All is fine') # TODO: Probably want to find a more appropriate message. - self.set_font_normal() + self.setFontNormal() def __init__(self, initial_missing_columns, initial_duplicate_classes): super().__init__() From 67b5807fdbaddfcefd2b1f7db3b7430594a4d190 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 09:35:58 +0100 Subject: [PATCH 212/396] Unsplit line. --- src/ascii_dialog/dialog.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d2aa0da070..cb94d08ec5 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -157,8 +157,7 @@ def attemptGuesses(self) -> None: self.initial_starting_pos = guess_starting_position(split_csv) - guessed_colcount = guess_column_count(split_csv, - self.initial_starting_pos) + guessed_colcount = guess_column_count(split_csv, self.initial_starting_pos) self.col_editor.setCols(guessed_colcount) columns = guess_columns(guessed_colcount, self.currentDatasetType()) From f90e2ea6094f7a085e1de3be57e30da935bcf0ca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 09:37:28 +0100 Subject: [PATCH 213/396] Split import into two lines. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index cb94d08ec5..9f0ff96d97 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,5 +1,6 @@ from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, Qt -from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication +from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, \ + QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication from PySide6.QtCore import QModelIndex, QPoint, Slot from selection_menu import SelectionMenu from warning_label import WarningLabel From a5c64fcf0a2102f1721ad5f8c651e8ff0eb53694 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 10:25:04 +0100 Subject: [PATCH 214/396] Looks like a few names didn't change automatically --- src/ascii_dialog/col_editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 064e8d079f..abd398ad72 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -59,16 +59,16 @@ def setColOrder(self, cols: list[str]): """ try: for i, col_name in enumerate(cols): - self.option_widgets[i].set_current_column(col_name) + self.option_widgets[i].setCurrentColumn(col_name) except IndexError: pass # Can ignore because it means we've run out of widgets. def colNames(self) -> list[str]: """Get a list of all of the currently selected columns.""" - return [widget.current_column for widget in self.option_widgets] + return [widget.currentColumn for widget in self.option_widgets] def replaceOptions(self, new_options: list[str]) -> None: """Replace options from which the user can choose for each column.""" self.options = new_options for widget in self.option_widgets: - widget.replace_options(new_options) + widget.replaceOptions(new_options) From 09942b86191a3fc2fa08a25241636dae661d20b5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 10:51:12 +0100 Subject: [PATCH 215/396] Double clicking a unit selects it. Thought this was already here but apparently not. --- src/ascii_dialog/unit_selector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 32be62a1dc..07ffc9e5c6 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -57,6 +57,7 @@ def __init__(self, default_group='length', allow_group_edit=True): # TODO: Are they all named units? self.unit_list_widget.populateList(self.currentUnitGroup().units) self.unit_list_widget.itemSelectionChanged.connect(self.selectionChanged) + self.unit_list_widget.itemDoubleClicked.connect(self.selectUnit) self.select_button = QPushButton('Select Unit') self.select_button.pressed.connect(self.selectUnit) From 1d90603f76f7e178c0f0c48577d3ce239c664d10 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:12:16 +0100 Subject: [PATCH 216/396] Added an unload button. --- src/ascii_dialog/dialog.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9f0ff96d97..002a6422f6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -36,6 +36,8 @@ def __init__(self): } self.filename_label = QLabel("Click the button below to load a file.") + self.unloadButton = QPushButton("Unload") + self.unloadButton.clicked.connect(self.unload) self.filename_chooser = QComboBox() self.filename_chooser.currentTextChanged.connect(self.updateCurrentFile) @@ -105,6 +107,7 @@ def __init__(self): self.layout = QVBoxLayout(self) self.layout.addWidget(self.filename_label) + self.layout.addWidget(self.unloadButton) self.layout.addWidget(self.filename_chooser) self.layout.addWidget(self.load_button) self.layout.addLayout(self.dataset_layout) @@ -271,6 +274,13 @@ def load(self) -> None: except OSError: QMessageBox.critical(self, 'File Read Error', ' There was an error reading that file.') + @Slot() + def unload(self) -> None: + del self.files[self.current_filename] + self.filename_chooser.removeItem(self.filename_chooser.currentIndex()) + # Filename chooser should now revert back to a different file. + self.updateCurrentFile() + @Slot() def updateColcount(self) -> None: """Triggered when the amount of columns the user has selected has From e44e7d0074609ea2f5fc72d51d15bcd62ba4388b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:28:41 +0100 Subject: [PATCH 217/396] Use a horizontal layout. --- src/ascii_dialog/dialog.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 002a6422f6..c195786cd8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -35,9 +35,16 @@ def __init__(self): 'Tab': True } + # Filename, and unload button + + self.filename_unload_layout = QHBoxLayout() self.filename_label = QLabel("Click the button below to load a file.") self.unloadButton = QPushButton("Unload") self.unloadButton.clicked.connect(self.unload) + self.filename_unload_layout.addWidget(self.filename_label) + self.filename_unload_layout.addWidget(self.unloadButton) + + # Filename chooser self.filename_chooser = QComboBox() self.filename_chooser.currentTextChanged.connect(self.updateCurrentFile) @@ -106,8 +113,7 @@ def __init__(self): self.layout = QVBoxLayout(self) - self.layout.addWidget(self.filename_label) - self.layout.addWidget(self.unloadButton) + self.layout.addLayout(self.filename_unload_layout) self.layout.addWidget(self.filename_chooser) self.layout.addWidget(self.load_button) self.layout.addLayout(self.dataset_layout) From 22c5c0a6003298a3bb006cbe65d56b51ab0fb32c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:31:50 +0100 Subject: [PATCH 218/396] Handle case where there are no more files. --- src/ascii_dialog/dialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c195786cd8..efc6263ad1 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -321,7 +321,10 @@ def updateCurrentFile(self) -> None: """ self.current_filename = self.filename_chooser.currentText() - self.fillTable() + if self.current_filename == '': + self.table.clear() + else: + self.fillTable() @Slot() def seperatorToggle(self) -> None: From 79e49ce1a3580330fac002ff5c108f99cf6313e3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:34:22 +0100 Subject: [PATCH 219/396] Disable table when there's no data. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index efc6263ad1..dae5e9d59e 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -323,7 +323,9 @@ def updateCurrentFile(self) -> None: self.current_filename = self.filename_chooser.currentText() if self.current_filename == '': self.table.clear() + self.table.setDisabled(True) else: + self.table.setDisabled(False) self.fillTable() @Slot() From 139ee2af4b44d95185231915681b6c845974944c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:50:39 +0100 Subject: [PATCH 220/396] Handle a unicode decode error. --- src/ascii_dialog/dialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index dae5e9d59e..563d3bbf7e 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -278,7 +278,10 @@ def load(self) -> None: self.filename_chooser.setCurrentText(basename) except OSError: - QMessageBox.critical(self, 'File Read Error', ' There was an error reading that file.') + QMessageBox.critical(self, 'File Read Error', 'There was an error accessing that file.') + except UnicodeDecodeError: + QMessageBox.critical(self, 'File Read Error', """There was an error reading that file. +This could potentially be because the file is not an ASCII format.""") @Slot() def unload(self) -> None: From c5cf089e0beb8c0d58e3c227043cb849b9f4479f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 13:54:05 +0100 Subject: [PATCH 221/396] Set filename label on update. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 563d3bbf7e..64491cd5ea 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -324,6 +324,7 @@ def updateCurrentFile(self) -> None: """ self.current_filename = self.filename_chooser.currentText() + self.filename_label.setText(self.current_filename) if self.current_filename == '': self.table.clear() self.table.setDisabled(True) From ec1b4930dc97bbeeedede4bfc6404a182c51e204 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 13:59:50 +0100 Subject: [PATCH 222/396] Reset the label back to original after unload. --- src/ascii_dialog/dialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 64491cd5ea..d555e4c0fc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -12,6 +12,7 @@ import re TABLE_MAX_ROWS = 1000 +NOFILE_TEXT = "Click the button below to load a file." dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) @@ -38,7 +39,7 @@ def __init__(self): # Filename, and unload button self.filename_unload_layout = QHBoxLayout() - self.filename_label = QLabel("Click the button below to load a file.") + self.filename_label = QLabel(NOFILE_TEXT) self.unloadButton = QPushButton("Unload") self.unloadButton.clicked.connect(self.unload) self.filename_unload_layout.addWidget(self.filename_label) @@ -327,6 +328,7 @@ def updateCurrentFile(self) -> None: self.filename_label.setText(self.current_filename) if self.current_filename == '': self.table.clear() + self.filename_label.setText(NOFILE_TEXT) self.table.setDisabled(True) else: self.table.setDisabled(False) From 8ab825ea1fd19d061ea0f1cf4c8578ff4af54e75 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 14:00:11 +0100 Subject: [PATCH 223/396] For now, ignore the horizontal size hint. --- src/ascii_dialog/column_unit.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index e8e7d1a162..3bdfbde100 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -1,13 +1,18 @@ #!/usr/bin/env python3 from PySide6.QtCore import Signal, Slot -from PySide6.QtWidgets import QComboBox, QCompleter, QHBoxLayout, QWidget +from PySide6.QtWidgets import QComboBox, QCompleter, QHBoxLayout, QSizePolicy, QWidget from PySide6.QtGui import QRegularExpressionValidator from sasdata.dataset_types import unit_kinds from sasdata.quantities.units import symbol_lookup from unit_selector import UnitSelector +def configure_size_policy(combo_box: QComboBox) -> None: + policy = combo_box.sizePolicy() + policy.setHorizontalPolicy(QSizePolicy.Policy.Ignored) + combo_box.setSizePolicy(policy) + class ColumnUnit(QWidget): """Widget with 2 combo boxes: one allowing the user to pick a column, and another to specify the units for that column.""" @@ -25,6 +30,7 @@ def createColComboBox(self, options: list[str]) -> QComboBox: """Create the combo box for specifying the column based on the given options.""" new_combo_box = QComboBox() + configure_size_policy(new_combo_box) for option in options: new_combo_box.addItem(option) new_combo_box.setEditable(True) @@ -36,6 +42,7 @@ def createColComboBox(self, options: list[str]) -> QComboBox: def createUnitComboBox(self, selected_option: str) -> QComboBox: """Create the combo box for specifying the unit for selected_option""" new_combo_box = QComboBox() + configure_size_policy(new_combo_box) new_combo_box.setEditable(True) # word_list = ['alpha', 'omega', 'omicron', 'zeta'] # completer = QCompleter(word_list, self) From df546fbd7ace7e5c7f168c2c1f09fd83a0a9b435 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 14:05:17 +0100 Subject: [PATCH 224/396] Fixed error that sometimes happens when selecting. --- src/ascii_dialog/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d555e4c0fc..2f5918888f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -368,7 +368,11 @@ def showContextMenu(self, point: QPoint) -> None: def changeInclusion(self, indexes: list[QModelIndex], new_value: bool): for index in indexes: + # This will happen if the user has selected a point which exists before the starting line. To prevent an + # error, this code will skip that position. row = index.row() + if row < self.startline_entry.value(): + continue self.table.cellWidget(row, 0).setChecked(new_value) self.updateRowStatus(row) From 9022cc43bc87dd04e3aaa69e9990f000fc98840b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 16:02:29 +0100 Subject: [PATCH 225/396] Disable the button when there's none to unload. --- src/ascii_dialog/dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2f5918888f..37caa9b405 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -41,6 +41,7 @@ def __init__(self): self.filename_unload_layout = QHBoxLayout() self.filename_label = QLabel(NOFILE_TEXT) self.unloadButton = QPushButton("Unload") + self.unloadButton.setDisabled(True) self.unloadButton.clicked.connect(self.unload) self.filename_unload_layout.addWidget(self.filename_label) self.filename_unload_layout.addWidget(self.unloadButton) @@ -330,8 +331,10 @@ def updateCurrentFile(self) -> None: self.table.clear() self.filename_label.setText(NOFILE_TEXT) self.table.setDisabled(True) + self.unloadButton.setDisabled(True) else: self.table.setDisabled(False) + self.unloadButton.setDisabled(False) self.fillTable() @Slot() From f2224f118bf0dd1adf565fb4aced630f8a37f957 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Sep 2024 08:45:36 +0100 Subject: [PATCH 226/396] Set current filename to none when there is none. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 37caa9b405..90b80e0e08 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -332,6 +332,8 @@ def updateCurrentFile(self) -> None: self.filename_label.setText(NOFILE_TEXT) self.table.setDisabled(True) self.unloadButton.setDisabled(True) + # Set this to None because other methods are expecting this. + self.current_filename = None else: self.table.setDisabled(False) self.unloadButton.setDisabled(False) From ab684b909cc4e9a195684fc5e09da8a8c8764086 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Sep 2024 08:45:44 +0100 Subject: [PATCH 227/396] Give a window title. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 90b80e0e08..0a4361d1f9 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -36,6 +36,8 @@ def __init__(self): 'Tab': True } + self.setWindowTitle('ASCII File Reader') + # Filename, and unload button self.filename_unload_layout = QHBoxLayout() From 090da54d47fc11cdc66ba26340d0921182168473 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 09:39:17 +0100 Subject: [PATCH 228/396] Added a done button. --- src/ascii_dialog/dialog.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 0a4361d1f9..81370d212c 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -115,6 +115,11 @@ def __init__(self): # Warning Label self.warning_label = WarningLabel(self.requiredMissing(), self.duplicateColumns()) + # Done button + # TODO: Not entirely sure what to call/label this. Just going with 'done' for now. + + self.done_button = QPushButton('Done') + self.layout = QVBoxLayout(self) self.layout.addLayout(self.filename_unload_layout) @@ -127,6 +132,7 @@ def __init__(self): self.layout.addWidget(self.col_editor) self.layout.addWidget(self.table) self.layout.addWidget(self.warning_label) + self.layout.addWidget(self.done_button) @property def raw_csv(self) -> list[str] | None: From 14c1aed7d0aead8ab52608ea1f6ba6ca938eb722 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:18:15 +0100 Subject: [PATCH 229/396] Get the column tuple. --- src/ascii_dialog/col_editor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index abd398ad72..1fbac18283 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,6 +1,7 @@ from PySide6.QtGui import QRegularExpressionValidator from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtCore import Slot, Signal +from sasdata.quantities.units import NamedUnit from column_unit import ColumnUnit @@ -67,6 +68,9 @@ def colNames(self) -> list[str]: """Get a list of all of the currently selected columns.""" return [widget.currentColumn for widget in self.option_widgets] + def columns(self) -> list[tuple[str, NamedUnit]]: + return [(widget.currentColumn, widget.currentUnit) for widget in self.option_widgets] + def replaceOptions(self, new_options: list[str]) -> None: """Replace options from which the user can choose for each column.""" self.options = new_options From ce9f2d47633ee91212d572499a041adb8d4f2cda Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:18:37 +0100 Subject: [PATCH 230/396] Make this a property. --- src/ascii_dialog/col_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 1fbac18283..1bfa73d3b8 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -68,6 +68,7 @@ def colNames(self) -> list[str]: """Get a list of all of the currently selected columns.""" return [widget.currentColumn for widget in self.option_widgets] + @property def columns(self) -> list[tuple[str, NamedUnit]]: return [(widget.currentColumn, widget.currentUnit) for widget in self.option_widgets] From 899e93a52db4685f5647940e55104c96ebc7fbce Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:42:26 +0100 Subject: [PATCH 231/396] Added exclude lines to match param in dataclass. --- src/ascii_dialog/dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 81370d212c..570aaf931d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -146,6 +146,9 @@ def rows_is_included(self) -> list[bool] | None: return None return self.files_is_included[self.current_filename] + @property + def excluded_lines(self) -> set[int]: + return set([i for i, included in self.files_is_included if included]) def splitLine(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has From 83df09a56ebc65da5d230a6c63659fe8a4f27e65 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:43:01 +0100 Subject: [PATCH 232/396] Fixed logic error. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 570aaf931d..3aabaa0305 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -148,7 +148,7 @@ def rows_is_included(self) -> list[bool] | None: @property def excluded_lines(self) -> set[int]: - return set([i for i, included in self.files_is_included if included]) + return set([i for i, included in self.files_is_included if not included]) def splitLine(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has From 1d3e91bcdd625cbe4473a9355616af3b11f46460 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:44:09 +0100 Subject: [PATCH 233/396] Hook up the done button to an event. --- src/ascii_dialog/dialog.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 3aabaa0305..5f3f7da075 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -9,6 +9,7 @@ from guess import guess_column_count, guess_columns, guess_starting_position from os import path from sasdata.dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans +from sasdata.temp_ascii_reader import load_data, AsciiReaderParams import re TABLE_MAX_ROWS = 1000 @@ -119,6 +120,7 @@ def __init__(self): # TODO: Not entirely sure what to call/label this. Just going with 'done' for now. self.done_button = QPushButton('Done') + self.done_button.connect(self.onDoneButton) self.layout = QVBoxLayout(self) @@ -420,6 +422,17 @@ def datasetOptions(self) -> list[str]: current_dataset_type = self.currentDatasetType() return current_dataset_type.required + current_dataset_type.optional + [''] + # TODO: Only works for one single file at the moment + def onDoneButton(self): + params = AsciiReaderParams( + self.filename_label.text(), + self.startline_entry.value(), + self.col_editor.columns, + self.excluded_lines, + self.seperators.items() + ) + # TODO: This value needs to be returned somehow. + if __name__ == "__main__": app = QApplication([]) From 85977b34b506d8e3371f36700104f6af8f6bea59 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:58:27 +0100 Subject: [PATCH 234/396] Fixed slot connection. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 5f3f7da075..819caaa2da 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -120,7 +120,7 @@ def __init__(self): # TODO: Not entirely sure what to call/label this. Just going with 'done' for now. self.done_button = QPushButton('Done') - self.done_button.connect(self.onDoneButton) + self.done_button.clicked.connect(self.onDoneButton) self.layout = QVBoxLayout(self) From 325806b789b8a6f24784b63e06b1a0dd38ca4360 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:00:13 +0100 Subject: [PATCH 235/396] Ascii dialog is now a dialog not a widget. --- src/ascii_dialog/dialog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 819caaa2da..a717c25918 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,6 +1,6 @@ from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, Qt from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, \ - QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication + QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication, QDialog from PySide6.QtCore import QModelIndex, QPoint, Slot from selection_menu import SelectionMenu from warning_label import WarningLabel @@ -17,7 +17,7 @@ dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) -class AsciiDialog(QWidget): +class AsciiDialog(QDialog): """A dialog window allowing the user to adjust various properties regarding how an ASCII file should be interpreted. This widget allows the user to visualise what the data will look like with the parameter the user has @@ -436,8 +436,8 @@ def onDoneButton(self): if __name__ == "__main__": app = QApplication([]) - widget = AsciiDialog() - widget.show() + dialog = AsciiDialog() + dialog.exec() exit(app.exec()) From a814c3604230f228aedf194aeb16ca66fa46059d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:14:49 +0100 Subject: [PATCH 236/396] I don't think these comments are needed anymore. --- src/ascii_dialog/column_unit.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 3bdfbde100..0246a9f96d 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -44,9 +44,6 @@ def createUnitComboBox(self, selected_option: str) -> QComboBox: new_combo_box = QComboBox() configure_size_policy(new_combo_box) new_combo_box.setEditable(True) - # word_list = ['alpha', 'omega', 'omicron', 'zeta'] - # completer = QCompleter(word_list, self) - # new_combo_box.setCompleter(completer) self.updateUnits(new_combo_box, selected_option) new_combo_box.currentTextChanged.connect(self.onUnitChange) return new_combo_box From 9cd4df80d2c782c796ad57bf2a49594765056d30 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:27:15 +0100 Subject: [PATCH 237/396] Changed how the current unit is found. Old way was throwing errors because symbol lookup doesn't have any of the inverse units (or indeed units of any other dimension). --- src/ascii_dialog/column_unit.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 0246a9f96d..6184d82ade 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -4,7 +4,7 @@ from PySide6.QtWidgets import QComboBox, QCompleter, QHBoxLayout, QSizePolicy, QWidget from PySide6.QtGui import QRegularExpressionValidator from sasdata.dataset_types import unit_kinds -from sasdata.quantities.units import symbol_lookup +from sasdata.quantities.units import symbol_lookup, NamedUnit from unit_selector import UnitSelector @@ -23,6 +23,7 @@ def __init__(self, options) -> None: self.layout = QHBoxLayout(self) self.layout.addWidget(self.col_widget) self.layout.addWidget(self.unit_widget) + self.current_option: str column_changed = Signal() @@ -50,6 +51,7 @@ def createUnitComboBox(self, selected_option: str) -> QComboBox: def updateUnits(self, unit_box: QComboBox, selected_option: str): unit_box.clear() + self.current_option = selected_option options = [unit.symbol for unit in unit_kinds[selected_option].units] # We don't have preferred units yet. In order to simulate this, just # take the first 5 options to display. @@ -98,6 +100,11 @@ def currentColumn(self): return self.col_widget.currentText() @property - def currentUnit(self): + def currentUnit(self) -> NamedUnit: """The currently selected unit.""" - return symbol_lookup[self.unit_widget.currentText()] + current_unit_symbol = self.unit_widget.currentText() + for unit in unit_kinds[self.current_option].units: + if current_unit_symbol == unit.symbol: + return unit.symbol + # This error shouldn't really happen so if it does, it indicates there is a bug in the code. + raise ValueError("Current unit doesn't seem to exist") From 7156abe5d03da298ad7a7d4043ba044f4faa1fe1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:50:29 +0100 Subject: [PATCH 238/396] Include the selected unit in the options. --- src/ascii_dialog/column_unit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 6184d82ade..b46f333e2c 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -92,6 +92,8 @@ def onUnitChange(self): if self.unit_widget.currentText() == 'Select More': selector = UnitSelector(unit_kinds[self.col_widget.currentText()].name, False) selector.exec() + # We need the selection unit in the list of options, or else QT has some dodgy behaviour. + self.unit_widget.insertItem(-1, selector.selected_unit.symbol) self.unit_widget.setCurrentText(selector.selected_unit.symbol) @property From c2d6c06c601a28d5d381055452c1cde5455cf09d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:58:04 +0100 Subject: [PATCH 239/396] Accept when clicking the done button. --- src/ascii_dialog/dialog.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a717c25918..73d3af04f8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -431,13 +431,15 @@ def onDoneButton(self): self.excluded_lines, self.seperators.items() ) - # TODO: This value needs to be returned somehow. + self.params = params + self.accept() if __name__ == "__main__": app = QApplication([]) dialog = AsciiDialog() - dialog.exec() + status = dialog.exec() + if status == QDialog.accepted: + print(dialog.params) - - exit(app.exec()) + exit() From a53f55edd319fd882aada3f20466a7f25208c340 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 12:01:34 +0100 Subject: [PATCH 240/396] Need to call items in order to destructure. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 73d3af04f8..a25a3969b8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -150,7 +150,7 @@ def rows_is_included(self) -> list[bool] | None: @property def excluded_lines(self) -> set[int]: - return set([i for i, included in self.files_is_included if not included]) + return set([i for i, included in self.files_is_included.items() if not included]) def splitLine(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has From b4c628072bfe556418ec0cb9102f3f9c9b041bab Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 13:32:54 +0100 Subject: [PATCH 241/396] Just hard code the value. accepted is a slot not a value. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a25a3969b8..47c6ef749e 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -439,7 +439,8 @@ def onDoneButton(self): dialog = AsciiDialog() status = dialog.exec() - if status == QDialog.accepted: + # 1 means the dialog was accepted. + if status == 1: print(dialog.params) exit() From 5590164c88dd65bfa875d2771cfd621901facc2b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 13:44:32 +0100 Subject: [PATCH 242/396] Fix excluded_lines. Was previously always returning an empty set. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 47c6ef749e..df351ca38f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -150,7 +150,7 @@ def rows_is_included(self) -> list[bool] | None: @property def excluded_lines(self) -> set[int]: - return set([i for i, included in self.files_is_included.items() if not included]) + return set([i for i, included in enumerate(self.rows_is_included) if not included]) def splitLine(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has From a529e52091e55622379fa926c4cbfc368064b388 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 14:58:29 +0100 Subject: [PATCH 243/396] Keep track of the full path. So that we can load the file later. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index df351ca38f..797f910281 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -28,6 +28,7 @@ def __init__(self): super().__init__() self.files: dict[str, list[str]] = {} + self.files_full_path: dict[str, str] = {} self.files_is_included: dict[str, list[bool]] = {} self.current_filename: str | None = None @@ -281,6 +282,7 @@ def load(self) -> None: # TODO: This assumes that no two files will be loaded with the same # name. This might not be a reasonable assumption. self.files[basename] = file_csv + self.files_full_path[basename] = filename self.current_filename = basename # Reset checkboxes self.files_is_included[basename] = [] From bd7e2db1486afc9ef1028be80cf3ade3a991c7de Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 15:03:16 +0100 Subject: [PATCH 244/396] Fix the params. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 797f910281..adc71088fc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -427,7 +427,7 @@ def datasetOptions(self) -> list[str]: # TODO: Only works for one single file at the moment def onDoneButton(self): params = AsciiReaderParams( - self.filename_label.text(), + self.files_full_path[self.current_filename], self.startline_entry.value(), self.col_editor.columns, self.excluded_lines, From c6344c0d61358ec2fc80a951743aae429deb0020 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 15:03:22 +0100 Subject: [PATCH 245/396] Load the data instead of printing params only. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index adc71088fc..11fef02ae2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -443,6 +443,7 @@ def onDoneButton(self): status = dialog.exec() # 1 means the dialog was accepted. if status == 1: - print(dialog.params) + loaded = load_data(dialog.params) + print(loaded.summary()) exit() From 8dcced01270901e31e82f59fd11be6cf8b36830a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 22 Oct 2024 11:07:12 +0100 Subject: [PATCH 246/396] Return the unit not the symbol. --- src/ascii_dialog/column_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index b46f333e2c..ba254ce239 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -107,6 +107,6 @@ def currentUnit(self) -> NamedUnit: current_unit_symbol = self.unit_widget.currentText() for unit in unit_kinds[self.current_option].units: if current_unit_symbol == unit.symbol: - return unit.symbol + return unit # This error shouldn't really happen so if it does, it indicates there is a bug in the code. raise ValueError("Current unit doesn't seem to exist") From f01c9949a60a79aa881920bedf6f5340bcf2d21b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 09:39:32 +0100 Subject: [PATCH 247/396] Send in an empty dict of metadata for now. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 11fef02ae2..bc4b496474 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -431,7 +431,8 @@ def onDoneButton(self): self.startline_entry.value(), self.col_editor.columns, self.excluded_lines, - self.seperators.items() + self.seperators.items(), + {} ) self.params = params self.accept() From da2eb957d2e63b34defb10cd54889cb5897a4ca0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 09:45:40 +0100 Subject: [PATCH 248/396] Added an edit metadata button. --- src/ascii_dialog/dialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index bc4b496474..6722555faa 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -40,15 +40,18 @@ def __init__(self): self.setWindowTitle('ASCII File Reader') - # Filename, and unload button + # Filename, unload button, and edit metadata button. self.filename_unload_layout = QHBoxLayout() self.filename_label = QLabel(NOFILE_TEXT) self.unloadButton = QPushButton("Unload") self.unloadButton.setDisabled(True) + self.editMetadataButton = QPushButton("Edit Metadata") + self.editMetadataButton.setDisabled(True) self.unloadButton.clicked.connect(self.unload) self.filename_unload_layout.addWidget(self.filename_label) self.filename_unload_layout.addWidget(self.unloadButton) + self.filename_unload_layout.addWidget(self.editMetadataButton) # Filename chooser self.filename_chooser = QComboBox() @@ -347,11 +350,13 @@ def updateCurrentFile(self) -> None: self.filename_label.setText(NOFILE_TEXT) self.table.setDisabled(True) self.unloadButton.setDisabled(True) + self.editMetadataButton.setDisabled(True) # Set this to None because other methods are expecting this. self.current_filename = None else: self.table.setDisabled(False) self.unloadButton.setDisabled(False) + self.editMetadataButton.setDisabled(False) self.fillTable() @Slot() From d02db130d4b27baa974d34bf18da889ccd10f931 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:08:46 +0100 Subject: [PATCH 249/396] Bring in the files for the metadata filename gui. The files used to live in the sasdata repo on the metadata-filename-gui branch. They're going to live here because the ASCII dialog depends on them, and having them on a different repo is going to be a headache. --- src/metadata_filename_gui/main.py | 85 +++++++++++++++++++ .../metadata_component_selector.py | 36 ++++++++ .../metadata_tree_widget.py | 26 ++++++ 3 files changed, 147 insertions(+) create mode 100644 src/metadata_filename_gui/main.py create mode 100644 src/metadata_filename_gui/metadata_component_selector.py create mode 100644 src/metadata_filename_gui/metadata_tree_widget.py diff --git a/src/metadata_filename_gui/main.py b/src/metadata_filename_gui/main.py new file mode 100644 index 0000000000..3f00aad203 --- /dev/null +++ b/src/metadata_filename_gui/main.py @@ -0,0 +1,85 @@ +from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel +from sasdata.metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget +from sys import argv +import re + +def build_font(text: str, classname: str = '') -> str: + match classname: + case 'token': + return f"{text}" + case 'separator': + return f"{text}" + case _: + return text + return f'{text}' + +class MetadataFilenameDialog(QWidget): + def __init__(self, filename: str): + super().__init__() + + self.filename = filename + # Key is the metadatum, value is the component selected for it. + self.component_metadata: dict[str, str] = {} + + self.filename_line_label = QLabel() + self.seperator_chars_label = QLabel('Seperators') + self.separator_chars = QLineEdit() + self.separator_chars.textChanged.connect(self.update_filename_separation) + + self.filename_separator_layout = QHBoxLayout() + self.filename_separator_layout.addWidget(self.filename_line_label) + self.filename_separator_layout.addWidget(self.seperator_chars_label) + self.filename_separator_layout.addWidget(self.separator_chars) + + self.metadata_tree = MetadataTreeWidget(self.component_metadata) + + # Have to update this now because it relies on the value of the separator, and tree. + self.update_filename_separation() + + + self.layout = QVBoxLayout(self) + self.layout.addLayout(self.filename_separator_layout) + self.layout.addWidget(self.metadata_tree) + + def split_filename(self) -> list[str]: + return re.split(f'([{self.separator_chars.text()}])', self.filename) + + def filename_components(self) -> list[str]: + splitted = re.split(f'{self.separator_chars.text()}', self.filename) + # If the last component has a file extensions, remove it. + last_component = splitted[-1] + if '.' in last_component: + pos = last_component.index('.') + last_component = last_component[:pos] + splitted[-1] = last_component + return splitted + + def formatted_filename(self) -> str: + sep_str = self.separator_chars.text() + if sep_str == '': + return f'{filename}' + # Won't escape characters; I'll handle that later. + separated = self.split_filename() + font_elements = '' + for i, token in enumerate(separated): + classname = 'token' if i % 2 == 0 else 'separator' + font_elements += build_font(token, classname) + return font_elements + + def update_filename_separation(self): + self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') + self.metadata_tree.draw_tree(self.filename_components()) + + + +if __name__ == "__main__": + app = QApplication([]) + if len(argv) < 2: + filename = input('Input filename to test: ') + else: + filename = argv[1] + widget = MetadataFilenameDialog(filename) + widget.show() + + + exit(app.exec()) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py new file mode 100644 index 0000000000..8ec446ba42 --- /dev/null +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -0,0 +1,36 @@ +from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout + +class MetadataComponentSelector(QWidget): + def __init__(self, metadatum: str, metadata_dict: dict[str, str]): + super().__init__() + self.options: list[str] + self.option_buttons: list[QPushButton] + self.layout = QHBoxLayout(self) + self.metadata_dict = metadata_dict + self.metadatum = metadatum + + def clear_options(self): + for i in reversed(range(self.layout.count() - 1)): + self.layout.takeAt(i).widget().deleteLater() + + def draw_options(self, new_options: list[str]): + self.clear_options() + self.options = new_options + self.option_buttons = [] + for option in self.options: + option_button = QPushButton(option) + option_button.setCheckable(True) + option_button.clicked.connect(self.selection_changed) + self.layout.addWidget(option_button) + self.option_buttons.append(option_button) + + def selection_changed(self): + selected_button: QPushButton = self.sender() + selected_component = selected_button.text() + for button in self.option_buttons: + if button != selected_button: + button.setChecked(False) + if selected_button.isChecked(): + self.metadata_dict[self.metadatum] = selected_component + else: + del self.metadata_dict[self.metadatum] diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py new file mode 100644 index 0000000000..c989dd0a04 --- /dev/null +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -0,0 +1,26 @@ +from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QLabel +from PySide6.QtCore import QAbstractItemModel +from sasdata.metadata_filename_gui.metadata_component_selector import MetadataComponentSelector + +class MetadataTreeWidget(QTreeWidget): + def __init__(self, metadata_dict: dict[str, str]): + super().__init__() + self.setColumnCount(2) + self.setHeaderLabels(['Name', 'Filename Components']) + self.metadata_dict = metadata_dict + + + def draw_tree(self, options: list[str]): + self.clear() + # TODO: This is placeholder data that'll need to be replaced by the real metadata. + metadata = {'Instrument': ['Slit width', 'Other']} + for top_level, items in metadata.items(): + top_level_item = QTreeWidgetItem([top_level]) + for metadatum in items: + selector = MetadataComponentSelector(metadatum, self.metadata_dict) + metadatum_item = QTreeWidgetItem([metadatum]) + selector.draw_options(options) + top_level_item.addChild(metadatum_item) + self.setItemWidget(metadatum_item, 1, selector) + self.insertTopLevelItem(0, top_level_item) + self.expandAll() From 1dd0b5ffa3e88e2bef33279f617d3215a444a992 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:22:14 +0100 Subject: [PATCH 250/396] Renamed file, and ade it a dialog. --- src/metadata_filename_gui/main.py | 85 ------------------- .../metadata_tree_widget.py | 2 +- 2 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 src/metadata_filename_gui/main.py diff --git a/src/metadata_filename_gui/main.py b/src/metadata_filename_gui/main.py deleted file mode 100644 index 3f00aad203..0000000000 --- a/src/metadata_filename_gui/main.py +++ /dev/null @@ -1,85 +0,0 @@ -from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel -from sasdata.metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget -from sys import argv -import re - -def build_font(text: str, classname: str = '') -> str: - match classname: - case 'token': - return f"{text}" - case 'separator': - return f"{text}" - case _: - return text - return f'{text}' - -class MetadataFilenameDialog(QWidget): - def __init__(self, filename: str): - super().__init__() - - self.filename = filename - # Key is the metadatum, value is the component selected for it. - self.component_metadata: dict[str, str] = {} - - self.filename_line_label = QLabel() - self.seperator_chars_label = QLabel('Seperators') - self.separator_chars = QLineEdit() - self.separator_chars.textChanged.connect(self.update_filename_separation) - - self.filename_separator_layout = QHBoxLayout() - self.filename_separator_layout.addWidget(self.filename_line_label) - self.filename_separator_layout.addWidget(self.seperator_chars_label) - self.filename_separator_layout.addWidget(self.separator_chars) - - self.metadata_tree = MetadataTreeWidget(self.component_metadata) - - # Have to update this now because it relies on the value of the separator, and tree. - self.update_filename_separation() - - - self.layout = QVBoxLayout(self) - self.layout.addLayout(self.filename_separator_layout) - self.layout.addWidget(self.metadata_tree) - - def split_filename(self) -> list[str]: - return re.split(f'([{self.separator_chars.text()}])', self.filename) - - def filename_components(self) -> list[str]: - splitted = re.split(f'{self.separator_chars.text()}', self.filename) - # If the last component has a file extensions, remove it. - last_component = splitted[-1] - if '.' in last_component: - pos = last_component.index('.') - last_component = last_component[:pos] - splitted[-1] = last_component - return splitted - - def formatted_filename(self) -> str: - sep_str = self.separator_chars.text() - if sep_str == '': - return f'{filename}' - # Won't escape characters; I'll handle that later. - separated = self.split_filename() - font_elements = '' - for i, token in enumerate(separated): - classname = 'token' if i % 2 == 0 else 'separator' - font_elements += build_font(token, classname) - return font_elements - - def update_filename_separation(self): - self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') - self.metadata_tree.draw_tree(self.filename_components()) - - - -if __name__ == "__main__": - app = QApplication([]) - if len(argv) < 2: - filename = input('Input filename to test: ') - else: - filename = argv[1] - widget = MetadataFilenameDialog(filename) - widget.show() - - - exit(app.exec()) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index c989dd0a04..49b855a211 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QLabel from PySide6.QtCore import QAbstractItemModel -from sasdata.metadata_filename_gui.metadata_component_selector import MetadataComponentSelector +from metadata_component_selector import MetadataComponentSelector class MetadataTreeWidget(QTreeWidget): def __init__(self, metadata_dict: dict[str, str]): From 70753548539d58c2da9ff6641fcf889fa217eae7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:22:31 +0100 Subject: [PATCH 251/396] Forgot to commit new file. --- .../metadata_filename_dialog.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/metadata_filename_gui/metadata_filename_dialog.py diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py new file mode 100644 index 0000000000..3accc0c302 --- /dev/null +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -0,0 +1,85 @@ +from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog +from metadata_tree_widget import MetadataTreeWidget +from sys import argv +import re + +def build_font(text: str, classname: str = '') -> str: + match classname: + case 'token': + return f"{text}" + case 'separator': + return f"{text}" + case _: + return text + return f'{text}' + +class MetadataFilenameDialog(QDialog): + def __init__(self, filename: str): + super().__init__() + + self.filename = filename + # Key is the metadatum, value is the component selected for it. + self.component_metadata: dict[str, str] = {} + + self.filename_line_label = QLabel() + self.seperator_chars_label = QLabel('Seperators') + self.separator_chars = QLineEdit() + self.separator_chars.textChanged.connect(self.update_filename_separation) + + self.filename_separator_layout = QHBoxLayout() + self.filename_separator_layout.addWidget(self.filename_line_label) + self.filename_separator_layout.addWidget(self.seperator_chars_label) + self.filename_separator_layout.addWidget(self.separator_chars) + + self.metadata_tree = MetadataTreeWidget(self.component_metadata) + + # Have to update this now because it relies on the value of the separator, and tree. + self.update_filename_separation() + + + self.layout = QVBoxLayout(self) + self.layout.addLayout(self.filename_separator_layout) + self.layout.addWidget(self.metadata_tree) + + def split_filename(self) -> list[str]: + return re.split(f'([{self.separator_chars.text()}])', self.filename) + + def filename_components(self) -> list[str]: + splitted = re.split(f'{self.separator_chars.text()}', self.filename) + # If the last component has a file extensions, remove it. + last_component = splitted[-1] + if '.' in last_component: + pos = last_component.index('.') + last_component = last_component[:pos] + splitted[-1] = last_component + return splitted + + def formatted_filename(self) -> str: + sep_str = self.separator_chars.text() + if sep_str == '': + return f'{filename}' + # Won't escape characters; I'll handle that later. + separated = self.split_filename() + font_elements = '' + for i, token in enumerate(separated): + classname = 'token' if i % 2 == 0 else 'separator' + font_elements += build_font(token, classname) + return font_elements + + def update_filename_separation(self): + self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') + self.metadata_tree.draw_tree(self.filename_components()) + + + +if __name__ == "__main__": + app = QApplication([]) + if len(argv) < 2: + filename = input('Input filename to test: ') + else: + filename = argv[1] + dialog = MetadataFilenameDialog(filename) + dialog.exec() + + + exit(app.exec()) From 903a42464b9e171a71325a8d274d3a4a87a7037d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:27:27 +0100 Subject: [PATCH 252/396] Added save button. --- src/metadata_filename_gui/metadata_filename_dialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 3accc0c302..466cd0da6b 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,4 +1,4 @@ -from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog +from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton from metadata_tree_widget import MetadataTreeWidget from sys import argv import re @@ -36,10 +36,13 @@ def __init__(self, filename: str): # Have to update this now because it relies on the value of the separator, and tree. self.update_filename_separation() + self.save_button = QPushButton('Save') + self.layout = QVBoxLayout(self) self.layout.addLayout(self.filename_separator_layout) self.layout.addWidget(self.metadata_tree) + self.layout.addWidget(self.save_button) def split_filename(self) -> list[str]: return re.split(f'([{self.separator_chars.text()}])', self.filename) From 24dc2c15d63bbcea081b3ef07edeb3fd2d86242f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:35:02 +0100 Subject: [PATCH 253/396] Hook up the save button to an event. --- src/metadata_filename_gui/metadata_filename_dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 466cd0da6b..6bd34fc9e3 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -37,7 +37,7 @@ def __init__(self, filename: str): self.update_filename_separation() self.save_button = QPushButton('Save') - + self.save_button.clicked.connect(self.on_save) self.layout = QVBoxLayout(self) self.layout.addLayout(self.filename_separator_layout) @@ -73,6 +73,10 @@ def update_filename_separation(self): self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') self.metadata_tree.draw_tree(self.filename_components()) + def on_save(self): + self.accept() + # Don't really need to do anything else. Anyone using this dialog can access the component_metadata dict. + if __name__ == "__main__": From 7f546ba70acfec93e56ee94e6e9271fb494be02e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:39:09 +0100 Subject: [PATCH 254/396] Print out the component metadata on success. --- src/metadata_filename_gui/metadata_filename_dialog.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 6bd34fc9e3..9c55dbf4c4 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -86,7 +86,6 @@ def on_save(self): else: filename = argv[1] dialog = MetadataFilenameDialog(filename) - dialog.exec() - - - exit(app.exec()) + status = dialog.exec() + if status == 1: + print(dialog.component_metadata) From 5bad16d22f60b53b8c652924883d428fe5d31fba Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 11:30:28 +0100 Subject: [PATCH 255/396] Fixed imports. --- src/ascii_dialog/dialog.py | 1 + src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 6722555faa..6e98669cfc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -10,6 +10,7 @@ from os import path from sasdata.dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans from sasdata.temp_ascii_reader import load_data, AsciiReaderParams +from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog import re TABLE_MAX_ROWS = 1000 diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 9c55dbf4c4..4c837173c7 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,5 +1,5 @@ from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton -from metadata_tree_widget import MetadataTreeWidget +from metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget from sys import argv import re From 1f100e542bdf453f236813468b167a41ca7cc4a4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 11:32:15 +0100 Subject: [PATCH 256/396] Add edit metadata button. --- src/ascii_dialog/dialog.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 6e98669cfc..2546e9ca1f 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -31,6 +31,9 @@ def __init__(self): self.files: dict[str, list[str]] = {} self.files_full_path: dict[str, str] = {} self.files_is_included: dict[str, list[bool]] = {} + # I'm not going to worry too much about deleting filenames from this dict below when they are unloaded. It + # shouldn't break anything. + self.filename_metadata: dict[str, dict[str, str]] = {} self.current_filename: str | None = None self.seperators: dict[str, bool] = { @@ -49,6 +52,7 @@ def __init__(self): self.unloadButton.setDisabled(True) self.editMetadataButton = QPushButton("Edit Metadata") self.editMetadataButton.setDisabled(True) + self.editMetadataButton.clicked.connect(self.editMetadata) self.unloadButton.clicked.connect(self.unload) self.filename_unload_layout.addWidget(self.filename_label) self.filename_unload_layout.addWidget(self.unloadButton) @@ -443,6 +447,13 @@ def onDoneButton(self): self.params = params self.accept() + def editMetadata(self): + dialog = MetadataFilenameDialog(self.current_filename) + status = dialog.exec() + if status == 1: + self.filename_metadata[self.current_filename] = dialog.component_metadata + + if __name__ == "__main__": app = QApplication([]) From 5ddc7ceea169b058eeceb33c24b887444b156343 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 11:32:28 +0100 Subject: [PATCH 257/396] Fixed import. --- src/metadata_filename_gui/metadata_tree_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 49b855a211..c91f0ef7f4 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QLabel from PySide6.QtCore import QAbstractItemModel -from metadata_component_selector import MetadataComponentSelector +from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector class MetadataTreeWidget(QTreeWidget): def __init__(self, metadata_dict: dict[str, str]): From d1748e83aa3f900e4ca932c5680a51bfcad7065e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:04:03 +0100 Subject: [PATCH 258/396] Get filename from self. No idea why this bug wasn't caught before. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 4c837173c7..3212b08235 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -60,7 +60,7 @@ def filename_components(self) -> list[str]: def formatted_filename(self) -> str: sep_str = self.separator_chars.text() if sep_str == '': - return f'{filename}' + return f'{self.filename}' # Won't escape characters; I'll handle that later. separated = self.split_filename() font_elements = '' From 10582c2c3d550877e65518b4eb1d0473390bbec1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:41:46 +0100 Subject: [PATCH 259/396] Created a property for the separator text. --- src/metadata_filename_gui/metadata_filename_dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 3212b08235..cf1fea5848 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -44,8 +44,12 @@ def __init__(self, filename: str): self.layout.addWidget(self.metadata_tree) self.layout.addWidget(self.save_button) + @property + def separator_text(self) -> str: + return self.separator_chars.text() + def split_filename(self) -> list[str]: - return re.split(f'([{self.separator_chars.text()}])', self.filename) + return re.split(f'([{self.separator_text}])', self.filename) def filename_components(self) -> list[str]: splitted = re.split(f'{self.separator_chars.text()}', self.filename) From ef5fa5def150bc259bc2b2c35d54a2b05e5b986d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:42:58 +0100 Subject: [PATCH 260/396] Added a dict for separators. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2546e9ca1f..c25916a597 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -34,6 +34,8 @@ def __init__(self): # I'm not going to worry too much about deleting filenames from this dict below when they are unloaded. It # shouldn't break anything. self.filename_metadata: dict[str, dict[str, str]] = {} + # This is useful for whenever the user wants to reopen the metadata editor. + self.filename_metadata_separator = {} self.current_filename: str | None = None self.seperators: dict[str, bool] = { From 25b01c0dd3f5c44415ac744b38d0be466c5522f8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:46:42 +0100 Subject: [PATCH 261/396] Take in initial values for these. --- src/metadata_filename_gui/metadata_filename_dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index cf1fea5848..f5ada68c67 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -14,16 +14,16 @@ def build_font(text: str, classname: str = '') -> str: return f'{text}' class MetadataFilenameDialog(QDialog): - def __init__(self, filename: str): + def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, initial_separator_text=''): super().__init__() self.filename = filename # Key is the metadatum, value is the component selected for it. - self.component_metadata: dict[str, str] = {} + self.component_metadata = initial_component_metadata self.filename_line_label = QLabel() self.seperator_chars_label = QLabel('Seperators') - self.separator_chars = QLineEdit() + self.separator_chars = QLineEdit(initial_separator_text) self.separator_chars.textChanged.connect(self.update_filename_separation) self.filename_separator_layout = QHBoxLayout() From 783b27157f3dce5503414a078eb27f7fd100d4bc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 30 Oct 2024 11:25:45 +0000 Subject: [PATCH 262/396] Set the separator field. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c25916a597..9417638ef8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -454,6 +454,7 @@ def editMetadata(self): status = dialog.exec() if status == 1: self.filename_metadata[self.current_filename] = dialog.component_metadata + self.filename_metadata_separator = dialog.separator_text if __name__ == "__main__": From 6c89ce76f3c1291e3f9ffd536f970ceab0dd1e70 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 10:21:22 +0000 Subject: [PATCH 263/396] Pass in previous metadata, using defaults. --- src/ascii_dialog/dialog.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9417638ef8..284b361fcd 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -450,11 +450,14 @@ def onDoneButton(self): self.accept() def editMetadata(self): - dialog = MetadataFilenameDialog(self.current_filename) + # current_metadata = self.filename_metadata[self.current_filename] + current_metadata = self.filename_metadata.get(self.current_filename, {}) + current_separator = self.filename_metadata_separator.get(self.current_filename, '') + dialog = MetadataFilenameDialog(self.current_filename, current_metadata, current_separator) status = dialog.exec() if status == 1: self.filename_metadata[self.current_filename] = dialog.component_metadata - self.filename_metadata_separator = dialog.separator_text + self.filename_metadata_separator[self.current_filename] = dialog.separator_text if __name__ == "__main__": From 0af4f40ce8e98d98cde330251e62af65d24e189c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 10:55:14 +0000 Subject: [PATCH 264/396] Selected components should persist. --- src/metadata_filename_gui/metadata_component_selector.py | 3 ++- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- src/metadata_filename_gui/metadata_tree_widget.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 8ec446ba42..055404328c 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -13,7 +13,7 @@ def clear_options(self): for i in reversed(range(self.layout.count() - 1)): self.layout.takeAt(i).widget().deleteLater() - def draw_options(self, new_options: list[str]): + def draw_options(self, new_options: list[str], selected_option: str | None): self.clear_options() self.options = new_options self.option_buttons = [] @@ -21,6 +21,7 @@ def draw_options(self, new_options: list[str]): option_button = QPushButton(option) option_button.setCheckable(True) option_button.clicked.connect(self.selection_changed) + option_button.setChecked(option == selected_option) self.layout.addWidget(option_button) self.option_buttons.append(option_button) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index f5ada68c67..ced3f2de9d 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -75,7 +75,7 @@ def formatted_filename(self) -> str: def update_filename_separation(self): self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') - self.metadata_tree.draw_tree(self.filename_components()) + self.metadata_tree.draw_tree(self.filename_components(), self.component_metadata) def on_save(self): self.accept() diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index c91f0ef7f4..804cc9298a 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -10,7 +10,7 @@ def __init__(self, metadata_dict: dict[str, str]): self.metadata_dict = metadata_dict - def draw_tree(self, options: list[str]): + def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): self.clear() # TODO: This is placeholder data that'll need to be replaced by the real metadata. metadata = {'Instrument': ['Slit width', 'Other']} @@ -19,7 +19,7 @@ def draw_tree(self, options: list[str]): for metadatum in items: selector = MetadataComponentSelector(metadatum, self.metadata_dict) metadatum_item = QTreeWidgetItem([metadatum]) - selector.draw_options(options) + selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) self.setItemWidget(metadatum_item, 1, selector) self.insertTopLevelItem(0, top_level_item) From 07cecd4e17a83da13ac1a5c177a82d686aa458c2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:46:14 +0000 Subject: [PATCH 265/396] Use the actual metadata. --- src/metadata_filename_gui/metadata_tree_widget.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 804cc9298a..817e0040ae 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -13,7 +13,14 @@ def __init__(self, metadata_dict: dict[str, str]): def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): self.clear() # TODO: This is placeholder data that'll need to be replaced by the real metadata. - metadata = {'Instrument': ['Slit width', 'Other']} + # metadata = {'Instrument': ['Slit width', 'Other']} + metadata = { + 'sasdata': ['aperture', 'collimation', 'detector', 'source'], + 'process': ['name', 'date', 'description', 'term', 'notes'], + 'sample': ['name', 'sample_id', 'thickness', 'transmission', 'temperature', 'position', 'orientation', 'details'], + 'transmission_spectrum': ['name', 'timestamp', 'transmission', 'transmission_deviation'], + 'other': ['title', 'run', 'definition'] + } for top_level, items in metadata.items(): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: From 28c0375c7b5b9346ba5ae724737b4c5dc1de3f28 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:50:26 +0000 Subject: [PATCH 266/396] Add widget for custom metadata entry. --- src/metadata_filename_gui/metadata_custom_entry.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/metadata_filename_gui/metadata_custom_entry.py diff --git a/src/metadata_filename_gui/metadata_custom_entry.py b/src/metadata_filename_gui/metadata_custom_entry.py new file mode 100644 index 0000000000..7725c7ae6f --- /dev/null +++ b/src/metadata_filename_gui/metadata_custom_entry.py @@ -0,0 +1,12 @@ +from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout + +class MetadataCustomEntry(QWidget): + def __init__(self): + super().__init__() + + self.entry_box = QLineEdit() + self.from_filename_button = QPushButton('From Filename') + + self.layout = QHBoxLayout() + self.layout.addWidget(self.entry_box) + self.layout.addWidget(self.from_filename_button) From ad35b79cb44bfa8653a4c78c39ec005810635559 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:52:15 +0000 Subject: [PATCH 267/396] Changed name to be more consistent. --- .../{metadata_custom_entry.py => metadata_custom_selector.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/metadata_filename_gui/{metadata_custom_entry.py => metadata_custom_selector.py} (90%) diff --git a/src/metadata_filename_gui/metadata_custom_entry.py b/src/metadata_filename_gui/metadata_custom_selector.py similarity index 90% rename from src/metadata_filename_gui/metadata_custom_entry.py rename to src/metadata_filename_gui/metadata_custom_selector.py index 7725c7ae6f..489d73120a 100644 --- a/src/metadata_filename_gui/metadata_custom_entry.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout -class MetadataCustomEntry(QWidget): +class MetadataCustomSelector(QWidget): def __init__(self): super().__init__() From 3ac829abb3188264ed5cb00a24973d28a0887796 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:53:45 +0000 Subject: [PATCH 268/396] Add a custom entry button to the layout. --- src/metadata_filename_gui/metadata_component_selector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 055404328c..b4434399a0 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -24,6 +24,9 @@ def draw_options(self, new_options: list[str], selected_option: str | None): option_button.setChecked(option == selected_option) self.layout.addWidget(option_button) self.option_buttons.append(option_button) + # This final button is to convert to use custom entry instead of this. + self.custom_entry_button = QPushButton('Custom') + self.layout.addWidget(self.custom_entry_button) def selection_changed(self): selected_button: QPushButton = self.sender() From 0d1304b1d20efa3301ba070dde33d218e9dcd861 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 13:52:31 +0000 Subject: [PATCH 269/396] Use a signal for when the custom button is pressed --- src/metadata_filename_gui/metadata_component_selector.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index b4434399a0..842cac831d 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -1,6 +1,12 @@ from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout +from PySide6.QtCore import Signal, Qt class MetadataComponentSelector(QWidget): + # Creating a separate signal for this because the custom button may be destroyed/recreated whenever the options are + # redrawn. + + custom_button_pressed = Signal(Qt.MouseButton()) + def __init__(self, metadatum: str, metadata_dict: dict[str, str]): super().__init__() self.options: list[str] @@ -26,6 +32,7 @@ def draw_options(self, new_options: list[str], selected_option: str | None): self.option_buttons.append(option_button) # This final button is to convert to use custom entry instead of this. self.custom_entry_button = QPushButton('Custom') + self.custom_entry_button.clicked.connect(self.custom_entry_button) self.layout.addWidget(self.custom_entry_button) def selection_changed(self): From 381ac0ee11d0ed5991b818fbb1e7ddba784af283 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 6 Nov 2024 14:07:29 +0000 Subject: [PATCH 270/396] Started with the selector widget. --- .../metadata_selector.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/metadata_filename_gui/metadata_selector.py diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py new file mode 100644 index 0000000000..42c12626da --- /dev/null +++ b/src/metadata_filename_gui/metadata_selector.py @@ -0,0 +1,24 @@ +from PySide6.QtWidgets import QWidget, QHBoxLayout +from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector +from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector + +class MetadataSelector(QWidget): + def __init__(self, metadatum: str, metadata_dict: dict[str, str]): + self.metadatum = metadatum + self.metadata_dict = metadata_dict + # Default to the name selector + self.selector_widget: QWidget = MetadataComponentSelector(metadatum, metadata_dict) + + # I can't seem to find any layou that just has one widgt in so this will do for now. + self.layout = QHBoxLayout() + self.layout.addWidget(self.selector_widget) + + def handle_selector_change(self): + # Need to keep this for when we delete it. + if self.selector_widget is MetadataComponentSelector: + # TODO: Will eventually have args + new_widget = MetadataCustomSelector() + elif self.selector_widget is MetadataCustomSelector(): + new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) + self.layout.replaceWidget(self.selector_widget, new_widget) + self.selector_widget = new_widget From 4e86f4c6229ff149b964f40c0834ca2f2c61f60d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 18 Nov 2024 09:12:48 +0000 Subject: [PATCH 271/396] Hook up event handlers properly. --- src/metadata_filename_gui/metadata_component_selector.py | 2 +- src/metadata_filename_gui/metadata_selector.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 842cac831d..74faca93e4 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -32,7 +32,7 @@ def draw_options(self, new_options: list[str], selected_option: str | None): self.option_buttons.append(option_button) # This final button is to convert to use custom entry instead of this. self.custom_entry_button = QPushButton('Custom') - self.custom_entry_button.clicked.connect(self.custom_entry_button) + self.custom_entry_button.clicked.connect(self.custom_button_pressed) self.layout.addWidget(self.custom_entry_button) def selection_changed(self): diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 42c12626da..fbd276be25 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -8,6 +8,7 @@ def __init__(self, metadatum: str, metadata_dict: dict[str, str]): self.metadata_dict = metadata_dict # Default to the name selector self.selector_widget: QWidget = MetadataComponentSelector(metadatum, metadata_dict) + self.selector_widget.custom_button_pressed.connect(self.handle_selector_change) # I can't seem to find any layou that just has one widgt in so this will do for now. self.layout = QHBoxLayout() @@ -18,6 +19,7 @@ def handle_selector_change(self): if self.selector_widget is MetadataComponentSelector: # TODO: Will eventually have args new_widget = MetadataCustomSelector() + new_widget.from_filename_button.connect(self.handle_selector_change) elif self.selector_widget is MetadataCustomSelector(): new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) self.layout.replaceWidget(self.selector_widget, new_widget) From 38483197e1724dfb384e2630945006454d46c263 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:26:52 +0000 Subject: [PATCH 272/396] Use the generic selector widget. --- src/metadata_filename_gui/metadata_component_selector.py | 8 ++++++-- src/metadata_filename_gui/metadata_selector.py | 4 +++- src/metadata_filename_gui/metadata_tree_widget.py | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 74faca93e4..5a34f6a24c 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -1,5 +1,5 @@ from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout -from PySide6.QtCore import Signal, Qt +from PySide6.QtCore import Signal, Qt, Slot class MetadataComponentSelector(QWidget): # Creating a separate signal for this because the custom button may be destroyed/recreated whenever the options are @@ -32,9 +32,13 @@ def draw_options(self, new_options: list[str], selected_option: str | None): self.option_buttons.append(option_button) # This final button is to convert to use custom entry instead of this. self.custom_entry_button = QPushButton('Custom') - self.custom_entry_button.clicked.connect(self.custom_button_pressed) + # self.custom_entry_button.clicked.connect(self.custom_button_pressed) + self.custom_entry_button.clicked.connect(self.handle_custom_button) self.layout.addWidget(self.custom_entry_button) + def handle_custom_button(self): + self.custom_button_pressed.emit() + def selection_changed(self): selected_button: QPushButton = self.sender() selected_component = selected_button.text() diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index fbd276be25..d7cef3dac8 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -3,12 +3,14 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, metadatum: str, metadata_dict: dict[str, str]): + def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, str]): self.metadatum = metadatum self.metadata_dict = metadata_dict + self.options = options # Default to the name selector self.selector_widget: QWidget = MetadataComponentSelector(metadatum, metadata_dict) self.selector_widget.custom_button_pressed.connect(self.handle_selector_change) + self.selector_widget.draw_options(self.options, metadata_dict.get(metadatum)) # I can't seem to find any layou that just has one widgt in so this will do for now. self.layout = QHBoxLayout() diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 817e0040ae..88c3b347e0 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -1,6 +1,7 @@ from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QLabel from PySide6.QtCore import QAbstractItemModel from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector +from metadata_filename_gui.metadata_selector import MetadataSelector class MetadataTreeWidget(QTreeWidget): def __init__(self, metadata_dict: dict[str, str]): @@ -24,9 +25,10 @@ def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): for top_level, items in metadata.items(): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: - selector = MetadataComponentSelector(metadatum, self.metadata_dict) + # selector = MetadataComponentSelector(metadatum, self.metadata_dict) + selector = MetadataSelector(metadatum, options, self.metadata_dict) metadatum_item = QTreeWidgetItem([metadatum]) - selector.draw_options(options, metadata_dict.get(metadatum)) + # selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) self.setItemWidget(metadatum_item, 1, selector) self.insertTopLevelItem(0, top_level_item) From 4a69c861f99c00c371d8ce2777dde2c6af5bc331 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:27:17 +0000 Subject: [PATCH 273/396] Init the base class. --- src/metadata_filename_gui/metadata_selector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index d7cef3dac8..cba3407ecb 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -4,6 +4,7 @@ class MetadataSelector(QWidget): def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, str]): + super().__init__() self.metadatum = metadatum self.metadata_dict = metadata_dict self.options = options From b7ccb0a697fe4415988b8bd0a0d17d2851287c61 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:28:39 +0000 Subject: [PATCH 274/396] Forgot to pass self into layout. --- src/metadata_filename_gui/metadata_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index cba3407ecb..54167544c5 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -14,7 +14,7 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, self.selector_widget.draw_options(self.options, metadata_dict.get(metadatum)) # I can't seem to find any layou that just has one widgt in so this will do for now. - self.layout = QHBoxLayout() + self.layout = QHBoxLayout(self) self.layout.addWidget(self.selector_widget) def handle_selector_change(self): From 420c9e5133c8ea9f14b493733c1fa9187b3600f6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:56:19 +0000 Subject: [PATCH 275/396] Handle when there is no new widget. --- src/metadata_filename_gui/metadata_selector.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 54167544c5..0b39273a72 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -25,5 +25,8 @@ def handle_selector_change(self): new_widget.from_filename_button.connect(self.handle_selector_change) elif self.selector_widget is MetadataCustomSelector(): new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) - self.layout.replaceWidget(self.selector_widget, new_widget) self.selector_widget = new_widget + else: + # Shouldn't happen as selector widget should be either of the above. + return + self.layout.replaceWidget(self.selector_widget, new_widget) From 74bd1d71d0a2de0e82dbd8e317c125dfdd544c88 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:58:22 +0000 Subject: [PATCH 276/396] Use isinstance. --- src/metadata_filename_gui/metadata_selector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 0b39273a72..f00021f3fc 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -19,11 +19,11 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, def handle_selector_change(self): # Need to keep this for when we delete it. - if self.selector_widget is MetadataComponentSelector: + if isinstance(self.selector_widget, MetadataComponentSelector): # TODO: Will eventually have args new_widget = MetadataCustomSelector() new_widget.from_filename_button.connect(self.handle_selector_change) - elif self.selector_widget is MetadataCustomSelector(): + elif isinstance(self.selector_widget, MetadataCustomSelector): new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) self.selector_widget = new_widget else: From 16d8493f5527c293609522c1454c527f35630a8e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:02:02 +0000 Subject: [PATCH 277/396] Fixed event handling. --- src/metadata_filename_gui/metadata_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index f00021f3fc..caf4e95925 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -22,7 +22,7 @@ def handle_selector_change(self): if isinstance(self.selector_widget, MetadataComponentSelector): # TODO: Will eventually have args new_widget = MetadataCustomSelector() - new_widget.from_filename_button.connect(self.handle_selector_change) + new_widget.from_filename_button.clicked.connect(self.handle_selector_change) elif isinstance(self.selector_widget, MetadataCustomSelector): new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) self.selector_widget = new_widget From 55af5672301fe96bfe0a7b88b2340a3ea727af31 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:02:18 +0000 Subject: [PATCH 278/396] Layout not inited properly. --- src/metadata_filename_gui/metadata_custom_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 489d73120a..5cc1cd9b3d 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -7,6 +7,6 @@ def __init__(self): self.entry_box = QLineEdit() self.from_filename_button = QPushButton('From Filename') - self.layout = QHBoxLayout() + self.layout = QHBoxLayout(self) self.layout.addWidget(self.entry_box) self.layout.addWidget(self.from_filename_button) From 605fff67ead00b9051271cd74b1f899681bca3ba Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:16:37 +0000 Subject: [PATCH 279/396] Need to delete, and reassign selector widget. --- src/metadata_filename_gui/metadata_selector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index caf4e95925..f35cc8817e 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -30,3 +30,5 @@ def handle_selector_change(self): # Shouldn't happen as selector widget should be either of the above. return self.layout.replaceWidget(self.selector_widget, new_widget) + self.selector_widget.deleteLater() + self.selector_widget = new_widget From f5eb7a5fcac13891ab32a738da6650d0f5fedcb4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:17:42 +0000 Subject: [PATCH 280/396] This line shouldn't be here. --- src/metadata_filename_gui/metadata_selector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index f35cc8817e..6be1e7215b 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -25,7 +25,6 @@ def handle_selector_change(self): new_widget.from_filename_button.clicked.connect(self.handle_selector_change) elif isinstance(self.selector_widget, MetadataCustomSelector): new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) - self.selector_widget = new_widget else: # Shouldn't happen as selector widget should be either of the above. return From bb3c506d27da02987ce17215b30c28aebd497442 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:35:24 +0000 Subject: [PATCH 281/396] Set the title of the metadata dialog. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index ced3f2de9d..3c9d0d061c 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -17,6 +17,8 @@ class MetadataFilenameDialog(QDialog): def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, initial_separator_text=''): super().__init__() + self.setWindowTitle('Metadata') + self.filename = filename # Key is the metadatum, value is the component selected for it. self.component_metadata = initial_component_metadata From 88b61c81e917825b69e5cd909934807814ff2be6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:38:09 +0000 Subject: [PATCH 282/396] Use separate functions for creating these widgets. --- src/metadata_filename_gui/metadata_selector.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 6be1e7215b..3bf485abce 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -17,14 +17,24 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, self.layout = QHBoxLayout(self) self.layout.addWidget(self.selector_widget) + def new_component_selector(self) -> MetadataComponentSelector: + new_selector = MetadataComponentSelector(self.metadatum, self.metadata_dict) + new_selector.custom_button_pressed.connect(self.handle_selector_change) + new_selector.draw_options(self.options, self.metadata_dict.get(self.metadatum)) + return new_selector + + def new_custom_selector(self) -> MetadataCustomSelector: + new_selector = MetadataCustomSelector() + new_selector.from_filename_button.clicked.connect(self.handle_selector_change) + return new_selector + def handle_selector_change(self): # Need to keep this for when we delete it. if isinstance(self.selector_widget, MetadataComponentSelector): # TODO: Will eventually have args - new_widget = MetadataCustomSelector() - new_widget.from_filename_button.clicked.connect(self.handle_selector_change) + new_widget = self.new_custom_selector() elif isinstance(self.selector_widget, MetadataCustomSelector): - new_widget = MetadataComponentSelector(self.metadatum, self.metadata_dict) + new_widget = self.new_component_selector() else: # Shouldn't happen as selector widget should be either of the above. return From ffdbc119bff5d2f3b104514b895c223a218a7a7c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:39:45 +0000 Subject: [PATCH 283/396] Use the new function. --- src/metadata_filename_gui/metadata_selector.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 3bf485abce..231a24f1a1 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -9,9 +9,7 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, self.metadata_dict = metadata_dict self.options = options # Default to the name selector - self.selector_widget: QWidget = MetadataComponentSelector(metadatum, metadata_dict) - self.selector_widget.custom_button_pressed.connect(self.handle_selector_change) - self.selector_widget.draw_options(self.options, metadata_dict.get(metadatum)) + self.selector_widget = self.new_component_selector() # I can't seem to find any layou that just has one widgt in so this will do for now. self.layout = QHBoxLayout(self) From 7aeef6a80e90d03cb382e716bd36ce60e02103cd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 08:55:28 +0000 Subject: [PATCH 284/396] Removed comment that doesn't make sense anymore. --- src/metadata_filename_gui/metadata_tree_widget.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 88c3b347e0..2f70732cb1 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -13,8 +13,6 @@ def __init__(self, metadata_dict: dict[str, str]): def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): self.clear() - # TODO: This is placeholder data that'll need to be replaced by the real metadata. - # metadata = {'Instrument': ['Slit width', 'Other']} metadata = { 'sasdata': ['aperture', 'collimation', 'detector', 'source'], 'process': ['name', 'date', 'description', 'term', 'notes'], From c7cb4a68a9f30190ddd9056a5c8f4f986fa55761 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:00:26 +0000 Subject: [PATCH 285/396] Handle value changes. --- .../metadata_custom_selector.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 5cc1cd9b3d..4677212a9f 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,12 +1,22 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout class MetadataCustomSelector(QWidget): - def __init__(self): + def __init__(self, metadatum: str, metadata_dict: dict[str, str]): super().__init__() + self.metadata_dict = metadata_dict + self.metadatum = metadatum self.entry_box = QLineEdit() + self.entry_box.textChanged.connect(self.selection_changed) self.from_filename_button = QPushButton('From Filename') self.layout = QHBoxLayout(self) self.layout.addWidget(self.entry_box) self.layout.addWidget(self.from_filename_button) + + def selection_changed(self): + new_value = self.from_filename_button.text() + if new_value != '': + self.metadata_dict[self.metadatum] = new_value + elif self.metadatum in self.metadata_dict: + del self.metadata_dict[self.metadatum] From 869edbd7e28c2c631ad0d78065a16a6ff69c28a8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:10:34 +0000 Subject: [PATCH 286/396] Pass in the new args. --- src/metadata_filename_gui/metadata_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 231a24f1a1..7bcad65930 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -22,7 +22,7 @@ def new_component_selector(self) -> MetadataComponentSelector: return new_selector def new_custom_selector(self) -> MetadataCustomSelector: - new_selector = MetadataCustomSelector() + new_selector = MetadataCustomSelector(self.metadatum, self.metadata_dict) new_selector.from_filename_button.clicked.connect(self.handle_selector_change) return new_selector From 4bc011173a04fb22b58f88f338be282983015ec8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:13:21 +0000 Subject: [PATCH 287/396] Fixed typos in comments. --- src/metadata_filename_gui/metadata_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 7bcad65930..35e37e918f 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -11,7 +11,7 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, # Default to the name selector self.selector_widget = self.new_component_selector() - # I can't seem to find any layou that just has one widgt in so this will do for now. + # I can't seem to find any layout that just has one widget in so this will do for now. self.layout = QHBoxLayout(self) self.layout.addWidget(self.selector_widget) From 26d3bca70fa84e3e15d4c7ed211cc6b90979c6bd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:14:53 +0000 Subject: [PATCH 288/396] Choose default widget based on current option. --- src/metadata_filename_gui/metadata_selector.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 35e37e918f..6274e2e8e1 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -8,8 +8,11 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, self.metadatum = metadatum self.metadata_dict = metadata_dict self.options = options - # Default to the name selector - self.selector_widget = self.new_component_selector() + current_option = metadata_dict.get(metadatum) + if current_option is None or current_option in options: + self.selector_widget = self.new_component_selector() + else: + self.selector_widget = self.new_custom_selector() # I can't seem to find any layout that just has one widget in so this will do for now. self.layout = QHBoxLayout(self) From c22cff183b9f12b0af008b338634bef9d0c5b6e6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:38:46 +0000 Subject: [PATCH 289/396] Set the field from the already set metadata. --- src/metadata_filename_gui/metadata_custom_selector.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 4677212a9f..b7d6652e9b 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -6,7 +6,9 @@ def __init__(self, metadatum: str, metadata_dict: dict[str, str]): self.metadata_dict = metadata_dict self.metadatum = metadatum - self.entry_box = QLineEdit() + prexisting_value = metadata_dict.get(metadatum) + initial_value = prexisting_value if prexisting_value is not None else '' + self.entry_box = QLineEdit(initial_value) self.entry_box.textChanged.connect(self.selection_changed) self.from_filename_button = QPushButton('From Filename') From 53a9df124cdc9f51caa07ddf45849e056fac834c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:49:49 +0000 Subject: [PATCH 290/396] Wrong control. --- src/metadata_filename_gui/metadata_custom_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index b7d6652e9b..854e6b3244 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -17,7 +17,7 @@ def __init__(self, metadatum: str, metadata_dict: dict[str, str]): self.layout.addWidget(self.from_filename_button) def selection_changed(self): - new_value = self.from_filename_button.text() + new_value = self.entry_box.text() if new_value != '': self.metadata_dict[self.metadatum] = new_value elif self.metadatum in self.metadata_dict: From 82c633d261a9c6915ef24ba1fb78838bba58c93f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 09:09:14 +0000 Subject: [PATCH 291/396] Pass raw_metadata into params. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 284b361fcd..d7113e8148 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -444,7 +444,7 @@ def onDoneButton(self): self.col_editor.columns, self.excluded_lines, self.seperators.items(), - {} + self.filename_metadata[self.current_filename] ) self.params = params self.accept() From bcf3ea7e466fc59820af1455a657900a8727bd60 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 09:10:26 +0000 Subject: [PATCH 292/396] This should be a TODO comment. So it doesn't get lost. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 3c9d0d061c..40819f605e 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -67,7 +67,7 @@ def formatted_filename(self) -> str: sep_str = self.separator_chars.text() if sep_str == '': return f'{self.filename}' - # Won't escape characters; I'll handle that later. + # TODO: Won't escape characters; I'll handle that later. separated = self.split_filename() font_elements = '' for i, token in enumerate(separated): From 8ad0054764dcf7aaa47b8d68bedcf11ac010296f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 11:02:25 +0000 Subject: [PATCH 293/396] Updated this map with a todo warning. --- src/metadata_filename_gui/metadata_tree_widget.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 2f70732cb1..760ef83ffe 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -13,8 +13,12 @@ def __init__(self, metadata_dict: dict[str, str]): def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): self.clear() + # TODO: I'm not sure whether I like this approach. Maybe use some reflection from the Metadata class instead. metadata = { - 'sasdata': ['aperture', 'collimation', 'detector', 'source'], + 'source': ['name', 'radiation', 'type', 'probe_particle', 'beam_size_name', 'beam_size', 'beam_shape', 'wavelength', 'wavelength_min', 'wavelength_max', 'wavelength_spread'], + 'detector': ['name', 'distance', 'offset', 'orientation', 'beam_center', 'pixel_size', 'slit_length'], + 'aperture': ['name', 'type', 'size_name', 'size', 'distance'], + 'collimation': ['name', 'lengths'], 'process': ['name', 'date', 'description', 'term', 'notes'], 'sample': ['name', 'sample_id', 'thickness', 'transmission', 'temperature', 'position', 'orientation', 'details'], 'transmission_spectrum': ['name', 'timestamp', 'transmission', 'transmission_deviation'], From 189115fe49912efbe5e057bf5d4028c79faedb6c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 11:46:42 +0000 Subject: [PATCH 294/396] Gonna try a different approach. --- src/ascii_dialog/dialog.py | 3 ++- src/metadata_filename_gui/metadata_selector.py | 2 +- src/metadata_filename_gui/metadata_tree_data.py | 15 +++++++++++++++ src/metadata_filename_gui/metadata_tree_widget.py | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/metadata_filename_gui/metadata_tree_data.py diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d7113e8148..33d5ebde91 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -11,6 +11,7 @@ from sasdata.dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans from sasdata.temp_ascii_reader import load_data, AsciiReaderParams from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog +from metadata_filename_gui.metadata_tree_data import initial_metadata_dict import re TABLE_MAX_ROWS = 1000 @@ -451,7 +452,7 @@ def onDoneButton(self): def editMetadata(self): # current_metadata = self.filename_metadata[self.current_filename] - current_metadata = self.filename_metadata.get(self.current_filename, {}) + current_metadata = self.filename_metadata.get(self.current_filename, initial_metadata_dict.copy()) current_separator = self.filename_metadata_separator.get(self.current_filename, '') dialog = MetadataFilenameDialog(self.current_filename, current_metadata, current_separator) status = dialog.exec() diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 6274e2e8e1..09a7efa254 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -3,7 +3,7 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, str]): + def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, dict[str, str]]): super().__init__() self.metadatum = metadatum self.metadata_dict = metadata_dict diff --git a/src/metadata_filename_gui/metadata_tree_data.py b/src/metadata_filename_gui/metadata_tree_data.py new file mode 100644 index 0000000000..c2a5690f70 --- /dev/null +++ b/src/metadata_filename_gui/metadata_tree_data.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + + +metadata = { + 'source': ['name', 'radiation', 'type', 'probe_particle', 'beam_size_name', 'beam_size', 'beam_shape', 'wavelength', 'wavelength_min', 'wavelength_max', 'wavelength_spread'], + 'detector': ['name', 'distance', 'offset', 'orientation', 'beam_center', 'pixel_size', 'slit_length'], + 'aperture': ['name', 'type', 'size_name', 'size', 'distance'], + 'collimation': ['name', 'lengths'], + 'process': ['name', 'date', 'description', 'term', 'notes'], + 'sample': ['name', 'sample_id', 'thickness', 'transmission', 'temperature', 'position', 'orientation', 'details'], + 'transmission_spectrum': ['name', 'timestamp', 'transmission', 'transmission_deviation'], + 'other': ['title', 'run', 'definition'] +} + +initial_metadata_dict = {key: {} for key, _ in metadata.items()} diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 760ef83ffe..c1b3ae15df 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -28,7 +28,7 @@ def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: # selector = MetadataComponentSelector(metadatum, self.metadata_dict) - selector = MetadataSelector(metadatum, options, self.metadata_dict) + selector = MetadataSelector(metadatum, options, self.metadata_dict[top_level]) metadatum_item = QTreeWidgetItem([metadatum]) # selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) From 2c7f19c240c50d79c613656ecac4c30f9d52d6d2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 13:59:38 +0000 Subject: [PATCH 295/396] Fixed bug where not editing metadata wouldn't work --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 33d5ebde91..1506fb6268 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -445,7 +445,7 @@ def onDoneButton(self): self.col_editor.columns, self.excluded_lines, self.seperators.items(), - self.filename_metadata[self.current_filename] + self.filename_metadata.get(self.current_filename, {}), ) self.params = params self.accept() From 0f4225270d3964f299dbf3f1200dea94d518ad30 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 26 Nov 2024 10:27:18 +0000 Subject: [PATCH 296/396] Strip all of the lines. Was causing bugs with ASCII data that has trailing, and leading whitespace. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 1506fb6268..dc42997afe 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -290,6 +290,7 @@ def load(self) -> None: try: with open(filename) as file: file_csv = file.readlines() + file_csv = [line.strip() for line in file_csv] # TODO: This assumes that no two files will be loaded with the same # name. This might not be a reasonable assumption. self.files[basename] = file_csv From de195b81673dc86fc4fab3955a0fce0211ceff90 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 27 Nov 2024 10:31:33 +0000 Subject: [PATCH 297/396] First step towards a master metadata dictionary. --- src/ascii_dialog/dialog.py | 1 + src/metadata_filename_gui/metadata_filename_dialog.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index dc42997afe..7202fef185 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -35,6 +35,7 @@ def __init__(self): # I'm not going to worry too much about deleting filenames from this dict below when they are unloaded. It # shouldn't break anything. self.filename_metadata: dict[str, dict[str, str]] = {} + self.master_metadata_dict: dict[str, dict[str, int]] = {} # This is useful for whenever the user wants to reopen the metadata editor. self.filename_metadata_separator = {} self.current_filename: str | None = None diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 40819f605e..966806ea9b 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -14,7 +14,8 @@ def build_font(text: str, classname: str = '') -> str: return f'{text}' class MetadataFilenameDialog(QDialog): - def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, initial_separator_text=''): + def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, + master_metadata: dict[str, dict[int, str]]={}, initial_separator_text=''): super().__init__() self.setWindowTitle('Metadata') @@ -22,6 +23,7 @@ def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, self.filename = filename # Key is the metadatum, value is the component selected for it. self.component_metadata = initial_component_metadata + self.master_metadata = master_metadata self.filename_line_label = QLabel() self.seperator_chars_label = QLabel('Seperators') From 78609df39570bd64054638a3578b56ac2ca936d5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 28 Nov 2024 09:03:53 +0000 Subject: [PATCH 298/396] Pass along the master metadata dict. --- src/metadata_filename_gui/metadata_component_selector.py | 3 ++- src/metadata_filename_gui/metadata_custom_selector.py | 3 ++- src/metadata_filename_gui/metadata_selector.py | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 5a34f6a24c..fa3b24bc27 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -7,12 +7,13 @@ class MetadataComponentSelector(QWidget): custom_button_pressed = Signal(Qt.MouseButton()) - def __init__(self, metadatum: str, metadata_dict: dict[str, str]): + def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, dict[str, int]]): super().__init__() self.options: list[str] self.option_buttons: list[QPushButton] self.layout = QHBoxLayout(self) self.metadata_dict = metadata_dict + self.master_metadata = master_metadata self.metadatum = metadatum def clear_options(self): diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 854e6b3244..2aa7bff489 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,9 +1,10 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout class MetadataCustomSelector(QWidget): - def __init__(self, metadatum: str, metadata_dict: dict[str, str]): + def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, dict[str, int]]): super().__init__() self.metadata_dict = metadata_dict + self.master_metadata = master_metadata self.metadatum = metadatum prexisting_value = metadata_dict.get(metadatum) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 09a7efa254..ad4076d96e 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -3,10 +3,12 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, dict[str, str]]): + def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, dict[str, str]], + master_metadata: dict[str, dict[str, int]]): super().__init__() self.metadatum = metadatum self.metadata_dict = metadata_dict + self.master_metadata = master_metadata self.options = options current_option = metadata_dict.get(metadatum) if current_option is None or current_option in options: @@ -19,13 +21,13 @@ def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, self.layout.addWidget(self.selector_widget) def new_component_selector(self) -> MetadataComponentSelector: - new_selector = MetadataComponentSelector(self.metadatum, self.metadata_dict) + new_selector = MetadataComponentSelector(self.metadatum, self.metadata_dict, self.master_metadata) new_selector.custom_button_pressed.connect(self.handle_selector_change) new_selector.draw_options(self.options, self.metadata_dict.get(self.metadatum)) return new_selector def new_custom_selector(self) -> MetadataCustomSelector: - new_selector = MetadataCustomSelector(self.metadatum, self.metadata_dict) + new_selector = MetadataCustomSelector(self.metadatum, self.metadata_dict, self.master_metadata) new_selector.from_filename_button.clicked.connect(self.handle_selector_change) return new_selector From 19f94f358a250c985814db957fc7b1c6fb063a89 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 28 Nov 2024 11:01:25 +0000 Subject: [PATCH 299/396] Pass in the master metadata. --- src/metadata_filename_gui/metadata_tree_widget.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index c1b3ae15df..d1a7576806 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -4,11 +4,12 @@ from metadata_filename_gui.metadata_selector import MetadataSelector class MetadataTreeWidget(QTreeWidget): - def __init__(self, metadata_dict: dict[str, str]): + def __init__(self, metadata_dict: dict[str, str], master_metadata: dict[str, dict[str, int]]): super().__init__() self.setColumnCount(2) self.setHeaderLabels(['Name', 'Filename Components']) self.metadata_dict = metadata_dict + self.master_metadata = master_metadata def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): @@ -28,7 +29,7 @@ def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: # selector = MetadataComponentSelector(metadatum, self.metadata_dict) - selector = MetadataSelector(metadatum, options, self.metadata_dict[top_level]) + selector = MetadataSelector(metadatum, options, self.metadata_dict[top_level], self.master_metadata[top_level]) metadatum_item = QTreeWidgetItem([metadatum]) # selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) From f1b644077f93d0851ca0cddca3fca811e1f36e5c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 28 Nov 2024 11:05:05 +0000 Subject: [PATCH 300/396] Changed round a bunch of type hints. --- src/metadata_filename_gui/metadata_component_selector.py | 2 +- src/metadata_filename_gui/metadata_custom_selector.py | 2 +- src/metadata_filename_gui/metadata_selector.py | 4 ++-- src/metadata_filename_gui/metadata_tree_widget.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index fa3b24bc27..9c581098ed 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -7,7 +7,7 @@ class MetadataComponentSelector(QWidget): custom_button_pressed = Signal(Qt.MouseButton()) - def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, dict[str, int]]): + def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, int]): super().__init__() self.options: list[str] self.option_buttons: list[QPushButton] diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 2aa7bff489..87f9264b0b 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,7 +1,7 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout class MetadataCustomSelector(QWidget): - def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, dict[str, int]]): + def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, int]): super().__init__() self.metadata_dict = metadata_dict self.master_metadata = master_metadata diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index ad4076d96e..affd49ba60 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -3,8 +3,8 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, dict[str, str]], - master_metadata: dict[str, dict[str, int]]): + def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, str], + master_metadata: dict[str, int]): super().__init__() self.metadatum = metadatum self.metadata_dict = metadata_dict diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index d1a7576806..1a37799657 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -4,7 +4,7 @@ from metadata_filename_gui.metadata_selector import MetadataSelector class MetadataTreeWidget(QTreeWidget): - def __init__(self, metadata_dict: dict[str, str], master_metadata: dict[str, dict[str, int]]): + def __init__(self, metadata_dict: dict[str, dict[str, str]], master_metadata: dict[str, dict[str, int]]): super().__init__() self.setColumnCount(2) self.setHeaderLabels(['Name', 'Filename Components']) From 2ae7f4641de36a06c572b0b1e10bcce28b6797f5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 09:23:26 +0000 Subject: [PATCH 301/396] Master metadata should be the same for all files. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 7202fef185..00686b4950 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -35,7 +35,7 @@ def __init__(self): # I'm not going to worry too much about deleting filenames from this dict below when they are unloaded. It # shouldn't break anything. self.filename_metadata: dict[str, dict[str, str]] = {} - self.master_metadata_dict: dict[str, dict[str, int]] = {} + self.master_metadata_dict: dict[str, str] = {} # This is useful for whenever the user wants to reopen the metadata editor. self.filename_metadata_separator = {} self.current_filename: str | None = None From 0f2be4ea19992665b45e1f2d6a158d64f126c07a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 09:24:25 +0000 Subject: [PATCH 302/396] Give the master metadata a default value. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 00686b4950..700bf45a5b 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -35,7 +35,7 @@ def __init__(self): # I'm not going to worry too much about deleting filenames from this dict below when they are unloaded. It # shouldn't break anything. self.filename_metadata: dict[str, dict[str, str]] = {} - self.master_metadata_dict: dict[str, str] = {} + self.master_metadata_dict: dict[str, str] = initial_metadata_dict.copy() # This is useful for whenever the user wants to reopen the metadata editor. self.filename_metadata_separator = {} self.current_filename: str | None = None From 65c396b8fa19933fe7157c410b3ec33af7d0a5c4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 09:26:02 +0000 Subject: [PATCH 303/396] This should be type hinted. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 700bf45a5b..ff5650043b 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -37,7 +37,7 @@ def __init__(self): self.filename_metadata: dict[str, dict[str, str]] = {} self.master_metadata_dict: dict[str, str] = initial_metadata_dict.copy() # This is useful for whenever the user wants to reopen the metadata editor. - self.filename_metadata_separator = {} + self.filename_metadata_separator: dict[str, str] = {} self.current_filename: str | None = None self.seperators: dict[str, bool] = { From 90ea644fa9dc9049319170437e338b60883d6b6b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 10:09:29 +0000 Subject: [PATCH 304/396] Remove the metadata dict param. We're not using this anymore. Just getting it from the class' internal state. --- src/metadata_filename_gui/metadata_tree_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 1a37799657..ccf8e66f70 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -12,7 +12,7 @@ def __init__(self, metadata_dict: dict[str, dict[str, str]], master_metadata: di self.master_metadata = master_metadata - def draw_tree(self, options: list[str], metadata_dict: dict[str, str]): + def draw_tree(self, options: list[str]): self.clear() # TODO: I'm not sure whether I like this approach. Maybe use some reflection from the Metadata class instead. metadata = { From c43ed079dcb38941bf11e3c14dadd754eeef552f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 10:18:13 +0000 Subject: [PATCH 305/396] I think these are the wrong way round. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 966806ea9b..dda3aa505c 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -15,7 +15,7 @@ def build_font(text: str, classname: str = '') -> str: class MetadataFilenameDialog(QDialog): def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, - master_metadata: dict[str, dict[int, str]]={}, initial_separator_text=''): + master_metadata: dict[str, dict[str, int]]={}, initial_separator_text=''): super().__init__() self.setWindowTitle('Metadata') From 267ff9d176d8d2d04ec46dc01b34a3833b453fcd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 10:19:41 +0000 Subject: [PATCH 306/396] Give the master metadata to metadata filename. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index ff5650043b..b38ae99fdf 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -456,7 +456,7 @@ def editMetadata(self): # current_metadata = self.filename_metadata[self.current_filename] current_metadata = self.filename_metadata.get(self.current_filename, initial_metadata_dict.copy()) current_separator = self.filename_metadata_separator.get(self.current_filename, '') - dialog = MetadataFilenameDialog(self.current_filename, current_metadata, current_separator) + dialog = MetadataFilenameDialog(self.current_filename, current_metadata, self.master_metadata_dict, current_separator) status = dialog.exec() if status == 1: self.filename_metadata[self.current_filename] = dialog.component_metadata From 26df2ebd95c0d2f7b951ae6f31a5855fd6b5e88e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:01:26 +0000 Subject: [PATCH 307/396] Create some classes for metadata. To avoid getting into dict hell. --- src/metadata_filename_gui/internal_metadata.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/metadata_filename_gui/internal_metadata.py diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py new file mode 100644 index 0000000000..1859ff6eb1 --- /dev/null +++ b/src/metadata_filename_gui/internal_metadata.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass +from typing import TypeVar, Generic + +T = TypeVar('T') + + +@dataclass +class InternalMetadataCategory[T]: + values: dict[str, T] + +@dataclass +class InternalMetadata: + # Key is the filename. + filename_specific_metadata: dict[str, InternalMetadataCategory[str]] + master_metadata: dict[str, InternalMetadataCategory[int]] From 6d4504347095be8942aadc22a4c9005ad6b3c4dd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:02:06 +0000 Subject: [PATCH 308/396] Remove unneeded import. --- src/metadata_filename_gui/internal_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 1859ff6eb1..880679ffb1 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import TypeVar, Generic +from typing import TypeVar T = TypeVar('T') From 3ac3124f90c16dec4eee2c7ae9362b5479010245 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:23:58 +0000 Subject: [PATCH 309/396] Implemented get_metadata function. --- src/metadata_filename_gui/internal_metadata.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 880679ffb1..bf0c3ffd6f 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from typing import TypeVar +from re import split as re_split T = TypeVar('T') @@ -11,5 +12,19 @@ class InternalMetadataCategory[T]: @dataclass class InternalMetadata: # Key is the filename. - filename_specific_metadata: dict[str, InternalMetadataCategory[str]] + filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] master_metadata: dict[str, InternalMetadataCategory[int]] + + def get_metadata(self, category: str, value: str, filename: str, separator_pattern: str) -> str: + filename_components = re_split(filename, separator_pattern) + # We prioritise the master metadata. + + # TODO: Assumes category in master_metadata exists. Is this a reasonable assumption? May need to make sure it is + # definitely in the dictionary. + if value in self.master_metadata[category].values: + index = self.master_metadata[category].values[value] + return filename_components[index] + target_category = self.filename_specific_metadata[filename][category].values + if value in target_category: + return target_category[value] + raise ValueError('value does not exist in metadata.') From 11997726e86298fa7deb44182ba958256e21eaec Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:54:37 +0000 Subject: [PATCH 310/396] Update metadata function. --- src/metadata_filename_gui/internal_metadata.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index bf0c3ffd6f..bfa4037d86 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -28,3 +28,11 @@ def get_metadata(self, category: str, value: str, filename: str, separator_patte if value in target_category: return target_category[value] raise ValueError('value does not exist in metadata.') + + def update_metadata(self, category: str, key: str, filename: str, new_value: str | int): + if isinstance(new_value, str): + self.filename_specific_metadata[filename][category].values[key] = new_value + # TODO: What about the master metadata? Until that's gone, that still takes precedence. + elif isinstance(new_value, int): + self.master_metadata[category].values[key] = new_value + raise TypeError('Invalid type for new_value') From 5aeac896aac9d8fe6d970c7ce809c4013c2be1db Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 13:38:04 +0000 Subject: [PATCH 311/396] Add defaults. --- src/metadata_filename_gui/internal_metadata.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index bfa4037d86..be7deceeab 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -1,19 +1,20 @@ from dataclasses import dataclass from typing import TypeVar from re import split as re_split +from metadata_filename_gui.metadata_tree_data import metadata as initial_metadata T = TypeVar('T') @dataclass class InternalMetadataCategory[T]: - values: dict[str, T] + values: dict[str, T] = {} @dataclass class InternalMetadata: # Key is the filename. - filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] - master_metadata: dict[str, InternalMetadataCategory[int]] + filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = {} + master_metadata: dict[str, InternalMetadataCategory[int]] = {} def get_metadata(self, category: str, value: str, filename: str, separator_pattern: str) -> str: filename_components = re_split(filename, separator_pattern) From 054872d9272e09573e2d6fb536dcf783b017fe2b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 13:44:30 +0000 Subject: [PATCH 312/396] Implemented add file function. --- src/metadata_filename_gui/internal_metadata.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index be7deceeab..c2d1510956 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -37,3 +37,10 @@ def update_metadata(self, category: str, key: str, filename: str, new_value: str elif isinstance(new_value, int): self.master_metadata[category].values[key] = new_value raise TypeError('Invalid type for new_value') + + def _default_categories(self) -> dict[str, InternalMetadataCategory[str | int]]: + return {key: InternalMetadataCategory() for key in initial_metadata.keys()} + + def add_file(self, new_filename: str): + # TODO: Fix typing here. Pyright is showing errors. + self.filename_specific_metadata[new_filename] = self._default_categories() From 464384ec4eebe399b759d189e82415d76f4fccd1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 13:46:07 +0000 Subject: [PATCH 313/396] Make default_categories not a method. So it can be used on the master metadata dict. --- src/metadata_filename_gui/internal_metadata.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index c2d1510956..0c60e030bd 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -10,11 +10,14 @@ class InternalMetadataCategory[T]: values: dict[str, T] = {} +def default_categories() -> dict[str, InternalMetadataCategory[str | int]]: + return {key: InternalMetadataCategory() for key in initial_metadata.keys()} + @dataclass class InternalMetadata: # Key is the filename. filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = {} - master_metadata: dict[str, InternalMetadataCategory[int]] = {} + master_metadata: dict[str, InternalMetadataCategory[int]] = default_categories() def get_metadata(self, category: str, value: str, filename: str, separator_pattern: str) -> str: filename_components = re_split(filename, separator_pattern) @@ -38,9 +41,6 @@ def update_metadata(self, category: str, key: str, filename: str, new_value: str self.master_metadata[category].values[key] = new_value raise TypeError('Invalid type for new_value') - def _default_categories(self) -> dict[str, InternalMetadataCategory[str | int]]: - return {key: InternalMetadataCategory() for key in initial_metadata.keys()} - def add_file(self, new_filename: str): # TODO: Fix typing here. Pyright is showing errors. - self.filename_specific_metadata[new_filename] = self._default_categories() + self.filename_specific_metadata[new_filename] = default_categories() From 4d07d645bb9afede0bb7b8ca37f76c9debf55f55 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 15:01:08 +0000 Subject: [PATCH 314/396] Changed the signature of get metadata. --- src/metadata_filename_gui/internal_metadata.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 0c60e030bd..933f6e03be 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -19,8 +19,7 @@ class InternalMetadata: filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = {} master_metadata: dict[str, InternalMetadataCategory[int]] = default_categories() - def get_metadata(self, category: str, value: str, filename: str, separator_pattern: str) -> str: - filename_components = re_split(filename, separator_pattern) + def get_metadata(self, category: str, value: str, filename_components: list[str] = []) -> str: # We prioritise the master metadata. # TODO: Assumes category in master_metadata exists. Is this a reasonable assumption? May need to make sure it is From f2cfafe48fb04f0172105b071d5fc3c28ed16f8b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 15:05:11 +0000 Subject: [PATCH 315/396] Start using the new internal metadata class. --- src/ascii_dialog/dialog.py | 9 +++---- .../metadata_filename_dialog.py | 7 +++--- .../metadata_selector.py | 10 ++++---- .../metadata_tree_widget.py | 25 ++++++------------- 4 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index b38ae99fdf..a70d0a4e01 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -12,6 +12,7 @@ from sasdata.temp_ascii_reader import load_data, AsciiReaderParams from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog from metadata_filename_gui.metadata_tree_data import initial_metadata_dict +from metadata_filename_gui.internal_metadata import InternalMetadata import re TABLE_MAX_ROWS = 1000 @@ -32,12 +33,8 @@ def __init__(self): self.files: dict[str, list[str]] = {} self.files_full_path: dict[str, str] = {} self.files_is_included: dict[str, list[bool]] = {} - # I'm not going to worry too much about deleting filenames from this dict below when they are unloaded. It - # shouldn't break anything. - self.filename_metadata: dict[str, dict[str, str]] = {} - self.master_metadata_dict: dict[str, str] = initial_metadata_dict.copy() # This is useful for whenever the user wants to reopen the metadata editor. - self.filename_metadata_separator: dict[str, str] = {} + self.internal_metadata = InternalMetadata() self.current_filename: str | None = None self.seperators: dict[str, bool] = { @@ -456,7 +453,7 @@ def editMetadata(self): # current_metadata = self.filename_metadata[self.current_filename] current_metadata = self.filename_metadata.get(self.current_filename, initial_metadata_dict.copy()) current_separator = self.filename_metadata_separator.get(self.current_filename, '') - dialog = MetadataFilenameDialog(self.current_filename, current_metadata, self.master_metadata_dict, current_separator) + dialog = MetadataFilenameDialog(self.current_filename, self.internal_metadata, current_separator) status = dialog.exec() if status == 1: self.filename_metadata[self.current_filename] = dialog.component_metadata diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index dda3aa505c..b1d38d02a8 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,5 +1,6 @@ from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton from metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget +from metadata_filename_gui.internal_metadata import InternalMetadata from sys import argv import re @@ -14,16 +15,14 @@ def build_font(text: str, classname: str = '') -> str: return f'{text}' class MetadataFilenameDialog(QDialog): - def __init__(self, filename: str, initial_component_metadata: dict[str, str]={}, - master_metadata: dict[str, dict[str, int]]={}, initial_separator_text=''): + def __init__(self, filename: str, initial_metadata: InternalMetadata, initial_separator_text=''): super().__init__() self.setWindowTitle('Metadata') self.filename = filename # Key is the metadatum, value is the component selected for it. - self.component_metadata = initial_component_metadata - self.master_metadata = master_metadata + self.initial_metadata = initial_metadata self.filename_line_label = QLabel() self.seperator_chars_label = QLabel('Seperators') diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index affd49ba60..94687004c4 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -1,16 +1,16 @@ from PySide6.QtWidgets import QWidget, QHBoxLayout +from metadata_filename_gui.internal_metadata import InternalMetadata from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, metadatum: str, options: list[str], metadata_dict: dict[str, str], - master_metadata: dict[str, int]): + def __init__(self, category: str, metadatum: str, options: list[str], metadata: InternalMetadata, filename: str, separator_pattern: str): super().__init__() + self.category = category self.metadatum = metadatum - self.metadata_dict = metadata_dict - self.master_metadata = master_metadata + self.metadata: InternalMetadata = metadata self.options = options - current_option = metadata_dict.get(metadatum) + current_option = self.metadata.get_metadata(self.category, metadatum, options) if current_option is None or current_option in options: self.selector_widget = self.new_component_selector() else: diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index ccf8e66f70..d8634c5ecf 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -2,34 +2,23 @@ from PySide6.QtCore import QAbstractItemModel from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector from metadata_filename_gui.metadata_selector import MetadataSelector +from metadata_filename_gui.internal_metadata import InternalMetadata +from metadata_filename_gui.metadata_tree_data import metadata as metadata_categories class MetadataTreeWidget(QTreeWidget): - def __init__(self, metadata_dict: dict[str, dict[str, str]], master_metadata: dict[str, dict[str, int]]): + def __init__(self, metadata: InternalMetadata): super().__init__() self.setColumnCount(2) self.setHeaderLabels(['Name', 'Filename Components']) - self.metadata_dict = metadata_dict - self.master_metadata = master_metadata + self.metadata: InternalMetadata = metadata - - def draw_tree(self, options: list[str]): + def draw_tree(self, options: list[str], full_filename: str): self.clear() - # TODO: I'm not sure whether I like this approach. Maybe use some reflection from the Metadata class instead. - metadata = { - 'source': ['name', 'radiation', 'type', 'probe_particle', 'beam_size_name', 'beam_size', 'beam_shape', 'wavelength', 'wavelength_min', 'wavelength_max', 'wavelength_spread'], - 'detector': ['name', 'distance', 'offset', 'orientation', 'beam_center', 'pixel_size', 'slit_length'], - 'aperture': ['name', 'type', 'size_name', 'size', 'distance'], - 'collimation': ['name', 'lengths'], - 'process': ['name', 'date', 'description', 'term', 'notes'], - 'sample': ['name', 'sample_id', 'thickness', 'transmission', 'temperature', 'position', 'orientation', 'details'], - 'transmission_spectrum': ['name', 'timestamp', 'transmission', 'transmission_deviation'], - 'other': ['title', 'run', 'definition'] - } - for top_level, items in metadata.items(): + for top_level, items in metadata_categories.items(): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: # selector = MetadataComponentSelector(metadatum, self.metadata_dict) - selector = MetadataSelector(metadatum, options, self.metadata_dict[top_level], self.master_metadata[top_level]) + selector = MetadataSelector(top_level, metadatum, options, self.metadata, full_filename, self.separator_pattern) metadatum_item = QTreeWidgetItem([metadatum]) # selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) From f093cfcb7209aa76135d714efd26c6af9d8c1271 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 15:13:25 +0000 Subject: [PATCH 316/396] Need to use factory for defaults. --- src/metadata_filename_gui/internal_metadata.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 933f6e03be..ac2db5227c 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import TypeVar from re import split as re_split from metadata_filename_gui.metadata_tree_data import metadata as initial_metadata @@ -8,7 +8,7 @@ @dataclass class InternalMetadataCategory[T]: - values: dict[str, T] = {} + values: dict[str, T] = field(default_factory=dict) def default_categories() -> dict[str, InternalMetadataCategory[str | int]]: return {key: InternalMetadataCategory() for key in initial_metadata.keys()} @@ -16,8 +16,8 @@ def default_categories() -> dict[str, InternalMetadataCategory[str | int]]: @dataclass class InternalMetadata: # Key is the filename. - filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = {} - master_metadata: dict[str, InternalMetadataCategory[int]] = default_categories() + filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = field(default_factory=dict) + master_metadata: dict[str, InternalMetadataCategory[int]] = field(default_factory=default_categories) def get_metadata(self, category: str, value: str, filename_components: list[str] = []) -> str: # We prioritise the master metadata. From 4460094f3e0d2f9e1a1dee9cadb884e71f22efd0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:39:46 +0000 Subject: [PATCH 317/396] Add a separator field. --- src/metadata_filename_gui/internal_metadata.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index ac2db5227c..78bcad6718 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -17,6 +17,7 @@ def default_categories() -> dict[str, InternalMetadataCategory[str | int]]: class InternalMetadata: # Key is the filename. filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = field(default_factory=dict) + filename_separator: dict[str, str] = field(default_factory=dict) master_metadata: dict[str, InternalMetadataCategory[int]] = field(default_factory=default_categories) def get_metadata(self, category: str, value: str, filename_components: list[str] = []) -> str: From 9186f3f0c1b154957b6c2902d2a5c7995e345a44 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:41:36 +0000 Subject: [PATCH 318/396] Don't need the separator argument anymore. --- src/metadata_filename_gui/metadata_filename_dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index b1d38d02a8..0c72d55faf 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -15,9 +15,13 @@ def build_font(text: str, classname: str = '') -> str: return f'{text}' class MetadataFilenameDialog(QDialog): - def __init__(self, filename: str, initial_metadata: InternalMetadata, initial_separator_text=''): + def __init__(self, filename: str, initial_metadata: InternalMetadata): super().__init__() + # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the + # filename.) + initial_separator_text = initial_metadata.filename_separator.get(filename, '_') + self.setWindowTitle('Metadata') self.filename = filename From ae8d02958bc4f88b36dda769678ab4087efcdbea Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:45:08 +0000 Subject: [PATCH 319/396] Type hint to keep pyright happy. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a70d0a4e01..cb72420ad0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -34,7 +34,7 @@ def __init__(self): self.files_full_path: dict[str, str] = {} self.files_is_included: dict[str, list[bool]] = {} # This is useful for whenever the user wants to reopen the metadata editor. - self.internal_metadata = InternalMetadata() + self.internal_metadata: InternalMetadata = InternalMetadata() self.current_filename: str | None = None self.seperators: dict[str, bool] = { From e9de4f608418b38de031d870c2ba177855e19bd4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:48:31 +0000 Subject: [PATCH 320/396] Some more argument fixes. --- src/ascii_dialog/dialog.py | 4 +--- src/metadata_filename_gui/metadata_filename_dialog.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index cb72420ad0..0a64f8f3c7 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -451,9 +451,7 @@ def onDoneButton(self): def editMetadata(self): # current_metadata = self.filename_metadata[self.current_filename] - current_metadata = self.filename_metadata.get(self.current_filename, initial_metadata_dict.copy()) - current_separator = self.filename_metadata_separator.get(self.current_filename, '') - dialog = MetadataFilenameDialog(self.current_filename, self.internal_metadata, current_separator) + dialog = MetadataFilenameDialog(self.current_filename, self.internal_metadata) status = dialog.exec() if status == 1: self.filename_metadata[self.current_filename] = dialog.component_metadata diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 0c72d55faf..6d70c8cdfc 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -26,7 +26,7 @@ def __init__(self, filename: str, initial_metadata: InternalMetadata): self.filename = filename # Key is the metadatum, value is the component selected for it. - self.initial_metadata = initial_metadata + self.internal_metadata = initial_metadata self.filename_line_label = QLabel() self.seperator_chars_label = QLabel('Seperators') @@ -38,7 +38,7 @@ def __init__(self, filename: str, initial_metadata: InternalMetadata): self.filename_separator_layout.addWidget(self.seperator_chars_label) self.filename_separator_layout.addWidget(self.separator_chars) - self.metadata_tree = MetadataTreeWidget(self.component_metadata) + self.metadata_tree = MetadataTreeWidget(self.internal_metadata) # Have to update this now because it relies on the value of the separator, and tree. self.update_filename_separation() @@ -82,7 +82,7 @@ def formatted_filename(self) -> str: def update_filename_separation(self): self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') - self.metadata_tree.draw_tree(self.filename_components(), self.component_metadata) + self.metadata_tree.draw_tree(self.filename_components(), self.filename) def on_save(self): self.accept() From e98c125ebbd5d4d89a2dd4aac4fe85e3a6329f01 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:06:36 +0000 Subject: [PATCH 321/396] Don't need the separator param. --- src/metadata_filename_gui/metadata_selector.py | 2 +- src/metadata_filename_gui/metadata_tree_widget.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 94687004c4..f70dae6cde 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -4,7 +4,7 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, category: str, metadatum: str, options: list[str], metadata: InternalMetadata, filename: str, separator_pattern: str): + def __init__(self, category: str, metadatum: str, options: list[str], metadata: InternalMetadata, filename: str): super().__init__() self.category = category self.metadatum = metadatum diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index d8634c5ecf..1dc825bff0 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -18,7 +18,7 @@ def draw_tree(self, options: list[str], full_filename: str): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: # selector = MetadataComponentSelector(metadatum, self.metadata_dict) - selector = MetadataSelector(top_level, metadatum, options, self.metadata, full_filename, self.separator_pattern) + selector = MetadataSelector(top_level, metadatum, options, self.metadata, full_filename) metadatum_item = QTreeWidgetItem([metadatum]) # selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) From 64f1dd4a72b07db2d02247ef0f0783c2c4b8843a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:19:15 +0000 Subject: [PATCH 322/396] Implemented clear_metadata function. --- src/metadata_filename_gui/internal_metadata.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 78bcad6718..ef604b379a 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -41,6 +41,9 @@ def update_metadata(self, category: str, key: str, filename: str, new_value: str self.master_metadata[category].values[key] = new_value raise TypeError('Invalid type for new_value') + def clear_metadata(self, category: str, key: str, filename: str): + del self.filename_specific_metadata[filename][category].values[key] + def add_file(self, new_filename: str): # TODO: Fix typing here. Pyright is showing errors. self.filename_specific_metadata[new_filename] = default_categories() From 56ebc7f0407d7c75ec44de831d14dd8f24fab6bd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:20:25 +0000 Subject: [PATCH 323/396] Component selector uses the new metadata class. --- .../metadata_component_selector.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 9c581098ed..03bf6f9152 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -1,20 +1,23 @@ from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout from PySide6.QtCore import Signal, Qt, Slot +from metadata_filename_gui.internal_metadata import InternalMetadata + class MetadataComponentSelector(QWidget): # Creating a separate signal for this because the custom button may be destroyed/recreated whenever the options are # redrawn. custom_button_pressed = Signal(Qt.MouseButton()) - def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, int]): + def __init__(self, category: str, metadatum: str, filename: str, internal_metadata: InternalMetadata): super().__init__() self.options: list[str] self.option_buttons: list[QPushButton] self.layout = QHBoxLayout(self) - self.metadata_dict = metadata_dict - self.master_metadata = master_metadata + self.internal_metadata = internal_metadata self.metadatum = metadatum + self.category = category + self.filename = filename def clear_options(self): for i in reversed(range(self.layout.count() - 1)): @@ -42,11 +45,13 @@ def handle_custom_button(self): def selection_changed(self): selected_button: QPushButton = self.sender() - selected_component = selected_button.text() - for button in self.option_buttons: + button_index = -1 + for i, button in enumerate(self.option_buttons): if button != selected_button: button.setChecked(False) + else: + button_index = i if selected_button.isChecked(): - self.metadata_dict[self.metadatum] = selected_component + self.internal_metadata.update_metadata(self.category, self.metadatum, self.filename, button_index) else: - del self.metadata_dict[self.metadatum] + self.internal_metadata.clear_metadata(self.category, self.metadatum, self.filename) From b0aeb07d3ccc003c4126c85042e050406d31bdb1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:31:13 +0000 Subject: [PATCH 324/396] Get the filename components in the class itself. --- src/metadata_filename_gui/internal_metadata.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index ef604b379a..a62ee542d0 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -20,14 +20,19 @@ class InternalMetadata: filename_separator: dict[str, str] = field(default_factory=dict) master_metadata: dict[str, InternalMetadataCategory[int]] = field(default_factory=default_categories) - def get_metadata(self, category: str, value: str, filename_components: list[str] = []) -> str: + def filename_components(self, filename: str) -> list[str]: + return re_split(self.filename_separator[filename], filename) + + def get_metadata(self, category: str, value: str, filename: str) -> str: + components = self.filename_components(filename) + # We prioritise the master metadata. # TODO: Assumes category in master_metadata exists. Is this a reasonable assumption? May need to make sure it is # definitely in the dictionary. if value in self.master_metadata[category].values: index = self.master_metadata[category].values[value] - return filename_components[index] + return components[index] target_category = self.filename_specific_metadata[filename][category].values if value in target_category: return target_category[value] From dfde8ca0ed0c55193e49976d2cd7d1175437f8af Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:33:39 +0000 Subject: [PATCH 325/396] Replicate the behaviour of the old system. --- src/metadata_filename_gui/internal_metadata.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index a62ee542d0..714ffc6dab 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -23,7 +23,7 @@ class InternalMetadata: def filename_components(self, filename: str) -> list[str]: return re_split(self.filename_separator[filename], filename) - def get_metadata(self, category: str, value: str, filename: str) -> str: + def get_metadata(self, category: str, value: str, filename: str, error_on_not_found=False) -> str | None: components = self.filename_components(filename) # We prioritise the master metadata. @@ -36,7 +36,10 @@ def get_metadata(self, category: str, value: str, filename: str) -> str: target_category = self.filename_specific_metadata[filename][category].values if value in target_category: return target_category[value] - raise ValueError('value does not exist in metadata.') + if error_on_not_found: + raise ValueError('value does not exist in metadata.') + else: + return None def update_metadata(self, category: str, key: str, filename: str, new_value: str | int): if isinstance(new_value, str): From 3abeac9433adeaa52909386b4143427a209559fd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:52:30 +0000 Subject: [PATCH 326/396] Updated the clear metadata function. --- src/metadata_filename_gui/internal_metadata.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 714ffc6dab..71c2eab57f 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -50,7 +50,11 @@ def update_metadata(self, category: str, key: str, filename: str, new_value: str raise TypeError('Invalid type for new_value') def clear_metadata(self, category: str, key: str, filename: str): - del self.filename_specific_metadata[filename][category].values[key] + category_obj = self.filename_specific_metadata[filename][category] + if key in category_obj.values: + del category_obj.values[key] + if key in self.master_metadata[category].values: + del self.master_metadata[category].values[key] def add_file(self, new_filename: str): # TODO: Fix typing here. Pyright is showing errors. From c283d1734a4c56b23ad886e95ba3d2d745e2b005 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:53:00 +0000 Subject: [PATCH 327/396] Update for internal metadata. --- .../metadata_custom_selector.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 87f9264b0b..4377fa2766 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,13 +1,16 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout +from metadata_filename_gui.internal_metadata import InternalMetadata + class MetadataCustomSelector(QWidget): - def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadata: dict[str, int]): + def __init__(self, category:str, metadatum: str, internal_metadata: InternalMetadata, filename: str): super().__init__() - self.metadata_dict = metadata_dict - self.master_metadata = master_metadata + self.internal_metadata = internal_metadata self.metadatum = metadatum + self.category = category + self.filename = filename - prexisting_value = metadata_dict.get(metadatum) + prexisting_value = self.internal_metadata.get_metadata(category, metadatum, filename) initial_value = prexisting_value if prexisting_value is not None else '' self.entry_box = QLineEdit(initial_value) self.entry_box.textChanged.connect(self.selection_changed) @@ -20,6 +23,6 @@ def __init__(self, metadatum: str, metadata_dict: dict[str, str], master_metadat def selection_changed(self): new_value = self.entry_box.text() if new_value != '': - self.metadata_dict[self.metadatum] = new_value - elif self.metadatum in self.metadata_dict: - del self.metadata_dict[self.metadatum] + self.internal_metadata.update_metadata(self.category, self.metadatum, self.filename, new_value) + else: + self.internal_metadata.clear_metadata(self.category, self.metadatum, self.filename) From 338ac4805118044a4446dea38232506802dcd210 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:53:13 +0000 Subject: [PATCH 328/396] Use new params. --- src/metadata_filename_gui/metadata_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index f70dae6cde..070cf42abd 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -10,7 +10,7 @@ def __init__(self, category: str, metadatum: str, options: list[str], metadata: self.metadatum = metadatum self.metadata: InternalMetadata = metadata self.options = options - current_option = self.metadata.get_metadata(self.category, metadatum, options) + current_option = self.metadata.get_metadata(self.category, metadatum, filename) if current_option is None or current_option in options: self.selector_widget = self.new_component_selector() else: From 973599ca65d3e30b56dc05537d1d26665b38b317 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:57:14 +0000 Subject: [PATCH 329/396] Make sure a separator is initialised. --- src/metadata_filename_gui/metadata_filename_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 6d70c8cdfc..e40707f881 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -21,6 +21,7 @@ def __init__(self, filename: str, initial_metadata: InternalMetadata): # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the # filename.) initial_separator_text = initial_metadata.filename_separator.get(filename, '_') + initial_metadata.filename_separator[filename] = initial_separator_text self.setWindowTitle('Metadata') From 088994fb70bbf84e2d23c76375a42344f71f58c8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:59:14 +0000 Subject: [PATCH 330/396] Use the name load file. This was getting hard to search for. --- src/ascii_dialog/dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 0a64f8f3c7..8cca2f3ffe 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -64,7 +64,7 @@ def __init__(self): self.filename_chooser.currentTextChanged.connect(self.updateCurrentFile) self.load_button = QPushButton("Load File") - self.load_button.clicked.connect(self.load) + self.load_button.clicked.connect(self.load_file) ## Dataset type selection self.dataset_layout = QHBoxLayout() @@ -274,7 +274,7 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: @Slot() - def load(self) -> None: + def load_file(self) -> None: """Open the file loading dialog, and load the file the user selects.""" result = QFileDialog.getOpenFileName(self) # Happens when the user cancels without selecting a file. There isn't a From f08c0a0caa844ed308ab860199e151aa7dc74a30 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:19:39 +0000 Subject: [PATCH 331/396] Need to add file to metadata when loaded. --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8cca2f3ffe..9359b7ede1 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -303,6 +303,7 @@ def load_file(self) -> None: # the table to be drawn. self.filename_chooser.addItem(basename) self.filename_chooser.setCurrentText(basename) + self.internal_metadata.add_file(basename) except OSError: QMessageBox.critical(self, 'File Read Error', 'There was an error accessing that file.') From f0aa11a4cc5ec2ba61fe6f3a5e5a00fae0c0e18e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:19:51 +0000 Subject: [PATCH 332/396] Forgot to add these :P --- src/metadata_filename_gui/metadata_selector.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 070cf42abd..74439f461d 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -10,6 +10,7 @@ def __init__(self, category: str, metadatum: str, options: list[str], metadata: self.metadatum = metadatum self.metadata: InternalMetadata = metadata self.options = options + self.filename = filename current_option = self.metadata.get_metadata(self.category, metadatum, filename) if current_option is None or current_option in options: self.selector_widget = self.new_component_selector() @@ -21,13 +22,14 @@ def __init__(self, category: str, metadatum: str, options: list[str], metadata: self.layout.addWidget(self.selector_widget) def new_component_selector(self) -> MetadataComponentSelector: - new_selector = MetadataComponentSelector(self.metadatum, self.metadata_dict, self.master_metadata) + new_selector = MetadataComponentSelector(self.category, self.metadatum, self.filename, self.metadata) new_selector.custom_button_pressed.connect(self.handle_selector_change) new_selector.draw_options(self.options, self.metadata_dict.get(self.metadatum)) + new_selector.draw_options(self.options, self.metadata.get_metadata(self.category, self.metadatum, self.filename)) return new_selector def new_custom_selector(self) -> MetadataCustomSelector: - new_selector = MetadataCustomSelector(self.metadatum, self.metadata_dict, self.master_metadata) + new_selector = MetadataCustomSelector(self.category, self.metadatum, self.metadata, self.filename) new_selector.from_filename_button.clicked.connect(self.handle_selector_change) return new_selector From bd8ec7c0ca2980db9cb6c37f5d2d5c88f363cea6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:21:23 +0000 Subject: [PATCH 333/396] Remove old draw options call. --- src/metadata_filename_gui/metadata_selector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 74439f461d..0d32ddd831 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -24,7 +24,6 @@ def __init__(self, category: str, metadatum: str, options: list[str], metadata: def new_component_selector(self) -> MetadataComponentSelector: new_selector = MetadataComponentSelector(self.category, self.metadatum, self.filename, self.metadata) new_selector.custom_button_pressed.connect(self.handle_selector_change) - new_selector.draw_options(self.options, self.metadata_dict.get(self.metadatum)) new_selector.draw_options(self.options, self.metadata.get_metadata(self.category, self.metadatum, self.filename)) return new_selector From 72dcd5e26ae61bc3ee696466a27d388d9b07cc25 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:23:07 +0000 Subject: [PATCH 334/396] Missing else. --- src/metadata_filename_gui/internal_metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py index 71c2eab57f..ff9d3f89f4 100644 --- a/src/metadata_filename_gui/internal_metadata.py +++ b/src/metadata_filename_gui/internal_metadata.py @@ -47,7 +47,8 @@ def update_metadata(self, category: str, key: str, filename: str, new_value: str # TODO: What about the master metadata? Until that's gone, that still takes precedence. elif isinstance(new_value, int): self.master_metadata[category].values[key] = new_value - raise TypeError('Invalid type for new_value') + else: + raise TypeError('Invalid type for new_value') def clear_metadata(self, category: str, key: str, filename: str): category_obj = self.filename_specific_metadata[filename][category] From 185e8d958c5e8a377b1fc5a0e480455d53766a3e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:27:28 +0000 Subject: [PATCH 335/396] Pull out the new internal metadata. --- src/ascii_dialog/dialog.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 9359b7ede1..248f1f4ca8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -455,8 +455,7 @@ def editMetadata(self): dialog = MetadataFilenameDialog(self.current_filename, self.internal_metadata) status = dialog.exec() if status == 1: - self.filename_metadata[self.current_filename] = dialog.component_metadata - self.filename_metadata_separator[self.current_filename] = dialog.separator_text + self.internal_metadata = dialog.internal_metadata if __name__ == "__main__": From c26616bfc7fba8ce654bff2f0b16921d339883ce Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:29:07 +0000 Subject: [PATCH 336/396] Don't need this comment. --- src/ascii_dialog/dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 248f1f4ca8..eb926a8ea4 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -451,7 +451,6 @@ def onDoneButton(self): self.accept() def editMetadata(self): - # current_metadata = self.filename_metadata[self.current_filename] dialog = MetadataFilenameDialog(self.current_filename, self.internal_metadata) status = dialog.exec() if status == 1: From fd738c65246343233edc2c6afe7af973fda693a2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:06:31 +0000 Subject: [PATCH 337/396] Take into account this is now a list. --- src/ascii_dialog/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index eb926a8ea4..2f93e7ffa4 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -465,6 +465,7 @@ def editMetadata(self): # 1 means the dialog was accepted. if status == 1: loaded = load_data(dialog.params) - print(loaded.summary()) + for datum in loaded: + print(datum.summary()) exit() From 70d57e3e8513c3d287f107e6bfde2a981d9e261c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:20:19 +0000 Subject: [PATCH 338/396] Use the replaced class from sasdata. --- src/ascii_dialog/dialog.py | 4 +- .../internal_metadata.py | 62 ------------------- .../metadata_component_selector.py | 4 +- .../metadata_custom_selector.py | 2 +- .../metadata_filename_dialog.py | 4 +- .../metadata_selector.py | 2 +- .../metadata_tree_widget.py | 7 ++- 7 files changed, 12 insertions(+), 73 deletions(-) delete mode 100644 src/metadata_filename_gui/internal_metadata.py diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2f93e7ffa4..f5173f34c6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -12,7 +12,7 @@ from sasdata.temp_ascii_reader import load_data, AsciiReaderParams from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog from metadata_filename_gui.metadata_tree_data import initial_metadata_dict -from metadata_filename_gui.internal_metadata import InternalMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata import re TABLE_MAX_ROWS = 1000 @@ -34,7 +34,7 @@ def __init__(self): self.files_full_path: dict[str, str] = {} self.files_is_included: dict[str, list[bool]] = {} # This is useful for whenever the user wants to reopen the metadata editor. - self.internal_metadata: InternalMetadata = InternalMetadata() + self.internal_metadata: AsciiReaderMetadata = AsciiReaderMetadata() self.current_filename: str | None = None self.seperators: dict[str, bool] = { diff --git a/src/metadata_filename_gui/internal_metadata.py b/src/metadata_filename_gui/internal_metadata.py deleted file mode 100644 index ff9d3f89f4..0000000000 --- a/src/metadata_filename_gui/internal_metadata.py +++ /dev/null @@ -1,62 +0,0 @@ -from dataclasses import dataclass, field -from typing import TypeVar -from re import split as re_split -from metadata_filename_gui.metadata_tree_data import metadata as initial_metadata - -T = TypeVar('T') - - -@dataclass -class InternalMetadataCategory[T]: - values: dict[str, T] = field(default_factory=dict) - -def default_categories() -> dict[str, InternalMetadataCategory[str | int]]: - return {key: InternalMetadataCategory() for key in initial_metadata.keys()} - -@dataclass -class InternalMetadata: - # Key is the filename. - filename_specific_metadata: dict[str, dict[str, InternalMetadataCategory[str]]] = field(default_factory=dict) - filename_separator: dict[str, str] = field(default_factory=dict) - master_metadata: dict[str, InternalMetadataCategory[int]] = field(default_factory=default_categories) - - def filename_components(self, filename: str) -> list[str]: - return re_split(self.filename_separator[filename], filename) - - def get_metadata(self, category: str, value: str, filename: str, error_on_not_found=False) -> str | None: - components = self.filename_components(filename) - - # We prioritise the master metadata. - - # TODO: Assumes category in master_metadata exists. Is this a reasonable assumption? May need to make sure it is - # definitely in the dictionary. - if value in self.master_metadata[category].values: - index = self.master_metadata[category].values[value] - return components[index] - target_category = self.filename_specific_metadata[filename][category].values - if value in target_category: - return target_category[value] - if error_on_not_found: - raise ValueError('value does not exist in metadata.') - else: - return None - - def update_metadata(self, category: str, key: str, filename: str, new_value: str | int): - if isinstance(new_value, str): - self.filename_specific_metadata[filename][category].values[key] = new_value - # TODO: What about the master metadata? Until that's gone, that still takes precedence. - elif isinstance(new_value, int): - self.master_metadata[category].values[key] = new_value - else: - raise TypeError('Invalid type for new_value') - - def clear_metadata(self, category: str, key: str, filename: str): - category_obj = self.filename_specific_metadata[filename][category] - if key in category_obj.values: - del category_obj.values[key] - if key in self.master_metadata[category].values: - del self.master_metadata[category].values[key] - - def add_file(self, new_filename: str): - # TODO: Fix typing here. Pyright is showing errors. - self.filename_specific_metadata[new_filename] = default_categories() diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index 03bf6f9152..e334baccb6 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -1,7 +1,7 @@ from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout from PySide6.QtCore import Signal, Qt, Slot -from metadata_filename_gui.internal_metadata import InternalMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata class MetadataComponentSelector(QWidget): # Creating a separate signal for this because the custom button may be destroyed/recreated whenever the options are @@ -9,7 +9,7 @@ class MetadataComponentSelector(QWidget): custom_button_pressed = Signal(Qt.MouseButton()) - def __init__(self, category: str, metadatum: str, filename: str, internal_metadata: InternalMetadata): + def __init__(self, category: str, metadatum: str, filename: str, internal_metadata: AsciiReaderMetadata): super().__init__() self.options: list[str] self.option_buttons: list[QPushButton] diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index 4377fa2766..fd04f48ddf 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout -from metadata_filename_gui.internal_metadata import InternalMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata class MetadataCustomSelector(QWidget): def __init__(self, category:str, metadatum: str, internal_metadata: InternalMetadata, filename: str): diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index e40707f881..8e6759058d 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton from metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget -from metadata_filename_gui.internal_metadata import InternalMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata from sys import argv import re @@ -15,7 +15,7 @@ def build_font(text: str, classname: str = '') -> str: return f'{text}' class MetadataFilenameDialog(QDialog): - def __init__(self, filename: str, initial_metadata: InternalMetadata): + def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): super().__init__() # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 0d32ddd831..4268431309 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -1,5 +1,5 @@ from PySide6.QtWidgets import QWidget, QHBoxLayout -from metadata_filename_gui.internal_metadata import InternalMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 1dc825bff0..c2b560f18f 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -2,15 +2,16 @@ from PySide6.QtCore import QAbstractItemModel from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector from metadata_filename_gui.metadata_selector import MetadataSelector -from metadata_filename_gui.internal_metadata import InternalMetadata +from metadata_filename_gui.internal_metadata import AsciiReaderMetadata from metadata_filename_gui.metadata_tree_data import metadata as metadata_categories +from sasdata.ascii_reader_metadata import AsciiReaderMetadata class MetadataTreeWidget(QTreeWidget): - def __init__(self, metadata: InternalMetadata): + def __init__(self, metadata: AsciiReaderMetadata): super().__init__() self.setColumnCount(2) self.setHeaderLabels(['Name', 'Filename Components']) - self.metadata: InternalMetadata = metadata + self.metadata: AsciiReaderMetadata = metadata def draw_tree(self, options: list[str], full_filename: str): self.clear() From 02d10152dea93897f533d21d3cddefd47ff31a32 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:21:22 +0000 Subject: [PATCH 339/396] Whoops forgot to save these files. --- src/metadata_filename_gui/metadata_custom_selector.py | 2 +- src/metadata_filename_gui/metadata_selector.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index fd04f48ddf..a33d9a5c1f 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -3,7 +3,7 @@ from sasdata.ascii_reader_metadata import AsciiReaderMetadata class MetadataCustomSelector(QWidget): - def __init__(self, category:str, metadatum: str, internal_metadata: InternalMetadata, filename: str): + def __init__(self, category:str, metadatum: str, internal_metadata: AsciiReaderMetadata, filename: str): super().__init__() self.internal_metadata = internal_metadata self.metadatum = metadatum diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 4268431309..88d52c2a1a 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -4,11 +4,11 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, category: str, metadatum: str, options: list[str], metadata: InternalMetadata, filename: str): + def __init__(self, category: str, metadatum: str, options: list[str], metadata: AsciiReaderMetadata, filename: str): super().__init__() self.category = category self.metadatum = metadatum - self.metadata: InternalMetadata = metadata + self.metadata: AsciiReaderMetadata = metadata self.options = options self.filename = filename current_option = self.metadata.get_metadata(self.category, metadatum, filename) From a1046f44ad09ea1c4489df8706ec5d6cf9a613a2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:21:52 +0000 Subject: [PATCH 340/396] Forgot to remove old import. --- src/metadata_filename_gui/metadata_tree_widget.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index c2b560f18f..417d0e2832 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -2,7 +2,6 @@ from PySide6.QtCore import QAbstractItemModel from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector from metadata_filename_gui.metadata_selector import MetadataSelector -from metadata_filename_gui.internal_metadata import AsciiReaderMetadata from metadata_filename_gui.metadata_tree_data import metadata as metadata_categories from sasdata.ascii_reader_metadata import AsciiReaderMetadata From e6c85ce573ed60c8d1a5f57382a0fa30ced6a207 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:26:29 +0000 Subject: [PATCH 341/396] Use the new params. --- src/ascii_dialog/dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f5173f34c6..858749c1e7 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -440,12 +440,12 @@ def datasetOptions(self) -> list[str]: # TODO: Only works for one single file at the moment def onDoneButton(self): params = AsciiReaderParams( - self.files_full_path[self.current_filename], + self.files.keys(), self.startline_entry.value(), self.col_editor.columns, self.excluded_lines, self.seperators.items(), - self.filename_metadata.get(self.current_filename, {}), + self.internal_metadata ) self.params = params self.accept() From c9674114cfcf7578ccd89da4ec81a9c325cf8a68 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:36:08 +0000 Subject: [PATCH 342/396] Send the full file path. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 858749c1e7..b50959e5d0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -440,7 +440,7 @@ def datasetOptions(self) -> list[str]: # TODO: Only works for one single file at the moment def onDoneButton(self): params = AsciiReaderParams( - self.files.keys(), + self.files_full_path.values(), self.startline_entry.value(), self.col_editor.columns, self.excluded_lines, From c7b9d5fff6506cdc7565622e288cda20fc230967 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:53:12 +0000 Subject: [PATCH 343/396] Should only split in metadata class. --- .../metadata_filename_dialog.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 8e6759058d..158cedce9d 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -59,16 +59,6 @@ def separator_text(self) -> str: def split_filename(self) -> list[str]: return re.split(f'([{self.separator_text}])', self.filename) - def filename_components(self) -> list[str]: - splitted = re.split(f'{self.separator_chars.text()}', self.filename) - # If the last component has a file extensions, remove it. - last_component = splitted[-1] - if '.' in last_component: - pos = last_component.index('.') - last_component = last_component[:pos] - splitted[-1] = last_component - return splitted - def formatted_filename(self) -> str: sep_str = self.separator_chars.text() if sep_str == '': @@ -83,7 +73,7 @@ def formatted_filename(self) -> str: def update_filename_separation(self): self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') - self.metadata_tree.draw_tree(self.filename_components(), self.filename) + self.metadata_tree.draw_tree(self.internal_metadata.filename_components(self.filename), self.filename) def on_save(self): self.accept() From 580bf5b944e142c31cc4effa79e5eec9e547edeb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 15:17:04 +0000 Subject: [PATCH 344/396] Default separator should be set in dialog.py --- src/ascii_dialog/dialog.py | 4 ++++ src/metadata_filename_gui/metadata_filename_dialog.py | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index b50959e5d0..d1e94f0480 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -301,6 +301,10 @@ def load_file(self) -> None: self.attemptGuesses() # This will trigger the update current file event which will cause # the table to be drawn. + # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the + # filename.) + initial_separator_text = '_' + self.internal_metadata.filename_separator[basename] = initial_separator_text self.filename_chooser.addItem(basename) self.filename_chooser.setCurrentText(basename) self.internal_metadata.add_file(basename) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 158cedce9d..dc8030ab8d 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -20,8 +20,7 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the # filename.) - initial_separator_text = initial_metadata.filename_separator.get(filename, '_') - initial_metadata.filename_separator[filename] = initial_separator_text + initial_separator_text = initial_metadata.filename_separator[filename] self.setWindowTitle('Metadata') From ba0b0b6cb12938262caf962a5d54dff3df49634b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Dec 2024 09:31:48 +0000 Subject: [PATCH 345/396] Load multiple files at the same time. --- src/ascii_dialog/dialog.py | 73 ++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d1e94f0480..0fe555aef6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -276,44 +276,47 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: @Slot() def load_file(self) -> None: """Open the file loading dialog, and load the file the user selects.""" - result = QFileDialog.getOpenFileName(self) + filenames, result = QFileDialog.getOpenFileNames(self) # Happens when the user cancels without selecting a file. There isn't a # file to load in this case. - if result[1] == '': + if result == '': return - filename = result[0] - basename = path.basename(filename) - self.filename_label.setText(basename) - - try: - with open(filename) as file: - file_csv = file.readlines() - file_csv = [line.strip() for line in file_csv] - # TODO: This assumes that no two files will be loaded with the same - # name. This might not be a reasonable assumption. - self.files[basename] = file_csv - self.files_full_path[basename] = filename - self.current_filename = basename - # Reset checkboxes - self.files_is_included[basename] = [] - # Attempt guesses when this is the first file that has been loaded. - if len(self.files) == 1: - self.attemptGuesses() - # This will trigger the update current file event which will cause - # the table to be drawn. - # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the - # filename.) - initial_separator_text = '_' - self.internal_metadata.filename_separator[basename] = initial_separator_text - self.filename_chooser.addItem(basename) - self.filename_chooser.setCurrentText(basename) - self.internal_metadata.add_file(basename) - - except OSError: - QMessageBox.critical(self, 'File Read Error', 'There was an error accessing that file.') - except UnicodeDecodeError: - QMessageBox.critical(self, 'File Read Error', """There was an error reading that file. -This could potentially be because the file is not an ASCII format.""") + for filename in filenames: + + basename = path.basename(filename) + self.filename_label.setText(basename) + + try: + with open(filename) as file: + file_csv = file.readlines() + file_csv = [line.strip() for line in file_csv] + # TODO: This assumes that no two files will be loaded with the same + # name. This might not be a reasonable assumption. + self.files[basename] = file_csv + self.files_full_path[basename] = filename + # Reset checkboxes + self.files_is_included[basename] = [] + if len(self.files) == 1: + # Default behaviour is going to be to set this to the first file we load. This seems sensible but + # may provoke further discussion. + self.current_filename = basename + # This will trigger the update current file event which will cause + # the table to be drawn. + # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the + # filename.) + initial_separator_text = '_' + self.internal_metadata.filename_separator[basename] = initial_separator_text + self.filename_chooser.addItem(basename) + self.filename_chooser.setCurrentText(basename) + self.internal_metadata.add_file(basename) + + except OSError: + QMessageBox.critical(self, 'File Read Error', f'There was an error reading {basename}') + except UnicodeDecodeError: + QMessageBox.critical(self, 'File Read Error', f"""There was an error decoding {basename}. +This could potentially be because the file {basename} an ASCII format.""") + # Attempt guesses on the first file that was loaded. + self.attemptGuesses() @Slot() def unload(self) -> None: From 9938705bdcb4cf7c61dfa78336764db11eefaa09 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 09:50:56 +0000 Subject: [PATCH 346/396] Updated name to avoid conflict with QT function. --- src/ascii_dialog/dialog.py | 2 +- src/ascii_dialog/warning_label.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 0fe555aef6..46fbb968fc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -350,7 +350,7 @@ def updateColumn(self) -> None: self.fillTable() required_missing = self.requiredMissing() duplicates = self.duplicateColumns() - self.warning_label.update(required_missing, duplicates) + self.warning_label.update_warning(required_missing, duplicates) @Slot() def updateCurrentFile(self) -> None: diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 0d6eb0ecbb..4e552f612e 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -14,7 +14,7 @@ def setFontRed(self): def setFontNormal(self): self.setStyleSheet('') - def update(self, missing_columns, duplicate_columns): + def update_warning(self, missing_columns, duplicate_columns): """Determine, and set the appropriate warning messages given how many columns are missing, and how many columns are duplicated.""" if len(missing_columns) != 0: @@ -29,4 +29,4 @@ def update(self, missing_columns, duplicate_columns): def __init__(self, initial_missing_columns, initial_duplicate_classes): super().__init__() - self.update(initial_missing_columns, initial_duplicate_classes) + self.update_warning(initial_missing_columns, initial_duplicate_classes) From 22b87e2ee87703d05f50b4cae34d9ef65b8aa07d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 09:52:05 +0000 Subject: [PATCH 347/396] Added type hints. --- src/ascii_dialog/warning_label.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 4e552f612e..73119f9b71 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -14,7 +14,7 @@ def setFontRed(self): def setFontNormal(self): self.setStyleSheet('') - def update_warning(self, missing_columns, duplicate_columns): + def update_warning(self, missing_columns: list[str], duplicate_columns: list[str]): """Determine, and set the appropriate warning messages given how many columns are missing, and how many columns are duplicated.""" if len(missing_columns) != 0: From f4c33a351e2de3c2667d5c05bb9376066593795b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:17:11 +0000 Subject: [PATCH 348/396] Add unparasable lines to warning label. --- src/ascii_dialog/dialog.py | 2 +- src/ascii_dialog/warning_label.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 46fbb968fc..f3f1258017 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -350,7 +350,7 @@ def updateColumn(self) -> None: self.fillTable() required_missing = self.requiredMissing() duplicates = self.duplicateColumns() - self.warning_label.update_warning(required_missing, duplicates) + self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included) @Slot() def updateCurrentFile(self) -> None: diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 73119f9b71..7c324aa260 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -14,15 +14,31 @@ def setFontRed(self): def setFontNormal(self): self.setStyleSheet('') - def update_warning(self, missing_columns: list[str], duplicate_columns: list[str]): + def update_warning(self, missing_columns: list[str], duplicate_columns: list[str], lines: list[list[str]] | None = None, rows_is_included: list[bool] | None = None): """Determine, and set the appropriate warning messages given how many columns are missing, and how many columns are duplicated.""" + unparsable = 0 + if lines is not None and rows_is_included is not None: + # FIXME: I feel like I am repeating a lot of logic from the table filling. Is there a way I can abstract + # this? + for i, line in enumerate(lines): + if rows_is_included[i]: + # TODO: Is there really no builtin function for this? I don't like using try/except like this. + for item in line: + try: + _ = int(item) + except: + unparsable += 1 if len(missing_columns) != 0: self.setText(f'The following columns are missing: {missing_columns}') self.setFontRed() elif len(duplicate_columns) > 0: self.setText(f'There are duplicate columns.') self.setFontRed() + elif unparsable > 0: + # FIXME: This error message could perhaps be a bit clearer. + self.setText(f'{unparsable} lines cannot be parsed. They will be ignored.') + self.setFontRed() else: self.setText('All is fine') # TODO: Probably want to find a more appropriate message. self.setFontNormal() From 40737327f116735407b48cda218cc94650dd2cae Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:45:12 +0000 Subject: [PATCH 349/396] Take into account the startpos. --- src/ascii_dialog/dialog.py | 2 +- src/ascii_dialog/warning_label.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f3f1258017..96cd0116ae 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -350,7 +350,7 @@ def updateColumn(self) -> None: self.fillTable() required_missing = self.requiredMissing() duplicates = self.duplicateColumns() - self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included) + self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.startline_entry.value()) @Slot() def updateCurrentFile(self) -> None: diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 7c324aa260..abb1d27979 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -14,7 +14,7 @@ def setFontRed(self): def setFontNormal(self): self.setStyleSheet('') - def update_warning(self, missing_columns: list[str], duplicate_columns: list[str], lines: list[list[str]] | None = None, rows_is_included: list[bool] | None = None): + def update_warning(self, missing_columns: list[str], duplicate_columns: list[str], lines: list[list[str]] | None = None, rows_is_included: list[bool] | None = None, starting_pos: int = 0): """Determine, and set the appropriate warning messages given how many columns are missing, and how many columns are duplicated.""" unparsable = 0 @@ -22,11 +22,11 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str # FIXME: I feel like I am repeating a lot of logic from the table filling. Is there a way I can abstract # this? for i, line in enumerate(lines): - if rows_is_included[i]: + if rows_is_included[i] and i >= starting_pos: # TODO: Is there really no builtin function for this? I don't like using try/except like this. for item in line: try: - _ = int(item) + _ = float(item) except: unparsable += 1 if len(missing_columns) != 0: From f61849255185df06b00d4c44228e1d7fdfaf613b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:55:01 +0000 Subject: [PATCH 350/396] Update warning label is a function. --- src/ascii_dialog/dialog.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 96cd0116ae..a8992d3dc8 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -272,6 +272,10 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: item_font.setStrikeOut(False) item.setFont(item_font) + def updateWarningLabel(self): + required_missing = self.requiredMissing() + duplicates = self.duplicateColumns() + self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.startline_entry.value()) @Slot() def load_file(self) -> None: @@ -348,9 +352,7 @@ def updateSeperator(self) -> None: def updateColumn(self) -> None: """Triggered when any of the columns has been changed.""" self.fillTable() - required_missing = self.requiredMissing() - duplicates = self.duplicateColumns() - self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.startline_entry.value()) + self.updateWarningLabel() @Slot() def updateCurrentFile(self) -> None: From 1765b04709c1a1195337e0b7426f327c46d3af40 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:57:04 +0000 Subject: [PATCH 351/396] Call new function when its needed. --- src/ascii_dialog/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a8992d3dc8..5ea6b83354 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -337,16 +337,19 @@ def updateColcount(self) -> None: """ self.col_editor.setCols(self.colcount_entry.value()) self.fillTable() + self.updateWarningLabel() @Slot() def updateStartpos(self) -> None: """Triggered when the starting position of the data has changed.""" self.fillTable() + self.updateWarningLabel() @Slot() def updateSeperator(self) -> None: """Changed when the user modifies the set of seperators being used.""" self.fillTable() + self.updateWarningLabel() @Slot() def updateColumn(self) -> None: @@ -375,6 +378,7 @@ def updateCurrentFile(self) -> None: self.unloadButton.setDisabled(False) self.editMetadataButton.setDisabled(False) self.fillTable() + self.updateWarningLabel() @Slot() def seperatorToggle(self) -> None: From 53542b944006addc697bf2e04e3d4c3027ca936c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:57:42 +0000 Subject: [PATCH 352/396] Forgot to save :P --- src/ascii_dialog/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 5ea6b83354..f2b556079e 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -386,6 +386,7 @@ def seperatorToggle(self) -> None: check_box = self.sender() self.seperators[check_box.text()] = check_box.isChecked() self.fillTable() + self.updateWarningLabel() @Slot() def changeDatasetType(self) -> None: From 826895a3b258536e028e8471afc8b7fd80176d5e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 14:43:15 +0000 Subject: [PATCH 353/396] Fixed how lines are counted. --- src/ascii_dialog/warning_label.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index abb1d27979..d96573671e 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -24,11 +24,12 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str for i, line in enumerate(lines): if rows_is_included[i] and i >= starting_pos: # TODO: Is there really no builtin function for this? I don't like using try/except like this. - for item in line: - try: + try: + for item in line: _ = float(item) - except: - unparsable += 1 + except: + unparsable += 1 + if len(missing_columns) != 0: self.setText(f'The following columns are missing: {missing_columns}') self.setFontRed() From 81e80a24f3efd6575d1e677134fd8eb1185325b8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 15:03:35 +0000 Subject: [PATCH 354/396] Try an orange font for this. Not too sure about it. May change later. --- src/ascii_dialog/warning_label.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index d96573671e..cac9c193fe 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -11,6 +11,9 @@ class WarningLabel(QLabel): def setFontRed(self): self.setStyleSheet("QLabel { color: red}") + def setFontOrange(self): + self.setStyleSheet("QLabel { color: orange}") + def setFontNormal(self): self.setStyleSheet('') @@ -39,7 +42,7 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str elif unparsable > 0: # FIXME: This error message could perhaps be a bit clearer. self.setText(f'{unparsable} lines cannot be parsed. They will be ignored.') - self.setFontRed() + self.setFontOrange() else: self.setText('All is fine') # TODO: Probably want to find a more appropriate message. self.setFontNormal() From 0d2dea5c53f14f1e881cf20c2fff677bd3779235 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 08:51:34 +0000 Subject: [PATCH 355/396] Type hint to stop pyright from complaining. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f2b556079e..644f18682a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -124,7 +124,7 @@ def __init__(self): self.table.customContextMenuRequested.connect(self.showContextMenu) # Warning Label - self.warning_label = WarningLabel(self.requiredMissing(), self.duplicateColumns()) + self.warning_label: WarningLabel = WarningLabel(self.requiredMissing(), self.duplicateColumns()) # Done button # TODO: Not entirely sure what to call/label this. Just going with 'done' for now. From 881ee796ee60b9d88528434e0b4452d57edc1b48 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 08:53:07 +0000 Subject: [PATCH 356/396] Don't pass the CSV if there isn't one. --- src/ascii_dialog/dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 644f18682a..d2a33a8e38 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -275,7 +275,11 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: def updateWarningLabel(self): required_missing = self.requiredMissing() duplicates = self.duplicateColumns() - self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.startline_entry.value()) + if self.raw_csv is None: + # We don't have any actual data yet so we're just updating the warning based on the column. + self.warning_label.update_warning(required_missing, duplicates) + else: + self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.startline_entry.value()) @Slot() def load_file(self) -> None: From a2983502e68909aa329f3f19767a756ae6932b69 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 11:41:20 +0000 Subject: [PATCH 357/396] Separate this into another file. Avoids a circular dependency error. --- src/ascii_dialog/constants.py | 5 +++++ src/ascii_dialog/dialog.py | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/ascii_dialog/constants.py diff --git a/src/ascii_dialog/constants.py b/src/ascii_dialog/constants.py new file mode 100644 index 0000000000..bf789cf38c --- /dev/null +++ b/src/ascii_dialog/constants.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + + +TABLE_MAX_ROWS = 1000 +NOFILE_TEXT = "Click the button below to load a file." diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d2a33a8e38..a3f72c039a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -13,11 +13,9 @@ from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog from metadata_filename_gui.metadata_tree_data import initial_metadata_dict from sasdata.ascii_reader_metadata import AsciiReaderMetadata +from constants import TABLE_MAX_ROWS, NOFILE_TEXT import re -TABLE_MAX_ROWS = 1000 -NOFILE_TEXT = "Click the button below to load a file." - dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) class AsciiDialog(QDialog): From 4683dba24127b77f41d68dd32c31aa587e053f4e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 11:55:04 +0000 Subject: [PATCH 358/396] Fixed a crash when we have > 1000 rows. --- src/ascii_dialog/warning_label.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index cac9c193fe..4e76352978 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -1,6 +1,5 @@ - - from PySide6.QtWidgets import QLabel +from constants import TABLE_MAX_ROWS class WarningLabel(QLabel): @@ -25,7 +24,10 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str # FIXME: I feel like I am repeating a lot of logic from the table filling. Is there a way I can abstract # this? for i, line in enumerate(lines): - if rows_is_included[i] and i >= starting_pos: + # Right now, rows_is_included only includes a limited number of rows as there is a maximum that can be + # shown in the table without it being really laggy. We're just going to assume the lines after it should + # be included. + if (i >= TABLE_MAX_ROWS or rows_is_included[i]) and i >= starting_pos: # TODO: Is there really no builtin function for this? I don't like using try/except like this. try: for item in line: From 8fb5cc05795898ff7b1fc03a884c3430bf933adb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 12:13:58 +0000 Subject: [PATCH 359/396] Started implementing default units. --- src/ascii_dialog/default_units.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/ascii_dialog/default_units.py diff --git a/src/ascii_dialog/default_units.py b/src/ascii_dialog/default_units.py new file mode 100644 index 0000000000..540654a273 --- /dev/null +++ b/src/ascii_dialog/default_units.py @@ -0,0 +1,12 @@ +# NOTE: This module will probably be a lot more involved once how this is getting into the configuration will be sorted. + +import sasdata.quantities.units as unit + +# Based on the email Jeff sent me./ +default_units = { + 'Q': [unit.per_nanometer, unit.per_angstrom], + # TODO: I think the unit group for scattering intensity may be wrong. Defaulting to nanometers for now but I know + # this isn't right + 'I': [unit.per_nanometer] +} + From ea48e84ca91583dc7feaf7164850e602070b8054 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 12:35:23 +0000 Subject: [PATCH 360/396] Use the new preferred units. --- src/ascii_dialog/column_unit.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index ba254ce239..f921488cb8 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -7,6 +7,7 @@ from sasdata.quantities.units import symbol_lookup, NamedUnit from unit_selector import UnitSelector +from default_units import default_units def configure_size_policy(combo_box: QComboBox) -> None: policy = combo_box.sizePolicy() @@ -52,10 +53,10 @@ def createUnitComboBox(self, selected_option: str) -> QComboBox: def updateUnits(self, unit_box: QComboBox, selected_option: str): unit_box.clear() self.current_option = selected_option - options = [unit.symbol for unit in unit_kinds[selected_option].units] - # We don't have preferred units yet. In order to simulate this, just - # take the first 5 options to display. - for option in options[:5]: + # Use the list of preferred units but fallback to the first 5 if there aren't any for this particular column. + unit_options = default_units.get(self.current_option, unit_kinds[selected_option].units) + option_symbols = [unit.symbol for unit in unit_options] + for option in option_symbols[:5]: unit_box.addItem(option) unit_box.addItem('Select More') From c02011777f79d3b23b367cf2e7cb983692535c9e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 09:37:46 +0000 Subject: [PATCH 361/396] Add some radios for choosing what to separate on. --- .../metadata_filename_dialog.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index dc8030ab8d..30d194bf54 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,4 +1,4 @@ -from PySide6.QtWidgets import QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton +from PySide6.QtWidgets import QBoxLayout, QButtonGroup, QRadioButton, QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton from metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget from sasdata.ascii_reader_metadata import AsciiReaderMetadata from sys import argv @@ -29,12 +29,21 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.internal_metadata = initial_metadata self.filename_line_label = QLabel() + self.separate_on_group = QButtonGroup() + self.character_radio = QRadioButton("Character") + self.separate_on_group.addButton(self.character_radio) + self.casing_radio = QRadioButton("Casing") + self.separate_on_group.addButton(self.casing_radio) + self.separate_on_layout = QHBoxLayout() + self.separate_on_layout.addWidget(self.filename_line_label) + self.separate_on_layout.addWidget(self.character_radio) + self.separate_on_layout.addWidget(self.casing_radio) + self.seperator_chars_label = QLabel('Seperators') self.separator_chars = QLineEdit(initial_separator_text) self.separator_chars.textChanged.connect(self.update_filename_separation) self.filename_separator_layout = QHBoxLayout() - self.filename_separator_layout.addWidget(self.filename_line_label) self.filename_separator_layout.addWidget(self.seperator_chars_label) self.filename_separator_layout.addWidget(self.separator_chars) @@ -47,6 +56,7 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.save_button.clicked.connect(self.on_save) self.layout = QVBoxLayout(self) + self.layout.addLayout(self.separate_on_layout) self.layout.addLayout(self.filename_separator_layout) self.layout.addWidget(self.metadata_tree) self.layout.addWidget(self.save_button) From f8d86eefeac11bcb2c981d610c40d34340a69d01 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 09:49:30 +0000 Subject: [PATCH 362/396] Default to using the character. --- src/metadata_filename_gui/metadata_filename_dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 30d194bf54..d9c13b3376 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -39,6 +39,9 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.separate_on_layout.addWidget(self.character_radio) self.separate_on_layout.addWidget(self.casing_radio) + # Right now, we're going to assume we're separating by character but we need to detect this later. + self.character_radio.setChecked(True) + self.seperator_chars_label = QLabel('Seperators') self.separator_chars = QLineEdit(initial_separator_text) self.separator_chars.textChanged.connect(self.update_filename_separation) From 55e63ecb734dea78db394505d6b856603784b5fe Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 09:52:08 +0000 Subject: [PATCH 363/396] Use a separate propety for the expression. --- src/metadata_filename_gui/metadata_filename_dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index d9c13b3376..ac2a157793 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -68,8 +68,12 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): def separator_text(self) -> str: return self.separator_chars.text() + @property + def separator_expr(self) -> str: + return f'([{self.separator_text}])' + def split_filename(self) -> list[str]: - return re.split(f'([{self.separator_text}])', self.filename) + return re.split(self.separator_expr, self.filename) def formatted_filename(self) -> str: sep_str = self.separator_chars.text() From 1a28f394af38e9e623f94d0f88f16fa6af31e2d9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 10:15:57 +0000 Subject: [PATCH 364/396] Split on casing as well. --- src/metadata_filename_gui/metadata_filename_dialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index ac2a157793..366729a7e8 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -73,7 +73,12 @@ def separator_expr(self) -> str: return f'([{self.separator_text}])' def split_filename(self) -> list[str]: - return re.split(self.separator_expr, self.filename) + # This is assuming one of these radios is checked. This *should* be the case since it will have a default value. + if self.character_radio.isChecked(): + return re.split(self.separator_expr, self.filename) + elif self.casing_radio.isChecked(): + return re.findall(r'[A-Z][a-z]*', self.filename) + raise ValueError('Neither character, nor casing is selected.') def formatted_filename(self) -> str: sep_str = self.separator_chars.text() From ff2f9814085bf88286fb842b5fc74d2232501f39 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 10:38:07 +0000 Subject: [PATCH 365/396] Use new constant for casing regex. --- src/metadata_filename_gui/metadata_filename_dialog.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 366729a7e8..df92df143f 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QBoxLayout, QButtonGroup, QRadioButton, QWidget, QApplication, QVBoxLayout, QLineEdit, QHBoxLayout, QLabel, QDialog, QPushButton from metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget -from sasdata.ascii_reader_metadata import AsciiReaderMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata, CASING_REGEX from sys import argv import re @@ -77,7 +77,8 @@ def split_filename(self) -> list[str]: if self.character_radio.isChecked(): return re.split(self.separator_expr, self.filename) elif self.casing_radio.isChecked(): - return re.findall(r'[A-Z][a-z]*', self.filename) + return re.findall(CASING_REGEX, self.filename) + raise ValueError('Neither character, nor casing is selected.') def formatted_filename(self) -> str: From 17f0020df18e9f3186d1abcd8c675e549e4fb074 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 10:40:45 +0000 Subject: [PATCH 366/396] Try hooking up to this event. --- src/metadata_filename_gui/metadata_filename_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index df92df143f..ff879271aa 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -35,6 +35,7 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.casing_radio = QRadioButton("Casing") self.separate_on_group.addButton(self.casing_radio) self.separate_on_layout = QHBoxLayout() + self.separate_on_group.buttonPressed.connect(self.update_filename_separation) self.separate_on_layout.addWidget(self.filename_line_label) self.separate_on_layout.addWidget(self.character_radio) self.separate_on_layout.addWidget(self.casing_radio) From 8d39ed098ed990a3ceaecb5e43aba7b89f683875 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 11:19:52 +0000 Subject: [PATCH 367/396] We should probably use our internal split function. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index ff879271aa..eed0a76d1b 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -96,7 +96,7 @@ def formatted_filename(self) -> str: def update_filename_separation(self): self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') - self.metadata_tree.draw_tree(self.internal_metadata.filename_components(self.filename), self.filename) + self.metadata_tree.draw_tree(self.split_filename(), self.filename) def on_save(self): self.accept() From fa15585bf187fc062f5f2478590def6e29032fcc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 11:20:01 +0000 Subject: [PATCH 368/396] Fixed regex. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index eed0a76d1b..99b9591328 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -71,7 +71,7 @@ def separator_text(self) -> str: @property def separator_expr(self) -> str: - return f'([{self.separator_text}])' + return f'[{self.separator_text}]' def split_filename(self) -> list[str]: # This is assuming one of these radios is checked. This *should* be the case since it will have a default value. From 216a8f4d17bf2ffbdfbc9484255ee7176b2b4873 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 11:27:44 +0000 Subject: [PATCH 369/396] Was using the wrong event. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 99b9591328..335da3ceb2 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -35,7 +35,7 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.casing_radio = QRadioButton("Casing") self.separate_on_group.addButton(self.casing_radio) self.separate_on_layout = QHBoxLayout() - self.separate_on_group.buttonPressed.connect(self.update_filename_separation) + self.separate_on_group.buttonToggled.connect(self.update_filename_separation) self.separate_on_layout.addWidget(self.filename_line_label) self.separate_on_layout.addWidget(self.character_radio) self.separate_on_layout.addWidget(self.casing_radio) From 6c689bd288b674094074e9c0759ab2c819757bca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:10:38 +0000 Subject: [PATCH 370/396] Changed where options is generated. --- src/metadata_filename_gui/metadata_filename_dialog.py | 3 ++- src/metadata_filename_gui/metadata_selector.py | 6 +++--- src/metadata_filename_gui/metadata_tree_widget.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 335da3ceb2..fdaae083fc 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -88,6 +88,7 @@ def formatted_filename(self) -> str: return f'{self.filename}' # TODO: Won't escape characters; I'll handle that later. separated = self.split_filename() + separated = self.internal_metadata.filename_components(self.filename, False, True) font_elements = '' for i, token in enumerate(separated): classname = 'token' if i % 2 == 0 else 'separator' @@ -96,7 +97,7 @@ def formatted_filename(self) -> str: def update_filename_separation(self): self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') - self.metadata_tree.draw_tree(self.split_filename(), self.filename) + self.metadata_tree.draw_tree(self.filename) def on_save(self): self.accept() diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index 88d52c2a1a..be54c56b22 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -4,15 +4,15 @@ from metadata_filename_gui.metadata_custom_selector import MetadataCustomSelector class MetadataSelector(QWidget): - def __init__(self, category: str, metadatum: str, options: list[str], metadata: AsciiReaderMetadata, filename: str): + def __init__(self, category: str, metadatum: str, metadata: AsciiReaderMetadata, filename: str): super().__init__() self.category = category self.metadatum = metadatum self.metadata: AsciiReaderMetadata = metadata - self.options = options self.filename = filename + self.options = self.metadata.filename_components(filename) current_option = self.metadata.get_metadata(self.category, metadatum, filename) - if current_option is None or current_option in options: + if current_option is None or current_option in self.options: self.selector_widget = self.new_component_selector() else: self.selector_widget = self.new_custom_selector() diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 417d0e2832..55cae54c26 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -12,13 +12,13 @@ def __init__(self, metadata: AsciiReaderMetadata): self.setHeaderLabels(['Name', 'Filename Components']) self.metadata: AsciiReaderMetadata = metadata - def draw_tree(self, options: list[str], full_filename: str): + def draw_tree(self, full_filename: str): self.clear() for top_level, items in metadata_categories.items(): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: # selector = MetadataComponentSelector(metadatum, self.metadata_dict) - selector = MetadataSelector(top_level, metadatum, options, self.metadata, full_filename) + selector = MetadataSelector(top_level, metadatum, self.metadata, full_filename) metadatum_item = QTreeWidgetItem([metadatum]) # selector.draw_options(options, metadata_dict.get(metadatum)) top_level_item.addChild(metadatum_item) From 38b86e920b5f696207515df514470a14faff514f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:15:43 +0000 Subject: [PATCH 371/396] Need to set this earlier to stop the event firing --- src/metadata_filename_gui/metadata_filename_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index fdaae083fc..ab5731d85f 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -34,14 +34,14 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.separate_on_group.addButton(self.character_radio) self.casing_radio = QRadioButton("Casing") self.separate_on_group.addButton(self.casing_radio) + # Right now, we're going to assume we're separating by character but we need to detect this later. + self.character_radio.setChecked(True) self.separate_on_layout = QHBoxLayout() self.separate_on_group.buttonToggled.connect(self.update_filename_separation) self.separate_on_layout.addWidget(self.filename_line_label) self.separate_on_layout.addWidget(self.character_radio) self.separate_on_layout.addWidget(self.casing_radio) - # Right now, we're going to assume we're separating by character but we need to detect this later. - self.character_radio.setChecked(True) self.seperator_chars_label = QLabel('Seperators') self.separator_chars = QLineEdit(initial_separator_text) From 7e1b60ec5b1015332c42f41baafaef46a11c82fe Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:17:28 +0000 Subject: [PATCH 372/396] This shouldn't be here anymore. --- src/metadata_filename_gui/metadata_filename_dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index ab5731d85f..1fe9a42b89 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -87,7 +87,6 @@ def formatted_filename(self) -> str: if sep_str == '': return f'{self.filename}' # TODO: Won't escape characters; I'll handle that later. - separated = self.split_filename() separated = self.internal_metadata.filename_components(self.filename, False, True) font_elements = '' for i, token in enumerate(separated): From 64194f492f93b7a0b01c0a8fb222445b88e8f929 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:20:42 +0000 Subject: [PATCH 373/396] These functions aren't needed anymore. --- .../metadata_filename_dialog.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 1fe9a42b89..fdb72001d8 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -65,23 +65,6 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.layout.addWidget(self.metadata_tree) self.layout.addWidget(self.save_button) - @property - def separator_text(self) -> str: - return self.separator_chars.text() - - @property - def separator_expr(self) -> str: - return f'[{self.separator_text}]' - - def split_filename(self) -> list[str]: - # This is assuming one of these radios is checked. This *should* be the case since it will have a default value. - if self.character_radio.isChecked(): - return re.split(self.separator_expr, self.filename) - elif self.casing_radio.isChecked(): - return re.findall(CASING_REGEX, self.filename) - - raise ValueError('Neither character, nor casing is selected.') - def formatted_filename(self) -> str: sep_str = self.separator_chars.text() if sep_str == '': From 75a06e78ac495adf9bd6f9dcff6f9fca034bd56f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:30:42 +0000 Subject: [PATCH 374/396] Update the separator before updating stuff. --- src/metadata_filename_gui/metadata_filename_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index fdb72001d8..471b9eb9e7 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -78,6 +78,7 @@ def formatted_filename(self) -> str: return font_elements def update_filename_separation(self): + self.internal_metadata.filename_separator[self.filename] = self.separator_chars.text() if self.character_radio.isChecked() else True self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') self.metadata_tree.draw_tree(self.filename) From 9248d88d6d9b4ee947d94cdcf952acafe0a9ff47 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 08:23:18 +0000 Subject: [PATCH 375/396] Call purge unreachable on separator update. --- src/metadata_filename_gui/metadata_filename_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 471b9eb9e7..8c73c806e6 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -79,6 +79,7 @@ def formatted_filename(self) -> str: def update_filename_separation(self): self.internal_metadata.filename_separator[self.filename] = self.separator_chars.text() if self.character_radio.isChecked() else True + self.internal_metadata.purge_unreachable(self.filename) self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') self.metadata_tree.draw_tree(self.filename) From 7f2cb4a15fcae8e17476bbef3237d07a760e2a7e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 08:26:19 +0000 Subject: [PATCH 376/396] Disable the separator box if casing is enabled. --- src/metadata_filename_gui/metadata_filename_dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 8c73c806e6..54bf5a72b1 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -78,6 +78,10 @@ def formatted_filename(self) -> str: return font_elements def update_filename_separation(self): + if self.casing_radio.isChecked(): + self.separator_chars.setDisabled(True) + else: + self.separator_chars.setDisabled(False) self.internal_metadata.filename_separator[self.filename] = self.separator_chars.text() if self.character_radio.isChecked() else True self.internal_metadata.purge_unreachable(self.filename) self.filename_line_label.setText(f'Filename: {self.formatted_filename()}') From c407b24404218972e88e66d00887eca4913fab2e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 08:30:12 +0000 Subject: [PATCH 377/396] Return unformatted if we're using casing separation. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 54bf5a72b1..672d2064ff 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -67,7 +67,7 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): def formatted_filename(self) -> str: sep_str = self.separator_chars.text() - if sep_str == '': + if sep_str == '' or self.casing_radio.isChecked(): return f'{self.filename}' # TODO: Won't escape characters; I'll handle that later. separated = self.internal_metadata.filename_components(self.filename, False, True) From a03290d5993fd3e7f28056d0d3eb60e7d4ffc601 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 09:03:21 +0000 Subject: [PATCH 378/396] Fixed error when there are no capital letters. --- src/metadata_filename_gui/metadata_filename_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 672d2064ff..25fe439169 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -42,6 +42,8 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.separate_on_layout.addWidget(self.character_radio) self.separate_on_layout.addWidget(self.casing_radio) + if not any([char.isupper() for char in self.filename]): + self.casing_radio.setDisabled(True) self.seperator_chars_label = QLabel('Seperators') self.separator_chars = QLineEdit(initial_separator_text) From 950cd04e48ac22de9bc249d102f3a4f7dd45ef8c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 09:46:04 +0000 Subject: [PATCH 379/396] Use a property for starting pos. So this can be represented in 1 based indexing for the user. --- src/ascii_dialog/dialog.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index a3f72c039a..771b456095 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -90,6 +90,7 @@ def __init__(self): self.startline_layout = QHBoxLayout() self.startline_label = QLabel('Starting Line') self.startline_entry = QSpinBox() + self.startline_entry.setMinimum(1) self.startline_entry.valueChanged.connect(self.updateStartpos) self.startline_layout.addWidget(self.startline_label) self.startline_layout.addWidget(self.startline_entry) @@ -144,6 +145,14 @@ def __init__(self): self.layout.addWidget(self.warning_label) self.layout.addWidget(self.done_button) + @property + def starting_pos(self) -> int: + return self.startline_entry.value() - 1 + + @starting_pos.setter + def starting_pos(self, value: int): + self.startline_entry.setValue(value + 1) + @property def raw_csv(self) -> list[str] | None: if self.current_filename is None: @@ -188,6 +197,7 @@ def attemptGuesses(self) -> None: """ split_csv = [self.splitLine(line.strip()) for line in self.raw_csv] + # TODO: I'm not sure if there is any point in holding this initial value. Can possibly be refactored. self.initial_starting_pos = guess_starting_position(split_csv) guessed_colcount = guess_column_count(split_csv, self.initial_starting_pos) @@ -196,7 +206,7 @@ def attemptGuesses(self) -> None: columns = guess_columns(guessed_colcount, self.currentDatasetType()) self.col_editor.setColOrder(columns) self.colcount_entry.setValue(guessed_colcount) - self.startline_entry.setValue(self.initial_starting_pos) + self.starting_pos = self.initial_starting_pos def fillTable(self) -> None: """Write the data to the table based on the parameters the user has @@ -210,7 +220,6 @@ def fillTable(self) -> None: self.table.clear() - starting_pos = self.startline_entry.value() col_count = self.colcount_entry.value() self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS + 1)) @@ -233,7 +242,7 @@ def fillTable(self) -> None: else: initial_state = True self.rows_is_included.append(initial_state) - if i >= starting_pos: + if i >= self.starting_pos: row_status = RowStatusWidget(initial_state, i) row_status.status_changed.connect(self.updateRowStatus) self.table.setCellWidget(i, 0, row_status) @@ -256,13 +265,12 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: be included in the data being loaded, or not. """ - starting_pos = self.startline_entry.value() for column in range(1, self.table.columnCount() + 1): item = self.table.item(row, column) if item is None: continue item_font = item.font() - if not item_checked or row < starting_pos: + if not item_checked or row < self.starting_pos: item.setForeground(QColor.fromString('grey')) item_font.setStrikeOut(True) else: @@ -277,7 +285,7 @@ def updateWarningLabel(self): # We don't have any actual data yet so we're just updating the warning based on the column. self.warning_label.update_warning(required_missing, duplicates) else: - self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.startline_entry.value()) + self.warning_label.update_warning(required_missing, duplicates, [self.splitLine(line) for line in self.raw_csv], self.rows_is_included, self.starting_pos) @Slot() def load_file(self) -> None: @@ -420,7 +428,7 @@ def changeInclusion(self, indexes: list[QModelIndex], new_value: bool): # This will happen if the user has selected a point which exists before the starting line. To prevent an # error, this code will skip that position. row = index.row() - if row < self.startline_entry.value(): + if row < self.starting_pos: continue self.table.cellWidget(row, 0).setChecked(new_value) self.updateRowStatus(row) @@ -457,7 +465,7 @@ def datasetOptions(self) -> list[str]: def onDoneButton(self): params = AsciiReaderParams( self.files_full_path.values(), - self.startline_entry.value(), + self.starting_pos, self.col_editor.columns, self.excluded_lines, self.seperators.items(), From d41fbd7ae14160464cb8b2d1c1643d300dba91e2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 10:06:02 +0000 Subject: [PATCH 380/396] Update warning on selection/deselection. --- src/ascii_dialog/dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 771b456095..f514cbc661 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -437,11 +437,13 @@ def changeInclusion(self, indexes: list[QModelIndex], new_value: bool): def selectItems(self) -> None: """Include all of the items that have been selected in the table.""" self.changeInclusion(self.table.selectedIndexes(), True) + self.updateWarningLabel() @Slot() def deselectItems(self) -> None: """Don't include all of the items that have been selected in the table.""" self.changeInclusion(self.table.selectedIndexes(), False) + self.updateWarningLabel() def requiredMissing(self) -> list[str]: """Returns all the columns that are required by the dataset type but From 6eddc96dcdc4ad0f61b89fdc1f64b7c11cbc1d68 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 10:51:19 +0000 Subject: [PATCH 381/396] Add a setter for the current unit. --- src/ascii_dialog/column_unit.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index f921488cb8..0381d21c99 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -111,3 +111,7 @@ def currentUnit(self) -> NamedUnit: return unit # This error shouldn't really happen so if it does, it indicates there is a bug in the code. raise ValueError("Current unit doesn't seem to exist") + + @currentUnit.setter + def currentUnit(self, new_value: NamedUnit): + self.unit_widget.setCurrentText(new_value.symbol) From 4f61e1580c7a58ebda6f6dd2d51580fb61bb454f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 12:05:01 +0000 Subject: [PATCH 382/396] Make sure units match on uncertanties. --- src/ascii_dialog/col_editor.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 1bfa73d3b8..901338a021 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -2,8 +2,9 @@ from PySide6.QtWidgets import QComboBox, QHBoxLayout, QWidget from PySide6.QtCore import Slot, Signal from sasdata.quantities.units import NamedUnit +from sasdata.ascii_reader_metadata import pairings from column_unit import ColumnUnit - +from typing import cast class ColEditor(QWidget): """An editor widget which allows the user to specify the columns of the data @@ -12,6 +13,14 @@ class ColEditor(QWidget): @Slot() def onColumnUpdate(self): + column_changed = cast(ColumnUnit, self.sender()) + pairing = pairings.get(column_changed.currentColumn) + if not pairing is None: + for col_unit in self.option_widgets: + # Second condition is important because otherwise, this event will keep being called, and the GUI will + # go into an infinite loop. + if col_unit.currentColumn == pairing and col_unit.currentUnit != column_changed.currentUnit: + col_unit.currentUnit = column_changed.currentUnit self.column_changed.emit() @@ -21,7 +30,7 @@ def __init__(self, cols: int, options: list[str]): self.cols = cols self.options = options self.layout = QHBoxLayout(self) - self.option_widgets = [] + self.option_widgets: list[ColumnUnit] = [] for _ in range(cols): new_widget = ColumnUnit(self.options) new_widget.column_changed.connect(self.onColumnUpdate) From 4446b41edfea61365e9a447e7ec449ccfdcf7dc5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 13:14:57 +0000 Subject: [PATCH 383/396] Trigger column changed when the unit changes. --- src/ascii_dialog/column_unit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 0381d21c99..1572965703 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -96,6 +96,7 @@ def onUnitChange(self): # We need the selection unit in the list of options, or else QT has some dodgy behaviour. self.unit_widget.insertItem(-1, selector.selected_unit.symbol) self.unit_widget.setCurrentText(selector.selected_unit.symbol) + self.column_changed.emit() @property def currentColumn(self): From eb3c15ba15c079d5d5d370ace4eafa49550d1a8d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 13:27:07 +0000 Subject: [PATCH 384/396] Stop event triggering when unit is empty. --- src/ascii_dialog/column_unit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 1572965703..fa437109e3 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -90,13 +90,17 @@ def onOptionChange(self): @Slot() def onUnitChange(self): - if self.unit_widget.currentText() == 'Select More': + new_text = self.unit_widget.currentText() + if new_text == 'Select More': selector = UnitSelector(unit_kinds[self.col_widget.currentText()].name, False) selector.exec() # We need the selection unit in the list of options, or else QT has some dodgy behaviour. self.unit_widget.insertItem(-1, selector.selected_unit.symbol) self.unit_widget.setCurrentText(selector.selected_unit.symbol) - self.column_changed.emit() + # This event could get triggered when the units have just been cleared, and not actually updated. We don't want + # to trigger it in this case. + elif not new_text == '': + self.column_changed.emit() @property def currentColumn(self): From 2bc464eb9887631cbd83affaf1b08b43b625a881 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 13 Dec 2024 09:13:31 +0000 Subject: [PATCH 385/396] Make sure ALL the columns are numbers. --- src/ascii_dialog/guess.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 96ba9bc210..50fad24658 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -25,6 +25,11 @@ def guess_starting_position(split_csv: list[list[str]]) -> int: """ for i, row in enumerate(split_csv): - if row[0].replace('.', '').replace('-', '').isdigit(): - return i + all_nums = True + for column in row: + if not column.replace('.', '').replace('-', '').isdigit(): + all_nums = False + break + if all_nums: + return i return 0 From 7ec2f80ef1e2936a4ba5792cd202bd57ab3c9ab6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 13 Dec 2024 15:04:02 +0000 Subject: [PATCH 386/396] Use the new init separator method for the metadata. --- src/ascii_dialog/dialog.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index f514cbc661..3e7e0494ed 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -316,10 +316,7 @@ def load_file(self) -> None: self.current_filename = basename # This will trigger the update current file event which will cause # the table to be drawn. - # TODO: Will probably change this default later (or a more sophisticated way of getting this default from the - # filename.) - initial_separator_text = '_' - self.internal_metadata.filename_separator[basename] = initial_separator_text + self.internal_metadata.init_separator(basename) self.filename_chooser.addItem(basename) self.filename_chooser.setCurrentText(basename) self.internal_metadata.add_file(basename) From 9c391e6cad2e0e73bbe0be6f7e8bff6b59741013 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:49:33 +0000 Subject: [PATCH 387/396] Should just be using split line from the reader. To reduce code duplication. --- src/ascii_dialog/dialog.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 3e7e0494ed..2f562b7fd2 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -9,7 +9,7 @@ from guess import guess_column_count, guess_columns, guess_starting_position from os import path from sasdata.dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans -from sasdata.temp_ascii_reader import load_data, AsciiReaderParams +from sasdata.temp_ascii_reader import load_data, AsciiReaderParams, split_line from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog from metadata_filename_gui.metadata_tree_data import initial_metadata_dict from sasdata.ascii_reader_metadata import AsciiReaderMetadata @@ -174,21 +174,7 @@ def splitLine(self, line: str) -> list[str]: selected on the widget. """ - expr = '' - for seperator, isenabled in self.seperators.items(): - if isenabled: - if expr != r'': - expr += r'|' - match seperator: - case 'Comma': - seperator_text = r',' - case 'Whitespace': - seperator_text = r'\s+' - case 'Tab': - seperator_text = r'\t' - expr += seperator_text - - return re.split(expr, line) + return split_line(self.seperators, line) def attemptGuesses(self) -> None: """Attempt to guess various parameters of the data to provide some From 0514fe528cfcfe220b5f133a21792d7c632a1484 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:52:26 +0000 Subject: [PATCH 388/396] Shouldn't be calling items here. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 2f562b7fd2..4ead5dbdd0 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -453,7 +453,7 @@ def onDoneButton(self): self.starting_pos, self.col_editor.columns, self.excluded_lines, - self.seperators.items(), + self.seperators, self.internal_metadata ) self.params = params From ba95c609ff5aee885597d4dd48ea4fd0afd6bf05 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:53:54 +0000 Subject: [PATCH 389/396] Convert to list to make the interpreter happy. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 4ead5dbdd0..7524a6be01 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -449,7 +449,7 @@ def datasetOptions(self) -> list[str]: # TODO: Only works for one single file at the moment def onDoneButton(self): params = AsciiReaderParams( - self.files_full_path.values(), + list(self.files_full_path.values()), self.starting_pos, self.col_editor.columns, self.excluded_lines, From 91777ba82a9cdb6f3ee1aa36f29a7578d0b5fcc9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:54:07 +0000 Subject: [PATCH 390/396] This isn't true anymore. --- src/ascii_dialog/dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 7524a6be01..58e8522abd 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -446,7 +446,6 @@ def datasetOptions(self) -> list[str]: current_dataset_type = self.currentDatasetType() return current_dataset_type.required + current_dataset_type.optional + [''] - # TODO: Only works for one single file at the moment def onDoneButton(self): params = AsciiReaderParams( list(self.files_full_path.values()), From 667983578e47cb39d235fd912bcdb57e9c56fcf1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 09:50:34 +0100 Subject: [PATCH 391/396] Type hinting to stop linting from complaining. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 58e8522abd..8a5620b168 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -106,7 +106,7 @@ def __init__(self): ## Column Editor options = self.datasetOptions() - self.col_editor = ColEditor(self.colcount_entry.value(), options) + self.col_editor: ColEditor = ColEditor(self.colcount_entry.value(), options) self.dataset_combobox.currentTextChanged.connect(self.changeDatasetType) self.col_editor.column_changed.connect(self.updateColumn) From 2f1d9abd5f017006220a76e3cdc735dc60edb2f8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 09:50:45 +0100 Subject: [PATCH 392/396] Changed the params for the new order. --- src/ascii_dialog/dialog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 8a5620b168..e9f6c31505 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -449,11 +449,11 @@ def datasetOptions(self) -> list[str]: def onDoneButton(self): params = AsciiReaderParams( list(self.files_full_path.values()), - self.starting_pos, - self.col_editor.columns, - self.excluded_lines, self.seperators, - self.internal_metadata + self.internal_metadata, + self.col_editor.columns, + self.starting_pos, + self.excluded_lines ) self.params = params self.accept() From 5a96ad3c815e9c80e239ca321c8a36a317ff84ff Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 10:02:00 +0100 Subject: [PATCH 393/396] Updated order again. --- src/ascii_dialog/dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index e9f6c31505..4b48e3ccbc 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -449,11 +449,11 @@ def datasetOptions(self) -> list[str]: def onDoneButton(self): params = AsciiReaderParams( list(self.files_full_path.values()), - self.seperators, self.internal_metadata, self.col_editor.columns, self.starting_pos, - self.excluded_lines + self.excluded_lines, + self.seperators, ) self.params = params self.accept() From 98165e9f7f27b3870fbebeb50ae51cc03a5121e5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 10:14:35 +0100 Subject: [PATCH 394/396] Changed order yet again. --- src/ascii_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index 4b48e3ccbc..c2a69df537 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -449,8 +449,8 @@ def datasetOptions(self) -> list[str]: def onDoneButton(self): params = AsciiReaderParams( list(self.files_full_path.values()), - self.internal_metadata, self.col_editor.columns, + self.internal_metadata, self.starting_pos, self.excluded_lines, self.seperators, From c48612232173921dde897d755123a625b53f1ead Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 15 Jan 2025 14:23:25 +0000 Subject: [PATCH 395/396] Add logic from starting the ascii reader from dev. --- src/sas/qtgui/MainWindow/GuiManager.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py index 88ad4f402b..a9fee95217 100644 --- a/src/sas/qtgui/MainWindow/GuiManager.py +++ b/src/sas/qtgui/MainWindow/GuiManager.py @@ -13,6 +13,7 @@ from PySide6.QtCore import Qt, QLocale import matplotlib as mpl +from sasdata.temp_ascii_reader import load_data import sas.system.version @@ -1384,4 +1385,12 @@ def particleEditor(self): def asciiLoader(self): - pass \ No newline at end of file + from ascii_dialog.dialog import AsciiDialog + dialog = AsciiDialog() + status = dialog.exec() + if status == 1: + loaded = load_data(dialog.params) + for datum in loaded: + logger.info(datum.summary()) + else: + logger.error('ASCII Reader Closed') From 4e601c73bf7a0a1b7ea8953fde5273f0ecd87915 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 15 Jan 2025 14:32:40 +0000 Subject: [PATCH 396/396] Don't use relative module paths. --- src/ascii_dialog/col_editor.py | 2 +- src/ascii_dialog/column_unit.py | 4 ++-- src/ascii_dialog/dialog.py | 12 ++++++------ src/ascii_dialog/unit_preference_line.py | 2 +- src/ascii_dialog/unit_preferences.py | 2 +- src/ascii_dialog/unit_selector.py | 2 +- src/ascii_dialog/warning_label.py | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 901338a021..5ab8fd4c6d 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -3,7 +3,7 @@ from PySide6.QtCore import Slot, Signal from sasdata.quantities.units import NamedUnit from sasdata.ascii_reader_metadata import pairings -from column_unit import ColumnUnit +from ascii_dialog.column_unit import ColumnUnit from typing import cast class ColEditor(QWidget): diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index fa437109e3..d1dbd2a773 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -6,8 +6,8 @@ from sasdata.dataset_types import unit_kinds from sasdata.quantities.units import symbol_lookup, NamedUnit -from unit_selector import UnitSelector -from default_units import default_units +from ascii_dialog.unit_selector import UnitSelector +from ascii_dialog.default_units import default_units def configure_size_policy(combo_box: QComboBox) -> None: policy = combo_box.sizePolicy() diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index c2a69df537..fd7e0a56d1 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -2,18 +2,18 @@ from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, \ QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication, QDialog from PySide6.QtCore import QModelIndex, QPoint, Slot -from selection_menu import SelectionMenu -from warning_label import WarningLabel -from col_editor import ColEditor -from row_status_widget import RowStatusWidget -from guess import guess_column_count, guess_columns, guess_starting_position +from ascii_dialog.selection_menu import SelectionMenu +from ascii_dialog.warning_label import WarningLabel +from ascii_dialog.col_editor import ColEditor +from ascii_dialog.row_status_widget import RowStatusWidget +from ascii_dialog.guess import guess_column_count, guess_columns, guess_starting_position from os import path from sasdata.dataset_types import DatasetType, dataset_types, one_dim, two_dim, sesans from sasdata.temp_ascii_reader import load_data, AsciiReaderParams, split_line from metadata_filename_gui.metadata_filename_dialog import MetadataFilenameDialog from metadata_filename_gui.metadata_tree_data import initial_metadata_dict from sasdata.ascii_reader_metadata import AsciiReaderMetadata -from constants import TABLE_MAX_ROWS, NOFILE_TEXT +from ascii_dialog.constants import TABLE_MAX_ROWS, NOFILE_TEXT import re dataset_dictionary = dict([(dataset.name, dataset) for dataset in [one_dim, two_dim, sesans]]) diff --git a/src/ascii_dialog/unit_preference_line.py b/src/ascii_dialog/unit_preference_line.py index 5e00d0f571..80d27ef332 100644 --- a/src/ascii_dialog/unit_preference_line.py +++ b/src/ascii_dialog/unit_preference_line.py @@ -4,7 +4,7 @@ from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QWidget from sasdata.quantities.units import NamedUnit, UnitGroup -from unit_selector import UnitSelector +from ascii_dialog.unit_selector import UnitSelector class UnitPreferenceLine(QWidget): def __init__(self, column_name: str, initial_unit: NamedUnit, group: UnitGroup): diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py index fb49fc8879..3d37698cc2 100644 --- a/src/ascii_dialog/unit_preferences.py +++ b/src/ascii_dialog/unit_preferences.py @@ -4,7 +4,7 @@ from PySide6.QtWidgets import QApplication, QScrollArea, QVBoxLayout, QWidget from sasdata.quantities.units import NamedUnit from sasdata.dataset_types import unit_kinds -from unit_preference_line import UnitPreferenceLine +from ascii_dialog.unit_preference_line import UnitPreferenceLine import random class UnitPreferences(QWidget): diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 07ffc9e5c6..4f48c22c4a 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -2,7 +2,7 @@ from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QPushButton, QVBoxLayout from sasdata.quantities.units import NamedUnit, UnitGroup, unit_group_names, unit_groups -from unit_list_widget import UnitListWidget +from ascii_dialog.unit_list_widget import UnitListWidget all_unit_groups = list(unit_groups.values()) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 4e76352978..a5eeac96a5 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -1,5 +1,5 @@ from PySide6.QtWidgets import QLabel -from constants import TABLE_MAX_ROWS +from ascii_dialog.constants import TABLE_MAX_ROWS class WarningLabel(QLabel):