# TUI user interface # # Copyright (C) 2015 Patrick "P. J." McDermott # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . _tui_msg='' tui_init_ui() { term_init term_raw term_noecho term_hide_cursor term_clear } tui_exit_ui() { term_attr_reset term_clear term_show_cursor term_echo term_cooked } tui_message() { local msg="${1}" shift 1 _tui_msg="${msg}" return 0 } _tui_box() { local w=${1} local h=${2} shift 2 local y= local x= local i= local line='' # Reset SGR and clear the screen. term_attr_reset term_clear # Write the persistent message (if any). term_cursor_position $(expr $(term_lines) - 1) 3 term_write "${_tui_msg}" # Set the cursor position to center the box. y=$(expr \( $(term_lines) - ${_tui_msg+3} - ${h} \) / 2 + 1) x=$(expr \( $(term_columns) - ${w} \) / 2 + 1) term_cursor_position ${y} ${x} # Build the line. i=0 while [ ${i} -lt ${w} ]; do line="${line} " i=$(($i + 1)) done # Draw white box background lines. term_fg_color_set black term_bg_color_set white i=0 while [ ${i} -lt ${h} ]; do term_write "${line}" term_cursor_back ${w} term_cursor_down 1 i=$(($i + 1)) done # Reset the cursor to the top-left corner of the box. term_cursor_up ${h} printf '%d %d' ${y} ${x} return 0 } _tui_dialog() { local lvl="${1}" local fmt="${2}" shift 2 local msg='' # Format the message. msg="$(printf "${fmt}" "${@}")" # Draw the box. _tui_box $(expr ${#msg} + 8) 5 >/dev/null # Draw an icon. term_cursor_forward 2 term_cursor_down 1 case "${lvl}" in info) term_fg_color_set blue term_write '(i)' term_fg_color_set black ;; warn) term_fg_color_set yellow term_write '/!\' term_fg_color_set black ;; err) term_fg_color_set red term_write '(x)' term_fg_color_set black ;; esac # Write the message. term_cursor_forward 1 term_write "${msg}" # Draw a (selected) button. term_cursor_back 4 term_cursor_down 2 term_attr_on reverse term_write '[OK]' term_attr_off reverse while :; do case "$(term_getch)" in KEY_ENTER | KEY_SPACE) break;; esac done return 0 } tui_dbg() { : No output. } tui_info() { : No output. } tui_warn() { local fmt="${1}" shift 1 _tui_dialog warn "${fmt}" "${@}" } tui_err() { local fmt="${1}" shift 1 _tui_dialog err "${fmt}" "${@}" } tui_show_menu() { local title="${1}" shift 1 local w= local label= local lsth= local scrh= local scrbh= local h= local y= local x= local i= local bline='' local lline='' local focus=0 local scrbpos= local scrpos=0 local curpos=0 # Calculate size and draw the box. w=$(expr ${#title} + 4) for label in "${@}"; do if [ $(expr ${#label} + 6) -gt ${w} ]; then w=$(expr ${#label} + 6) fi done lsth=${#} h=$(($lsth + 8)) if [ ${h} -gt $(term_lines) ]; then h=$(term_lines) fi scrh=$(($h - 8)) scrbh=$(($scrh * $scrh / $lsth)) read -r y x <<-EOF $(_tui_box ${w} ${h}) EOF # Build box and label lines. i=0 while [ ${i} -lt $(($w - 6)) ]; do bline="${bline}-" lline="${lline} " i=$(($i + 1)) done # Write the title. term_cursor_down 1 term_cursor_forward 2 term_write "${title}" # Event loop. while :; do # Draw a button. term_cursor_position $(($y + $h - 2)) $(($x + $w - 6)) if [ ${focus} -eq 1 ]; then term_attr_on reverse term_write '[OK]' term_attr_off reverse else term_write '[OK]' fi # Draw a box. term_cursor_position $(($y + 3)) $(($x + 2)) term_write "+${bline}+" term_cursor_position $(($y + $h - 4)) $(($x + 2)) term_write "+${bline}+" i=0 # Add ${lsth} - 1 to always round up to the next integer. scrbpos=$(expr \ \( ${scrpos} \* ${scrh} + ${lsth} - 1 \) / ${lsth}) while [ ${i} -lt ${scrh} ]; do term_cursor_position $(($y + 4 + $i)) $(($x + 2)) term_write '|' term_cursor_position $(($y + 4 + $i)) $(($x + $w - 3)) if [ ${i} -ge ${scrbpos} ] && \ [ ${i} -lt $(($scrbpos + $scrbh)) ] then term_attr_on reverse term_write ' ' term_attr_off reverse else term_write '|' fi i=$(($i + 1)) done # Draw labels. i=-1 for label in "${@}"; do i=$(($i + 1)) [ ${i} -ge ${scrpos} ] || continue [ ${i} -lt $(($scrpos + $scrh)) ] || break if [ ${i} -eq ${curpos} ]; then term_attr_on reverse term_cursor_position \ $(($y + 4 + $i - $scrpos)) $(($x + 3)) term_write "${lline}" term_cursor_position \ $(($y + 4 + $i - $scrpos)) $(($x + 3)) term_write "${label}" term_attr_off reverse else term_cursor_position \ $(($y + 4 + $i - $scrpos)) $(($x + 3)) term_write "${lline}" term_cursor_position \ $(($y + 4 + $i - $scrpos)) $(($x + 3)) term_write "${label}" fi done # Input. key="$(term_getch)" if [ "x${key}" = xKEY_TAB ]; then focus=$(expr \( ${focus} + 1 \) % 2) elif [ ${focus} -eq 0 ]; then case "${key}" in KEY_ENTER) break;; KEY_UP | 'k') [ ${curpos} -gt 0 ] || continue curpos=$(($curpos - 1)) if [ ${curpos} -lt ${scrpos} ]; then scrpos=${curpos} fi ;; KEY_DOWN | 'j') [ $(($curpos + 1)) -lt ${lsth} ] || \ continue curpos=$(($curpos + 1)) if [ ${curpos} -ge $(($scrpos + \ $scrh)) ]; then scrpos=$(($curpos - $scrh + 1)) fi ;; KEY_HOME | 'g') curpos=0 scrpos=0 ;; KEY_END | 'G') curpos=$(($lsth - 1)) scrpos=$(($lsth - $scrh)) ;; KEY_*);; esac elif [ ${focus} -eq 1 ]; then case "${key}" in KEY_ENTER | KEY_SPACE) break;; esac fi done return 0 } tui_show_prompt() { local title="${1}" local input="${2}" local len=${3} shift 3 local w= local y= local x= local i= local iline='' local bline='' local curpos= local focus=0 local key= local mask= local buffer= # Calculate width and draw the box. w=$(expr ${#title} + 4) if [ $(expr ${len} + 7) -gt ${w} ]; then w=$(expr ${len} + 7) fi read -r y x <<-EOF $(_tui_box ${w} 9) EOF # Write the title. term_cursor_down 1 term_cursor_forward 2 term_write "${title}" # Build blank input line. i=0 while [ ${i} -le ${len} ]; do bline="${bline}-" iline="${iline} " i=$(($i + 1)) done curpos=${#input} # Event loop. while :; do # Draw a button. term_cursor_position $(($y + 7)) $(($x + $w - 6)) if [ ${focus} -eq 1 ]; then term_attr_on reverse term_write '[OK]' term_attr_off reverse else term_write '[OK]' fi # Draw a textbox. term_cursor_position $(($y + 3)) $(($x + 2)) term_write "+${bline}+" term_cursor_position $(($y + 4)) $(($x + 2)) term_write "|${iline}|" term_cursor_position $(($y + 5)) $(($x + 2)) term_write "+${bline}+" # Draw input. term_cursor_position $(($y + 4)) $(($x + 3)) term_write "${input}" # Put the cursor in the text box. if [ ${focus} -eq 0 ]; then term_show_cursor term_cursor_position $(($y + 4)) $(($x + 3 + $curpos)) else term_hide_cursor fi # Input. key="$(term_getch)" if [ "x${key}" = xKEY_TAB ]; then focus=$(expr \( ${focus} + 1 \) % 2) elif [ ${focus} -eq 0 ]; then case "${key}" in KEY_ENTER) term_hide_cursor break ;; KEY_LEFT) [ ${curpos} -gt 0 ] || continue curpos=$(($curpos - 1)) ;; KEY_RIGHT) [ ${curpos} -lt ${#input} ] || continue curpos=$(($curpos + 1)) ;; KEY_HOME) curpos=0 ;; KEY_END) curpos=${#input} ;; KEY_BACKSPACE) [ ${curpos} -gt 0 ] || continue i=1 mask='' while [ ${i} -lt ${curpos} ]; do mask="${mask}." i=$(($i + 1)) done cmd="s/^\\(${mask}\\).\\(.*\\)\$/\1\2/" input="$(printf '%s' "${input}" | sed \ "${cmd}")" curpos=$(($curpos - 1)) ;; KEY_DEL) [ ${curpos} -lt ${#input} ] || continue i=0 mask='' while [ ${i} -lt ${curpos} ]; do mask="${mask}." i=$(($i + 1)) done cmd="s/^\\(${mask}\\).\\(.*\\)\$/\1\2/" input="$(printf '%s' "${input}" | sed \ "${cmd}")" ;; KEY_SPACE) [ ${#input} -lt ${len} ] || continue key=' ' i=0 mask='' while [ ${i} -lt ${curpos} ]; do mask="${mask}." i=$(($i + 1)) done cmd="s/^\\(${mask}\\).*\$/\1/" buffer="$(printf '%s' "${input}" | sed \ "${cmd}")${key}" i=${curpos} mask='' while [ ${i} -lt ${#input} ]; do mask="${mask}." i=$(($i + 1)) done cmd="s/^.*\\(${mask}\\)\$/\1/" input="${buffer}$(printf '%s' \ "${input}" | sed "${cmd}")" curpos=$(($curpos + 1)) ;; KEY_*);; *) [ ${#input} -lt ${len} ] || continue i=0 mask='' while [ ${i} -lt ${curpos} ]; do mask="${mask}." i=$(($i + 1)) done cmd="s/^\\(${mask}\\).*\$/\1/" buffer="$(printf '%s' "${input}" | sed \ "${cmd}")${key}" i=${curpos} mask='' while [ ${i} -lt ${#input} ]; do mask="${mask}." i=$(($i + 1)) done cmd="s/^.*\\(${mask}\\)\$/\1/" input="${buffer}$(printf '%s' \ "${input}" | sed "${cmd}")" curpos=$(($curpos + 1)) ;; esac elif [ ${focus} -eq 1 ]; then case "${key}" in KEY_ENTER | KEY_SPACE) break;; esac fi done printf '%s' "${input}" return 0 }