@@ -108,3 +108,184 @@ function ui#requestInputImpl
108108}
109109
110110# @endsection
111+
112+ # -----------
113+ # @section Form support
114+
115+ # @description
116+ # Repeat form until verified or canceled, place result in assoc `__form_result`
117+ function ui#runFormImpl
118+ {
119+ local __rawResult
120+ local -a __resultLines
121+ local __dialogStatus=1
122+ while [ "$__dialogStatus" != 0 ]; do
123+ __rawResult=$(
124+ "ui#baseDialog" --form --title="$1" \
125+ --separator=$'\n' --num-output --item-separator="|" \
126+ "${__FORM_ARGS[@]}" "${__FORM_DATA[@]}"
127+ )
128+ #shellcheck disable=2181 # not possible here for readability reasons
129+ if [ "$?" -gt 0 ]; then
130+ #dialog got canceled
131+ return 1
132+ fi
133+ readarray -t __resultLines <<< "$__rawResult"
134+ "ui#verifyFormVars" LOOP
135+ __dialogStatus="$?"
136+ done
137+ # apply transformations, if any
138+ "ui#verifyFormVars" RESULT __FORM_DATA
139+
140+ # write the 'real' lines into the result assoc
141+ local idx
142+ for idx in "${!__resultLines[@]}"; do
143+ varName="${__CONFIRM_PARAMETERS[$idx]}"
144+ [ "$varName" != "__skip__" ] || continue
145+
146+ __form_result["$varName"]="${__FORM_DATA[$idx]}"
147+ done
148+ }
149+
150+ # @description
151+ # While a form dialog window is build from input given to [ui:form](../user-interface.shl.md),
152+ # the code needs to keep track of the added fields to ensure that the results can be validated and returned correctly.
153+ # Missing a single argument in the field definitions for the underlying utility, `yad`, can have disastrous effects,
154+ # as it seems to operate on a purely index-based strategy while also splitting `option` arguments from `data`.
155+ # This means that missing just a single `initial data` argument for one field will shift **all** of the following
156+ # arguments left by one entry.
157+ #
158+ # This function makes sure the argument line remains consistent, regardless of field type. It also maintain meta
159+ # information about the form contents/variable names. This meta information is needed at the end when the user
160+ # clicks on OK/Confirm.
161+ #
162+ # **Full explanation**
163+ # Initial arguments represent button actions, combo value lists, the checked state of checkboxes etc,
164+ # However, they are **not** the text/label used as 'explanation'. These are part of an 'option'. Example:
165+ #
166+ # ```bash
167+ # yad --form --field="Some detail":LBL --field="Confirm":CHK true
168+ # ```
169+ #
170+ # One might expect that this yields a dialog with a label and a pre-checked checkbox. This is not what happens.
171+ # Labels do not use initial data, but the argument processing still requires it.
172+ # The label is the first field and does not have initial data following its `--field` argument,
173+ # so it takes the initial data given with index 0, which is 'true'.
174+ # The checkbox would have to take data index 1, but that does not exist, thus it remains unchecked.
175+ # Working variants of the example above:
176+ #
177+ #```bash
178+ # # interleaving initial data and fields
179+ # yad --form --field="Some detail":LBL '' --field="Confirm":CHK true
180+ #
181+ # #initial data at the end
182+ # yad --form --field="Some detail":LBL --field="Confirm":CHK '' true
183+ #```
184+ #
185+ # @arg $1 string field type
186+ # @arg $2 string name of the variable meant to contain the result
187+ # @arg $3 string field explanation
188+ # @arg $4 string initial data (optional) - will be passed as empty string if not given.
189+ # Please not that most fields are not functional without initial data.
190+ # The empty string only prevents the worst case, shifting values to unexpected places.
191+ function ui#formAddField
192+ {
193+ local fieldType="$1"
194+ local varName="$2"
195+ local message="$3"
196+ local iniData=${4:-''}
197+
198+ # Skip creation of long label. Needed to prevent recursion between 'ui#formAddField' and 'ui#formLabel'
199+ [ "$fieldType" != LBL ] || "ui#formLabel" message
200+
201+ __FORM_ARGS+=("--field=${message}:${fieldType:- }")
202+ __FORM_DATA+=("${iniData}")
203+ __CONFIRM_PARAMETERS+=("${varName}")
204+ }
205+
206+ # @endsection
207+
208+ # -----------
209+ # @section Implementation-specific helper functions
210+
211+ # @description
212+ # In form dialogs, question labels are normally placed in front of the inputs, in a tabular manner.
213+ # Sentences which are too long make the dialog look strange. To prevent this, the text must be moved
214+ # into a dedicated read-only label above the actual text.
215+ # This is what this function does: Any sentence longer than 3 words is declared as label instead.
216+ # This function meant to be called within the context of a running `form`.
217+ # First argument must be the name of the variable containing the sentence to check.
218+ # The variable's content will be modified accordingly.
219+ function ui#formLabel
220+ {
221+ local -n __label="$1"
222+ __label=$(lc "$__label")
223+ local __words=""
224+ _explode __words "$__label"
225+ if [ "${#__words[@]}" -gt 3 ]; then
226+ "ui#formAddField" LBL __skip__ "$__label"
227+ __label=""
228+ fi
229+ }
230+
231+ # @description
232+ # To be used from within 'ui#runFormImpl'. Goes over arrays and verifies content of __resultLines.
233+ # Behaviour of verifier functions can potentially be different in LOOP and RESULT modes:
234+ # - stdout in LOOP mode shall be used to change initial value when re-opening the form
235+ # - In RESULT mode, if required, a mapping of the form-internal keys to the expected output is to be done.
236+ # This is mostly the case for choice questions which get indices as raw values, but should return 'key' strings.
237+ #
238+ # When given `$2`, the output of verifiers is written at the array index corresponding to input.
239+ # For correct mapping of values to verifiers, a correctly populated array __CONFIRM_PARAMETERS is required.
240+ # It must be of the same length as `__resultLines` and map indices to var names, or use `__skip__` for lines to ignore.
241+ #
242+ # @arg $1 mode-string LOOP/RESULT
243+ # @arg $2 arr-name result array name
244+ function ui#verifyFormVars
245+ {
246+ if [ -v "$2" ]; then
247+ local -n resultArr="$2"
248+ else
249+ local -a resultArr
250+ fi
251+ local verifier varName adjustedValue
252+ local numErrors=0
253+ local idx
254+ for idx in "${!__resultLines[@]}"; do
255+ varName="${__CONFIRM_PARAMETERS[$idx]}"
256+ [ "$varName" != "__skip__" ] || continue
257+
258+ verifier=()
259+ _explode verifier "${__VERIFIERS[$varName]:-"ui#verifyPassActual"}"
260+ if ! adjustedValue=$(VMODE="$1" "${verifier[@]}" "${__resultLines[$idx]}" "${__FORM_DATA[$idx]}"); then
261+ (( ++numErrors ))
262+ fi
263+
264+ # shellcheck disable=2034 # either reference to outside, or local to swallow values (unused on purpose)
265+ resultArr[idx]="$adjustedValue"
266+ done
267+ return "$numErrors"
268+ }
269+
270+ # @description
271+ # Implementation of a verifier for choice-questions in forms.
272+ # @arg $1 name of array containing original choice keys (provided by ui:askChoice)
273+ # @arg $2 (changed) value of the current loop iteration/retry
274+ function ui#verifyChoice
275+ {
276+ local -n arr="$1"
277+ if [ "$VMODE" = "LOOP" ]; then
278+ # For now: Just re-build the choice list.
279+ # Later on: translate $2 into `^` at the right place to keep pre-selected value on restart
280+ _join \| "${arr[@]}"
281+ elif [ "$VMODE" = "RESULT" ]; then
282+ echo -n "${arr[$2]}"
283+ fi
284+ }
285+
286+ function ui#verifyPassActual
287+ {
288+ printf '%s' "$1"
289+ }
290+
291+ # @endsection
0 commit comments