# Simple GUI to choose what bits to set in a 32-bit hex value
# Run using "wish" as "wish hexy.tcl"
#

wm title . "Bit Twiddler"
wm resizable . 0 0

frame .numbers
grid .numbers -row 0 -columnspan 32

set hexvalue "0x00000000"
set decvalue "0"
set sdecvalue "0"
set dotdecvalue "0.0.0.0"
set minbit 0
set maxbit 0
set rangevalue 0
set hrangevalue 0

label .numbers.hex -textvariable hexvalue
label .numbers.dec -textvariable decvalue
label .numbers.sdec -textvariable sdecvalue
grid .numbers.hex .numbers.dec .numbers.sdec

label .numbers.dotdec -textvariable dotdecvalue
grid .numbers.dotdec -row 0 -column 3

label .prompt -text "Bit Pattern:"
grid .prompt -row 1 -columnspan 32

for {set i 0} {$i < 32} {incr i} {
        set j [expr {31 - $i}]

        label .bit$j -text $j
        checkbutton .cb$j -command { manage_hexvalue } -variable cb$j -onvalue [expr {1 << $j}] -text {}
        if {$i < 16} {
                grid .bit$j -row 2 -column $i
                grid .cb$j -row 3 -column $i
        } else {
                grid .bit$j -row 4 -column [expr $i - 16]
                grid .cb$j -row 5 -column [expr $i - 16]
        }

}

frame .bf
grid .bf -row 6 -columnspan 32

button .bf.a0 -text "All 0's"     -command { eval_hex 00000000 }
button .bf.a1 -text "All 1's"     -command { eval_hex FFFFFFFF }
button .bf.sl -text "Shift Left"  -command { global hexvalue; eval_hex [format "%X" [expr int($hexvalue << 1)]] }
button .bf.sr -text "Shift Right" -command { global hexvalue; eval_hex [format "%X" [expr int($hexvalue >> 1)]] }
button .bf.tb -text "Toggle"      -command { global hexvalue; eval_hex [format "%X" [expr int(~$hexvalue)]] }
grid .bf.a0 .bf.a1 .bf.sl .bf.sr .bf.tb

proc manage_hexvalue {} {
        global minbit
        global maxbit
        global hexvalue
        global decvalue
        global sdecvalue
        global dotdecvalue

        set result 0
        for {set i 0} {$i < 32} {incr i} {
                global cb$i
                set cbname cb$i
                set result [expr $result | [set $cbname]]
        }
        set hexvalue [format "0x%08X" $result]
        set decvalue [format "%u" $result]
        set sdecvalue [format "%d" $result]

        set dot1 [expr ($result >> 24) & 0xFF]
        set dot2 [expr ($result >> 16) & 0xFF]
        set dot3 [expr ($result >>  8) & 0xFF]
        set dot4 [expr ($result >>  0) & 0xFF]
        set dotdecvalue [format "%d.%d.%d.%d" $dot1 $dot2 $dot3 $dot4]

        set_range $minbit $maxbit
}

frame .he
grid .he -row 7 -columnspan 32

label .he.prompt2 -text "Enter hex value to see bit pattern:"
entry .he.hexentry -width 20 -justify center -bg white -fg black \
        -validate key -validatecommand { val_entry %P }
grid .he.prompt2 .he.hexentry


proc val_bit { ifvalidstr } {
        if { [string length $ifvalidstr] > 2 } { bell; return 0 }
        if { ![string is digit $ifvalidstr] } { bell; return 0 }
        if { ($ifvalidstr < 0) || ($ifvalidstr > 31) } { bell; return 0 }

        return 1
}

proc set_range { min max } {
        global decvalue
        global rangevalue
        global hrangevalue


        if { [string length $min] == 0 } { return }
        if { [string length $max] == 0 } { return }
        if { $min > $max } { return }
        if { $min < 0 || $min > 31 || $max < 0 || $max > 31 } { return }

        set lobits [expr ((1 << $min) - 1)]
        if { $max == 31 } {
                set wantbits [expr ~$lobits]
        } else {
                set hibits [expr ((1 << ($max + 1)) - 1)]
                set wantbits [expr $hibits & ~$lobits]
        }
        set rangevalue [expr ($decvalue & $wantbits) >> $min]

        set hrangevalue [format "0x%X" $rangevalue]
}

proc val_minbit { ifvalidstr } {
        global maxbit

        if { [string length $ifvalidstr] == 0 } { return 1 }
        if { ![val_bit $ifvalidstr] } { return 0 }

        set_range $ifvalidstr $maxbit
        return 1
}

proc val_maxbit { ifvalidstr } {
        global minbit

        if { [string length $ifvalidstr] == 0 } { return 1 }
        if { ![val_bit $ifvalidstr] } { return 0 }

        set_range $minbit $ifvalidstr
        return 1
}

frame .vr
grid .vr -row 8 -columnspan 32
label .vr.prompt3 -text "Value of bits between "
entry .vr.min -width 2 -bg white -fg black -textvariable minbit \
        -validate key -validatecommand { val_minbit %P }
label .vr.prompt4 -text "and "
entry .vr.max -width 2 -bg white -fg black -textvariable maxbit \
        -validate key -validatecommand { val_maxbit %P }
label .vr.prompt5 -text " : "
label .vr.vr -textvariable rangevalue
label .vr.prompt6 -text "("
label .vr.vhr -textvariable hrangevalue
label .vr.prompt7 -text ")"
grid .vr.prompt3 .vr.min .vr.prompt4 .vr.max .vr.prompt5 .vr.vr .vr.prompt6 .vr.vhr .vr.prompt7

proc val_entry { ifvalidstr } {
        if {[string length $ifvalidstr] > 8 } { bell; return 0 }
        if {![string is xdigit $ifvalidstr]} { bell; return 0 }

        eval_hex $ifvalidstr
        return 1
}

proc eval_hex { hexentry } {

        if {![string length $hexentry]} { set hexentry 0 }

        set hexentry [regsub {^0[xX]} $hexentry ""]

        set hexstr [binary format I* 0x$hexentry]
        binary scan $hexstr B* binstr

        for {set i 0} {$i < 32} {incr i} {
                set nextbit [string index $binstr end-$i]
                if {$nextbit} {
                        .cb$i select
                } else {
                        .cb$i deselect
                }
        }

        manage_hexvalue
}
