--- /dev/null
+#!/bin/bash
+
+if [[ $(type -t gsu_is_a_number) != "function" ]]; then
+ GSU_DIR=${GSU_DIR:=$HOME/.gsu}
+ . $GSU_DIR/common || exit 1
+fi
+
+export GSU_NODE_NAME_PATTERN='[a-zA-Z_]'
+
+get_geometry()
+{
+ local x y
+ result="$(stty size)"
+ if (($? != 0)); then
+ gsu_msg "fatal: could not get terminal geometry"
+ exit 1
+ fi
+ x="${result#* }"
+ y="${result%% *}"
+ (($x > 190)) && x=190
+ result="$y $x"
+}
+
+gsu_infobox()
+{
+ get_geometry
+ dialog --infobox "$1" $result
+}
+
+gsu_checklist_all_on()
+{
+ local header="$1"
+ local items="$2"
+ local i state opts num=0
+
+ get_geometry
+ ops="$result 16"
+ for i in $items; do
+ let num++
+ opts+=" $i $num on"
+ done
+ result=$(dialog --checklist "$header" $opts 3>&1 1>&2 2>&3 3>&-)
+ ret="$?"
+}
+
+gsu_radiolist()
+{
+ local header="$1"
+ local selected_item="$2"
+ local items="$3"
+ local i state ops num=0
+
+ get_geometry
+ ops="$result 16"
+ for i in $items; do
+ let num++
+ if [[ "$i" == "$selected_item" ]]; then
+ state="on"
+ else
+ state="off"
+ fi
+ ops+=" $i $num $state"
+ done
+ result=$(dialog --radiolist "$header" $ops 3>&1 1>&2 2>&3 3>&-)
+ ret="$?"
+}
+
+gsu_inputbox()
+{
+ local g text="$1" init="$2"
+
+ get_geometry
+ g="$result"
+ result="$(dialog --inputbox "$text" $g "$init" 3>&1 1>&2 2>&3 3>&-)"
+ ret="$?"
+}
+
+gsu_textbox()
+{
+ local file="$1"
+
+ get_geometry
+ dialog --textbox "$file" $result
+}
+
+# dialog segfaults if message is too long. Hence we always use a temporary file
+gsu_msgbox()
+{
+ local tmp="$(mktemp gsu_msgbox.XXXXXXXXXX)"
+
+ if (($? != 0)); then
+ dialog --msgbox "mktemp error" 0 0
+ return
+ fi
+ echo "$1" > "$tmp"
+ gsu_textbox "$tmp"
+ rm -f "$tmp"
+}
+
+gsu_cmd_output_box()
+{
+ local tmp="$(mktemp)"
+
+ if (($? != 0)); then
+ dialog --msgbox "mktemp error" 0 0
+ return
+ fi
+ $@ > "$tmp" 2>&1
+ echo "exit code: $?" >> "$tmp"
+ gsu_textbox "$tmp"
+ rm -f "$tmp"
+}
+
+gsu_yesno()
+{
+ local text="$1"
+
+ get_geometry
+ dialog --yesno "$text" $result
+ ret=$?
+ if (($ret == 0)); then
+ result="yes"
+ elif (($ret == 1)); then
+ result="no"
+ else
+ result=
+ fi
+}
+
+gsu_menu()
+{
+ local header="${1:-root}"
+ local items="$2"
+ local i state opts num=0
+
+ get_geometry
+ opts="$result 16"
+ for i in $items; do
+ let num++
+ opts+=" $i $num"
+ done
+ result="$(dialog --menu "$header" $opts 3>&1 1>&2 2>&3 3>&-)"
+ ret="$?"
+}
+
+_get_level()
+{
+ local tmp="${1%%$GSU_NODE_NAME_PATTERN*}"
+ result="${#tmp}"
+}
+
+_get_subtree()
+{
+ local tree="$1" root="${2%/}"
+ local TAB=' '
+
+ first="$(grep -n "$root/" <<< "$tree")"
+ [[ -z "$first" ]] && return
+
+ line_num="${first%%:*}"
+ _get_level "${first#*:}"
+ level="$result"
+
+ #echo "line: $line_num, root: $root, indent level: $level"
+ result="$(sed -e "1,${line_num}d;" <<< "$tree" \
+ | sed -e "/^$TAB\{1,$level\}$GSU_NODE_NAME_PATTERN/,\$d" \
+ | sed -e "/^$TAB\{$(($level + 2))\}/d")"
+ ret="$level"
+}
+
+_get_root_nodes()
+{
+ local tree="$1" TAB=' '
+
+ result="$(grep "^${TAB}${GSU_NODE_NAME_PATTERN}" <<< "$tree")"
+}
+
+_browse()
+{
+ local header="$1" old_header
+ local tree="$2" subtree="$3"
+
+ while :; do
+ gsu_menu "$header" "$subtree"
+ (($ret != 0)) && return
+ [[ -z "$result" ]] && return
+ if [[ "${result%/}" != "$result" ]]; then
+ old_header="$header"
+ header="$result"
+ _get_subtree "$tree" "$header"
+ _browse "$header" "$tree" "$result"
+ header="$old_header"
+ continue
+ fi
+ eval ${gsu_name}_$result
+ done
+}
+
+gsu_gui()
+{
+ local tree="$1" subtree
+
+ _gsu_setup
+ _get_root_nodes "$tree"
+ subtree="$result"
+ _browse "$gsu_name main menu" "$tree" "$subtree"
+}