Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ulc_mm_package/QtGUI/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ def _maybe_start_study_form(self):

def get_study_metadata(self):
self.study_metadata = self.study_form_dialog.get_form_input()
self.study_form_dialog.close()
self.save_form()

def save_form(self):
Expand Down
101 changes: 76 additions & 25 deletions ulc_mm_package/QtGUI/study_metadata_form.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Tuple

from PyQt5.QtCore import QDate
from PyQt5.QtGui import QIntValidator
Expand Down Expand Up @@ -84,7 +84,6 @@ def create_widget_for_field(field_def):

default = field_def.get("default")
if default is not None:
print("default is")
idx = w.findText(default)
if idx >= 0:
w.setCurrentIndex(idx)
Expand Down Expand Up @@ -139,7 +138,7 @@ def __init__(self, cfg: dict, parent=None):
self.btn_start.setEnabled(False)
layout.addRow(label, widget)

self.btn_start.clicked.connect(self.accept)
self.btn_start.clicked.connect(self.validate)
btn_cancel.clicked.connect(self.close)

layout.addRow(btn_cancel, self.btn_start)
Expand All @@ -162,34 +161,76 @@ def __init__(self, cfg: dict, parent=None):
# run an initial check to set OK button state
self.check_required()

def _get_widget_text(self, w: QWidget) -> Optional[str]:
if isinstance(w, QTextEdit):
return w.toPlainText()
elif isinstance(w, QLineEdit):
return w.text()
elif isinstance(w, QComboBox):
return "" if w.currentIndex() < 0 else w.currentText()
elif isinstance(w, QListWidget):
return "\n".join(i.text() for i in w.selectedItems())

if hasattr(w, "text"):
return w.text()
return None

def _is_empty(self, w) -> bool:
text = self._get_widget_text(w)
return (text is not None) and (not text.strip())

def _has_value_and_valid(self, w: QWidget) -> bool:
if self._is_empty(w):
return True

if isinstance(w, QTextEdit):
return bool(w.toPlainText().strip())
elif isinstance(w, QLineEdit):
text = w.text().strip()
return bool(text) and w.hasAcceptableInput()
elif isinstance(w, QComboBox):
return w.currentIndex() >= 0
elif isinstance(w, QListWidget):
return len(w.selectedItems()) >= 0

if hasattr(w, "text"):
return bool(w.text().strip())

return False

def _check_widget(self, w: QWidget, field: dict) -> Tuple[bool, str]:
required = field.get("required")
if self._is_empty(w) and required:
return False, "Required"

if not self._has_value_and_valid(w):
return False, "Invalid"

return True, ""

def check_required(self):
"""Ensure that all required widgets have a value."""
all_filled = True
for w in self._required_widgets:
if hasattr(w, "toPlainText"):
if not w.toPlainText().strip():
all_filled = False
break
elif hasattr(w, "text"):
if not w.text().strip():
all_filled = False
break
elif hasattr(w, "currentIndex"):
# -1 index means no selection made
if w.currentIndex() < 0:
all_filled = False
break
if all_filled:
self.btn_start.setEnabled(True)
else:
self.btn_start.setEnabled(False)
self.btn_start.setEnabled(
all([self._has_value_and_valid(x) for x in self._required_widgets])
)

def validate(self):
for lbl, (w, field) in self._widgets.items():
valid, error_msg = self._check_widget(w, field)
if not valid:
print(lbl, field, error_msg)
w.setFocus()
return
self.accept()

def get_form_input(self):
result = {"study_id": self.config_data["study_description"]["key"]}
errors = []
for name, (widget, field) in self._widgets.items():
possible_choices = []
subkeys = []
for _, (widget, field) in self._widgets.items():
t = field["datatype"]

key = field.get("key")
if t == "string":
value = (
widget.toPlainText() if field.get("multiline") else widget.text()
Expand All @@ -209,10 +250,20 @@ def get_form_input(self):
value = widget.currentData()
elif t == "multiselect":
value = [x.text() for x in widget.selectedItems()]
possible_choices = [
widget.item(i).text() for i in range(widget.count())
]
subkeys = field["subkeys"]
else:
value = None

result[name] = value
# Accommodate the multiselect case separately (i.e. save each choice as its own col)
if isinstance(value, list):
selected_set = set(value)
for choice, choice_key in zip(possible_choices, subkeys):
result[choice_key] = choice in selected_set
else:
result[key] = value

if errors:
raise ValueError("\n".join(errors))
Expand Down
7 changes: 5 additions & 2 deletions ulc_mm_package/study_configurations/_example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ name = "Example Study"

# Metadata fields
[[metadata]]
key = "key1_blah"
key = "key1"
label = "numbers galore"
datatype = "int"
required = true

[[metadata]]
key = "key2_blah"
key = "key2"
label = "string stuff"
datatype = "string"
required = true
Expand All @@ -21,12 +21,15 @@ label = "choices"
datatype = "enum"
choices = ["control", "low_dose", "high_dose"]
default = "control"
required = true

[[metadata]]
key = "key4"
label = "Multiselect"
datatype = "multiselect"
choices = ["pickme", "orme", "orwho", "or", "allofus"]
subkeys = ["choice1", "choice2", "choice3", "choice4", "choice5"]
required = false


[[metadata]]
Expand Down