From e3b0095399e2370708511246dace15d147881011 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 09:44:57 +0100 Subject: [PATCH 001/434] 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 7c8f9e2f6e3d733944e53586773c2c55e18facbe Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 09:45:39 +0100 Subject: [PATCH 002/434] 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 3f92522f5fd50184ac8df94c6e962ed7b98fc0d3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:12:41 +0100 Subject: [PATCH 003/434] 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 8c42c5e000dcab9d0319faf227a13b2f4b5be683 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:16:36 +0100 Subject: [PATCH 004/434] 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 42ba46e25791808fc1e963ee4a75554100ea7635 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:19:32 +0100 Subject: [PATCH 005/434] 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 d856b274b399632eaefe5fecbe6b423b34b0926d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:23:36 +0100 Subject: [PATCH 006/434] 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 ef0d551263dfc1a2830eafcf9ffac4cf81fd789e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:36:48 +0100 Subject: [PATCH 007/434] 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 463e2222135c5e975c685b9679c8973ca02b92a9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:39:01 +0100 Subject: [PATCH 008/434] 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 21f826af94dd07eda21bde2281bdf4ee04553a79 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 10:55:09 +0100 Subject: [PATCH 009/434] 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 aed40803edd27d3aa78eb12c50e9316d72cde52e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:06:43 +0100 Subject: [PATCH 010/434] 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 2b872923e05d9c41978721cd01273404937bd7cd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:17:53 +0100 Subject: [PATCH 011/434] 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 cd8fd7b1756d3951ae136ed408402b41d0f82f40 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:21:16 +0100 Subject: [PATCH 012/434] 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 d8ab54e4486f6ba6bef51ea87b6a4ed9c99df28b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:39:00 +0100 Subject: [PATCH 013/434] 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 4c85725f584af74fa76cb15a55ef5035bde016aa Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:39:14 +0100 Subject: [PATCH 014/434] 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 5aa8d12fd1bcc45db3be48f13809f8e875f68e3e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:42:47 +0100 Subject: [PATCH 015/434] 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 378ed0b9ef69722043ba41a72dfa3a2a1a255d27 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:44:36 +0100 Subject: [PATCH 016/434] 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 f52bf8d6473e4bfd2223b572f9b9331599d5f701 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:47:53 +0100 Subject: [PATCH 017/434] 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 7d7863e5d72536862ae31b814cb5c3e846ac18e5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 11:50:35 +0100 Subject: [PATCH 018/434] 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 6dedd714102d75c4f5d26176a7bb4f3c3b7161da Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:37:22 +0100 Subject: [PATCH 019/434] 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 3ac6eef92bbdd1658a21677389913a6e9e2f24bc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:47:03 +0100 Subject: [PATCH 020/434] 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 be6ba533c10730c376d1948123e3d5e2dab0fc2a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:49:33 +0100 Subject: [PATCH 021/434] 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 1e352fa1bf95f23fbba3c7aca527eb51dd2ac68d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:53:02 +0100 Subject: [PATCH 022/434] 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 2feb8552fb881ddc0476ea1363437e2e3627ef54 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 13:56:47 +0100 Subject: [PATCH 023/434] 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 9889351762fd2599db772957768bb11f1e05435a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 14:10:38 +0100 Subject: [PATCH 024/434] 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 83d9b761f42a597ed7636d72aed0f525a0706366 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:32:43 +0100 Subject: [PATCH 025/434] 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 42f522700d9c28713486012a0a0366fadebb8f95 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:34:50 +0100 Subject: [PATCH 026/434] 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 c0afe4fa07dda191ec8b12e1c48e1c01956b8f6b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:38:05 +0100 Subject: [PATCH 027/434] 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 1bc763d268abfda0e05960f9d028372f1ea88e6e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:40:51 +0100 Subject: [PATCH 028/434] 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 06aeaf3d781e61d97b7adbd4400050fcc09a94a5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:47:48 +0100 Subject: [PATCH 029/434] 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 90f7a7f06e03acc8bec8713082d912adc0c5cf82 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:55:53 +0100 Subject: [PATCH 030/434] 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 f1ea9f6db1af44505fdceb913b0559e88fd0b591 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 15:56:00 +0100 Subject: [PATCH 031/434] 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 922c5ac3f31e2d15d21f2ef89f1aa820137fd78a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:02:33 +0100 Subject: [PATCH 032/434] 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 bcfa1f33135afc6faf04b92492cecab0097cf306 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:23:19 +0100 Subject: [PATCH 033/434] 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 083d7b17b47fdbcabb2fd6ca4e447d571aa170f1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:28:44 +0100 Subject: [PATCH 034/434] 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 c7f41656f7a9703d309426c13e3ef0994f373f26 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 29 Jul 2024 16:31:03 +0100 Subject: [PATCH 035/434] 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 6ac29a8fa6f799c13adeb180a5f7424f8209b204 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 08:10:05 +0100 Subject: [PATCH 036/434] 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 dddfe04a8fb2304bd278cd3bad390b8a2e4d0141 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 08:10:24 +0100 Subject: [PATCH 037/434] 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 127809db4c56c9888239c45f2497b47a83a254a3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:01:33 +0100 Subject: [PATCH 038/434] 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 f6c58dee8f7ee81e1dd9b7dcb936808de5dcada4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:26:09 +0100 Subject: [PATCH 039/434] 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 c4eaa71f0bafa07c6d06f82e7d68324a56d01b1a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:39:38 +0100 Subject: [PATCH 040/434] 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 e7129037ed0df45a88d954078b88ef478fe21dd5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:41:28 +0100 Subject: [PATCH 041/434] 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 37473dc5ddba4173d3dfd5d4e597324fda92fef0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 09:44:33 +0100 Subject: [PATCH 042/434] 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 0e420004619ea71dead9b37ee1487174b5bc08d8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 10:23:46 +0100 Subject: [PATCH 043/434] 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 0ac34d7a668ee837906f9ef45f975b6dadc890eb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:07:36 +0100 Subject: [PATCH 044/434] 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 a95ebd6209c834335fffb8fbb67d678d31e05662 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:08:18 +0100 Subject: [PATCH 045/434] 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 2644cc27a6a301e6bd4dcffedf94869ac8b2774e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:16:16 +0100 Subject: [PATCH 046/434] 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 138b26a5c49cc598f70e20aad4025ed9187ad2ee Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:26:13 +0100 Subject: [PATCH 047/434] + 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 398742beb9acdc7f4455e605b110f86f2a89ae88 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:32:40 +0100 Subject: [PATCH 048/434] 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 61f7bb3522bcd847eb771425f53e8e0b7c0464fd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:32:49 +0100 Subject: [PATCH 049/434] 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 c0bebb0a701e727c05fe244c9af65bbdc7f79053 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:34:51 +0100 Subject: [PATCH 050/434] 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 69970790a7131287587cdd7951e964be42257720 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:38:22 +0100 Subject: [PATCH 051/434] 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 f4314093e46d73e3b46a1a0d6f04a21f4b4f0cfb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:46:56 +0100 Subject: [PATCH 052/434] 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 eaf821c54a888b5f18b39bf347790222c13e85be Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:50:21 +0100 Subject: [PATCH 053/434] 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 0ab5c3aeb12df6e8581f1f9dc87dbf76139b79ff Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 11:50:26 +0100 Subject: [PATCH 054/434] 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 29b4b9dfdf767f96594c33321952d7c574257405 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 12:03:04 +0100 Subject: [PATCH 055/434] 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 e12056ed17c583a953fd207d312d0cf0f0fb2312 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:19:43 +0100 Subject: [PATCH 056/434] 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 16fd04c434093387a5b2154167129a08217a1c7c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:25:49 +0100 Subject: [PATCH 057/434] 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 c552045a8d1790f5aa0b8754c14569e47d992df1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:32:52 +0100 Subject: [PATCH 058/434] 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 b9f1ab995089c24e9944394744b0af444051cb70 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:47:07 +0100 Subject: [PATCH 059/434] 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 5f88f60f89ce2521698c7271176ccdaa83737ec0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:51:24 +0100 Subject: [PATCH 060/434] 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 87f767d538e770bc35b12c444c793b8c68080b56 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:53:15 +0100 Subject: [PATCH 061/434] 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 1169a23ab4eb78c00b4e9991d19e8338e040339b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 13:54:24 +0100 Subject: [PATCH 062/434] 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 079e889f0dd9eebab459982573f0389601f9d74c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:35:46 +0100 Subject: [PATCH 063/434] 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 4308711d2c0e6a870a91b76475d8669f8544f347 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:36:22 +0100 Subject: [PATCH 064/434] 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 525596d7a03d467fb81d053b5c798b39e11ba495 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:37:32 +0100 Subject: [PATCH 065/434] 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 2400cff8928c6dcd5348291c8b206aa516613f08 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 15:38:33 +0100 Subject: [PATCH 066/434] 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 4e713d55ef4892d991118769cbd29c8503742585 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 30 Jul 2024 16:37:58 +0100 Subject: [PATCH 067/434] 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 546091bdc7614a2fc77cb6ac98247a802a3ca8e5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 09:30:14 +0100 Subject: [PATCH 068/434] 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 dc2300433448b6093e8fe14ae695be2d24a24136 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 10:27:35 +0100 Subject: [PATCH 069/434] 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 a65d08ac55141d327b4cadb611860caaa4e52154 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 10:27:45 +0100 Subject: [PATCH 070/434] 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 80612f1d05723ece7aaca5481c175cbd9c01f73c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 31 Jul 2024 11:55:05 +0100 Subject: [PATCH 071/434] 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 308c11a18703b121996f3201cda3a69a443f32a9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 10:44:03 +0100 Subject: [PATCH 072/434] 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 7604482f7293ed420db1aa56ceab60105efdad15 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 10:51:27 +0100 Subject: [PATCH 073/434] 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 9143828627fd16af49caaa0a6913eff67c0ad6f5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 10:59:15 +0100 Subject: [PATCH 074/434] 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 b2759900b4229adfc4089efe1b9ee1348f02dfd3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 11:05:41 +0100 Subject: [PATCH 075/434] 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 7ee146f0441bc232c65a041e991ca089a63a168c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 11:49:22 +0100 Subject: [PATCH 076/434] 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 cdf515127cee5f2393b8962b67cb3b9f24c28c28 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:13:18 +0100 Subject: [PATCH 077/434] 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 8d34f06ffe3ae6ccd3f18ac5149750a147c85536 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:22:06 +0100 Subject: [PATCH 078/434] 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 532bf49eff8ae8d1f90ebc8edb00478aefd13dd9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:26:08 +0100 Subject: [PATCH 079/434] 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 b03b08cb2feba953607d154704565354d51ef7d2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:26:16 +0100 Subject: [PATCH 080/434] 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 fdc2f29d36640883a640c14d0557e6908fa54f90 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:30:50 +0100 Subject: [PATCH 081/434] 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 a6b8281cac6eff0dd85b7a4d40bdd872c057de21 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:31:33 +0100 Subject: [PATCH 082/434] 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 46c3c625dcab2b2a9f378dcbe12e3646fe19566e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:40:07 +0100 Subject: [PATCH 083/434] 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 8fa17278c8a4185aac47257dc666495d8abcfab5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:43:00 +0100 Subject: [PATCH 084/434] 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 5a2aec579dd57054649b2a48c3848d44c424307f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 13:46:12 +0100 Subject: [PATCH 085/434] 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 f334abb165477b59f0b966d40a00ed23722365a3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 14:10:10 +0100 Subject: [PATCH 086/434] 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 cb02bd8c3952a24ed3fb2f337f73706ebf737cb4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 14:51:01 +0100 Subject: [PATCH 087/434] 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 ee3a9f398d2c2943f824b35249fc4abd469b33c7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 15:42:33 +0100 Subject: [PATCH 088/434] 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 9c43b9725e495badf8d07bbe24049306a5ebf227 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 15:50:43 +0100 Subject: [PATCH 089/434] 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 93d3b77c1d8f61d79151a504ca6aa28a0b2e5847 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 15:56:19 +0100 Subject: [PATCH 090/434] 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 24f8a42b6f4b852b266e39b0d7dfcfd02a4ea873 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 1 Aug 2024 16:05:17 +0100 Subject: [PATCH 091/434] 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 78a5342f02f6ecec74f109a89d92d262d918805a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 07:43:23 +0100 Subject: [PATCH 092/434] 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 4885020ecc7bfc107fe85d761dca921f74cdeb33 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 07:54:21 +0100 Subject: [PATCH 093/434] 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 abc8bf7e0a8d75d178d7852d6a6eb0362546437e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:19:32 +0100 Subject: [PATCH 094/434] 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 1dd2d07d5de6c97a4ef72e76d673d5372836a2b9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:23:42 +0100 Subject: [PATCH 095/434] 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 79c4d05197669b76244b3ed23b8d4476f1739e75 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:40:39 +0100 Subject: [PATCH 096/434] 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 f1d268751a4e439b69544f597a8ef1a2d41e6e8c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 08:55:57 +0100 Subject: [PATCH 097/434] 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 0ddb571a4337a5759871feb528c1ee667a999c6a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 10:38:17 +0100 Subject: [PATCH 098/434] 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 b6857935cdbe05ceca37134787ebdc2ffdfa1207 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:04:05 +0100 Subject: [PATCH 099/434] 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 44d5ce3db1378073221c9d68ff31eab235112cc6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:05:35 +0100 Subject: [PATCH 100/434] 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 a0839b963b72f73a61211e55502ad8c86a5a6b3e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:20:20 +0100 Subject: [PATCH 101/434] 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 7034da017e2519142f0d408c1f0051e7b92ea7ef Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:26:10 +0100 Subject: [PATCH 102/434] 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 a0b4d7f97b973d0f701f90a44ce958be677b4c82 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:35:08 +0100 Subject: [PATCH 103/434] 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 84ca87eb81255115a85a3b8c791e66f6a78667bf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:36:58 +0100 Subject: [PATCH 104/434] 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 e386af6f988b6619dbe5f304f58e4e5b1cf37454 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 11:59:20 +0100 Subject: [PATCH 105/434] 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 b89b6cbd31d726e71df753cccf1c8ca7a626a13d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 12:01:31 +0100 Subject: [PATCH 106/434] 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 7ebd89032e7403d2c11c8dcb0de09ab365dd58de Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 2 Aug 2024 12:17:09 +0100 Subject: [PATCH 107/434] 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 18e144611ccea765993fde4eb4329ef42b53f643 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 09:42:41 +0100 Subject: [PATCH 108/434] 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 3937f341384480f59a0a7d65576cf121ec0e81cb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 09:54:35 +0100 Subject: [PATCH 109/434] 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 666848f20464124efc9d09e86f87e1eb163a3efc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:28:03 +0100 Subject: [PATCH 110/434] 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 dc69d20f102d2b08ccef7ca6d9a00a7f509824c5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:28:47 +0100 Subject: [PATCH 111/434] 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 4c2784b67e2679ae8583928ab4dac0291f5ede0e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:35:52 +0100 Subject: [PATCH 112/434] 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 5cca614bb330912810cf0b1cd764c5334b8e8fe8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 10:52:57 +0100 Subject: [PATCH 113/434] 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 3075a40b612beeedb78d0e259570f2f14484f3e4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:02:44 +0100 Subject: [PATCH 114/434] 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 855acc3cd1cd3a16d096ee87a4b88279d8830635 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:29:33 +0100 Subject: [PATCH 115/434] 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 c01a03330cdd6882cd2be3a1320b735869ec9b0a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:38:45 +0100 Subject: [PATCH 116/434] 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 8f3f091b94c717662937205369ef18fb00e53bd4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 11:54:06 +0100 Subject: [PATCH 117/434] 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 ad50b3eb15ab813d5efb4cb239f97e32c59ee101 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:00:49 +0100 Subject: [PATCH 118/434] 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 f5faf2cdfcc7ba7b60152419225f50980f1a83e0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:01:05 +0100 Subject: [PATCH 119/434] 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 5ea26a008f5e6fef044de2c14fc35817f4bfee6c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:04:15 +0100 Subject: [PATCH 120/434] 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 5a7afc75dc07a82f93617cf2b9f2f72e7c5ecbca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:06:37 +0100 Subject: [PATCH 121/434] 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 54199d947011df759421d71b884aaf32717814e6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:14:03 +0100 Subject: [PATCH 122/434] 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 e1b2d743c56b3ab12eb7c6b9e725f4c6a75eb7ba Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 12:15:09 +0100 Subject: [PATCH 123/434] 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 01ef94872d68280a83a049adbf48054ba7a0d79c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:00:51 +0100 Subject: [PATCH 124/434] 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 be0d21a866cfe7faf57479424df661caf7f7051b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:31:34 +0100 Subject: [PATCH 125/434] 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 1bc839cbd63ea960b20e3a2361a5b8fcb5b9cb89 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:31:54 +0100 Subject: [PATCH 126/434] 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 f7bde65aa5e494ac3abf4f68e4540e8b484458ca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 14:55:36 +0100 Subject: [PATCH 127/434] 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 00d942050a60abd463c85973d07f711572b087ce Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:03:35 +0100 Subject: [PATCH 128/434] 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 899ca66e3decf6d109ed50d76e51a65d397f9cae Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:13:00 +0100 Subject: [PATCH 129/434] 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 60f358ed85e300733d16424bdc9de7499c32f283 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:33:21 +0100 Subject: [PATCH 130/434] 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 0454a9416932cfaad2f12c6f27d9194691d79380 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 15:33:29 +0100 Subject: [PATCH 131/434] 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 ba869f0c80b08b8700970a0f1301f62d60f9d6ac Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 5 Aug 2024 17:14:45 +0100 Subject: [PATCH 132/434] 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 63bfb31c835d7dae5a4daed9d4b9506a39ca21e7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:05:43 +0100 Subject: [PATCH 133/434] 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 5062190f653baf744e021050d4bd735e5c4f8151 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:06:17 +0100 Subject: [PATCH 134/434] 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 548f308ca30b867b95e032bf7605ff5af3913b28 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:07:30 +0100 Subject: [PATCH 135/434] 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 efc2bb4e124f48cd84f35ae839a5681dd319ad5d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:07:56 +0100 Subject: [PATCH 136/434] 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 90b665051a07bd5dc96c1f987eb987dece368cb1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:10:59 +0100 Subject: [PATCH 137/434] 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 8f91586787d7ade8b3152fd55ac89b43449c00f4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:11:26 +0100 Subject: [PATCH 138/434] 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 43d270dae512932848863e0a05fb810a9835cff6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:12:07 +0100 Subject: [PATCH 139/434] 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 1b4618d44cc4c18be74071c203a2ae04fc585b86 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:14:38 +0100 Subject: [PATCH 140/434] 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 45ab3994ec680a9e732da05f9be3095618c2c9d3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 08:59:01 +0100 Subject: [PATCH 141/434] 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 145a1f3f77a36fcd0b769772051af67abb13a998 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:01:40 +0100 Subject: [PATCH 142/434] 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 5c5dd12cd07cd0750cff7f3e9fa97ff28398144f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:01:46 +0100 Subject: [PATCH 143/434] 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 635b13db022da5188730d249231af02decdce1e7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:02:56 +0100 Subject: [PATCH 144/434] 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 8138cdc4b3e061bdbb0372b25ec24a2457e4a64a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:41:33 +0100 Subject: [PATCH 145/434] 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 b274ad710b7075c773e18f52d018e37ad41cb79d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:47:37 +0100 Subject: [PATCH 146/434] 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 79bf50a5843c7dc90d3cbd0ad553b11e2d249fc0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:51:08 +0100 Subject: [PATCH 147/434] 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 00d1df8df334012f1a33605014d6073c4112a9ab Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 09:56:38 +0100 Subject: [PATCH 148/434] 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 f0b166b2f16450355eeaa68bdf9aff5c138335fc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:00:40 +0100 Subject: [PATCH 149/434] 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 45bb1a9cc0b838000c0e842315788a5e0a4f094b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:02:13 +0100 Subject: [PATCH 150/434] 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 366ae24561acbabd03a1c80ee9ae785940806cee Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:08:18 +0100 Subject: [PATCH 151/434] 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 539c9bf22f6bfa51c3a87e2ba2ea665e23c26bb8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:14:37 +0100 Subject: [PATCH 152/434] 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 3ad9b0619534e9f3ba14acefb5d39aab3ce90d31 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:15:21 +0100 Subject: [PATCH 153/434] 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 629e7e3c4331f504dda218964762c0503ef0fe72 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:15:57 +0100 Subject: [PATCH 154/434] 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 7d1e29d8b0c7c7d0dd636af48dae11302569e875 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:22:45 +0100 Subject: [PATCH 155/434] 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 aafa26d01d17230a8992d5070e8ff79440b20bd6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:29:28 +0100 Subject: [PATCH 156/434] 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 f1f324a468984a702769c067237c64f05a24a63a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:37:52 +0100 Subject: [PATCH 157/434] 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 2a7e9d60dbd1859d9171cc51d2a012ac55480dff Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 10:38:21 +0100 Subject: [PATCH 158/434] 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 890795cd7fc1e83cd6001e428a6502d6dcad835e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:15:12 +0100 Subject: [PATCH 159/434] 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 72d980775ae125e2943338ce6d0b82e2acd089c2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:19:12 +0100 Subject: [PATCH 160/434] 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 70ad0ea2a097d24f78640debaa572592dc7e05e5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:25:20 +0100 Subject: [PATCH 161/434] 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 c39e00aa85d283ace85f6be0767b15b2b07878bd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:27:10 +0100 Subject: [PATCH 162/434] 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 77a15dc17c1be571fb2165d04e7c356b79897bcb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:44:18 +0100 Subject: [PATCH 163/434] 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 6038ffc45b14259ed5c1e66d066b49395bd27e66 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:46:23 +0100 Subject: [PATCH 164/434] 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 a1edb79b40a8a583d23ad7d08429788142a820da Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:51:11 +0100 Subject: [PATCH 165/434] 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 39490b7862bc64b2a4ced12969b92921419ebc97 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:54:48 +0100 Subject: [PATCH 166/434] 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 9a4f50aebc2a228eb950517850d0427f985c704d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 11:55:29 +0100 Subject: [PATCH 167/434] 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 bc393a5e0219d1f5cc0a34ed82aa1428b3cffdca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 6 Aug 2024 13:22:00 +0100 Subject: [PATCH 168/434] 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 fcd08de159f44560257c443f41af2407c82f9469 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 8 Aug 2024 13:22:13 +0100 Subject: [PATCH 169/434] 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 40dc03bd4b9bf1a64827eea45ad7b2fd0c085924 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 8 Aug 2024 14:19:25 +0100 Subject: [PATCH 170/434] 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 fd0e0eaae57b9745a6b6513604cecd86d88614ec Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 09:57:57 +0100 Subject: [PATCH 171/434] 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 bc8492bdf3b9f042d9d24bc98c94609a2c3b1cb7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 10:16:49 +0100 Subject: [PATCH 172/434] 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 c1fb623c3ab11d6ffcba4b3c0b9f6ae3de82becd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:15:11 +0100 Subject: [PATCH 173/434] 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 ea51447154782c8fee30b025d331bf32b6a58b9c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:23:03 +0100 Subject: [PATCH 174/434] 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 314072546ddd95cdc860d706c7c405c91984b445 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:27:20 +0100 Subject: [PATCH 175/434] 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 0208a6a29501efb811036754213218e3245c98b0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:39:57 +0100 Subject: [PATCH 176/434] 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 71583041f6478dc16f11463f3066d650fbac3642 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:43:37 +0100 Subject: [PATCH 177/434] 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 6fe876c5cb06a89a97ef6e1a76f3179de8c88b93 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 11:45:58 +0100 Subject: [PATCH 178/434] 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 02ace02822d47b28db42e5baef6d61163dcb52c4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 15:21:40 +0100 Subject: [PATCH 179/434] 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 c51d7c719cd213738c6ef07beb67c54c93ecacbc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 15:33:49 +0100 Subject: [PATCH 180/434] 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 e61db45eb49463a3e5ef8f84b9565eff4a131e51 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:06:12 +0100 Subject: [PATCH 181/434] 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 f04005d7266fa355229e8afbca7326a31904f699 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:16:18 +0100 Subject: [PATCH 182/434] 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 956af6453aff112de3a490ff71a9b304acb02f99 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:18:32 +0100 Subject: [PATCH 183/434] 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 9b2b2439d8483221a98826773b8de0a7e895add6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 16:23:25 +0100 Subject: [PATCH 184/434] 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 b7fd39a678258eec3e4e9ec5ba421d32effefa3c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 17:05:24 +0100 Subject: [PATCH 185/434] 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 eba0ed5005b6ccae1bc695a32e5b531f86c37f3f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 17:07:01 +0100 Subject: [PATCH 186/434] 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 ddafdda5b5d1d4a1bad1efd4b5a8253808222fd5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 9 Aug 2024 17:12:33 +0100 Subject: [PATCH 187/434] 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 60ee649ca44e0f3a3c09021f61e114e0bf12cd36 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:09:46 +0100 Subject: [PATCH 188/434] 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 b3926951962587c046ffa64dc2c4b2442e4cc770 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:24:16 +0100 Subject: [PATCH 189/434] 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 6125d86ebe8ba616b3626b5fb1dbe2f7f7fba6c1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:28:28 +0100 Subject: [PATCH 190/434] 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 c525e63bf185ad415f0e2326902dcf961e41333b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 08:54:09 +0100 Subject: [PATCH 191/434] 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 05a487b0cc79aa448b50f4cd7a0c749370dcb691 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 09:30:31 +0100 Subject: [PATCH 192/434] 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 a83f08418c10c18760f9490062780d549a346225 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 09:38:09 +0100 Subject: [PATCH 193/434] 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 c426da3a8e6e0be6f9b3506b257878fbd2f2be3a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 09:48:11 +0100 Subject: [PATCH 194/434] 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 3fc435009bfbaf7fbf9c83286c8140b8534c242b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 10:07:50 +0100 Subject: [PATCH 195/434] 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 1e24088b75822af24f9578168d7188f47c2b9af9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 10:52:50 +0100 Subject: [PATCH 196/434] 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 e443e40880de85c580e3677cd1d26f7897cee4c3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 10:57:58 +0100 Subject: [PATCH 197/434] 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 50711f31feaaefef9cb204cc8077f9112a34b57c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:10:36 +0100 Subject: [PATCH 198/434] 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 0ea0b8dc4cea48e108c5dd465b71c94a2210ab53 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:12:34 +0100 Subject: [PATCH 199/434] 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 36cbb3e8267b7f06551b8c026822f4a8f732ead6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:20:27 +0100 Subject: [PATCH 200/434] 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 77b1d8c54f1d83cdc14cb9c21d14bab246572690 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 11:33:07 +0100 Subject: [PATCH 201/434] 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 37003c8a2731d6ed2488996d44e86a8701d2179f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 12 Aug 2024 16:01:00 +0100 Subject: [PATCH 202/434] 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 5fd75e079b3a66d92eac0b6d0dd268b3155a19c2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 08:34:11 +0100 Subject: [PATCH 203/434] 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 0e57d9c4e6a02a41ce317ff19bec96caa4e154b6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 09:36:05 +0100 Subject: [PATCH 204/434] 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 54bf0e387c53ffb65c87cb7b28d1fdf809e5b68e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 10:36:19 +0100 Subject: [PATCH 205/434] 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 51a3423a50cbc6b55201d26030044554eb7f4bac Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 13:30:06 +0100 Subject: [PATCH 206/434] 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 7f219715aae9608a174f5fbffecaee58ffdbb8c3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 13 Aug 2024 13:33:55 +0100 Subject: [PATCH 207/434] 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 4ddb385e399bfd1e672c60b969fad3801cadec60 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 14 Aug 2024 13:22:24 +0100 Subject: [PATCH 208/434] 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 b5b0fea925..f22446c21c 100644 --- a/build_tools/requirements.txt +++ b/build_tools/requirements.txt @@ -38,4 +38,4 @@ zope requests # Alphabetized list of OS-specific packages -pywin32; platform_system == "Windows" +pywin32; platform_system == "Windows" \ No newline at end of file From 38deeb3f79d0c15148ead645e6556378c03541cb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Sep 2024 09:09:25 +0100 Subject: [PATCH 209/434] 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 baf4bd5af5ac3af823679a8fe99d0a79ebd17567 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Sep 2024 09:12:12 +0100 Subject: [PATCH 210/434] 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 ac75e4c3ce3078f7c6de4a072de5bbe15fe386a9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Sep 2024 09:23:56 +0100 Subject: [PATCH 211/434] 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 8dff30d1d71a43be19ec4497f365640f743ec8af Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 09:35:58 +0100 Subject: [PATCH 212/434] 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 e62d67ef06859fae25c267cea55037809ae397c6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 09:37:28 +0100 Subject: [PATCH 213/434] 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 e127495fdd7b4c4923aecf34290ea4ca38d34f82 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 10:25:04 +0100 Subject: [PATCH 214/434] 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 521d1640edde86c19923f482a262ee91f2cd6e06 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 10:51:12 +0100 Subject: [PATCH 215/434] 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 e139150f27e77e003e3277fed440d6037d9731e5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:12:16 +0100 Subject: [PATCH 216/434] 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 00e6578d36a8f99c9c0d9b534b40c86cec0c5147 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:28:41 +0100 Subject: [PATCH 217/434] 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 18c384b9b40f096d747bc5d791688a91d620443d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:31:50 +0100 Subject: [PATCH 218/434] 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 9cd1ac3894e2979e4933d49b5dc6b621d20e9457 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:34:22 +0100 Subject: [PATCH 219/434] 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 b21d2de21091c1662854a83eff398951b63fe747 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 11:50:39 +0100 Subject: [PATCH 220/434] 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 4c9e490874e89e9dfa77388f2e844a002624c73c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 13:54:05 +0100 Subject: [PATCH 221/434] 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 5f5958d497c5f400773426c10daa3c21591ea557 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 13:59:50 +0100 Subject: [PATCH 222/434] 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 03d34ab2795dccb4ea8794a992be94f084deb075 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 14:00:11 +0100 Subject: [PATCH 223/434] 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 409b4f6c40c24cd33ddc6a47af7a7584d0553b15 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 14:05:17 +0100 Subject: [PATCH 224/434] 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 f8e9f5211fa54f1175a294f3a1a14d39626010ba Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Sep 2024 16:02:29 +0100 Subject: [PATCH 225/434] 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 314de06ecb0f5c4744ffdec544dc34653cc92788 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Sep 2024 08:45:36 +0100 Subject: [PATCH 226/434] 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 d1f222b0fa034b875372d8f74fab6318d63842d7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Sep 2024 08:45:44 +0100 Subject: [PATCH 227/434] 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 b355f0e4f25f81ba6bc72a5a3dbd91ae2197d0da Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 09:39:17 +0100 Subject: [PATCH 228/434] 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 4a5bfa89df08a56c83cd2c76ea8b435cceaf7978 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:18:15 +0100 Subject: [PATCH 229/434] 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 a028ad60a6e82e92cf9fe83429248c26e501e736 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:18:37 +0100 Subject: [PATCH 230/434] 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 a7ab5e85a950d98046fa91775e796c32220925ee Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:42:26 +0100 Subject: [PATCH 231/434] 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 def0a6eb3c999a5215d5bbbb0444737b9e400c37 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:43:01 +0100 Subject: [PATCH 232/434] 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 43c22b70076dbfb063395147b6597eae5bbac210 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:44:09 +0100 Subject: [PATCH 233/434] 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 c57d86bc49bd1f1a4fca31a0169f59ecbc722052 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 10:58:27 +0100 Subject: [PATCH 234/434] 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 13ab6d338a9fbe66cae6ab37a65f54a3d01bc9e9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:00:13 +0100 Subject: [PATCH 235/434] 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 ece282f9a6e2548eb961f3055dd3faa16bfd7596 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:14:49 +0100 Subject: [PATCH 236/434] 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 cbca3afac13702b0040ac67cc26e9be0f0501eb9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:27:15 +0100 Subject: [PATCH 237/434] 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 a05c8fa4b641469f9b450108379454d61476e6b1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:50:29 +0100 Subject: [PATCH 238/434] 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 c308ee0d3bec7c38c220690c8977512284e9693c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 11:58:04 +0100 Subject: [PATCH 239/434] 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 fcc9ae1a40ed049c4cae75b255a7c2455bc2740c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 12:01:34 +0100 Subject: [PATCH 240/434] 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 bded22a543a2c10038afe312ada5ca196b20d18b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 13:32:54 +0100 Subject: [PATCH 241/434] 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 4758ce560fd9794301c624a6820769860cb475d4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 13:44:32 +0100 Subject: [PATCH 242/434] 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 aaf1df41dccaaede87c9911cfec18ea4d53045d5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 14:58:29 +0100 Subject: [PATCH 243/434] 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 a8fb8979e82efe7af46ad8c514f6e28d793f7051 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 15:03:16 +0100 Subject: [PATCH 244/434] 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 505e143a13552703d6b9f094953168f3929f68bf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 21 Oct 2024 15:03:22 +0100 Subject: [PATCH 245/434] 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 a4566924c6aaf8622cbca6fe762f1c9bfa1b3b6b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 22 Oct 2024 11:07:12 +0100 Subject: [PATCH 246/434] 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 d66f839786dd2e0a48e5f1e7888daac5af495379 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 09:39:32 +0100 Subject: [PATCH 247/434] 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 99ea5d49e278264997c82b2b1000e67e478cb53d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 09:45:40 +0100 Subject: [PATCH 248/434] 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 e79f2cc27d5eaa4e737c2ccd5ad2ce8f81b3cb5b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:08:46 +0100 Subject: [PATCH 249/434] 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 5510c0586be9adc23bff87b22a6c9704b3616f76 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:22:14 +0100 Subject: [PATCH 250/434] 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 6b4a19d50052eb48442dc74d05c4fd4376efaf92 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:22:31 +0100 Subject: [PATCH 251/434] 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 5a5d6ce2323b0666ab4b71f67a98de7f8324b266 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:27:27 +0100 Subject: [PATCH 252/434] 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 8245d65b833d1c49c9dd7500802096ae8b0c0091 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:35:02 +0100 Subject: [PATCH 253/434] 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 641e87126678311ff775f44bf7f01dac43144f69 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 10:39:09 +0100 Subject: [PATCH 254/434] 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 0ec32a8567f22df04c1c6dd9c3c6aa261dcf057a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 11:30:28 +0100 Subject: [PATCH 255/434] 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 c01413b30e901a1b1365b9cf63bdddff4797725a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 11:32:15 +0100 Subject: [PATCH 256/434] 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 b3585b014a09751ea425787e93ac335ded4f1308 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 11:32:28 +0100 Subject: [PATCH 257/434] 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 f6d151b1780dd45897cd5465053e3c86d7b5ca94 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:04:03 +0100 Subject: [PATCH 258/434] 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 598d96daaa392ad76ff7222a21f3f215d054e77c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:41:46 +0100 Subject: [PATCH 259/434] 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 2e174b44f8abee9fc808304b13216c3f44a0ed0b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:42:58 +0100 Subject: [PATCH 260/434] 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 daa8a1c4a017fbe0b6b26fe02003dc7ff0f90691 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 25 Oct 2024 13:46:42 +0100 Subject: [PATCH 261/434] 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 8b999ddfdc6fa673b23f296825f46e609f050711 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 30 Oct 2024 11:25:45 +0000 Subject: [PATCH 262/434] 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 13295666b2063ec2e25d44b8007b4ce973ec8cb7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 10:21:22 +0000 Subject: [PATCH 263/434] 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 6917bc515e016933d6cac06b65d9d8e9fe7c4b8e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 10:55:14 +0000 Subject: [PATCH 264/434] 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 2764215d86c21035644d18ea5442c3e67e3e4abe Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:46:14 +0000 Subject: [PATCH 265/434] 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 972085c145b5022fdeefd2a328b368d7342395f3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:50:26 +0000 Subject: [PATCH 266/434] 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 be754d230eb10b82ed9c51f7f5917c7d489b44fc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:52:15 +0000 Subject: [PATCH 267/434] 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 dbe0a0468f632dddbbf9113b4f95e82245883dd6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 11:53:45 +0000 Subject: [PATCH 268/434] 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 1f1a2ee960961bb8c6390fd14fc6ee218b1c82ce Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 1 Nov 2024 13:52:31 +0000 Subject: [PATCH 269/434] 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 a5de73e8440ee91bf9f11397063a9518a845d5b4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 6 Nov 2024 14:07:29 +0000 Subject: [PATCH 270/434] 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 de086fb6d674e5eb299fce71955a8d076298c9df Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 18 Nov 2024 09:12:48 +0000 Subject: [PATCH 271/434] 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 4706dfbd39fd78cf7a6ebed92da0ce4a74d8edd3 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:26:52 +0000 Subject: [PATCH 272/434] 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 91ced7b213d7338cc8ba5f1615d224d20c092f2a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:27:17 +0000 Subject: [PATCH 273/434] 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 162ee2a9bc2a562ed02b6bd1c062d55c44b4960f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:28:39 +0000 Subject: [PATCH 274/434] 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 fffac5711d9d47e7a26b3a67efc66278670c728f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:56:19 +0000 Subject: [PATCH 275/434] 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 8f379b9f5e628edbe10f33f889080d45755f0fcd Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 10:58:22 +0000 Subject: [PATCH 276/434] 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 2763af071e2da4d02eeffdc6b74f1f60072d0cc1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:02:02 +0000 Subject: [PATCH 277/434] 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 88e29859c88b98d65f879fd52c364302e633f981 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:02:18 +0000 Subject: [PATCH 278/434] 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 6a590193129f49da9e0439ba3d0b691e0c810f53 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:16:37 +0000 Subject: [PATCH 279/434] 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 621b3306f38d59b0c2f1989c4294bdd0c2535bd7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:17:42 +0000 Subject: [PATCH 280/434] 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 3c509bea77ee142bb875d9b3dc3a9bb2b6f5ef65 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:35:24 +0000 Subject: [PATCH 281/434] 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 2d37744f24dc67198c0c7fdb3f83b8e997c26a0a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:38:09 +0000 Subject: [PATCH 282/434] 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 494040df6b85bea819ed5faf3086ccef2b811425 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 21 Nov 2024 11:39:45 +0000 Subject: [PATCH 283/434] 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 84ca5c9daf04b443e63813a8a0e80e5e348ba071 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 08:55:28 +0000 Subject: [PATCH 284/434] 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 c2a4481947bb639a3639c941f39d997eb35247ed Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:00:26 +0000 Subject: [PATCH 285/434] 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 60f6c3fc2318d788f8b81bcd0dc89912e488aa0b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:10:34 +0000 Subject: [PATCH 286/434] 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 974006306aab961fcf0ea123e52c7ea69b6d2e4f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:13:21 +0000 Subject: [PATCH 287/434] 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 5d7c3409e162945875150ded41a052804480039c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:14:53 +0000 Subject: [PATCH 288/434] 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 ca469460ba49a2adcaf9c238442a6adf9112e90d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:38:46 +0000 Subject: [PATCH 289/434] 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 be5de49ea4cfdc5d40ca0a8f24366923ae27196a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 22 Nov 2024 09:49:49 +0000 Subject: [PATCH 290/434] 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 24b9a41fa9b99880656977f75a030ca9427939bb Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 09:09:14 +0000 Subject: [PATCH 291/434] 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 a95c46ddeba951d948c6e8e88174761b1ae94cc6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 09:10:26 +0000 Subject: [PATCH 292/434] 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 9a19d022c61598a3b193be5d802576d30f6a0e68 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 11:02:25 +0000 Subject: [PATCH 293/434] 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 9ff4d5aee414cf76ccb920068c7520fc974ecd1a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 11:46:42 +0000 Subject: [PATCH 294/434] 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 09e07593d68ad709510c866d72d685f5084462e2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 25 Nov 2024 13:59:38 +0000 Subject: [PATCH 295/434] 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 9f4d0de27db3e825f524c3b434e832a0d5578222 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 26 Nov 2024 10:27:18 +0000 Subject: [PATCH 296/434] 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 f6e421480ab73552fce5c1d90096e9126085ff9f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 27 Nov 2024 10:31:33 +0000 Subject: [PATCH 297/434] 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 256d12425bc94bc9e1617fd24f50617c48c362bf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 28 Nov 2024 09:03:53 +0000 Subject: [PATCH 298/434] 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 4cc3a55cd995ece2559d8a270cdab7e54bb77dd8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 28 Nov 2024 11:01:25 +0000 Subject: [PATCH 299/434] 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 ff2a1709bdfff7343db7394d55e3461b6d213d81 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 28 Nov 2024 11:05:05 +0000 Subject: [PATCH 300/434] 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 15688672399e2afa24137324f3c1e3481e8e5625 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 09:23:26 +0000 Subject: [PATCH 301/434] 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 13d82b7b10ced155dadd50f76684661267091970 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 09:24:25 +0000 Subject: [PATCH 302/434] 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 17826bd4fd636ef915443336bafda8658a47e946 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 09:26:02 +0000 Subject: [PATCH 303/434] 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 3d0d9d2704571e20913b0465fa576c60f828f763 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 10:09:29 +0000 Subject: [PATCH 304/434] 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 2dd581e1a9ea63b0d60f29c7ca2a3a2270c9fb95 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 10:18:13 +0000 Subject: [PATCH 305/434] 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 67be830ac0dbe528163e2bacc60d37092940085a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 10:19:41 +0000 Subject: [PATCH 306/434] 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 a8d667001a6028b135e069ebdfd51c686738030d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:01:26 +0000 Subject: [PATCH 307/434] 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 9acab294bfb75db154c804b5e26adde23e985570 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:02:06 +0000 Subject: [PATCH 308/434] 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 f67e460c7569fccb6ce9b385b0561986942b232d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:23:58 +0000 Subject: [PATCH 309/434] 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 0214d8fee75f6fc722e351842ec00225ceff37ef Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 11:54:37 +0000 Subject: [PATCH 310/434] 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 a1eb9f49f30d68abcc7f669973018b78eae57b18 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 13:38:04 +0000 Subject: [PATCH 311/434] 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 1a98295fffb64868540331057c966ada1e6e11d6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 13:44:30 +0000 Subject: [PATCH 312/434] 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 2b58b9f4bd7622df83e229d2a480e6a29d6441b7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 13:46:07 +0000 Subject: [PATCH 313/434] 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 dc7ed0aba12cd41f342f0359faefa4331f124413 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 15:01:08 +0000 Subject: [PATCH 314/434] 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 2a8dea33abcfb316f80a89869cd455dcc32ce0c7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 15:05:11 +0000 Subject: [PATCH 315/434] 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 51de209ed7acf23b5f482f2f8b9e7d039b70f161 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 2 Dec 2024 15:13:25 +0000 Subject: [PATCH 316/434] 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 7e48111ba8bb4e0fafe137f080a62ca2b13ba003 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:39:46 +0000 Subject: [PATCH 317/434] 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 663b6ae0b4f68ab03f0a1ebcbc30531a2573ec7d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:41:36 +0000 Subject: [PATCH 318/434] 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 44e6a4faacbd2c038bb58ad7a0b83bb7189e9291 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:45:08 +0000 Subject: [PATCH 319/434] 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 559c4de82f4bcf45cea040d9ac145bbd01ddea00 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 09:48:31 +0000 Subject: [PATCH 320/434] 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 312e74df4f94a8b000181c6033aac4506e70f988 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:06:36 +0000 Subject: [PATCH 321/434] 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 6d5c3ade6c9d2d1d53eb13c43816fcf08d11fb8d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:19:15 +0000 Subject: [PATCH 322/434] 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 f9da54e30a08042d2db5f87e74b98368f235a57e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:20:25 +0000 Subject: [PATCH 323/434] 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 0843a99a46475144f069733d4d8f46e87a9fa8e8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:31:13 +0000 Subject: [PATCH 324/434] 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 7a704ce62a1a45e46dadf851a7f3757db723103b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:33:39 +0000 Subject: [PATCH 325/434] 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 f74aa3d7d64e29ed91e614a9eb4a08bd035239e9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:52:30 +0000 Subject: [PATCH 326/434] 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 a7d6664be20cbaf6aa80fbb59fb25bbcfdc4619b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:53:00 +0000 Subject: [PATCH 327/434] 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 e7cbe2cbdeaa9d67802a081021dcd91013a90bd6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:53:13 +0000 Subject: [PATCH 328/434] 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 5fa54f6ac8f9fa8e3b7ef40e2ce880d1ad5cd68c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:57:14 +0000 Subject: [PATCH 329/434] 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 abf84e1d9d08aee067a9f2f77ab2c239fb1103ab Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 10:59:14 +0000 Subject: [PATCH 330/434] 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 fc07e311cd30a58172dc90e46b1f4f192a410811 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:19:39 +0000 Subject: [PATCH 331/434] 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 a4496b6dcf86b36664bbd03d78f83153a1056553 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:19:51 +0000 Subject: [PATCH 332/434] 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 5da7ceff486ac88c70d1917e667aea07f87b991a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:21:23 +0000 Subject: [PATCH 333/434] 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 9b8e4fb273c01415f1ecd4997211a8f9edce9e1a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:23:07 +0000 Subject: [PATCH 334/434] 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 3b55c2ab5492bbe6d75a11da56f51a8e9a9a2dae Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:27:28 +0000 Subject: [PATCH 335/434] 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 5870223f1b997ccc0df3f09e2ab7de9ebd8dd164 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 3 Dec 2024 11:29:07 +0000 Subject: [PATCH 336/434] 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 350e8131eb77786203620fcca255fbb45d64ea18 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:06:31 +0000 Subject: [PATCH 337/434] 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 4d8fd9ab3d09de3f381f335d575404d55b6d52a9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:20:19 +0000 Subject: [PATCH 338/434] 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 d505517bc9f89c2a189d822fbfb91f0f9b2d2915 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:21:22 +0000 Subject: [PATCH 339/434] 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 51dda75850e36c27a7ecc36a01351d7c66ab3e84 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:21:52 +0000 Subject: [PATCH 340/434] 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 723e886b2796ce49564181a4f666603655d3bc81 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:26:29 +0000 Subject: [PATCH 341/434] 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 bf25ae3f07923ba8446f4b68d9b60ea3641d4772 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:36:08 +0000 Subject: [PATCH 342/434] 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 ea6765a5c02583b184182c74f46be358c2af552b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 09:53:12 +0000 Subject: [PATCH 343/434] 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 d00cb5a10d56265becf45b283778449da6fd1cf7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 4 Dec 2024 15:17:04 +0000 Subject: [PATCH 344/434] 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 58b60b7997d148d3e00f57f4cdbdf63b670eed80 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 6 Dec 2024 09:31:48 +0000 Subject: [PATCH 345/434] 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 033d69f6873985a8e3dd69c38fe61f8711cdab1b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 09:50:56 +0000 Subject: [PATCH 346/434] 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 a0a9784eb62186fd460b2026fce7ab7322b1abdf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 09:52:05 +0000 Subject: [PATCH 347/434] 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 0014226d4d79fab1262c99d035d732675c2697ad Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:17:11 +0000 Subject: [PATCH 348/434] 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 83f30e5ea1b22126a008d93f37773342b199c685 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:45:12 +0000 Subject: [PATCH 349/434] 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 3239f18d3eb0afb54cb28f68dc8275fd2f41fb82 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:55:01 +0000 Subject: [PATCH 350/434] 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 65e3e4a863c0fd9a1d5039a0de4a74eb47f6f4c5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:57:04 +0000 Subject: [PATCH 351/434] 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 2086a449af373e2664ac7b3044a72af2b9e325fc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 13:57:42 +0000 Subject: [PATCH 352/434] 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 57667182fe6c5a016b7322e20551fb62c3c69fe7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 14:43:15 +0000 Subject: [PATCH 353/434] 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 1319bf090732e706479988344641ad07263b9cc7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 9 Dec 2024 15:03:35 +0000 Subject: [PATCH 354/434] 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 cee9027be56ea7f59c444de2baae0588bfce9411 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 08:51:34 +0000 Subject: [PATCH 355/434] 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 fd3953ee6c5f6f7e7989bfcfae1e525bc144c1d6 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 08:53:07 +0000 Subject: [PATCH 356/434] 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 a3870e2ca28922d6a6a7982d233edb022178f1de Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 11:41:20 +0000 Subject: [PATCH 357/434] 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 f74f96da33d5cd452c02d17cacff4eedde6ef690 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 11:55:04 +0000 Subject: [PATCH 358/434] 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 5ff75337c65c9c07e14543d14e53535eccafbc9b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 12:13:58 +0000 Subject: [PATCH 359/434] 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 20516e80e455c38bed7fd1e3b5fa33876bfca3ef Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 10 Dec 2024 12:35:23 +0000 Subject: [PATCH 360/434] 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 76e378bb752c5cacefc67368a8446df2d91de5e4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 09:37:46 +0000 Subject: [PATCH 361/434] 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 133ffb05776f9303c947985f3c84d994404a4830 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 09:49:30 +0000 Subject: [PATCH 362/434] 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 6fcfdeeddbd4324794f26bd0a3125de9f582a886 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 09:52:08 +0000 Subject: [PATCH 363/434] 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 bbeb2da2377f61df2e96467007c46a8f8e15f9b5 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 10:15:57 +0000 Subject: [PATCH 364/434] 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 255cbc9ea2083cb6b6158643330ee13db666a3af Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 10:38:07 +0000 Subject: [PATCH 365/434] 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 7cc1530bb5df8f2a5a7b9362da20e8b7bf7f2978 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 10:40:45 +0000 Subject: [PATCH 366/434] 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 8cd3b918bb8de5574f6b3ccc5dc8ea49e01fdf22 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 11:19:52 +0000 Subject: [PATCH 367/434] 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 378e791eea717a2a6670e5b40668ccf7c9b29cb0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 11:20:01 +0000 Subject: [PATCH 368/434] 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 e6d713b8d333ecf71512379d256551690cfbf7f9 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 11:27:44 +0000 Subject: [PATCH 369/434] 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 268ae4343fbac148cd79e8c2f833b5c90565cdaf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:10:38 +0000 Subject: [PATCH 370/434] 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 c36b2fe8bdfc0f77eb694a3cf1353f3cd6d6eee1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:15:43 +0000 Subject: [PATCH 371/434] 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 91dbcb56e2e8f04a29128d9bc7c7a70dfef5ac1c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:17:28 +0000 Subject: [PATCH 372/434] 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 4f49df23070806a869532d30c7a205eebc4973a0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:20:42 +0000 Subject: [PATCH 373/434] 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 dfbe18cd260b9576513799c65bef0d7fcd86541e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 11 Dec 2024 15:30:42 +0000 Subject: [PATCH 374/434] 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 e78c89da593049f90fbfecaad43d3c59dd681f7f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 08:23:18 +0000 Subject: [PATCH 375/434] 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 3b50828ca28b4a9db07135a60ffaf2b117e8c82d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 08:26:19 +0000 Subject: [PATCH 376/434] 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 308fd0e7921ce16d7ca461dbec41edd11d36dc9c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 08:30:12 +0000 Subject: [PATCH 377/434] 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 3450780c639e9347723124d05ee4d8d8001df03c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 09:03:21 +0000 Subject: [PATCH 378/434] 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 8cb16c501f6004914b2879211947dd17239cbd5c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 09:46:04 +0000 Subject: [PATCH 379/434] 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 f9857c4711466b357014b12bf5226d132c850317 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 10:06:02 +0000 Subject: [PATCH 380/434] 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 650b05f9861ddeb5594d181ec868828cf33ba612 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 10:51:19 +0000 Subject: [PATCH 381/434] 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 f8fa870d857175637bb592e8229333d3e87fc100 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 12:05:01 +0000 Subject: [PATCH 382/434] 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 b620a273adce3a132721732b3741a322e3a6e957 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 13:14:57 +0000 Subject: [PATCH 383/434] 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 6ff1421e13318e4733e184f08854e7f4cd6970f0 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 12 Dec 2024 13:27:07 +0000 Subject: [PATCH 384/434] 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 333ca8f8636fb8600c814cd0b83a287b134c4740 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 13 Dec 2024 09:13:31 +0000 Subject: [PATCH 385/434] 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 0eee5bfdabdc48f56f27c58d52643ec2a7ca3a5b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 13 Dec 2024 15:04:02 +0000 Subject: [PATCH 386/434] 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 c4e3ee656b7a332f9d5100216e07b0663a78f6b9 Mon Sep 17 00:00:00 2001 From: lucas-wilkins Date: Tue, 30 Jul 2024 10:40:34 +0100 Subject: [PATCH 387/434] Add dev menu --- src/sas/qtgui/MainWindow/GuiManager.py | 14 +++++++++++ src/sas/qtgui/MainWindow/UI/MainWindowUI.ui | 25 +++++++++++++++++++ .../ParticleEditor/DesignWindow.py | 7 ++++++ src/sas/system/config/config.py | 5 ++-- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py index 961ec9b358..11bbfe0586 100644 --- a/src/sas/qtgui/MainWindow/GuiManager.py +++ b/src/sas/qtgui/MainWindow/GuiManager.py @@ -783,6 +783,10 @@ def addTriggers(self): self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome) self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update) self._workspace.actionWhat_s_New.triggered.connect(self.actionWhatsNew) + # Dev + self._workspace.actionParticle_Editor.triggered.connect(self.particleEditor) + self._workspace.actionAscii_Loader.triggered.connect(self.asciiLoader) + self.communicate.sendDataToGridSignal.connect(self.showBatchOutput) self.communicate.resultPlotUpdateSignal.connect(self.showFitResults) @@ -1440,3 +1444,13 @@ def resetProject(self): # file manager self.filesWidget.reset() + + # ============= DEV ================= + + def particleEditor(self): + from sas.qtgui.Perspectives.ParticleEditor.DesignWindow import show_particle_editor + show_particle_editor() + + + def asciiLoader(self): + pass diff --git a/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui b/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui index 65a8656e34..04c12aad30 100755 --- a/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui +++ b/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui @@ -167,6 +167,15 @@ + + + Dev + + + + + + @@ -175,6 +184,7 @@ + @@ -652,6 +662,21 @@ Close Project + + + Ascii Loader + + + + + Particle Editor + + + + + Dev Tools + + diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py b/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py index 21d05636f0..2716ff4a20 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py @@ -397,6 +397,13 @@ def qSampling(self) -> QSample: return QSample(min_q, max_q, n_samples, is_log) +particle_editor_window = None +def show_particle_editor(): + global particle_editor_window + + particle_editor_window = DesignWindow() + particle_editor_window.show() + def main(): """ Demo/testing window""" diff --git a/src/sas/system/config/config.py b/src/sas/system/config/config.py index d264f05b54..700b948c1c 100644 --- a/src/sas/system/config/config.py +++ b/src/sas/system/config/config.py @@ -162,8 +162,6 @@ def __init__(self): self.SHOW_WELCOME_PANEL = False - - # OpenCL option - should be a string, either, "none", a number, or pair of form "A:B" self.SAS_OPENCL = "none" @@ -216,6 +214,9 @@ def __init__(self): # Stack plots when using slicers # If true, plots generated when using slicers will be on the same canvas self.STACK_PLOTS = True + + # Developer menu + self.DEV_MENU = False # # Lock the class down, this is necessary both for From cce2198b406b2176c9d3a9917bd58527acf750b0 Mon Sep 17 00:00:00 2001 From: lucas-wilkins Date: Tue, 30 Jul 2024 10:52:43 +0100 Subject: [PATCH 388/434] Make menu show when config setting made --- src/sas/qtgui/MainWindow/GuiManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py index 11bbfe0586..37cf6ecc82 100644 --- a/src/sas/qtgui/MainWindow/GuiManager.py +++ b/src/sas/qtgui/MainWindow/GuiManager.py @@ -784,6 +784,7 @@ def addTriggers(self): self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update) self._workspace.actionWhat_s_New.triggered.connect(self.actionWhatsNew) # Dev + self._workspace.menuDev.menuAction().setVisible(config.DEV_MENU) self._workspace.actionParticle_Editor.triggered.connect(self.particleEditor) self._workspace.actionAscii_Loader.triggered.connect(self.asciiLoader) From f739db162473185ff1481c6c691cae362dd12cdf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:49:33 +0000 Subject: [PATCH 389/434] 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 d104f2c0e2e0e0f887f26ef595698047080298ec Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:52:26 +0000 Subject: [PATCH 390/434] 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 521c0766db222f768ecc8fbd8b3a2906a555b820 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:53:54 +0000 Subject: [PATCH 391/434] 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 a527bbe1a27333370e7d2d8ada7aea3a3a2ba982 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 18 Dec 2024 11:54:07 +0000 Subject: [PATCH 392/434] 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 620e01f886ec20d598fb61134185ba0536c1530e Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 09:50:34 +0100 Subject: [PATCH 393/434] 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 91a2b42ff3651c1497591aa7c190937c10d9dcde Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 09:50:45 +0100 Subject: [PATCH 394/434] 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 d8beb425865c20a5d382fe5fa91cf6e066c17179 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 10:02:00 +0100 Subject: [PATCH 395/434] 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 4f1c916231717b8a957a3641036d989e2b53d8af Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 8 Jan 2025 10:14:35 +0100 Subject: [PATCH 396/434] 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 fade71b410daa8217b730d514ab7e8dce7ea92f4 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 15 Jan 2025 14:23:25 +0000 Subject: [PATCH 397/434] Add logic from starting the ascii reader from dev. --- src/sas/qtgui/MainWindow/GuiManager.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py index 37cf6ecc82..5b52029007 100644 --- a/src/sas/qtgui/MainWindow/GuiManager.py +++ b/src/sas/qtgui/MainWindow/GuiManager.py @@ -8,7 +8,13 @@ from packaging.version import Version from PySide6.QtCore import QLocale, Qt from PySide6.QtGui import QStandardItem -from PySide6.QtWidgets import QDockWidget, QLabel, QMessageBox, QProgressBar, QTextBrowser +from PySide6.QtWidgets import QDockWidget, QLabel, QProgressBar, QTextBrowser + +from sasdata.temp_ascii_reader import load_data + +import sas.system.version +from sas.system.version import __version__ as SASVIEW_VERSION, __release_date__ as SASVIEW_RELEASE_DATE + from twisted.internet import reactor import sas @@ -1454,4 +1460,12 @@ def particleEditor(self): def asciiLoader(self): - pass + 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 5d34467439af377569379da18e57e6334cb272a1 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 15 Jan 2025 14:32:40 +0000 Subject: [PATCH 398/434] 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): From df4ce6fc454fd85bc205a2baa7d9f331481385b7 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 09:25:18 +0000 Subject: [PATCH 399/434] Got rid of filename label, and move edit metadata. --- src/ascii_dialog/dialog.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index fd7e0a56d1..0c4487840a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -46,16 +46,10 @@ def __init__(self): # 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.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) - self.filename_unload_layout.addWidget(self.editMetadataButton) # Filename chooser self.filename_chooser = QComboBox() @@ -128,8 +122,14 @@ def __init__(self): # Done button # TODO: Not entirely sure what to call/label this. Just going with 'done' for now. + self.done_line = QHBoxLayout() + self.editMetadataButton = QPushButton("Edit Metadata") + self.editMetadataButton.setDisabled(True) + self.editMetadataButton.clicked.connect(self.editMetadata) self.done_button = QPushButton('Done') self.done_button.clicked.connect(self.onDoneButton) + self.done_line.addWidget(self.done_button) + self.done_line.addWidget(self.editMetadataButton) self.layout = QVBoxLayout(self) @@ -143,7 +143,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) + self.layout.addLayout(self.done_line) @property def starting_pos(self) -> int: @@ -284,7 +284,6 @@ def load_file(self) -> None: for filename in filenames: basename = path.basename(filename) - self.filename_label.setText(basename) try: with open(filename) as file: @@ -357,10 +356,8 @@ 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.filename_label.setText(NOFILE_TEXT) self.table.setDisabled(True) self.unloadButton.setDisabled(True) self.editMetadataButton.setDisabled(True) From 5a1506c5ddd52e4769b05b133fb047419cfa3b44 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 09:25:57 +0000 Subject: [PATCH 400/434] Rename load file to select file. --- 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 0c4487840a..ad3b37589c 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -55,8 +55,8 @@ def __init__(self): self.filename_chooser = QComboBox() self.filename_chooser.currentTextChanged.connect(self.updateCurrentFile) - self.load_button = QPushButton("Load File") - self.load_button.clicked.connect(self.load_file) + self.select_button = QPushButton("Select File") + self.select_button.clicked.connect(self.load_file) ## Dataset type selection self.dataset_layout = QHBoxLayout() @@ -135,7 +135,7 @@ def __init__(self): self.layout.addLayout(self.filename_unload_layout) self.layout.addWidget(self.filename_chooser) - self.layout.addWidget(self.load_button) + self.layout.addWidget(self.select_button) self.layout.addLayout(self.dataset_layout) self.layout.addLayout(self.sep_layout) self.layout.addLayout(self.startline_layout) From 603ad770375013620cec46e279f062536193166d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 09:30:04 +0000 Subject: [PATCH 401/434] Add a cancel 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 ad3b37589c..d85d23408d 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -128,8 +128,11 @@ def __init__(self): self.editMetadataButton.clicked.connect(self.editMetadata) self.done_button = QPushButton('Done') self.done_button.clicked.connect(self.onDoneButton) + self.cancel_button = QPushButton('Cancel') + self.cancel_button.clicked.connect(self.onCancel) self.done_line.addWidget(self.done_button) self.done_line.addWidget(self.editMetadataButton) + self.done_line.addWidget(self.cancel_button) self.layout = QVBoxLayout(self) @@ -455,6 +458,9 @@ def onDoneButton(self): self.params = params self.accept() + def onCancel(self): + self.reject() + def editMetadata(self): dialog = MetadataFilenameDialog(self.current_filename, self.internal_metadata) status = dialog.exec() From 67a7c1d63963636feb0fed319653fc4ea4d861dc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 09:46:48 +0000 Subject: [PATCH 402/434] Disbale units when column is ignored. --- src/ascii_dialog/column_unit.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index d1dbd2a773..ab3bc99960 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -54,11 +54,15 @@ def updateUnits(self, unit_box: QComboBox, selected_option: str): unit_box.clear() self.current_option = selected_option # 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') + if self.current_option == '': + unit_box.setDisabled(True) + else: + unit_box.setDisabled(False) + 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') def replaceOptions(self, new_options) -> None: From 9765f47a49e97b5c33e2e1088f33b85f2270a5ca Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 10:26:40 +0000 Subject: [PATCH 403/434] Centre the checkbox and remove label. --- src/ascii_dialog/row_status_widget.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ascii_dialog/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index eade62b67f..892fc1d84e 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -1,29 +1,32 @@ #!/usr/bin/env python3 -from PySide6.QtCore import Signal, Slot -from PySide6.QtWidgets import QCheckBox +from PySide6.QtCore import Signal, Slot, Qt +from PySide6.QtWidgets import QCheckBox, QHBoxLayout, QWidget -class RowStatusWidget(QCheckBox): +class RowStatusWidget(QWidget): """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.checkbox = QCheckBox() + self.checkbox.setChecked(initial_value) self.updateLabel() - self.stateChanged.connect(self.onStateChange) + self.checkbox.stateChanged.connect(self.onStateChange) + self.layout = QHBoxLayout(self) + self.layout.addWidget(self.checkbox, alignment=Qt.AlignmentFlag.AlignCenter) status_changed = Signal(int) def updateLabel(self): """Update the label of the check box depending on whether it is checked, or not.""" - if self.isChecked(): - self.setText('Included') - else: - self.setText('Not Included') + pass @Slot() def onStateChange(self): self.updateLabel() self.status_changed.emit(self.row) + + def isChecked(self): + return self.checkbox.isChecked From af0a3ef1f2a342d0cdac4e554b6c562221c0b2af Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 10:59:03 +0000 Subject: [PATCH 404/434] Try this for clearer. --- 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 a5eeac96a5..5a2fd2c733 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -39,7 +39,7 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str 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.setText(f'There are columns which are repeated.') self.setFontRed() elif unparsable > 0: # FIXME: This error message could perhaps be a bit clearer. From 4c528f671c4d37dbbdba817d5d2d261175e8aa72 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 11:02:08 +0000 Subject: [PATCH 405/434] Maybe lets just empty the message. --- src/ascii_dialog/warning_label.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 5a2fd2c733..07b08d6066 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -46,8 +46,7 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str self.setText(f'{unparsable} lines cannot be parsed. They will be ignored.') self.setFontOrange() else: - self.setText('All is fine') # TODO: Probably want to find a more appropriate message. - self.setFontNormal() + self.setText('') def __init__(self, initial_missing_columns, initial_duplicate_classes): super().__init__() From 79c8b22ceaed091491fa47d87497b05a5157f1bf Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 11:04:04 +0000 Subject: [PATCH 406/434] Implement set checked method as well. --- 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 892fc1d84e..baa48aa911 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -30,3 +30,6 @@ def onStateChange(self): def isChecked(self): return self.checkbox.isChecked + + def setChecked(self, new_value: bool): + self.checkbox.setChecked(new_value) From 29a8d3beda0b64c5ab6091240bc31988115d4b00 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 11:15:30 +0000 Subject: [PATCH 407/434] Didn't call method properly. --- 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 baa48aa911..4e3921e5ee 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -29,7 +29,7 @@ def onStateChange(self): self.status_changed.emit(self.row) def isChecked(self): - return self.checkbox.isChecked + return self.checkbox.isChecked() def setChecked(self, new_value: bool): self.checkbox.setChecked(new_value) From c1346ffb6afa1b6aa8954149c6aa3baefb268758 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 11:15:48 +0000 Subject: [PATCH 408/434] Add type hinting here. --- 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 4e3921e5ee..33f9a16de8 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -28,7 +28,7 @@ def onStateChange(self): self.updateLabel() self.status_changed.emit(self.row) - def isChecked(self): + def isChecked(self) -> bool: return self.checkbox.isChecked() def setChecked(self, new_value: bool): From f1189ea37b273cb55e426344c3969b97a2bf928d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 15:38:54 +0000 Subject: [PATCH 409/434] Try this for resetting the foreground color. --- 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 d85d23408d..f29e12ab2a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,4 +1,4 @@ -from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, Qt +from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, QPalette 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 @@ -263,7 +263,7 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: item.setForeground(QColor.fromString('grey')) item_font.setStrikeOut(True) else: - item.setForeground(QColor.fromString('black')) + item.setForeground(QApplication.palette().text()) item_font.setStrikeOut(False) item.setFont(item_font) From dec8dc7ee6681163185fd44566a6f45e18d928fc Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 15:41:52 +0000 Subject: [PATCH 410/434] Fixed 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 f29e12ab2a..c8dc576c23 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,4 +1,4 @@ -from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, QPalette +from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, QPalette, Qt 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 7e2bfa4d99829a648313a4ebc5e2831a8764053a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Tue, 28 Jan 2025 16:15:32 +0000 Subject: [PATCH 411/434] Use Lucas' mode idea for the column count. --- src/ascii_dialog/guess.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 50fad24658..5835653d6f 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -1,8 +1,10 @@ from sasdata.dataset_types import DatasetType +from scipy.stats import mode 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]) + candidate_lines = split_csv[starting_pos::] + return int(mode([len(line) for line in candidate_lines]).mode) 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 From 557dc0049e36b0ef89b971b0a162f0833eb8fbbe Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 29 Jan 2025 11:20:27 +0000 Subject: [PATCH 412/434] Put unload, and the filename on same line. --- 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 c8dc576c23..aaf3294a97 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -49,12 +49,14 @@ def __init__(self): self.unloadButton = QPushButton("Unload") self.unloadButton.setDisabled(True) self.unloadButton.clicked.connect(self.unload) - self.filename_unload_layout.addWidget(self.unloadButton) - # Filename chooser self.filename_chooser = QComboBox() self.filename_chooser.currentTextChanged.connect(self.updateCurrentFile) + self.filename_unload_layout.addWidget(self.filename_chooser) + self.filename_unload_layout.addWidget(self.unloadButton) + + self.select_button = QPushButton("Select File") self.select_button.clicked.connect(self.load_file) @@ -137,7 +139,6 @@ def __init__(self): self.layout = QVBoxLayout(self) self.layout.addLayout(self.filename_unload_layout) - self.layout.addWidget(self.filename_chooser) self.layout.addWidget(self.select_button) self.layout.addLayout(self.dataset_layout) self.layout.addLayout(self.sep_layout) From b32509bea99a20b17ace6d22fc006ce9d5cb46ff Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 30 Jan 2025 10:21:49 +0000 Subject: [PATCH 413/434] Changed the bottom row. --- src/ascii_dialog/dialog.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index aaf3294a97..d8f67a7cad 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,6 +1,6 @@ from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, QPalette, Qt from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, \ - QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication, QDialog + QMessageBox, QPushButton, QSpacerItem, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication, QDialog from PySide6.QtCore import QModelIndex, QPoint, Slot from ascii_dialog.selection_menu import SelectionMenu from ascii_dialog.warning_label import WarningLabel @@ -125,16 +125,18 @@ def __init__(self): # TODO: Not entirely sure what to call/label this. Just going with 'done' for now. self.done_line = QHBoxLayout() + self.cancel_button = QPushButton('Cancel') + self.cancel_button.clicked.connect(self.onCancel) + self.done_line_spacer = QSpacerItem(70, 0) self.editMetadataButton = QPushButton("Edit Metadata") self.editMetadataButton.setDisabled(True) self.editMetadataButton.clicked.connect(self.editMetadata) self.done_button = QPushButton('Done') self.done_button.clicked.connect(self.onDoneButton) - self.cancel_button = QPushButton('Cancel') - self.cancel_button.clicked.connect(self.onCancel) - self.done_line.addWidget(self.done_button) - self.done_line.addWidget(self.editMetadataButton) self.done_line.addWidget(self.cancel_button) + self.done_line.addItem(self.done_line_spacer) + self.done_line.addWidget(self.editMetadataButton) + self.done_line.addWidget(self.done_button) self.layout = QVBoxLayout(self) From 8e14c285224b29c1b0a89033192e516300309a88 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 30 Jan 2025 10:21:54 +0000 Subject: [PATCH 414/434] Fixed indentation mistake in guess. --- src/ascii_dialog/guess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index 5835653d6f..b1720ab6f3 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -32,6 +32,6 @@ def guess_starting_position(split_csv: list[list[str]]) -> int: if not column.replace('.', '').replace('-', '').isdigit(): all_nums = False break - if all_nums: - return i + if all_nums: + return i return 0 From 1fc3d3cbb1fdf8aa6596f1e2ba515e0fbba96dce Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 30 Jan 2025 10:33:34 +0000 Subject: [PATCH 415/434] Changed how symbols are ignored. --- src/ascii_dialog/guess.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py index b1720ab6f3..1fa0298fa6 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -19,6 +19,8 @@ def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: return dataset_type.expected_orders[-1] +symbols_to_ignore = ['.', '-', 'e', 'E'] + 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 exist, try to look for a line where the @@ -29,7 +31,10 @@ def guess_starting_position(split_csv: list[list[str]]) -> int: for i, row in enumerate(split_csv): all_nums = True for column in row: - if not column.replace('.', '').replace('-', '').isdigit(): + amended_column = column + for symbol in symbols_to_ignore: + amended_column = amended_column.replace(symbol, '') + if not amended_column.isdigit(): all_nums = False break if all_nums: From 0c9fdc3d88994a44ec52f0afd08518a392f3329b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 30 Jan 2025 10:34:44 +0000 Subject: [PATCH 416/434] Add pluses to the symbols. --- 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 1fa0298fa6..f067e369ae 100644 --- a/src/ascii_dialog/guess.py +++ b/src/ascii_dialog/guess.py @@ -19,7 +19,7 @@ def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: return dataset_type.expected_orders[-1] -symbols_to_ignore = ['.', '-', 'e', 'E'] +symbols_to_ignore = ['.', '-', '+', 'e', 'E'] 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 From 6507aabc73963d87559dab002c48c879b2ecc637 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 24 Feb 2025 13:51:50 +0000 Subject: [PATCH 417/434] SasData should keep the metadata categories. --- src/metadata_filename_gui/metadata_tree_data.py | 2 ++ src/metadata_filename_gui/metadata_tree_widget.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/metadata_filename_gui/metadata_tree_data.py b/src/metadata_filename_gui/metadata_tree_data.py index c2a5690f70..20e7d66755 100644 --- a/src/metadata_filename_gui/metadata_tree_data.py +++ b/src/metadata_filename_gui/metadata_tree_data.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +# TODO: This file can probably be deleted. Just want to make sure nothing else +# depends on it. metadata = { 'source': ['name', 'radiation', 'type', 'probe_particle', 'beam_size_name', 'beam_size', 'beam_shape', 'wavelength', 'wavelength_min', 'wavelength_max', 'wavelength_spread'], diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 55cae54c26..5b8329b0c6 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -3,7 +3,7 @@ from metadata_filename_gui.metadata_component_selector import MetadataComponentSelector from metadata_filename_gui.metadata_selector import MetadataSelector from metadata_filename_gui.metadata_tree_data import metadata as metadata_categories -from sasdata.ascii_reader_metadata import AsciiReaderMetadata +from sasdata.ascii_reader_metadata import AsciiReaderMetadata, initial_metadata class MetadataTreeWidget(QTreeWidget): def __init__(self, metadata: AsciiReaderMetadata): @@ -14,7 +14,7 @@ def __init__(self, metadata: AsciiReaderMetadata): def draw_tree(self, full_filename: str): self.clear() - for top_level, items in metadata_categories.items(): + for top_level, items in initial_metadata.items(): top_level_item = QTreeWidgetItem([top_level]) for metadatum in items: # selector = MetadataComponentSelector(metadatum, self.metadata_dict) From b37f8bbd22f332fa096e0de852c0f9e1bb8aa3f2 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 10 Feb 2025 08:41:02 +0000 Subject: [PATCH 418/434] Updated units. --- src/ascii_dialog/default_units.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ascii_dialog/default_units.py b/src/ascii_dialog/default_units.py index 540654a273..ce8e971674 100644 --- a/src/ascii_dialog/default_units.py +++ b/src/ascii_dialog/default_units.py @@ -4,9 +4,6 @@ # 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] + 'Q': [unit.per_nanometer, unit.per_angstrom, unit.per_meter], + 'I': [unit.per_centimeter, unit.per_meter] } - From d4c4386cd09600ec048b7959145f0db173a59b9d Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 10 Feb 2025 08:41:10 +0000 Subject: [PATCH 419/434] Remove this comment. --- src/ascii_dialog/default_units.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/default_units.py b/src/ascii_dialog/default_units.py index ce8e971674..402da0e300 100644 --- a/src/ascii_dialog/default_units.py +++ b/src/ascii_dialog/default_units.py @@ -2,7 +2,6 @@ import sasdata.quantities.units as unit -# Based on the email Jeff sent me./ default_units = { 'Q': [unit.per_nanometer, unit.per_angstrom, unit.per_meter], 'I': [unit.per_centimeter, unit.per_meter] From 2e8e9b66a04a055bf5cc6f5fe6c1d66d27159a31 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 10 Feb 2025 08:49:53 +0000 Subject: [PATCH 420/434] Remove fixme comment. I think I must have changed this as I don't see much code repetition. --- src/ascii_dialog/warning_label.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 07b08d6066..5e0e53a70e 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -21,8 +21,6 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str 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): # 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 From 2be801fa1133d5701a5de2f3d91a122ff7ac6597 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 10 Feb 2025 09:07:04 +0000 Subject: [PATCH 421/434] Try this? --- 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 5e0e53a70e..ba72c01f6e 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -41,7 +41,7 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str 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.setText(f'{unparsable} lines failed to be read. They will be ignored.') self.setFontOrange() else: self.setText('') From 8474a58cea1eba3c0a1c7ecfb237104f6e5ff904 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 10 Feb 2025 09:12:06 +0000 Subject: [PATCH 422/434] Making casing consistent. --- src/ascii_dialog/dialog.py | 54 +++++++++++++++---------------- src/ascii_dialog/warning_label.py | 4 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/ascii_dialog/dialog.py b/src/ascii_dialog/dialog.py index d8f67a7cad..16c927fb9a 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -58,7 +58,7 @@ def __init__(self): self.select_button = QPushButton("Select File") - self.select_button.clicked.connect(self.load_file) + self.select_button.clicked.connect(self.loadFile) ## Dataset type selection self.dataset_layout = QHBoxLayout() @@ -152,28 +152,28 @@ def __init__(self): self.layout.addLayout(self.done_line) @property - def starting_pos(self) -> int: + def startingPos(self) -> int: return self.startline_entry.value() - 1 - @starting_pos.setter - def starting_pos(self, value: int): + @startingPos.setter + def startingPos(self, value: int): self.startline_entry.setValue(value + 1) @property - def raw_csv(self) -> list[str] | None: + def rawCsv(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: + def rowsIsIncluded(self) -> list[bool] | None: if self.current_filename is 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 enumerate(self.rows_is_included) if not included]) + def excludedLines(self) -> set[int]: + return set([i for i, included in enumerate(self.rowsIsIncluded) if not included]) def splitLine(self, line: str) -> list[str]: """Split a line in a CSV file based on which seperators the user has @@ -187,7 +187,7 @@ def attemptGuesses(self) -> None: default values. Uses the guess.py module """ - split_csv = [self.splitLine(line.strip()) for line in self.raw_csv] + split_csv = [self.splitLine(line.strip()) for line in self.rawCsv] # 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) @@ -198,7 +198,7 @@ def attemptGuesses(self) -> None: columns = guess_columns(guessed_colcount, self.currentDatasetType()) self.col_editor.setColOrder(columns) self.colcount_entry.setValue(guessed_colcount) - self.starting_pos = self.initial_starting_pos + self.startingPos = self.initial_starting_pos def fillTable(self) -> None: """Write the data to the table based on the parameters the user has @@ -207,20 +207,20 @@ def fillTable(self) -> None: """ # Don't try to fill the table if there's no data. - if self.raw_csv is None: + if self.rawCsv is None: return self.table.clear() col_count = self.colcount_entry.value() - self.table.setRowCount(min(len(self.raw_csv), TABLE_MAX_ROWS + 1)) + self.table.setRowCount(min(len(self.rawCsv), TABLE_MAX_ROWS + 1)) self.table.setColumnCount(col_count + 1) self.table.setHorizontalHeaderLabels(["Included"] + self.col_editor.colNames()) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # Now fill the table with data - for i, row in enumerate(self.raw_csv): + for i, row in enumerate(self.rawCsv): if i == TABLE_MAX_ROWS: # Fill with elipsis to indicate there is more data. for j in range(len(row_split)): @@ -229,12 +229,12 @@ def fillTable(self) -> None: self.table.setItem(i, j, elipsis_item) break - if i < len(self.rows_is_included): - initial_state = self.rows_is_included[i] + if i < len(self.rowsIsIncluded): + initial_state = self.rowsIsIncluded[i] else: initial_state = True - self.rows_is_included.append(initial_state) - if i >= self.starting_pos: + self.rowsIsIncluded.append(initial_state) + if i >= self.startingPos: row_status = RowStatusWidget(initial_state, i) row_status.status_changed.connect(self.updateRowStatus) self.table.setCellWidget(i, 0, row_status) @@ -244,7 +244,7 @@ def fillTable(self) -> None: continue # Ignore rows that have extra columns. item = QTableWidgetItem(col_value) self.table.setItem(i, j + 1, item) - self.setRowTypesetting(i, self.rows_is_included[i]) + self.setRowTypesetting(i, self.rowsIsIncluded[i]) self.table.show() @@ -262,7 +262,7 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: if item is None: continue item_font = item.font() - if not item_checked or row < self.starting_pos: + if not item_checked or row < self.startingPos: item.setForeground(QColor.fromString('grey')) item_font.setStrikeOut(True) else: @@ -273,14 +273,14 @@ def setRowTypesetting(self, row: int, item_checked: bool) -> None: def updateWarningLabel(self): required_missing = self.requiredMissing() duplicates = self.duplicateColumns() - if self.raw_csv is None: + if self.rawCsv 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) + self.warning_label.updateWarning(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.starting_pos) + self.warning_label.updateWarning(required_missing, duplicates, [self.splitLine(line) for line in self.rawCsv], self.rowsIsIncluded, self.startingPos) @Slot() - def load_file(self) -> None: + def loadFile(self) -> None: """Open the file loading dialog, and load the file the user selects.""" filenames, result = QFileDialog.getOpenFileNames(self) # Happens when the user cancels without selecting a file. There isn't a @@ -398,7 +398,7 @@ def changeDatasetType(self) -> 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.rowsIsIncluded[row] = new_status self.setRowTypesetting(row, new_status) @Slot() @@ -414,7 +414,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.starting_pos: + if row < self.startingPos: continue self.table.cellWidget(row, 0).setChecked(new_value) self.updateRowStatus(row) @@ -454,8 +454,8 @@ def onDoneButton(self): list(self.files_full_path.values()), self.col_editor.columns, self.internal_metadata, - self.starting_pos, - self.excluded_lines, + self.startingPos, + self.excludedLines, self.seperators, ) self.params = params diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index ba72c01f6e..216cce307d 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -16,7 +16,7 @@ def setFontOrange(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, starting_pos: int = 0): + def updateWarning(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 @@ -48,4 +48,4 @@ def update_warning(self, missing_columns: list[str], duplicate_columns: list[str def __init__(self, initial_missing_columns, initial_duplicate_classes): super().__init__() - self.update_warning(initial_missing_columns, initial_duplicate_classes) + self.updateWarning(initial_missing_columns, initial_duplicate_classes) From 9ec2cce51afe8fbf46e28dd250f1e605febde9c8 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 12 Feb 2025 09:17:26 +0000 Subject: [PATCH 423/434] Remove guess from the SasView repository. --- src/ascii_dialog/guess.py | 42 --------------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/ascii_dialog/guess.py diff --git a/src/ascii_dialog/guess.py b/src/ascii_dialog/guess.py deleted file mode 100644 index f067e369ae..0000000000 --- a/src/ascii_dialog/guess.py +++ /dev/null @@ -1,42 +0,0 @@ -from sasdata.dataset_types import DatasetType -from scipy.stats import mode - -def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: - """Guess the amount of columns present in the data.""" - candidate_lines = split_csv[starting_pos::] - return int(mode([len(line) for line in candidate_lines]).mode) - -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: - if len(order_list) >= col_count: - return order_list - - return dataset_type.expected_orders[-1] - -symbols_to_ignore = ['.', '-', '+', 'e', 'E'] - -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 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. - - """ - for i, row in enumerate(split_csv): - all_nums = True - for column in row: - amended_column = column - for symbol in symbols_to_ignore: - amended_column = amended_column.replace(symbol, '') - if not amended_column.isdigit(): - all_nums = False - break - if all_nums: - return i - return 0 From f184ebc743d5faba3dae50339c2ebb1b69c1931b Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 12 Feb 2025 09:20:52 +0000 Subject: [PATCH 424/434] Update 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 16c927fb9a..54108c68d6 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -6,7 +6,7 @@ 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 sasdata.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 135ad2b84906fce506f1630c82dcd701a37a1512 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 12 Feb 2025 10:17:38 +0000 Subject: [PATCH 425/434] Init should probably be at the top. --- src/ascii_dialog/col_editor.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 5ab8fd4c6d..4362d54c0a 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -11,19 +11,6 @@ class ColEditor(QWidget): from a set of options based on which dataset type has been selected.""" column_changed = Signal() - @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() - - def __init__(self, cols: int, options: list[str]): super().__init__() @@ -37,6 +24,18 @@ def __init__(self, cols: int, options: list[str]): self.layout.addWidget(new_widget) self.option_widgets.append(new_widget) + @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() + def setCols(self, new_cols: int): """Set the amount of columns for the user to edit.""" From c02e4822296c4002dee5796c185dade263308b66 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 12 Feb 2025 10:33:12 +0000 Subject: [PATCH 426/434] Move this logic into SasData. --- src/ascii_dialog/column_unit.py | 3 ++- src/ascii_dialog/default_units.py | 8 -------- 2 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 src/ascii_dialog/default_units.py diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index ab3bc99960..5b6aa29bf8 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -8,6 +8,7 @@ from ascii_dialog.unit_selector import UnitSelector from ascii_dialog.default_units import default_units +from sasdata.default_units import default_or_fallback, defaults_or_fallback def configure_size_policy(combo_box: QComboBox) -> None: policy = combo_box.sizePolicy() @@ -58,7 +59,7 @@ def updateUnits(self, unit_box: QComboBox, selected_option: str): unit_box.setDisabled(True) else: unit_box.setDisabled(False) - unit_options = default_units.get(self.current_option, unit_kinds[selected_option].units) + unit_options = defaults_or_fallback(self.current_option) option_symbols = [unit.symbol for unit in unit_options] for option in option_symbols[:5]: unit_box.addItem(option) diff --git a/src/ascii_dialog/default_units.py b/src/ascii_dialog/default_units.py deleted file mode 100644 index 402da0e300..0000000000 --- a/src/ascii_dialog/default_units.py +++ /dev/null @@ -1,8 +0,0 @@ -# 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 - -default_units = { - 'Q': [unit.per_nanometer, unit.per_angstrom, unit.per_meter], - 'I': [unit.per_centimeter, unit.per_meter] -} From f5dae932130923cae79b46f7c95493672567db89 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 12 Feb 2025 10:55:47 +0000 Subject: [PATCH 427/434] Removed the old import. --- src/ascii_dialog/column_unit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index 5b6aa29bf8..3ea9d538d5 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -7,7 +7,6 @@ from sasdata.quantities.units import symbol_lookup, NamedUnit from ascii_dialog.unit_selector import UnitSelector -from ascii_dialog.default_units import default_units from sasdata.default_units import default_or_fallback, defaults_or_fallback def configure_size_policy(combo_box: QComboBox) -> None: From 68b3b9544cb3fd70f7e2fb6bc9eebbaecfb5313f Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Wed, 12 Feb 2025 10:57:02 +0000 Subject: [PATCH 428/434] Import the right function. --- 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 3ea9d538d5..c0d75e4af4 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -7,7 +7,7 @@ from sasdata.quantities.units import symbol_lookup, NamedUnit from ascii_dialog.unit_selector import UnitSelector -from sasdata.default_units import default_or_fallback, defaults_or_fallback +from sasdata.default_units import defaults_or_fallback def configure_size_policy(combo_box: QComboBox) -> None: policy = combo_box.sizePolicy() From 550411202df06d634a36f780577902930a635d9c Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Thu, 13 Feb 2025 09:59:02 +0000 Subject: [PATCH 429/434] Handle ignored columns. --- src/ascii_dialog/col_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 4362d54c0a..656bbbafa7 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -77,8 +77,8 @@ def colNames(self) -> list[str]: 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] + def columns(self) -> list[tuple[str, NamedUnit | None]]: + return [(widget.currentColumn, widget.currentUnit if widget.currentColumn != "" else None) 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.""" From 323fa234af089fd49626e27fe778b2eb72d62e4a Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Fri, 14 Feb 2025 10:14:47 +0000 Subject: [PATCH 430/434] Fixed crashes when a filename just using casing. For separation. --- src/metadata_filename_gui/metadata_filename_dialog.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 25fe439169..8a2a67aab4 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -34,8 +34,10 @@ 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) + if isinstance(initial_separator_text, str): + self.character_radio.setChecked(True) + else: # if bool + self.casing_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) @@ -46,7 +48,10 @@ def __init__(self, filename: str, initial_metadata: AsciiReaderMetadata): self.casing_radio.setDisabled(True) self.seperator_chars_label = QLabel('Seperators') - self.separator_chars = QLineEdit(initial_separator_text) + if isinstance(initial_separator_text, str): + self.separator_chars = QLineEdit(initial_separator_text) + else: + self.separator_chars = QLineEdit() self.separator_chars.textChanged.connect(self.update_filename_separation) self.filename_separator_layout = QHBoxLayout() From 0e7379f654d87b63c1017f82e4e0dea7383cf630 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 17 Feb 2025 09:05:56 +0000 Subject: [PATCH 431/434] Removed unneeded emit call. --- 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 656bbbafa7..297404d48d 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -34,7 +34,6 @@ def onColumnUpdate(self): # 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() def setCols(self, new_cols: int): """Set the amount of columns for the user to edit.""" From 9e1d09ce9295ef2ac9133be83e216213470a0043 Mon Sep 17 00:00:00 2001 From: James Crake-Merani Date: Mon, 17 Feb 2025 10:07:56 +0000 Subject: [PATCH 432/434] Use the bidrectional pairings. --- src/ascii_dialog/col_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 297404d48d..8c5cc64ae8 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -2,7 +2,7 @@ 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 sasdata.ascii_reader_metadata import bidirectional_pairings from ascii_dialog.column_unit import ColumnUnit from typing import cast @@ -27,7 +27,7 @@ def __init__(self, cols: int, options: list[str]): @Slot() def onColumnUpdate(self): column_changed = cast(ColumnUnit, self.sender()) - pairing = pairings.get(column_changed.currentColumn) + pairing = bidirectional_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 From 9fbcd3e4d554b58977adb1c2418c2a1b0933c994 Mon Sep 17 00:00:00 2001 From: PaulSharp <44529197+DrPaulSharp@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:32:51 +0100 Subject: [PATCH 433/434] Fixes linting errors --- src/ascii_dialog/col_editor.py | 15 ++++--- src/ascii_dialog/column_unit.py | 8 ++-- src/ascii_dialog/dialog.py | 44 +++++++++++++------ src/ascii_dialog/row_status_widget.py | 2 +- src/ascii_dialog/selection_menu.py | 1 + src/ascii_dialog/unit_list_widget.py | 1 + src/ascii_dialog/unit_preference_line.py | 2 + src/ascii_dialog/unit_preferences.py | 8 +++- src/ascii_dialog/unit_selector.py | 1 + src/ascii_dialog/warning_label.py | 3 +- .../metadata_component_selector.py | 5 ++- .../metadata_custom_selector.py | 3 +- .../metadata_filename_dialog.py | 21 +++++++-- .../metadata_selector.py | 5 ++- .../metadata_tree_widget.py | 10 ++--- src/sas/qtgui/MainWindow/GuiManager.py | 6 +-- 16 files changed, 91 insertions(+), 44 deletions(-) diff --git a/src/ascii_dialog/col_editor.py b/src/ascii_dialog/col_editor.py index 8c5cc64ae8..6348bf805f 100644 --- a/src/ascii_dialog/col_editor.py +++ b/src/ascii_dialog/col_editor.py @@ -1,10 +1,13 @@ -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 typing import cast + +from PySide6.QtCore import Signal, Slot +from PySide6.QtWidgets import QHBoxLayout, QWidget + from sasdata.ascii_reader_metadata import bidirectional_pairings +from sasdata.quantities.units import NamedUnit + from ascii_dialog.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 @@ -28,7 +31,7 @@ def __init__(self, cols: int, options: list[str]): def onColumnUpdate(self): column_changed = cast(ColumnUnit, self.sender()) pairing = bidirectional_pairings.get(column_changed.currentColumn) - if not pairing is None: + if pairing is not 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. diff --git a/src/ascii_dialog/column_unit.py b/src/ascii_dialog/column_unit.py index c0d75e4af4..323d5ef0b5 100644 --- a/src/ascii_dialog/column_unit.py +++ b/src/ascii_dialog/column_unit.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 from PySide6.QtCore import Signal, Slot -from PySide6.QtWidgets import QComboBox, QCompleter, QHBoxLayout, QSizePolicy, QWidget from PySide6.QtGui import QRegularExpressionValidator +from PySide6.QtWidgets import QComboBox, QHBoxLayout, QSizePolicy, QWidget + from sasdata.dataset_types import unit_kinds -from sasdata.quantities.units import symbol_lookup, NamedUnit +from sasdata.default_units import defaults_or_fallback +from sasdata.quantities.units import NamedUnit from ascii_dialog.unit_selector import UnitSelector -from sasdata.default_units import defaults_or_fallback + 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 54108c68d6..6d22bacd35 100644 --- a/src/ascii_dialog/dialog.py +++ b/src/ascii_dialog/dialog.py @@ -1,20 +1,38 @@ -from PySide6.QtGui import QColor, QContextMenuEvent, QCursor, QPalette, Qt -from PySide6.QtWidgets import QAbstractScrollArea, QCheckBox, QComboBox, QFileDialog, QHBoxLayout, QHeaderView, QLabel, \ - QMessageBox, QPushButton, QSpacerItem, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QApplication, QDialog +from os import path + from PySide6.QtCore import QModelIndex, QPoint, Slot -from ascii_dialog.selection_menu import SelectionMenu -from ascii_dialog.warning_label import WarningLabel +from PySide6.QtGui import QColor, QCursor, Qt +from PySide6.QtWidgets import ( + QAbstractScrollArea, + QApplication, + QCheckBox, + QComboBox, + QDialog, + QFileDialog, + QHBoxLayout, + QHeaderView, + QLabel, + QMessageBox, + QPushButton, + QSpacerItem, + QSpinBox, + QTableWidget, + QTableWidgetItem, + QVBoxLayout, + QWidget, +) + +from sasdata.ascii_reader_metadata import AsciiReaderMetadata +from sasdata.dataset_types import DatasetType, dataset_types, one_dim, sesans, two_dim +from sasdata.guess import guess_column_count, guess_columns, guess_starting_position +from sasdata.temp_ascii_reader import AsciiReaderParams, load_data, split_line + from ascii_dialog.col_editor import ColEditor +from ascii_dialog.constants import TABLE_MAX_ROWS from ascii_dialog.row_status_widget import RowStatusWidget -from sasdata.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 ascii_dialog.selection_menu import SelectionMenu +from ascii_dialog.warning_label import WarningLabel 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 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/row_status_widget.py b/src/ascii_dialog/row_status_widget.py index 33f9a16de8..edddbed4de 100644 --- a/src/ascii_dialog/row_status_widget.py +++ b/src/ascii_dialog/row_status_widget.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from PySide6.QtCore import Signal, Slot, Qt +from PySide6.QtCore import Qt, Signal, Slot from PySide6.QtWidgets import QCheckBox, QHBoxLayout, QWidget diff --git a/src/ascii_dialog/selection_menu.py b/src/ascii_dialog/selection_menu.py index ef95ac04d5..e275686646 100644 --- a/src/ascii_dialog/selection_menu.py +++ b/src/ascii_dialog/selection_menu.py @@ -5,6 +5,7 @@ from PySide6.QtGui import QAction from PySide6.QtWidgets import QMenu, QWidget + class SelectionMenu(QMenu): select_all_event = Signal() deselect_all_event = Signal() diff --git a/src/ascii_dialog/unit_list_widget.py b/src/ascii_dialog/unit_list_widget.py index 1bd64c70bf..20a7c34ab0 100644 --- a/src/ascii_dialog/unit_list_widget.py +++ b/src/ascii_dialog/unit_list_widget.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from PySide6.QtWidgets import QListWidget, QListWidgetItem + from sasdata.quantities.units import NamedUnit diff --git a/src/ascii_dialog/unit_preference_line.py b/src/ascii_dialog/unit_preference_line.py index 80d27ef332..be6a15da56 100644 --- a/src/ascii_dialog/unit_preference_line.py +++ b/src/ascii_dialog/unit_preference_line.py @@ -2,10 +2,12 @@ from PySide6.QtCore import Slot from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QWidget + from sasdata.quantities.units import NamedUnit, UnitGroup from ascii_dialog.unit_selector import UnitSelector + class UnitPreferenceLine(QWidget): def __init__(self, column_name: str, initial_unit: NamedUnit, group: UnitGroup): super().__init__() diff --git a/src/ascii_dialog/unit_preferences.py b/src/ascii_dialog/unit_preferences.py index 3d37698cc2..9152675f41 100644 --- a/src/ascii_dialog/unit_preferences.py +++ b/src/ascii_dialog/unit_preferences.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 +import random + 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 +from sasdata.quantities.units import NamedUnit + from ascii_dialog.unit_preference_line import UnitPreferenceLine -import random + class UnitPreferences(QWidget): def __init__(self): diff --git a/src/ascii_dialog/unit_selector.py b/src/ascii_dialog/unit_selector.py index 4f48c22c4a..f039f4b6df 100644 --- a/src/ascii_dialog/unit_selector.py +++ b/src/ascii_dialog/unit_selector.py @@ -1,5 +1,6 @@ from PySide6.QtCore import Slot from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QLineEdit, QPushButton, QVBoxLayout + from sasdata.quantities.units import NamedUnit, UnitGroup, unit_group_names, unit_groups from ascii_dialog.unit_list_widget import UnitListWidget diff --git a/src/ascii_dialog/warning_label.py b/src/ascii_dialog/warning_label.py index 216cce307d..ccf2607797 100644 --- a/src/ascii_dialog/warning_label.py +++ b/src/ascii_dialog/warning_label.py @@ -1,4 +1,5 @@ from PySide6.QtWidgets import QLabel + from ascii_dialog.constants import TABLE_MAX_ROWS @@ -37,7 +38,7 @@ def updateWarning(self, missing_columns: list[str], duplicate_columns: list[str] self.setText(f'The following columns are missing: {missing_columns}') self.setFontRed() elif len(duplicate_columns) > 0: - self.setText(f'There are columns which are repeated.') + self.setText('There are columns which are repeated.') self.setFontRed() elif unparsable > 0: # FIXME: This error message could perhaps be a bit clearer. diff --git a/src/metadata_filename_gui/metadata_component_selector.py b/src/metadata_filename_gui/metadata_component_selector.py index e334baccb6..1800bc71c7 100644 --- a/src/metadata_filename_gui/metadata_component_selector.py +++ b/src/metadata_filename_gui/metadata_component_selector.py @@ -1,8 +1,9 @@ -from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout -from PySide6.QtCore import Signal, Qt, Slot +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import QHBoxLayout, QPushButton, QWidget 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 # redrawn. diff --git a/src/metadata_filename_gui/metadata_custom_selector.py b/src/metadata_filename_gui/metadata_custom_selector.py index a33d9a5c1f..7a42b1d817 100644 --- a/src/metadata_filename_gui/metadata_custom_selector.py +++ b/src/metadata_filename_gui/metadata_custom_selector.py @@ -1,7 +1,8 @@ -from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QHBoxLayout +from PySide6.QtWidgets import QHBoxLayout, QLineEdit, QPushButton, QWidget from sasdata.ascii_reader_metadata import AsciiReaderMetadata + class MetadataCustomSelector(QWidget): def __init__(self, category:str, metadatum: str, internal_metadata: AsciiReaderMetadata, filename: str): super().__init__() diff --git a/src/metadata_filename_gui/metadata_filename_dialog.py b/src/metadata_filename_gui/metadata_filename_dialog.py index 8a2a67aab4..48f4ae511f 100644 --- a/src/metadata_filename_gui/metadata_filename_dialog.py +++ b/src/metadata_filename_gui/metadata_filename_dialog.py @@ -1,8 +1,21 @@ -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, CASING_REGEX from sys import argv -import re + +from PySide6.QtWidgets import ( + QApplication, + QButtonGroup, + QDialog, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QRadioButton, + QVBoxLayout, +) + +from sasdata.ascii_reader_metadata import AsciiReaderMetadata + +from metadata_filename_gui.metadata_tree_widget import MetadataTreeWidget + def build_font(text: str, classname: str = '') -> str: match classname: diff --git a/src/metadata_filename_gui/metadata_selector.py b/src/metadata_filename_gui/metadata_selector.py index be54c56b22..b7b583d6ca 100644 --- a/src/metadata_filename_gui/metadata_selector.py +++ b/src/metadata_filename_gui/metadata_selector.py @@ -1,8 +1,11 @@ -from PySide6.QtWidgets import QWidget, QHBoxLayout +from PySide6.QtWidgets import QHBoxLayout, QWidget + 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 + class MetadataSelector(QWidget): def __init__(self, category: str, metadatum: str, metadata: AsciiReaderMetadata, filename: str): super().__init__() diff --git a/src/metadata_filename_gui/metadata_tree_widget.py b/src/metadata_filename_gui/metadata_tree_widget.py index 5b8329b0c6..f9bbf57398 100644 --- a/src/metadata_filename_gui/metadata_tree_widget.py +++ b/src/metadata_filename_gui/metadata_tree_widget.py @@ -1,10 +1,10 @@ -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 -from metadata_filename_gui.metadata_tree_data import metadata as metadata_categories +from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem + from sasdata.ascii_reader_metadata import AsciiReaderMetadata, initial_metadata +from metadata_filename_gui.metadata_selector import MetadataSelector + + class MetadataTreeWidget(QTreeWidget): def __init__(self, metadata: AsciiReaderMetadata): super().__init__() diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py index 5b52029007..5537daf9d7 100644 --- a/src/sas/qtgui/MainWindow/GuiManager.py +++ b/src/sas/qtgui/MainWindow/GuiManager.py @@ -9,14 +9,10 @@ from PySide6.QtCore import QLocale, Qt from PySide6.QtGui import QStandardItem from PySide6.QtWidgets import QDockWidget, QLabel, QProgressBar, QTextBrowser +from twisted.internet import reactor from sasdata.temp_ascii_reader import load_data -import sas.system.version -from sas.system.version import __version__ as SASVIEW_VERSION, __release_date__ as SASVIEW_RELEASE_DATE - -from twisted.internet import reactor - import sas # Perspectives From 20592d515b32e0b5cfc6b55f1b0fdc396fa2410f Mon Sep 17 00:00:00 2001 From: PaulSharp <44529197+DrPaulSharp@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:20:13 +0100 Subject: [PATCH 434/434] Fixes build --- build_tools/requirements.txt | 2 +- pyproject.toml | 5 +---- src/sas/system/config/config.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build_tools/requirements.txt b/build_tools/requirements.txt index f22446c21c..4d8ab0aaa3 100644 --- a/build_tools/requirements.txt +++ b/build_tools/requirements.txt @@ -25,7 +25,7 @@ pyparsing PySide6 pytools qtconsole -sasdata +sasdata @ git+https://github.com/SasView/sasdata.git@refactor_24 sasmodels scipy siphash24 diff --git a/pyproject.toml b/pyproject.toml index da7222d1a1..532ea7361c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,10 +13,7 @@ requires = [ "periodictable", "pyopengl", "pyside6", - "qtconsole", - "scipy", - "superqt", - "sasdata", + "sasdata @ git+https://github.com/SasView/sasdata.git@refactor_24", "sasmodels", "twisted", "uncertainties", diff --git a/src/sas/system/config/config.py b/src/sas/system/config/config.py index 700b948c1c..57c8d33144 100644 --- a/src/sas/system/config/config.py +++ b/src/sas/system/config/config.py @@ -214,7 +214,7 @@ def __init__(self): # Stack plots when using slicers # If true, plots generated when using slicers will be on the same canvas self.STACK_PLOTS = True - + # Developer menu self.DEV_MENU = False