YAD - tips'n'tricks

For discussions about programming, and for programming questions and advice


Moderator: Forum moderators

User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

Revisiting this code by @MochiMoppel on the old forum.
https://oldforum.puppylinux.com/viewtop ... 4#p1037564
I have a use for it but using the arrow keys results in keycodes. How to correct?
I guess it need a case statement to translate all functional keys (arrows, tab, space ,Win, BackSpace, Return). I intend to type characters in a placeholder amidst markup formatting. So that the markup interpreter displays the effected text on the cairo canvas in either yad --text-info or yad --form --field=:txt

xscreenshot-20250225T162217.png
xscreenshot-20250225T162217.png (71.05 KiB) Viewed 658 times
superhik
Posts: 63
Joined: Mon Jun 19, 2023 7:56 pm
Has thanked: 6 times
Been thanked: 26 times

Re: YAD - tips'n'tricks

Post by superhik »

stemsee wrote: Tue Feb 25, 2025 8:28 am

I have a use for it but using the arrow keys results in keycodes. How to correct?

Well, it can read the keys and skip them.

Code: Select all

while true ;do
	IFS='' read -rsN 1 ound
	
	if [[ $ound == $'\x1b' ]]; then
		read -rsn2 -t 0.1 dummy && continue
	fi

	if [[ $ound == $'\x7f' ]] ;then
		buffer=${buffer%?}
		echo -ne "\b" >&2
	else
		buffer=$buffer$ound
		echo -n "$ound" >&2
	fi
	echo -e "\f"
	echo "$buffer"
done | yad --text-info --width=200 --height=200 --tail

---------------------------
I experimented a bit, suppressing contol combinations was tricky.
You may also need word wrapping or character wrapping.
This is with word wrapping:

Code: Select all

#!/bin/bash

LINE_WIDTH=45     # Characters per line before wrapping

while true ;do
    IFS='' read -rsN 1 ound

    case $ound in  
        $'\x1b')  # Handle escape sequences (like arrow keys)
            read -rsn2 -t 0.1 input
            continue  
            ;;  
        $'\r'|$'\n')  # Ignore Enter key
            continue  
            ;;  
        $'\x7f')  # Handle Backspace
            buffer=${buffer%?}  
            echo -ne "\b \b" >&2  
            ;;  
        $'\t')  # Ignore Tab key
            continue  
            ;;  
        $' ')  # Handle Space (ensure it's visible)
            buffer+=$' '  
            echo -n " " >&2  
            ;;  
        $'\xE2')  # Windows/Super key (part of multi-byte sequence, ignore)
            read -rsn2 -t 0.1 input  
            continue  
            ;;  
        $'\x00'|$'\x01'|$'\x02'|$'\x03'|$'\x04'|$'\x05'|$'\x06'|\
        $'\x07'|$'\x08'|$'\x09'|$'\x0A'|$'\x0B'|$'\x0C'|$'\x0D'|\
        $'\x0E'|$'\x0F'|$'\x10'|$'\x11'|$'\x12'|$'\x13'|$'\x14'|\
        $'\x15'|$'\x16'|$'\x17'|$'\x18'|$'\x19'|$'\x1A'|$'\x1C'|\
        $'\x1D'|$'\x1E'|$'\x1F')  # Ignore all Ctrl key combinations
            continue  
            ;;
        *)
             buffer=$buffer$ound
             echo -n "$ound" >&2
    esac

    # Word wrapping logic
    formatted_buffer=""
    current_line=""
    for word in $buffer; do
        if [[ $((${#current_line} + ${#word} + 1)) -gt $LINE_WIDTH ]]; then
            formatted_buffer+="$current_line\n"
            current_line="$word"
        else
            if [[ -z "$current_line" ]]; then
                current_line="$word"
            else
                current_line+=" $word"
            fi
        fi
    done
    formatted_buffer+="$current_line"

    echo -e "\f"  
    echo -e "$formatted_buffer"  

done | yad --text-info --width=200 --height=200 --tail

For character wrapping you would do next

Code: Select all

    # Format output with line wrapping
    formatted_buffer=""
    for ((i=0; i<${#buffer}; i+=LINE_WIDTH)); do
        formatted_buffer+="${buffer:i:LINE_WIDTH}\n"
    done

But I wanted to combine both methods:
By default it will apply word wrapping, if the word exceeds line width if will do character wrapping and go to next line.
Without further ado, here it is >

Code: Select all

#!/bin/bash

LINE_WIDTH=45     # Characters per line before wrapping

while true ;do
    IFS='' read -rsN 1 ound

    case $ound in  
        $'\x1b')  # Handle escape sequences (like arrow keys)
            read -rsn2 -t 0.1 input
            continue  
            ;;  
        $'\r'|$'\n')  # Ignore Enter key
            continue  
            ;;  
        $'\x7f')  # Handle Backspace
            buffer=${buffer%?}  
            echo -ne "\b \b" >&2  
            ;;  
        $'\t')  # Ignore Tab key
            continue  
            ;;  
        $' ')  # Handle Space (ensure it's visible)
            buffer+=$' '  
            echo -n " " >&2  
            ;;  
        $'\xE2')  # Windows/Super key (part of multi-byte sequence, ignore)
            read -rsn2 -t 0.1 input  
            continue  
            ;;  
        $'\x00'|$'\x01'|$'\x02'|$'\x03'|$'\x04'|$'\x05'|$'\x06'|\
        $'\x07'|$'\x08'|$'\x09'|$'\x0A'|$'\x0B'|$'\x0C'|$'\x0D'|\
        $'\x0E'|$'\x0F'|$'\x10'|$'\x11'|$'\x12'|$'\x13'|$'\x14'|\
        $'\x15'|$'\x16'|$'\x17'|$'\x18'|$'\x19'|$'\x1A'|$'\x1C'|\
        $'\x1D'|$'\x1E'|$'\x1F')  # Ignore all Ctrl key combinations
            continue  
            ;;
        *)
             buffer=$buffer$ound
             echo -n "$ound" >&2
    esac


    # Format output with line wrapping
    #formatted_buffer=""
    # for ((i=0; i<${#buffer}; i+=LINE_WIDTH)); do
    #    formatted_buffer+="${buffer:i:LINE_WIDTH}\n"
    #done

    # Word + Character Wrapping Logic
    formatted_buffer=""
    current_line=""

    for word in $buffer; do
        if [[ ${#word} -gt $LINE_WIDTH ]]; then
            # If a word is too long, break it into chunks of LINE_WIDTH
            while [[ ${#word} -gt $LINE_WIDTH ]]; do
                formatted_buffer+="${word:0:LINE_WIDTH}\n"
                word="${word:LINE_WIDTH}"
            done
            current_line="$word"
        elif [[ $((${#current_line} + ${#word} + 1)) -gt $LINE_WIDTH ]]; then
            # Move to a new line when exceeding LINE_WIDTH
            formatted_buffer+="$current_line\n"
            current_line="$word"
        else
            # Append word to current line
            if [[ -z "$current_line" ]]; then
                current_line="$word"
            else
                current_line+=" $word"
            fi
        fi
    done
    formatted_buffer+="$current_line"

    echo -e "\f"  
    echo -e "$formatted_buffer"  

done | yad --text-info --width=200 --height=200 --tail
User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

thanks @superhik

I actually wanted the arrow keys to 'work' and also the return key.

Here is something along the lines of what I want. When typing from the terminal running the code it is formatting the text as expected but entering a return/newline exits the <span></span> formatting. I can pipe to yad --form --field=:txt --cycle-read and this dialogue is editable so i can directly input a carriage return/newline.

Code: Select all

FONTNAME='sans italic 18'
FCOL='blue'
while true ;do
	IFS='' read -rsN 1 ound
    case "$ound" in  
        $'\x1b')  # Handle escape sequences (like arrow keys)
            read -rsn2 -t 0.1 input
            continue  
            ;;  

        $'\x7f')  # Handle Backspace
            buffer=${buffer%?}  
            echo -ne "\b" >&2  
            ;;
         $'\r') buffer=${buffer%?}  
            echo -e "\n \n" >&2  
            ;;

        *) buffer="$buffer$ound"
           echo -ne "$ound" >&2
           ;;
    esac
	echo -e "\f"
	echo -e "<span font='$FONTNAME' fgcolor='$FCOL'>$buffer</span>"
done | yad --form --field=:txt --cycle-read

or

Code: Select all

FONTNAME='sans italic 18'
FCOL='blue'
while true ;do
	IFS='' read -rsN 1 ound
    case "$ound" in  
        $'\x1b')  # Handle escape sequences (like arrow keys)
            read -rsn2 -t 0.1 input
            continue  
            ;;  

        $'\x7f')  # Handle Backspace
            buffer=${buffer%?}  
            echo -ne "\b" >&2  
            ;;
         $'\r') buffer=${buffer%?}  
            echo -e "\n \n" >&2  
            ;;

        *) buffer="$buffer$ound"
           echo -ne "$ound" >&2
           ;;
    esac
	echo -e "\f"
	echo -e "<span font='$FONTNAME' fgcolor='$FCOL'>$buffer</span>"
done | yad --text-info --width=200 --height=200 --tail --enable-spell --formatted
superhik
Posts: 63
Joined: Mon Jun 19, 2023 7:56 pm
Has thanked: 6 times
Been thanked: 26 times

Re: YAD - tips'n'tricks

Post by superhik »

stemsee wrote: Wed Feb 26, 2025 4:25 am

thanks @superhik

I actually wanted the arrow keys to 'work' and also the return key.

Here is something along the lines of what I want. When typing from the terminal running the code it is formatting the text as expected but entering a return/newline exits the <span></span> formatting. I can pipe to yad --form --field=:txt --cycle-read and this dialogue is editable so i can directly input a carriage return/newline.

So you want to use pango markup.

Here's something to get you started.

Code: Select all

#!/bin/bash
buffer=""         # The text buffer
cursor_pos=0      # The cursor position (0 = start)

while true; do
    IFS='' read -rsN1 key

    case "$key" in
        $'\x1b')  # Escape sequence: arrow keys
            read -rsN2 -t 0.1 rest
            case "$rest" in
                "[C")  # Right arrow
                    if (( cursor_pos < ${#buffer} )); then
                        ((cursor_pos++))
                    fi
                    ;;
                "[D")  # Left arrow
                    if (( cursor_pos > 0 )); then
                        ((cursor_pos--))
                    fi
                    ;;
            esac
            ;;
        $'\x7f')  # Backspace
            if (( cursor_pos > 0 )); then
                buffer="${buffer:0:cursor_pos-1}${buffer:cursor_pos}"  # Remove character at cursor position
                ((cursor_pos--))
            fi
            ;;
        $'\r'|$'\n')  # Enter/Return: insert a newline into the buffer
            buffer="${buffer:0:cursor_pos}$key${buffer:cursor_pos}"  # Insert newline at cursor position
            ((cursor_pos++))
            ;;
        *)  # Regular character input
            buffer="${buffer:0:cursor_pos}$key${buffer:cursor_pos}"  # Insert character at cursor position
            ((cursor_pos++))
            ;;
    esac

    # Rebuild the styled output
    styled_buffer=""
    line_index=0  # Track line number

    for ((i=0; i<${#buffer}; i++)); do
        char="${buffer:$i:1}"

        # Check if we encounter a newline character
        if [[ "$char" == $'\n' ]]; then
            line_index=$((line_index + 1))  # New line, increment line_index
            styled_buffer+="$char"
        else
            # Apply styling for regular characters
            if (( i == cursor_pos )); then
                styled_buffer+="<span font_desc='Arial 14' foreground='red'>|</span>"
            fi
        styled_buffer+="<span font_desc='Arial 14' foreground='blue'>$char</span>"
        fi
    done

    # If the cursor is at the end, append a red cursor marker
    if (( cursor_pos == ${#buffer} )); then
        styled_buffer+="<span font_desc='Arial 14' foreground='red'>|</span>"
    fi

    # Output the formatted text to yad
    echo -e "\f"  # Clear the output
    echo "$styled_buffer"

done | yad --text-info --width=200 --height=200 --tail --enable-spell --formatted
User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

Thanks again @superhik

You make light work of it, clearly another expert to learn from!

If I may ask more of your skill, how to increment line_index with up and down arrows?

In fact right now I am using the up down arrows to change font and text color on the fly.

Code: Select all

                "[A") export FONT="$(yad --font)"
					;;
"[B") export FCOL="$(yad --color)"
					;;

I would like to be able to begin a new styled_buffer concatenated with the existent one with the new font and color. Presently all existing text adopts the new font and color retrospectively. Just keeping the $styled_buffer contents and adding a new <span font construction to subsequent additions would be desirable.

Also we still use the terminal to input text but it is no longer visible in the terminal only in the gui. Is there a choice?

superhik
Posts: 63
Joined: Mon Jun 19, 2023 7:56 pm
Has thanked: 6 times
Been thanked: 26 times

Re: YAD - tips'n'tricks

Post by superhik »

stemsee wrote: Thu Feb 27, 2025 5:01 am

Thanks again @superhik

You make light work of it, clearly another expert to learn from!

If I may ask more of your skill, how to increment line_index with up and down arrows?

In fact right now I am using the up down arrows to change font and text color on the fly.

Code: Select all

                "[A") export FONT="$(yad --font)"
					;;
"[B") export FCOL="$(yad --color)"
					;;

I would like to be able to begin a new styled_buffer concatenated with the existent one with the new font and color. Presently all existing text adopts the new font and color retrospectively. Just keeping the $styled_buffer contents and adding a new <span font construction to subsequent additions would be desirable.

Also we still use the terminal to input text but it is no longer visible in the terminal only in the gui. Is there a choice?

Should be this.

Code: Select all

#!/bin/bash
buffer=""          # The raw text buffer
cursor_pos=0       # The cursor position (0 = start)
total_lines=0      # Track total number of lines
current_font="Arial 14"  # Default font
current_color="blue"     # Default color
styles=()          # Array to store style for each character (font,color)

# Function to count lines and update total_lines
count_lines() {
    total_lines=$(echo -e "$buffer" | wc -l)
}

# Function to get current line number of cursor
get_current_line() {
    echo -e "${buffer:0:cursor_pos}" | wc -l
}

# Function to update terminal display
update_terminal() {
    tput clear >&2
    local before_cursor="${buffer:0:cursor_pos}"
    local after_cursor="${buffer:cursor_pos}"
    echo -ne "${before_cursor}|${after_cursor}" >&2
    local lines_before=$(echo -e "$before_cursor" | wc -l)
    local chars_last_line=$(echo -e "$before_cursor" | tail -n1 | wc -c)
    tput cup $((lines_before-1)) $((chars_last_line-1)) >&2
}

# Function to change font and color
change_style() {
    current_font="$1"
    current_color="$2"
}

# Function to build styled display buffer
build_display_buffer() {
    local display_buffer=""
    local i=0
    while (( i < ${#buffer} )); do
        if (( i == cursor_pos )); then
            display_buffer="${display_buffer}<span font_desc=\"Arial 14\" foreground=\"red\">|</span>"
        fi
        local char="${buffer:$i:1}"
        local style="${styles[$i]:-$current_font,$current_color}"  # Default to current if unset
        local font="${style%%,*}"
        local color="${style#*,}"
        if [[ "$char" == $'\n' ]]; then
            display_buffer="${display_buffer}\n"
        else
            char="$(echo "$char" | sed -E 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')" # Escape special characters
            display_buffer="${display_buffer}<span font_desc=\"${font}\" foreground=\"${color}\">${char}</span>"
        fi
        ((i++))
    done
    if (( cursor_pos == ${#buffer} )); then
        display_buffer="${display_buffer}<span font_desc=\"Arial 14\" foreground=\"red\">|</span>"
    fi
    echo "$display_buffer"
}

while true; do
    IFS='' read -rsN1 key

    case "$key" in
        $'\x1b')  # Escape sequence: arrow keys
            read -rsN2 -t 0.1 rest
            case "$rest" in
                "[C")  # Right arrow
                    if (( cursor_pos < ${#buffer} )); then
                        ((cursor_pos++))
                    fi
                    ;;
                "[D")  # Left arrow
                    if (( cursor_pos > 0 )); then
                        ((cursor_pos--))
                    fi
                    ;;
                "[A")  # Up arrow
                    if (( $(get_current_line) > 1 )); then
                        current_line=$(get_current_line)
                        chars_before=$(echo -e "${buffer:0:cursor_pos}" | tail -n1 | wc -c)
                        prev_lines=$(echo -e "$buffer" | head -n $((current_line-1)))
                        prev_line_length=$(echo -e "$prev_lines" | tail -n1 | wc -c)
                        cursor_pos=$(echo -e "$prev_lines" | wc -c)
                        ((cursor_pos--))
                        if (( chars_before - 1 < prev_line_length - 1 )); then
                            ((cursor_pos -= (prev_line_length - chars_before)))
                        fi
                    fi
                    ;;
                "[B")  # Down arrow
                    if (( $(get_current_line) < total_lines )); then
                        current_line=$(get_current_line)
                        chars_before=$(echo -e "${buffer:0:cursor_pos}" | tail -n1 | wc -c)
                        lines_before=$(echo -e "$buffer" | head -n $current_line | wc -c)
                        next_lines=$(echo -e "$buffer" | tail -n +$((current_line+1)))
                        next_line_length=$(echo -e "$next_lines" | head -n1 | wc -c)
                        cursor_pos=$lines_before
                        if (( chars_before - 1 < next_line_length - 1 )); then
                            ((cursor_pos += chars_before - 1))
                        else
                            ((cursor_pos += next_line_length - 1))
                        fi
                    fi
                    ;;
            esac
            ;;
        $'\x7f')  # Backspace
            if (( cursor_pos > 0 )); then
                buffer="${buffer:0:cursor_pos-1}${buffer:cursor_pos}"
                for ((i=cursor_pos-1; i<${#styles[@]}-1; i++)); do
                    styles[$i]=${styles[$((i+1))]}
                done
                unset styles[${#styles[@]}-1]
                ((cursor_pos--))
            fi
            ;;
        $'\r'|$'\n')  # Enter/Return
            buffer="${buffer:0:cursor_pos}${key}${buffer:cursor_pos}"
            for ((i=${#buffer}-1; i>cursor_pos; i--)); do
                styles[$i]=${styles[$((i-1))]}
            done
            styles[cursor_pos]=""
            ((cursor_pos++))
            ;;
        "!")  # Change to grey Times
            change_style "Times 14" "grey"
            continue
            ;;
        "@")  # Change to green Courier
            change_style "Courier 14" "green"
            continue
            ;;
        *)  # Regular character input
            buffer="${buffer:0:cursor_pos}${key}${buffer:cursor_pos}"
            for ((i=${#buffer}-1; i>cursor_pos; i--)); do
                styles[$i]=${styles[$((i-1))]}
            done
            styles[cursor_pos]="$current_font,$current_color"
            ((cursor_pos++))
            ;;
    esac

    # Update line count
    count_lines

    # Build and output styled display buffer
    display_buffer="$(build_display_buffer)"

    # Update terminal display
    update_terminal

    # Output to yad
    echo -e "\f"
    echo -e "$display_buffer"

done | yad --text-info --width=200 --height=200 --tail --enable-spell --formatted

Use "!" and "@" to change the color.
There is an array that stores style for each character, which may be slow it but could not make it work the other way.
Managing style tags is not that easy.
Sometimes up and down arrows are not positioning cursor correctly but I can't figure out why.

User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

IT works ... Many Thanks!

I have added a simple yad form to select font and color on '[' ... this way there are many fonts and colors to select. I will try to add underline, overline, rise and strikethrough to the style array.

xscreenshot-20250315T102549.png
xscreenshot-20250315T102549.png (47.65 KiB) Viewed 188 times

Code: Select all

        "[")  # Change font and color
        function markupfn {
		SETS=$(yad --title="Formatted Preview" --text="$TXT" --form --field=:fn "sans 14" --field=text:clr "black" --field=back:clr "white" --field=underline:cb "none,single,double" --field=strikethru:chk "false" --field=rise:cb "superscript,subscript")
		
		IFS='|' read -r font fcol bcol under strike rise<<<"$SETS"
		export underline="$under"
		export strike="$strike"
		export rise="$rise"
		
        change_style "$font" "$fcol"

		}; export -f markupfn
	   markupfn
            continue
            ;;
        "]")  # Change to black
            change_style "Sans Bold 14" "black"
            continue
            ;;
User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

I see what you mean about slowing down as the buffer content grows!

Better would be after each word instead of each character. This might be implemented by detecting word boundaries or spaces and substituting in the markup.

EDIT: Another approach might be to delete per character markup for sections with the same style, just keeping initial, <span font='' foreground=''> and end, </span> markup tags per group. This clean up would occur retrospectively with each newline character, and subsequently with each style change, changes acting upon the content of the buffer, or if the buffer is dumped to a file first then on that file.

User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

If i create a style_set array (which may be presets), which simply creates an indexed array of each successive change of style, with '</span>' being index 0

Code: Select all

# declare -a style_set=( '</span>' '<span font=\"${font[0]}\" foreground=\"${col[0]}>' '<span font=\"${font[1]}\" foreground=\"${col[1]}>' '<span font=\"${font[2]}\" foreground=\"${col[2]}>' '<span font=\"${font[3]}\" foreground=\"${col[3]}>' )
# echo "${style_set[0]}"
</span>
# 

. Each successive style change denotes a 'section'. This way every change is a new section, even if it is a previously used style, it is still a new section. This array can be used with a regex argument to delete duplicate format strings per 'styled' word, and this will occur every time a $'\s' character is entered. There will also be a character counter so we know exactly how many duplicates of '<span font='' foreground=''>' and '</span>' strings to remove, -1, for each word, before resetting the character counter. There will also be a space counter so that after every nth space, excess format strings can again be culled.

User avatar
stemsee
Posts: 830
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 142 times
Contact:

Re: YAD - tips'n'tricks

Post by stemsee »

To do:

1) style_sets array
2) character counter which resets with space key entry.
3) remove duplicate markup strings per word on space key entry.
4) remove duplicate markup strings per section on style changes.

Post Reply

Return to “Programming”