11from pathlib import Path
2- from typing import Dict , List , Optional
2+ from typing import Dict , List , Optional , Tuple
33
44from PyQt5 .QtCore import QDate
55from PyQt5 .QtGui import QIntValidator
@@ -84,7 +84,6 @@ def create_widget_for_field(field_def):
8484
8585 default = field_def .get ("default" )
8686 if default is not None :
87- print ("default is" )
8887 idx = w .findText (default )
8988 if idx >= 0 :
9089 w .setCurrentIndex (idx )
@@ -139,7 +138,7 @@ def __init__(self, cfg: dict, parent=None):
139138 self .btn_start .setEnabled (False )
140139 layout .addRow (label , widget )
141140
142- self .btn_start .clicked .connect (self .accept )
141+ self .btn_start .clicked .connect (self .validate )
143142 btn_cancel .clicked .connect (self .close )
144143
145144 layout .addRow (btn_cancel , self .btn_start )
@@ -162,33 +161,76 @@ def __init__(self, cfg: dict, parent=None):
162161 # run an initial check to set OK button state
163162 self .check_required ()
164163
164+ def _get_widget_text (self , w : QWidget ) -> Optional [str ]:
165+ if isinstance (w , QTextEdit ):
166+ return w .toPlainText ()
167+ elif isinstance (w , QLineEdit ):
168+ return w .text ()
169+ elif isinstance (w , QComboBox ):
170+ return "" if w .currentIndex () < 0 else w .currentText ()
171+ elif isinstance (w , QListWidget ):
172+ return "\n " .join (i .text () for i in w .selectedItems ())
173+
174+ if hasattr (w , "text" ):
175+ return w .text ()
176+ return None
177+
178+ def _is_empty (self , w ) -> bool :
179+ text = self ._get_widget_text (w )
180+ return (text is not None ) and (not text .strip ())
181+
182+ def _has_value_and_valid (self , w : QWidget ) -> bool :
183+ if self ._is_empty (w ):
184+ return True
185+
186+ if isinstance (w , QTextEdit ):
187+ return bool (w .toPlainText ().strip ())
188+ elif isinstance (w , QLineEdit ):
189+ text = w .text ().strip ()
190+ return bool (text ) and w .hasAcceptableInput ()
191+ elif isinstance (w , QComboBox ):
192+ return w .currentIndex () >= 0
193+ elif isinstance (w , QListWidget ):
194+ return len (w .selectedItems ()) >= 0
195+
196+ if hasattr (w , "text" ):
197+ return bool (w .text ().strip ())
198+
199+ return False
200+
201+ def _check_widget (self , w : QWidget , field : dict ) -> Tuple [bool , str ]:
202+ required = field .get ("required" )
203+ if self ._is_empty (w ) and required :
204+ return False , "Required"
205+
206+ if not self ._has_value_and_valid (w ):
207+ return False , "Invalid"
208+
209+ return True , ""
210+
165211 def check_required (self ):
166212 """Ensure that all required widgets have a value."""
167- all_filled = True
168- for w in self ._required_widgets :
169- if hasattr (w , "toPlainText" ):
170- if not w .toPlainText ().strip ():
171- all_filled = False
172- break
173- elif hasattr (w , "text" ):
174- if not w .text ().strip ():
175- all_filled = False
176- break
177- elif hasattr (w , "currentIndex" ):
178- # -1 index means no selection made
179- if w .currentIndex () < 0 :
180- all_filled = False
181- break
182- if all_filled :
183- self .btn_start .setEnabled (True )
184- else :
185- self .btn_start .setEnabled (False )
213+ self .btn_start .setEnabled (
214+ all ([self ._has_value_and_valid (x ) for x in self ._required_widgets ])
215+ )
216+
217+ def validate (self ):
218+ for lbl , (w , field ) in self ._widgets .items ():
219+ valid , error_msg = self ._check_widget (w , field )
220+ if not valid :
221+ print (lbl , field , error_msg )
222+ w .setFocus ()
223+ return
224+ self .accept ()
186225
187226 def get_form_input (self ):
188227 result = {"study_id" : self .config_data ["study_description" ]["key" ]}
189228 errors = []
190- for name , (widget , field ) in self ._widgets .items ():
229+ possible_choices = []
230+ subkeys = []
231+ for _ , (widget , field ) in self ._widgets .items ():
191232 t = field ["datatype" ]
233+ key = field ["key" ]
192234
193235 if t == "string" :
194236 value = (
@@ -209,10 +251,20 @@ def get_form_input(self):
209251 value = widget .currentData ()
210252 elif t == "multiselect" :
211253 value = [x .text () for x in widget .selectedItems ()]
254+ possible_choices = [
255+ widget .item (i ).text () for i in range (widget .count ())
256+ ]
257+ subkeys = field ["subkeys" ]
212258 else :
213259 value = None
214260
215- result [name ] = value
261+ # Accommodate the multiselect case separately (i.e. save each choice as its own col)
262+ if isinstance (value , list ):
263+ selected_set = set (value )
264+ for choice , choice_key in zip (possible_choices , subkeys ):
265+ result [choice_key ] = choice in selected_set
266+ else :
267+ result [key ] = value
216268
217269 if errors :
218270 raise ValueError ("\n " .join (errors ))
0 commit comments