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