diff options
Diffstat (limited to 'lib/lib')
-rwxr-xr-x | lib/lib/Math.tcl | 954 | ||||
-rwxr-xr-x | lib/lib/hexeditor.tcl | 2705 | ||||
-rwxr-xr-x | lib/lib/ihextools.tcl | 523 | ||||
-rwxr-xr-x | lib/lib/innerwindow.tcl | 360 | ||||
-rwxr-xr-x | lib/lib/settings.tcl | 293 |
5 files changed, 4835 insertions, 0 deletions
diff --git a/lib/lib/Math.tcl b/lib/lib/Math.tcl new file mode 100755 index 0000000..bf2be0d --- /dev/null +++ b/lib/lib/Math.tcl @@ -0,0 +1,954 @@ +#!/usr/bin/tclsh + +############################################################################ +# Copyright (C) 2007-2009 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# 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 2 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, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + +# -------------------------------------------------------------------------- +# DESCRIPTION +# Primarily implements convertions between numeric systems and angle units. +# -------------------------------------------------------------------------- + +## ---------------------------------------------------------------------- +## Converts between numeric systems and checks numbers types + # + # Supported num. systems: binary, octal, decimal, hexadecimal and ASCII. + # note: Excepting H->Q and Q->H, are all convertions computed + # directly (for speed improvement). + # By default maximal number length after dot is 20 (for DEC -> ...). + # ---------------------------------------------------------------------- + # + # USAGE: + # + # puts [ NumSystem::hex2dec F.4 ] ;# --> 15.25 + # puts [ NumSystem::hex2oct F.4 ] ;# --> 17.2 + # puts [ NumSystem::hex2bin F.4 ] ;# --> 1111.01 + # + # puts [ NumSystem::dec2hex 15.25 ] ;# --> F.4 + # puts [ NumSystem::dec2oct 15.25 ] ;# --> 17.2 + # puts [ NumSystem::dec2bin 15.25 ] ;# --> 1111.01 + # + # puts [ NumSystem::oct2hex 17.2 ] ;# --> F.4 + # puts [ NumSystem::oct2dec 17.2 ] ;# --> 15.25 + # puts [ NumSystem::oct2bin 17.2 ] ;# --> 1111.01 + # + # puts [ NumSystem::bin2hex 1111.01 ] ;# --> F.4 + # puts [ NumSystem::bin2dec 1111.01 ] ;# --> 15.25 + # puts [ NumSystem::bin2oct 1111.01 ] ;# --> 17.2 + # + # puts [ NumSystem::ascii2dec @ ] ;# --> 64 + # puts [ NumSystem::ascii2bin @ ] ;# --> 01000000 + # + # puts [ NumSystem::ishex F.4 ] ;# --> 1 + # puts [ NumSystem::isdec 15.25 ] ;# --> 1 + # puts [ NumSystem::isoct 17.2 ] ;# --> 1 + # puts [ NumSystem::isbin 1111.01 ] ;# --> 1 + # ----------------------------------------------------------------------- + +namespace eval NumSystem { + + variable precision {20} ;# maximal number of digits after dot + + # ----------------------------------------------------------------------- + # NUMERIC SYSTEMS CONVERTIONS + # ----------------------------------------------------------------------- + + # HEX -> ... + + ## Hexadecimal -> Decimal + # required procedures: `is_X', `hexoct_to_dec', `aux_hexoct_to_dec', `asserthex', `ishex' + # @parm Number number - value to convert + # @return Number - converted value + proc hex2dec {number} { + return [hexoct_to_dec 16 $number] + } + + ## Hexadecimal -> Octal + # required procedures: `is_X', `bin2oct', `hex2bin', `asserthex', `assertbin', `ishex', isbin' + # @parm Number number - value to convert + # @return Number - converted value + proc hex2oct {number} { + return [bin2oct [hex2bin $number]] + } + + ## Hexadecimal -> Binary + # required procedures: `asserthex', `ishex' + # @parm Number number - value to convert + # @return Number - converted value + proc hex2bin {number} { + + # verify value validity + asserthex $number + + # Number can be negative value + set sign {} + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + set sign {-} + } + + # make it upper-case + set number [string toupper $number] + + # split value to list of chars + set number [split $number ""] + + # convert value + set result {} + foreach char $number { + switch $char { + {0} {append result {0000}} + {1} {append result {0001}} + {2} {append result {0010}} + {3} {append result {0011}} + {4} {append result {0100}} + {5} {append result {0101}} + {6} {append result {0110}} + {7} {append result {0111}} + {8} {append result {1000}} + {9} {append result {1001}} + {A} {append result {1010}} + {B} {append result {1011}} + {C} {append result {1100}} + {D} {append result {1101}} + {E} {append result {1110}} + {F} {append result {1111}} + {.} {append result {.}} + } + } + + # return result + regsub {^0+} $result {} result + if {[regexp {\.} $result]} { + regsub {0+$} $result {} result + } + regsub {\.$} $result {} result + regsub {^\.} $result {0.} result + if {[string length $result] == 0} { + set result 0 + } + return $sign$result + } + + # DEC -> ... + + ## Decimal -> Hexadecimal + # required procedures: `is_X', `assertdec', isdec' + # @parm Number number - value to convert + # @return Number - converted value + proc dec2hex {number} { + return [dec_to_X 16 $number] + } + + ## Decimal -> Octal + # required procedures: `is_X', `assertdec', isdec' + # @parm Number number - value to convert + # @return Number - converted value + proc dec2oct {number} { + return [dec_to_X 8 $number] + } + + ## Decimal -> Binary + # required procedures: `is_X', `assertdec', `isdec' + # @parm Number number - value to convert + # @return Number - converted value + proc dec2bin {number} { + return [dec_to_X 2 $number] + } + + # OCT -> ... + + ## Octal -> Hexadecimal + # required procedures: `is_X', `bin2hex', `oct2bin', `bin_to_hexoct', + # `aux_hexoct_to_dec', `assertoct', `assertbin',`isbin',`isoct' + # @parm Number number - value to convert + # @return Number - converted value + proc oct2hex {number} { + return [bin2hex [oct2bin $number]] + } + + ## Octal -> Decimal + # required procedures: `is_X', `bin_to_hexoct', `aux_hexoct_to_dec', `assertoct',`isoct' + # @parm Number number - value to convert + # @return Number - converted value + proc oct2dec {number} { + return [hexoct_to_dec 8 $number] + } + + ## Octal -> Binary + # required procedures: `is_X', `assertoct', isoct' + # @parm Number number - value to convert + # @return Number - converted value + proc oct2bin {number} { + + # verify value validity + assertoct $number + + # Number can be negative value + set sign {} + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + set sign {-} + } + + # split value to list of chars + set number [split $number ""] + + # convert value + set result {} + foreach char $number { + switch $char { + {0} {append result {000}} + {1} {append result {001}} + {2} {append result {010}} + {3} {append result {011}} + {4} {append result {100}} + {5} {append result {101}} + {6} {append result {110}} + {7} {append result {111}} + {.} {append result {.}} + } + } + + # return result + regsub {^0+} $result {} result + if {[regexp {\.} $result]} { + regsub {0+$} $result {} result + } + regsub {\.$} $result {} result + regsub {^\.} $result {0.} result + if {[string length $result] == 0} { + set result 0 + } + return $sign$result + } + + # BIN -> ... + + ## Binary -> Hexadecimal + # required procedures: `is_X', `assertbin', isbin', `bin_to_hexoct' + # @parm Number number - value to convert + # @return Number - converted value + proc bin2hex {number} { + assertbin $number ;# verify value validity + return [bin_to_hexoct 16 $number] ;# convert value + } + + ## Binary -> Decimal + # required procedures: `is_X', `assertbin', isbin' + # @parm Number number - value to convert + # @return Number - converted value + proc bin2dec {number} { + + # verify value validity + assertdec $number + + # Number can be negative value + set sign {} + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + set sign {-} + } + + # split value to int. part + regexp {^\d+} $number int + + # split value to frac. part + if {[regexp {\.\d+$} $number frac]} { + set frac [string range $frac 1 end] + set nofrac 0 + } { + set frac {} + set nofrac 1 + } + + # compute int. part + set tmp [expr [string length $int] -1] + regexp {^\d+} [expr pow(2,$tmp)] tmp + set result 0 + foreach value [split $int ""] { + if {$value} { + set result [expr {$result+$tmp}] + } + set tmp [expr {$tmp / 2}] + if {$tmp == 0} {break} + } + set int $result + + # compute frac. part + if {!$nofrac} { + set tmp 0.5 + set result 0 + foreach value [split $frac ""] { + if {$value} { + set result [expr {$result+$tmp}] + } + set tmp [expr {$tmp / 2}] + } + regexp {\d+$} $result frac + + # return converted value with frac. + return $sign$int.$frac + } + + # return converted value without frac. + return $sign$int + } + + ## Binary -> Octal + # required procedures: `is_X', `assertbin', isbin', `bin_to_hexoct' + # @parm Number number - value to convert + # @return Number - converted value + proc bin2oct {number} { + assertbin $number ;# verify value validity + return [bin_to_hexoct 8 $number] ;# convert value + } + + ## Ascii char -> Bin + # required procedures: (none) + # @parm Char number - value to convert + # @return mixed - converted value or an empty string + proc ascii2bin {number} { + if {[string bytelength $number] != 1} { + return {} + } + + set result {} + scan $number {%c} result + if {$result != {}} { + return [dec2bin $result] + } + + return $result + } + + ## Ascii char -> Dec + # required procedures: (none) + # @parm Char number - value to convert + # @return mixed - converted value or an empty string + proc ascii2dec {number} { + if {[string bytelength $number] != 1} { + return {} + } + set result {} + scan $number {%c} result + + return $result + } + + # ----------------------------------------------------------------------- + # TYPE ASSERTION + # ----------------------------------------------------------------------- + + ## Raise error if the given string is not an hexadecimal value + # require procedures: `is_X',`ishex' + # @parm String number - string to evaluate + # @return mixed - void (failure) or 1 (successful) + proc asserthex {number} { + if {![ishex $number]} { + error "asserthex: Excepted hexadecimal value but got \"$number\"" + } { + return 1 + } + } + + ## Raise error if the given string is not an decimal value + # require procedures: `is_X',`isdec' + # @parm String number - string to evaluate + # @return mixed - void (failure) or 1 (successful) + proc assertdec {number} { + if {![isdec $number]} { + error "assertdec: Excepted decimal value but got \"$number\"" + } { + return 1 + } + } + + ## Raise error if the given string is not an octal value + # require procedures: `is_X',`isoct' + # @parm String number - string to evaluate + # @return mixed - void (failure) or 1 (successful) + proc assertoct {number} { + if {![isoct $number]} { + error "assertoct: Excepted octal value but got \"$number\"" + } { + return 1 + } + } + + ## Raise error if the given string is not an binary value + # require procedures: `is_X',`isbin' + # @parm String number - string to evaluate + # @return mixed - void (failure) or 1 (successful) + proc assertbin {number} { + if {![isbin $number]} { + error "assertbin: Excepted binary value but got \"$number\"" + } { + return 1 + } + } + + # ----------------------------------------------------------------------- + # TYPE CHECKING + # ----------------------------------------------------------------------- + + ## Check if the given string can be an Hexadecimal value + # require procedure: `is_X' + # @parm String number - value to evaluate + # @return bool + proc ishex {number} { + return [is_X {^[0-9A-Fa-f\.]+$} $number] + } + + ## Check if the given string can be an Decimal value + # require procedure: `is_X' + # @parm String number - value to evaluate + # @return bool + proc isdec {number} { + return [is_X {^[0-9\.]+$} $number] + } + + ## Check if the given string can be an Octal value + # require procedure: `is_X' + # @parm String number - value to evaluate + # @return bool + proc isoct {number} { + return [is_X {^[0-7\.]+$} $number] + } + + ## Check if the given string can be an Binary value + # require procedure: `is_X' + # @parm String number - value to evaluate + # @return bool + proc isbin {number} { + return [is_X {^[01\.]+$} $number] + } + + # ----------------------------------------------------------------------- + # AUXILIARY PROCEDURES + # ----------------------------------------------------------------------- + + ## Auxiliary procedure for convering hex. and oct. to dec. + # require procedures: `is_X',`aux_hexoct_to_dec', `assertoct', `asserthex', `ishex', `isoct' + # @access PRIVATE + # @parm base - source numeric system, posible values are: 8 and 16 + # @parm Number number - value to convert + # @return Number - converted value + proc hexoct_to_dec {base number} { + + # Number can be negative value + set sign {} + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + set sign {-} + } + + # make number upper-case + set number [string toupper $number] + + # verify value validity + if {$base == 8} { + assertoct $number + set char_len 3 + } { + asserthex $number + set char_len 4 + } + + # split value to int. part + regexp {^[^\.]+} $number int + + # split value to frac. part + if {[regexp {\.[^\.]+$} $number frac]} { + set frac [string range $frac 1 end] + set nofrac 0 + } { + set frac {} + set nofrac 1 + } + + # compute int. part + if {$base == 8} { + set int [expr "0$int"] + } { + set int [expr "0x$int"] + } + + # compute frac. part + if {!$nofrac} { + set frac [aux_hexoct_to_dec [split $frac {}] 1.0 $base] + regexp {\d+$} $frac frac + return $sign$int.$frac + } + return $sign$int + } + + ## Auxiliary procedure for convering hex. and oct. to dec. + # require procedures: none + # @access PRIVATE + # @parm List vals_list - value to convert splited to a single characters + # @parm Number v0 - decimal value of highes bit in the number multipled by 2 + # @parm base - source numeric system, posible values are: 8 and 16 + # @return Number - converted value + proc aux_hexoct_to_dec {vals_list v0 base} { + set result 0 + + foreach char $vals_list { + + if {$base == 8} { + set v3 $v0 + } { + set v3 [expr {$v0 / 2}] + } + set v2 [expr {$v3 / 2}] + set v1 [expr {$v2 / 2}] + set v0 [expr {$v1 / 2}] + + switch $char { + {0} {set bool_map {0 0 0 0}} + {1} {set bool_map {0 0 0 1}} + {2} {set bool_map {0 0 1 0}} + {3} {set bool_map {0 0 1 1}} + {4} {set bool_map {0 1 0 0}} + {5} {set bool_map {0 1 0 1}} + {6} {set bool_map {0 1 1 0}} + {7} {set bool_map {0 1 1 1}} + {8} {set bool_map {1 0 0 0}} + {9} {set bool_map {1 0 0 1}} + {A} {set bool_map {1 0 1 0}} + {B} {set bool_map {1 0 1 1}} + {C} {set bool_map {1 1 0 0}} + {D} {set bool_map {1 1 0 1}} + {E} {set bool_map {1 1 1 0}} + {F} {set bool_map {1 1 1 1}} + } + + foreach cond $bool_map value "$v3 $v2 $v1 $v0" { + if {$cond} { + set result [expr {$result+$value}] + } + } + } + return $result + } + + ## Auxiliary procedure for convering bin. to hex. and oct. + # require procedures: none + # @access PRIVATE + # @parm Int base - target numeric system, posible values are: 8 and 16 + # @parm Number number - value to convert + # @return Number - converted value + proc bin_to_hexoct {base number} { + + # Number can be negative value + set sign {} + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + set sign {-} + } + + # set some essential variables + if {$base == 8} { + set modulo 3 + set mod_1 2 + set padding {} + set convCmd {oct_to_bin} + } { + set modulo 4 + set mod_1 3 + set padding {0} + set convCmd {hex_to_bin} + } + + # split value to int. and frac. part + regexp {^\d+} $number int + if {[regexp {\.\d+$} $number frac]} { + set frac [string range $frac 1 end] + set nofrac 0 + } { + set frac {} + set nofrac 1 + } + + # convert int + set result {} + + set length [string length $int] + set length [expr {($length % $modulo) - 1}] + if {$length >= 0} { + set firstvalue [string range $int 0 $length] + set int [string range $int [expr {$length + 1}] end] + + switch $length { + {0} {set firstvalue "${padding}00$firstvalue"} + {1} {set firstvalue "${padding}0$firstvalue"} + {2} {set firstvalue "${padding}$firstvalue"} + } + + lappend result $firstvalue + } + + while {$int != ""} { + lappend result [string range $int 0 $mod_1] + set int [string range $int $modulo end] + } + + set int [$convCmd $result] + regsub {^0+} $int {} int + if {$int == {}} {set int 0} + + # convert frac + set result {} + if {!$nofrac} { + # make list + set idx -1 + while {$frac != ""} { + lappend result [string range $frac 0 $mod_1] + set frac [string range $frac $modulo end] + incr idx + } + + set lastValue [lindex $result $idx] + switch [string length $lastValue] { + {1} {lset result $idx "${lastValue}${padding}00"} + {2} {lset result $idx "${lastValue}${padding}0"} + {3} {lset result $idx "${lastValue}${padding}"} + } + + set frac [$convCmd $result] + regsub {0+$} $frac {} frac + if {$frac == {}} {set frac 0} + + # return converted value with frac. + return $sign$int.$frac + } + + # return converted value without frac. + return $sign$int + } + + ## Auxiliary procedure for convering dec to hex, oct, bin + # require procedures: `is_X', `assertdec', `isdec' + # @access PRIVATE + # @parm Int base - target numeric system, posible values are: 2, 8, 10, 16 + # @parm Number number - value to convert + # @return Number - converted value + proc dec_to_X {base number} { + variable precision + + # verify values validity + if {!($base==16 || $base==10 || $base==8 || $base==2)} { + error "dec_to_X: Unrecognized numeric system \"$base\". Possible values are: 2, 8, 10, 16" + } + assertdec $number + + # Number can be negative value + set sign {} + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + set sign {-} + } + + # split value to int. and frac. part + regexp {^\d+} $number int + if {[regexp {\.\d+$} $number frac]} { + set frac [string range $frac 1 end] + set nofrac 0 + } { + set frac {} + set nofrac 1 + } + + if {[string length $int] > 12} { + error "Unable to convert, value is too high" + } + + # convert integer part + set reminder $int + set int "" + while {$reminder > 0} { + set tmp [expr {$reminder % $base}] + if {$base == 16} { + switch $tmp { + 10 {set tmp A} + 11 {set tmp B} + 12 {set tmp C} + 13 {set tmp D} + 14 {set tmp E} + 15 {set tmp F} + } + } + set int ${tmp}${int} + regexp {^\d+} [expr {$reminder / $base}] reminder + } + if {$int == {}} {set int 0} + + # convert frac. part + if {!$nofrac} { + set reminder "0.$frac" + set frac "" + for {set i 0} {$i < $precision} {incr i} { + set reminder [expr {$reminder * $base}] + regexp {^\d+} $reminder tmp + set reminder [expr {$reminder - $tmp}] + if {$base == 16} { + switch $tmp { + 10 {set tmp A} + 11 {set tmp B} + 12 {set tmp C} + 13 {set tmp D} + 14 {set tmp E} + 15 {set tmp F} + } + } + append frac $tmp + if {$reminder == 0} {break} + } + if {$frac == {}} {set frac 0} + + # return converted value with frac. + return $sign$int.$frac + } + + # return converted value without frac. + return $sign$int + } + + ## Auxiliary procedure for convering oct to bin + # require procedures: none + # @access PRIVATE + # @parm List vals_list - value to convert splited to single characters + # @return Number - converted value + proc oct_to_bin {vals_list} { + + # iterate over items in list and traslate them + set result "" + foreach char $vals_list { + # convert item + switch $char { + {000} {append result 0} + {001} {append result 1} + {010} {append result 2} + {011} {append result 3} + {100} {append result 4} + {101} {append result 5} + {110} {append result 6} + {111} {append result 7} + } + } + + # done + return $result + } + + ## Auxiliary procedure for convering hex to bin + # require procedures: none + # @access PRIVATE + # @parm List vals_list - value to convert splited to single characters + # @return Number - converted value + proc hex_to_bin {vals_list} { + + # iterate over items in list and traslate them + set result "" + foreach char $vals_list { + # convert item + switch $char { + {0000} {append result 0} + {0001} {append result 1} + {0010} {append result 2} + {0011} {append result 3} + {0100} {append result 4} + {0101} {append result 5} + {0110} {append result 6} + {0111} {append result 7} + {1000} {append result 8} + {1001} {append result 9} + {1010} {append result A} + {1011} {append result B} + {1100} {append result C} + {1101} {append result D} + {1110} {append result E} + {1111} {append result F} + } + } + + # done + return $result + } + + ## Auxiliary procedure for num. checking + # Check if the given string contain 0 or 1 dot and match the given + # regular expression + # @access PRIVATE + # @parm String regexpr - reg. exp. of allowed symbols + # @parm String number - string to evaluate + # return bool + proc is_X {regexpr number} { + + # The given number can begin with minus sign + if {[string index $number 0] == {-}} { + set number [string range $number 1 end] + } + + # 1st condition (check for allowed symbols) + if {![regexp $regexpr $number]} { + return 0 + } + + # 2nd condition (must contain maximaly one dot) + set cnd1 [split $number {\.}] + if {[llength $cnd1] > 2} { + return 0 + } + + # 3rd condition (dot must not be at the begining or end) + if {[regexp {^\.} $number]} {return 0} + if {[regexp {\.$} $number]} {return 0} + + # return result + return 1 + } +} + + +## ---------------------------------------------------------------------- +## Converts between angle units and normalizes angle values + # + # Supported angle units: rad, deg, grad + # note: all converted angles are normalized before convertion + # ----------------------------------------------------------------------- + # + # USAGE: + # + # puts [ Angle::adjustAngle deg -700 ] ;# --> 20.0000000016 (should be exactly 20) + # + # puts [ Angle::rad2deg $Angle::PI] ;# --> 180.0 + # puts [ Angle::rad2grad $Angle::PI] ;# --> 200.0 + # + # puts [ Angle::deg2rad 180 ] ;# --> 3.141592654 + # puts [ Angle::deg2grad 180 ] ;# --> 200.0 + # + # puts [ Angle::grad2deg 200 ] ;# --> 180.0 + # puts [ Angle::grad2rad 200 ] ;# --> 3.141592654 + # + # puts $Angle::PI ;# --> 3.141592654 + # ----------------------------------------------------------------------- + +namespace eval Angle { + + variable PI {3.141592654} ;# Pi + + # CONVERSION OF ANGLE VALUES + # -------------------------- + + ## Radians -> Degrees + # require procedure: `adjustAngle' + # @parm Number angle - angle value to convert + # @return Nubmber - converted value + proc rad2deg {angle} { + variable PI + + set angle [adjustAngle rad $angle] + return [expr {(180 / $PI) * $angle}] + } + + ## Radians -> GRAD + # require procedure: `adjustAngle' + # @parm Number angle - angle value to convert + # @return Nubmber - converted value + proc rad2grad {angle} { + variable PI + + set angle [adjustAngle rad $angle] + return [expr {(200 / $PI) * $angle}] + } + + ## Degrees -> Radians + # require procedure: `adjustAngle' + # @parm Number angle - angle value to convert + # @return Nubmber - converted value + proc deg2rad {angle} { + variable PI + + set angle [adjustAngle deg $angle] + return [expr {($PI / 180) * $angle}] + } + + ## Degrees -> Radians + # require procedure: `adjustAngle' + # @parm Number angle - angle value to convert + # @return Nubmber - converted value + proc deg2grad {angle} { + set angle [adjustAngle deg $angle] + return [expr {(10 / 9.0) * $angle}] + } + + ## GRAD -> Degrees + # require procedure: `adjustAngle' + # @parm Number angle - angle value to convert + # @return Nubmber - converted value + proc grad2deg {angle} { + set angle [adjustAngle grad $angle] + return [expr {0.9 * $angle}] + } + + ## GRAD -> Radians + # require procedure: `adjustAngle' + # @parm Number angle - angle value to convert + # @return Nubmber - converted value + proc grad2rad {angle} { + variable PI + + set angle [adjustAngle grad $angle] + return [expr {($PI / 200) * $angle}] + } + + ## Ajust angle value and polarity + # @parm String unit - unit of angle (rad | deg | grad) + # @parm angle angle - value of angle + # @return angle - adjusted angle value + proc adjustAngle {unit angle} { + variable PI + + # verify if the given angle is a valid number + if {![regexp {^\-?\d+(\.\d+)?$} $angle]} { + error "adjustAngle: Excepted integer or float but got \"$angle\"" + } + + # determinate base for division + switch $unit { + {rad} {set base [expr {$PI * 2}]} + {deg} {set base 360.0} + {grad} {set base 400.0} + default {error "Unrecognized option \"$unit\""} + } + + # is negative or something else ? + if {$angle < 0} {set minus 1} {set minus 0} + + # adjust angle value + set angle [expr {$angle / $base}] + regsub {^[-]?\d+} $angle {0} angle + set angle [expr {$angle * $base}] + + # adjust angle polarity + if {$minus} {return [expr {$base - $angle}]} + return $angle + } +} diff --git a/lib/lib/hexeditor.tcl b/lib/lib/hexeditor.tcl new file mode 100755 index 0000000..ceb9192 --- /dev/null +++ b/lib/lib/hexeditor.tcl @@ -0,0 +1,2705 @@ +#!/usr/bin/tclsh +# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) + +############################################################################ +# Copyright (C) 2007-2009 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# 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 2 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, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + +# -------------------------------------------------------------------------- +# DESCRIPTION +# This class provides simple hexeditor with selectable view mode +# and optional ascii view. See constructor and section +# "GENERAL PUBLIC INTERFACE" for more details. +# -------------------------------------------------------------------------- + +class HexEditor { + common DEBUG 0 ;# Bool: More secure input data checking + # Font for editor text widget(s) - normal size + common view_font_n [font create \ + -family $::DEFAULT_FIXED_FONT \ + -size -15 \ + ] + # Font for editor headers - normal size + common header_font_n [font create \ + -family $::DEFAULT_FIXED_FONT \ + -size -15 \ + -weight bold \ + ] + # Font for editor headers - small size + common header_font_s [font create \ + -family $::DEFAULT_FIXED_FONT \ + -size -12 \ + -weight bold \ + ] + common view_font_s $header_font_s ;# Font for editor text widget(s) - small size + common header_bg {#9999FF} ;# Background color for headers + common header_fg {#FFFFFF} ;# Foreground color for headers + common n_row_bg {#DDDDDD} ;# Background color for Nth rows + common highlight_fg {#FFAA00} ;# Foreground color for chaged values + common highlight_bg {#888888} ;# Background color for background highlight (variant 0) + common highlight_bg1 {#FFDD33} ;# Background color for background highlight (variant 1) + common highlight_bg2 {#FFAA00} ;# Background color for background highlight (variant 2) + common unprintable_fg {#FF0000} ;# Foreground color for unprintable characters in ascii view + common current_full_bg {#00FF00} ;# Background color for cursor in active view + common current_half_bg {#AAFFAA} ;# Background color for cursor in inactive view + ## Variables related to find dialog + common find_dialog_win {} ;# Widget: Find dialog + common find_dialog_count 0 ;# Int: Counter find dialog opens + common text_to_find {} ;# String: Text/Value to find + common where_to_search left ;# String: Where to search (left or right view) + ## Array: Find options + # fc - Bool: Find option "From cursor" + # bw - Bool: Find option "Backwards" + common find_opt + + private variable left_top_button ;# ID of button in left top corner (select all) + private variable left_address_bar ;# ID of left address bar + private variable left_header ;# ID of left header (horizontal address bar) + private variable right_header ;# ID of right header (horizontal address bar) + private variable left_view ;# ID of text of left view + private variable right_view ;# ID of text of right view + private variable scrollbar ;# ID of vertical crollbar + private variable main_frame ;# ID of main frame (frame for all these widgets) + + private variable view_font ;# Current font for left (and right) view + private variable header_font ;# Current font for headers + private variable highlighted_cells ;# Array of Bool: highlighted cells + private variable bg_hg ;# Array of Bool: cells with background highlight (variant 0) + private variable bg_hg1 ;# Array of Bool: cells with background highlight (variant 1) + private variable bg_hg2 ;# Array of Bool: cells with background highlight (variant 2) + + private variable current_cell_changed_cmd {} ;# Command to call on event "CurrentCellChanged" + private variable cell_value_changed_cmd {} ;# Command to call on event "CellValueChanged" + private variable cell_enter_cmd {} ;# Command to call on event "CellMouseEnter" + private variable cell_leave_cmd {} ;# Command to call on event "CellMouseLeave" + private variable cell_motion_cmd {} ;# Command to call on event "CellMouseMotion" + private variable scroll_action_cmd {} ;# Command to call on event "Scroll" + private variable selection_action_cmd {} + private variable current_cell_changed_cmd_set 0 ;# Bool: current_cell_changed_cmd not empty + private variable cell_value_changed_cmd_set 0 ;# Bool: cell_value_changed_cmd not empty + private variable cell_enter_cmd_set 0 ;# Bool: cell_enter_cmd not empty + private variable cell_leave_cmd_set 0 ;# Bool: cell_leave_cmd not empty + private variable cell_motion_cmd_set 0 ;# Bool: cell_motion_cmd not empty + private variable scroll_action_cmd_set 0 ;# Bool: scroll_action_cmd not empty + private variable selection_action_cmd_set 0 + + private variable cell_under_cursor {0.0} ;# Text index of cell under mouse pointer + private variable in_cell 0 ;# Bool: mouse pointer in cell (see code below) + private variable motion_binding 0 ;# Bool: Bindings for special mouse events set + private variable view_mode ;# Current view mode (one of {dec hec oct bin}) + private variable ascii_view ;# Bool: Ascii view avaliable + private variable address_length ;# Int: Length of addresses on left address bar + private variable physical_height ;# Int: Height of view in rows + private variable width ;# Int: Number of cells in left view in one row + private variable height ;# Int: Number of rows in left view + private variable total_capacity ;# Int: Total editor capacity in bytes + private variable left_view_width ;# Int: Number of charcers in left view on one row + private variable value_length ;# Int: Number of characters in one cell + private variable cur_idx 0.0 ;# TextIndex: Last text index before selection + private variable selected_view {} ;# ID of active view + private variable popup_menu ;# ID of popup menu for both views + private variable selection_sync_in_P 0 ;# Bool: View selection synchronization in progress + private variable scroll_in_progress 0 ;# Bool: Scrolling procedure in progress + private variable cursor_address 0 ;# Int: Address of the current cell + private variable top_row 1 ;# Int: Number of topmost visible row + private variable disabled 0 ;# Bool: Editor disabled + private variable last_find_index {} ;# String: Index of first matched characted (find dialog) + private variable scrollbar_visible 0 ;# Bool: Scrollbar visibility flag + + ## Costructor + # @parm WidgetPath mainframe - Path where to create editor main frame + # @parm Int Width - Number of columns in row (max. 16) + # @parm Int Height - Number rows + # @parm Int addresslength - Number of characters on one row in left address bar + # @parm String mode - Initial view mode (one of {hex dec bin oct}) + # @parm Bool ascii - Display also ascii view + # @parm Bool small - Use small fonts + # @parm Int physicalheight - Heigh of views in rows + # @parm Int totalcapacity - Total capacity in Bytes + constructor {mainframe Width Height addresslength mode ascii small physicalheight totalcapacity} { + # Set object variables + set view_mode $mode + set ascii_view $ascii + set physical_height $physicalheight + set address_length $addresslength + set width $Width + set height $Height + set total_capacity $totalcapacity + + # Initalize array of highlighted cells + for {set i 0} {$i < $total_capacity} {incr i} { + set highlighted_cells($i) 0 + set bg_hg($i) 0 + set bg_hg1($i) 0 + set bg_hg2($i) 0 + } + + # Validate inputs arguments + if {$width > 16} { + error "Width cannot be grater than 16" + } + if {![string is boolean $small]} { + error "Invalid value for argument small: $small" + } + + # Determinate fonts + if {$small} { + set view_font $view_font_s + set header_font $header_font_s + } { + set view_font $view_font_n + set header_font $header_font_n + } + + # Create main frame + set main_frame [frame $mainframe] + bind $main_frame <Destroy> "catch {::itcl::delete object $this}" + + # Create GUI components + create_gui ;# Create text widgets + create_popup_menu ;# Create popup menu + create_tags ;# Create text tags + create_bindings ;# Create bindings + fill_headers ;# Fill headers with appropriate addresses + fill_views ;# Fill views with spaces + + # Finalize GUI initialization + $left_view mark set insert 1.0 + set_selected_view {left} + } + + ## Object destructor + destructor { + catch { + destroy $main_frame + } + } + + ## Create popup menu (for left & right view) + # @return void + private method create_popup_menu {} { + set popup_menu $main_frame.popup_menu + menuFactory { + {command {Copy} {Ctrl+C} 0 + "text_copy" {editcopy} {}} + {command {Paste} {Ctrl+V} 0 + "text_paste" {editpaste} {}} + {separator} + {command {Select all} {Ctrl+A} 0 + "text_selall" {} {}} + {separator} + {command {Find} {Ctrl+F} 0 + "find_dialog" {find} {}} + {command {Find next} {F3} 5 + "find_next" {1downarrow} {}} + {command {Find previous} {Shift+F3} 8 + "find_prev" {1uparrow} {}} + } $popup_menu 0 "$this " 0 {} + + # Configure menu entries + $popup_menu entryconfigure [::mc "Find next"] -state disabled + $popup_menu entryconfigure [::mc "Find previous"] -state disabled + } + + ## Create all hexeditor widgets expect popup menu + # @return void + private method create_gui {} { + # Determinate width of left view text widget and cell width + switch -- $view_mode { + {hex} { + set left_view_width [expr {$width * 3 - 1}] + set value_length 2 + } + {oct} { + set left_view_width [expr {$width * 4 - 1}] + set value_length 3 + } + {dec} { + set left_view_width [expr {$width * 4 - 1}] + set value_length 3 + } + default { + error "Invalid mode: $view_mode" + } + } + + # Create button "Select All" in left top corner + set left_top_button [button $main_frame.left_top_button \ + -bg $header_bg -bd 0 -padx 0 -pady 0 \ + -activebackground white -relief flat \ + -highlightthickness 0 \ + -command "$main_frame.left_view tag add sel 1.0 end" \ + ] + DynamicHelp::add $main_frame.left_top_button -text [mc "Select all"] + # Create left address bar + set left_address_bar [text $main_frame.left_address_bar \ + -height $physical_height -width $address_length \ + -font $header_font -bg $header_bg \ + -relief flat -bd 1 -fg $header_fg \ + -highlightthickness 0 -takefocus 0 \ + -yscrollcommand "$this scrollSet" \ + -cursor left_ptr \ + ] + grid $left_top_button -row 0 -column 0 -sticky nsew + grid $left_address_bar -row 1 -column 0 -sticky ns + + # Create horizontal header for left view + set left_header [text $main_frame.left_header \ + -height 1 -width $left_view_width \ + -font $header_font -bg $header_bg \ + -relief flat -bd 1 -fg $header_fg \ + -highlightthickness 0 -takefocus 0 \ + -cursor left_ptr \ + ] + grid $left_header -row 0 -column 1 + # Create horizontal header for ascii view + if {$ascii_view} { + grid [ttk::separator $main_frame.sep \ + -orient horizontal \ + ] -row 0 -rowspan 2 -column 2 -sticky ns + + set right_header [text $main_frame.right_header \ + -height 1 -width $width -bg $header_bg \ + -font $header_font -relief flat -bd 1 \ + -fg $header_fg -highlightthickness 0 \ + -takefocus 0 \ + -cursor left_ptr \ + ] + grid $right_header -row 0 -column 3 + } + + # Create text widget of the left view + set left_view [text $main_frame.left_view \ + -font $header_font -relief flat -bd 1 \ + -width $left_view_width -bg white \ + -highlightthickness 0 -height $physical_height \ + -yscrollcommand "$this scrollSet" \ + ] + grid $left_view -row 1 -column 1 -sticky ns + # Create text widget for ascii view + if {$ascii_view} { + set right_view [text $main_frame.right_view \ + -font $header_font -relief flat -bd 1 \ + -width $width -bg white \ + -highlightthickness 0 -height $physical_height \ + -exportselection 0 -insertwidth 0 \ + -yscrollcommand "$this scrollSet" \ + ] + grid $right_view -row 1 -column 3 -sticky ns + } + + # Create vertical scrollbar + set scrollbar [ttk::scrollbar $main_frame.scrollbar \ + -orient vertical \ + -command "$this scroll" \ + ] + set scrollbar_visible 0 + showHideScrollbar 1 + + grid rowconfigure $main_frame 1 -weight 1 + } + + ## Create event bindings for all hexeditor widgets (except popup menu) + # @return void + private method create_bindings {} { + ## LEFT PART + bindtags $left_header $left_header + bindtags $left_address_bar $left_address_bar + bindtags $left_view [list $left_view . all] + + foreach key {Left Right Up Down Home End Prior Next} { + bind $left_view <Key-$key> "$this left_view_movement 0 {$key}; break" + bind $left_view <Shift-Key-$key> "$this left_view_movement 1 {$key}; break" + } + for {set i 1} {$i < 21} {incr i} { + bind $left_view <Key-F$i> {continue} + } + bind $left_view <Control-Key> {continue} + bind $left_view <Alt-Key> {continue} + bind $left_view <Key-BackSpace> "$this left_view_movement 0 Left; break" + bind $left_view <Key-Menu> "$this popup_menu left %x %y %X %Y; break" + bind $left_view <ButtonRelease-3> "$this popup_menu left %x %y %X %Y; break" + bind $left_view <Key-Tab> "$this switch_views; break" + if {!$::MICROSOFT_WINDOWS} { + bind $left_view <Key-ISO_Left_Tab> "$this switch_views; break" + } + bind $left_view <KeyPress> "$this left_view_key %A; break" + bind $left_view <Button-1> "$this left_view_B1 %x %y; break" + bind $left_view <<Paste>> "$this text_paste; break" + bind $left_view <Control-Key-a> "$left_view tag add sel 1.0 end" + bind $left_view <<Selection>> "$this left_view_selection; break" + bind $left_view <FocusIn> "$this set_selected_view left" + bind $left_view <Key-Escape> {catch {%W tag remove sel 0.0 end}; break} + bind $left_view <Control-Key-f> "$this find_dialog; break" + bind $left_view <Control-Key-F> "$this find_dialog; break" + bind $left_view <F3> "$this find_next; break" + if {!$::MICROSOFT_WINDOWS} { + bind $left_view <XF86_Switch_VT_3> "$this find_prev; break" + } + bind $left_view <B1-Motion> " + $this left_view_B1_Motion %x %y + $this text_view_leave + break" + foreach key { + <ButtonRelease-1> <B1-Enter> <B1-Leave> + <B2-Motion> <Button-5> <Button-4> + <MouseWheel> <<Copy>> <Double-Button-1> + } { + bind $left_view $key "[bind Text $key]; break" + } + + bind $left_view <Button-4> "$this scroll scroll -3 units" + bind $left_view <Button-5> "$this scroll scroll +3 units" + bind $left_address_bar <Button-4> "$this scroll scroll -3 units" + bind $left_address_bar <Button-5> "$this scroll scroll +3 units" + bind $left_address_bar <MouseWheel> "[bind Text <MouseWheel>]; break" + + ## RIGHT PART + if {$ascii_view} { + bindtags $right_view [list $right_view . all] + bindtags $right_header $right_header + + foreach key {<Copy> Double-Button-1} { + bind $right_view <$key> {continue} + } + for {set i 1} {$i < 21} {incr i} { + bind $right_view <Key-F$i> {continue} + } + foreach event { + Key-Prior Key-Next Shift-Key-Up Shift-Key-Down + Shift-Key-Home Key-Home Shift-Key-Prior Shift-Key-Next + Button-1 Key-Up Key-Down + Shift-Key-Left Shift-Key-Right Shift-Key-End + } { + bind $right_view <$event> " + [bind Text <$event>] + $this right_view_adjust_cursor + break" + } + bind $right_view <Key-Left> "$this right_view_movement Left" + bind $right_view <Key-Right> "$this right_view_movement Right" + bind $right_view <Key-End> "$this right_view_movement End" + + bind $right_view <B1-Motion> " + [bind Text <B1-Motion>] + $this right_view_adjust_cursor + $this text_view_leave + break" + bind $right_view <Key-BackSpace> " + [bind Text <Key-Left>] + $this right_view_adjust_cursor + break" + bind $right_view <Key-Menu> "$this popup_menu right %x %y %X %Y; break" + bind $right_view <ButtonRelease-3> "$this popup_menu right %x %y %X %Y; break" + bind $right_view <Key-Tab> "$this switch_views; break" + if {!$::MICROSOFT_WINDOWS} { + bind $right_view <Key-ISO_Left_Tab> "$this switch_views; break" + } + bind $right_view <KeyPress> "$this right_view_key %A; break" + bind $right_view <<Paste>> "$this text_paste; break" + bind $right_view <Control-Key-a> "$right_view tag add sel 1.0 end" + bind $right_view <<Selection>> "$this right_view_selection; break" + bind $right_view <FocusIn> "$this set_selected_view right" + bind $right_view <Key-Escape> {catch {%W tag remove sel 0.0 end}; break} + bind $right_view <Control-Key-f> "$this find_dialog; break" + bind $right_view <Control-Key-F> "$this find_dialog; break" + bind $right_view <F3> "$this find_next; break" + if {!$::MICROSOFT_WINDOWS} { + bind $right_view <XF86_Switch_VT_3> "$this find_prev; break" + } + foreach key { + <ButtonRelease-1> <B1-Enter> <B1-Leave> + <MouseWheel> <<Copy>> <Double-Button-1> + <B2-Motion> + } { + bind $right_view $key "[bind Text $key]; break" + } + + bind $right_view <Button-4> "$this scroll scroll -3 units" + bind $right_view <Button-5> "$this scroll scroll +3 units" + } + } + + ## Create text tags + # @return void + private method create_tags {} { + # + ## LEFT PART + # + + # Cursor position + $left_address_bar tag configure tag_current_full \ + -font $header_font \ + -background $current_full_bg \ + -foreground {#000000} + # Cursor position for active view and inactive view + foreach widget [list $left_header $left_view] { + $widget tag configure tag_current_full \ + -font $header_font \ + -background $current_full_bg \ + -foreground {#000000} + $widget tag configure tag_current_half \ + -font $header_font \ + -background $current_half_bg \ + -foreground {#000000} + } + # Nth row backrgound + $left_view tag configure tag_n_row -background $n_row_bg + # Cell highlight + $left_view tag configure tag_hg \ + -foreground $highlight_fg \ + -font $header_font + $left_view tag configure tag_bg_hg \ + -background $highlight_bg \ + -font $header_font + $left_view tag configure tag_bg_hg1 \ + -background $highlight_bg1 \ + -font $header_font + $left_view tag configure tag_bg_hg2 \ + -background $highlight_bg2 \ + -font $header_font + # Other tags + $left_view tag configure normal_font \ + -font $view_font + + # Set tags priorities + $left_view tag raise sel tag_n_row + $left_view tag raise sel tag_current_full + $left_view tag raise sel tag_current_half + $left_view tag raise sel tag_bg_hg + $left_view tag raise sel tag_bg_hg1 + $left_view tag raise sel tag_bg_hg2 + $left_view tag raise tag_current_full normal_font + $left_view tag raise tag_current_half normal_font + $left_view tag raise tag_bg_hg normal_font + $left_view tag raise tag_bg_hg1 normal_font + $left_view tag raise tag_bg_hg2 normal_font + $left_view tag raise tag_bg_hg2 tag_bg_hg1 + $left_view tag raise tag_bg_hg tag_n_row + $left_view tag raise tag_bg_hg1 tag_n_row + $left_view tag raise tag_bg_hg2 tag_n_row + $left_view tag raise tag_current_full tag_n_row + $left_view tag raise tag_current_half tag_n_row + $left_view tag raise tag_current_full tag_bg_hg + $left_view tag raise tag_current_half tag_bg_hg + $left_view tag raise tag_current_full tag_bg_hg1 + $left_view tag raise tag_current_half tag_bg_hg1 + $left_view tag raise tag_current_full tag_bg_hg2 + $left_view tag raise tag_current_half tag_bg_hg2 + + # + ## RIGHT PART + # + if {$ascii_view} { + # Unprintable characters + $right_view tag configure tag_np \ + -font $view_font \ + -foreground $unprintable_fg + + # Cursor position for active view + $right_header tag configure tag_current_full \ + -font $header_font \ + -background $current_full_bg \ + -foreground {#000000} + # Cursor position for inactive view + $right_header tag configure tag_current_half \ + -font $header_font \ + -background $current_half_bg \ + -foreground {#000000} + + # Cursor position for active view + $right_view tag configure tag_current_full \ + -font $header_font \ + -background $current_full_bg + # Cursor position for inactive view + $right_view tag configure tag_current_half \ + -font $header_font \ + -background $current_half_bg + + # Nth row backrgound + $right_view tag configure tag_n_row -background $n_row_bg + # Cell highlight + $right_view tag configure tag_hg \ + -foreground $highlight_fg \ + -font $header_font + $right_view tag configure tag_bg_hg \ + -background $highlight_bg \ + -font $header_font + $right_view tag configure tag_bg_hg1 \ + -background $highlight_bg1 \ + -font $header_font + $right_view tag configure tag_bg_hg2 \ + -background $highlight_bg2 \ + -font $header_font + + # Other tags + $right_view tag configure normal_font \ + -font $view_font + + # Set tags priorities + $right_view tag raise sel tag_current_full + $right_view tag raise sel tag_current_half + $right_view tag raise sel tag_n_row + $right_view tag raise sel tag_bg_hg + $right_view tag raise sel tag_bg_hg1 + $right_view tag raise sel tag_bg_hg2 + $right_view tag raise tag_current_full normal_font + $right_view tag raise tag_current_half normal_font + $right_view tag raise tag_bg_hg normal_font + $right_view tag raise tag_bg_hg1 normal_font + $right_view tag raise tag_bg_hg2 normal_font + $right_view tag raise tag_bg_hg2 tag_bg_hg1 + $right_view tag raise tag_bg_hg tag_n_row + $right_view tag raise tag_bg_hg1 tag_n_row + $right_view tag raise tag_bg_hg2 tag_n_row + $right_view tag raise tag_current_full tag_n_row + $right_view tag raise tag_current_half tag_n_row + $right_view tag raise tag_current_full tag_bg_hg + $right_view tag raise tag_current_half tag_bg_hg + $right_view tag raise tag_current_full tag_bg_hg1 + $right_view tag raise tag_current_half tag_bg_hg1 + $right_view tag raise tag_current_full tag_bg_hg2 + $right_view tag raise tag_current_half tag_bg_hg2 + } + } + + ## Restore cell highlight + # @parm Int address - Cell address + # @return void + private method restore_cell_highlight {address} { + if {$highlighted_cells($address)} { + set highlighted_cells($address) 0 + setHighlighted $address 1 + } + if {$bg_hg($address)} { + set bg_hg($address) 0 + set_bg_hg $address 1 0 + } + if {$bg_hg1($address)} { + set bg_hg1($address) 0 + set_bg_hg $address 1 1 + } + if {$bg_hg2($address)} { + set bg_hg2($address) 0 + set_bg_hg $address 1 2 + } + } + + ## Fill headres with addresses + # @return void + private method fill_headers {} { + # Left horizontal header + fill_left_header + + # Left address bar + $left_address_bar delete 1.0 end + $left_address_bar insert end [string repeat {0} $address_length] + set line {} + set address {} + for {set i 1} {$i < $height} {incr i} { + set address [format {%X} [expr {$i * $width}]] + set line "\n" + append line [string repeat {0} \ + [expr {$address_length - [string length $address]}]] + append line $address + $left_address_bar insert end $line + } + + # Right horizontal header + set header_values [list 0 1 2 3 4 5 6 7 8 9 A B C D E F] + if {$ascii_view} { + $right_header delete 1.0 end + for {set i 0} {$i < $width} {incr i} { + $right_header insert end [lindex $header_values $i] + } + } + } + + ## Left horizontal header with cell addresses + # @return void + private method fill_left_header {} { + set header_values [list 0 1 2 3 4 5 6 7 8 9 A B C D E F] + $left_header delete 1.0 end + if {$view_mode == {hex}} { + set space { } + } { + $left_header insert end { } + set space { } + } + for {set i 0} {$i < $width} {incr i} { + if {$i} { + $left_header insert end $space + } + $left_header insert end {x} + $left_header insert end [lindex $header_values $i] + } + } + + ## Fill all views with spaces + # @return void + public method fill_views {} { + # Fill left view with spaces + $left_view delete 1.0 end + set line [string repeat { } $left_view_width] + $left_view insert end $line + $left_view tag add normal_font {insert linestart} {insert lineend} + for {set i 1} {$i < $height} {incr i} { + $left_view insert end "\n" + $left_view tag add normal_font {insert linestart} {insert lineend} + $left_view insert end $line + + if {![expr {$i % 3}]} { + $left_view tag add tag_n_row \ + [expr {$i - 1}].$left_view_width \ + [expr {$i + 1}].0 + } + } + + # Fill right view with spaces + if {$ascii_view} { + $right_view delete 1.0 end + set line [string repeat { } $width] + $right_view insert end $line + $right_view tag add normal_font {insert linestart} {insert lineend} + for {set i 1} {$i < $height} {incr i} { + $right_view insert end "\n" + $right_view tag add normal_font {insert linestart} {insert lineend} + $right_view insert end $line + + if {![expr {$i % 3}]} { + $right_view tag add tag_n_row \ + [expr {$i - 1}].$left_view_width \ + [expr {$i + 1}].0 + } + } + } + } + + ## Translate cell address to text indexes + # @parm Int address - address to translate + # @return List {row column_in_right_view start_col_in_left_view end_col_in_left_view} + private method address_to_index {address} { + # Local variable + set row [expr {$address / $width + 1}] ;# Row + set cell [expr {$address % $width}] ;# Column in right view + set start_col 0 ;# Start column in left view + + # Determinate start column + if {$cell} { + if {$view_mode != {hex}} { + set start_col [expr {$cell * 4}] + } { + set start_col [expr {$cell * 3}] + } + } + + # Determinate end column + set end_col $start_col + if {$view_mode != {hex}} { + incr end_col 3 + } { + incr end_col 2 + } + + # Return results + return [list $row $cell $start_col $end_col] + } + + ## Translate text index to address + # @parm String view - View from which is index to translate + # @parm TextIndex index - Indext to translate + # @return Int address + private method index_to_address {view index} { + # Left view + if {$view == {left}} { + if {$view_mode != {hex}} { + set step 4 + } { + set step 3 + } + scan [$left_view index $index] {%d.%d} row col + set cell [expr {($col / $step)}] + # Right view + } { + scan [$right_view index $index] {%d.%d} row cell + } + + # Return result + incr row -1 + return [expr {$row * $width + $cell}] + } + + ## Normalize column in left view + # @parm Int col - column to normalize + # @return {start_column end_column cell_number_in_row} + private method col_to_start_end {col} { + if {$view_mode != {hex}} { + set step 4 + } { + set step 3 + } + + set cell [expr {($col / $step)}] + set start [expr {$cell * $step}] + set end [expr {$start + $step - 1}] + + return [list $start $end $cell] + } + + ## Adjust cursor tags to the current cursor positions (for left view) + # @return void + private method left_view_adjust_cursor {} { + scan [$left_view index insert] {%d.%d} row col + + set boundaries [col_to_start_end $col] + set col_s [lindex $boundaries 0] + set col_e [lindex $boundaries 1] + set cell [lindex $boundaries 2] + set cursor_address_original $cursor_address + set cursor_address [expr {($row - 1) * $width + $cell}] + if {$cursor_address >= $total_capacity} { + set cursor_address $cursor_address_original + setCurrentCell $cursor_address_original + return + } + + # Clear cell highlight + if {$highlighted_cells($cursor_address)} { + setHighlighted $cursor_address 0 + } + # Execute command binded to event CurrentCellChanged + if {$current_cell_changed_cmd_set && $cursor_address_original != $cursor_address} { + eval "$current_cell_changed_cmd $cursor_address" + } + + ## Create cursor tags in right view + if {$ascii_view} { + $right_header tag remove tag_current_half 0.0 end + $right_header tag add tag_current_half 1.$cell 1.$cell+1c + + $right_view tag remove tag_current_half 0.0 end + $right_view tag add tag_current_half $row.$cell "$row.$cell +1c" + } + + ## Create cursor tags in left view + $left_address_bar tag remove tag_current_full 0.0 end + $left_address_bar tag add tag_current_full $row.0 $row.0+1l + + $left_header tag remove tag_current_full 0.0 end + $left_header tag add tag_current_full 1.$col_s 1.$col_e + + $left_view tag remove tag_current_full 0.0 end + $left_view tag add tag_current_full $row.$col_s $row.$col_e + } + + ## Create binding for <Motion> and <Leave> events for left and right view + # @return void + private method bind_mouse_motions {} { + if {$motion_binding} {return} + set motion_binding 1 + + bind $left_view <Motion> "$this left_view_motion %x %y %X %Y" + bind $left_view <Leave> "$this text_view_leave" + + if {$ascii_view} { + bind $right_view <Motion> "$this right_view_motion %x %y %X %Y" + bind $right_view <Leave> "$this text_view_leave" + } + } + + ## Binding for event <ButtonPress-1> in left view + # @parm Int x - Relative horizontal position of mouse pointer + # @parm Int y - Relative vertical position of mouse pointer + # @return void + private method left_view_move_insert {x y} { + set index [$left_view index @$x,$y] + scan $index {%d.%d} row col + + if {$view_mode != {hex}} { + if {($col % 4) == 3} { + set index [$left_view index "$index+1c"] + } + } { + if {($col % 3) == 2} { + set index [$left_view index "$index+1c"] + } + } + $left_view mark set insert $index + left_view_adjust_cursor + } + + ## Adjust cursor tags to the current cursor positions (for right view) + # @return void + public method right_view_adjust_cursor {} { + if {!$ascii_view} { + return + } + + scan [$right_view index insert] {%d.%d} row cell + if {$view_mode != {hex}} { + set step 4 + } { + set step 3 + } + set cursor_address_original $cursor_address + set cursor_address [expr {($row - 1) * $width + $cell}] + if {$cursor_address >= $total_capacity} { + set cursor_address [expr {$total_capacity - 1}] + set index [address_to_index $cursor_address] + set row [lindex $index 0] + set cell [lindex $index 1] + $right_view mark set insert $row.$cell + } + set col_s [expr {$cell * $step}] + set col_e [expr {$col_s + $step - 1}] + + # Clear cell highlight + if {$highlighted_cells($cursor_address)} { + setHighlighted $cursor_address 0 + } + # Execute command binded to event CurrentCellChanged + if {$current_cell_changed_cmd_set && $cursor_address_original != $cursor_address} { + eval "$current_cell_changed_cmd $cursor_address" + } + + ## Adjust cursor tags in right view + $right_header tag remove tag_current_full 0.0 end + $right_header tag add tag_current_full 1.$cell 1.$cell+1c + + $right_view tag remove tag_current_full 0.0 end + $right_view tag add tag_current_full $row.$cell "$row.$cell +1c" + + ## Adjust cursor tags in left view + $left_address_bar tag remove tag_current_full 0.0 end + $left_address_bar tag add tag_current_full $row.0 $row.0+1l + + $left_header tag remove tag_current_half 0.0 end + $left_header tag add tag_current_half 1.$col_s 1.$col_e + + $left_view tag remove tag_current_half 0.0 end + $left_view tag add tag_current_half $row.$col_s $row.$col_e + } + + ## Binding for event <KeyPress> in right view + # @parm String key - binary key code + # @return void + public method right_view_key {key} { + if {$disabled} {return} + if {!$ascii_view} { + return + } + # Key must be 8 bit printable character + if {![string is print -strict $key] || ([string bytelength $key] > 1)} { + return + } + + # Determinate row, column and index of insertion cursor + set index [$right_view index insert] + scan $index {%d.%d} row col + + # Check for valid position (insert mustn't be after the end of editor) + if {($row == $height) && ($col >= $width)} { + return + } + + # Convert value to decimal and check for valid ASCII value + binary scan $key c key + if {$key > 126 || $key < 0} { + return + } + + # Synchronize views + incr row -1 + set address [expr {$row * $width + $col}] + setValue $address $key + if {$cell_value_changed_cmd_set} { + eval "$cell_value_changed_cmd $address $key" + } + } + + ## Synchronize selection in right view with left view + # Binding for event <<Selection>> + # @return void + public method right_view_selection {} { + if {$selection_sync_in_P} {return} + set selection_sync_in_P 1 + + $left_view tag remove sel 0.0 end + if {![llength [$right_view tag nextrange sel 0.0]]} { + set selection_sync_in_P 0 + set anything_selected 0 + } { + set anything_selected 1 + } + + if {$selection_action_cmd_set} { + set flag $anything_selected + if {$flag} { + if {![string length [string trim [$right_view get sel.first sel.last]]]} { + set flag 0 + } + } + eval "$selection_action_cmd $flag" + } + + if {!$anything_selected} { + return + } + + if {!$ascii_view} { + return + } + + if {$view_mode != {hex}} { + set step 4 + } { + set step 3 + } + + scan [$right_view index sel.first] {%d.%d} start_row start_col + set start_col [expr {$start_col * $step}] + scan [$right_view index sel.last] {%d.%d} end_row end_col + set end_col [expr {$end_col * $step}] + + $left_view tag add sel $start_row.$start_col $end_row.$end_col + set selection_sync_in_P 0 + } + + ## Make scrollbar visible or not + # @parm Bool display - 1 == Visible; 0 == Invisible + # @return void + public method showHideScrollbar {display} { + + # Show scrollbar + if {!$scrollbar_visible && $display} { + set scrollbar_visible 1 + grid $scrollbar -row 0 -rowspan 2 -column 4 -sticky ns + + # Hide scrollbar + } elseif {$scrollbar_visible && !$display} { + set scrollbar_visible 0 + grid forget $scrollbar + } + } + + ## Set scrollbar and synchronize visible area in both views + # text $x -yscrollcommand "$this scrollSet" + # @parm float fraction0 - Fraction of topmost visible area + # @parm float fraction1 - Fraction of bottommost visible area + # @return void + public method scrollSet {fraction0 fraction1} { + $scrollbar set $fraction0 $fraction1 + scroll moveto $fraction0 + } + + ## Scroll both views, left address bar and adjust scrollbar + # $scrollbar -command "$this scrollSet" + # @parm String args - Here should be something like "moveto 0.1234" + # @return void + public method scroll args { + if {$scroll_in_progress} {return} + set scroll_in_progress 1 + + eval "$left_view yview $args" + + set idx [$left_view index @5,5] + scan $idx "%d.%d" row col + incr row -1 + set top_row $row + $left_view yview $row + $left_address_bar yview $row + + if {$ascii_view} { + $right_view yview $row + } + + if {$scroll_action_cmd_set} { + eval $scroll_action_cmd + } + + update idle + set scroll_in_progress 0 + } + + ## Synchronize selection in left view with left view + # Binding for event <<Selection>> + # @return void + public method left_view_selection {} { + if {$selection_sync_in_P} {return} + set selection_sync_in_P 1 + + if {$ascii_view} { + $right_view tag remove sel 0.0 end + } + if {![llength [$left_view tag nextrange sel 0.0]]} { + set selection_sync_in_P 0 + set anything_selected 0 + } { + set anything_selected 1 + } + + if {$selection_action_cmd_set} { + set flag $anything_selected + if {$flag} { + if {![string length [string trim [$left_view get sel.first sel.last]]]} { + set flag 0 + } + } + eval "$selection_action_cmd $flag" + } + + if {!$anything_selected} { + return + } + + if {!$ascii_view} { + return + } + + scan [$left_view index sel.first] {%d.%d} start_row start_col + set start_col [lindex [col_to_start_end $start_col] 2] + scan [$left_view index {sel.last-1c}] {%d.%d} end_row end_col + set end_col [lindex [col_to_start_end $end_col] 2] + incr end_col + + $right_view tag add sel $start_row.$start_col $end_row.$end_col + set selection_sync_in_P 0 + } + + ## Copy text from selected view + # @return void + public method text_copy {} { + if {![llength [$left_view tag nextrange sel 0.0]]} { + return + } + + if {$selected_view == {left}} { + clipboard clear + clipboard append [string trim [$left_view get sel.first sel.last]] + } elseif {($selected_view == {right}) && $ascii_view} { + clipboard clear + clipboard append [string trim [$right_view get sel.first sel.last]] + } + } + + ## Paste text to active view + # @return void + public method text_paste {} { + if {$disabled} {return} + + # Get clipboard contents + if {[catch { + set text [clipboard get] + }]} { + set text {} + } + # If clipboard empty then return + if {![string length $text]} { + return + } + + # Paste to left view + if {$selected_view == {left}} { + # Remove all characters invalid in current view mode + switch -- $view_mode { + {hex} { + regsub -all {[^0-9a-fA-F ]} $text {} text + set step 1 + } + {oct} { + regsub -all {[^0-7 ]} $text {} text + set step 2 + } + {dec} { + regsub -all {[^0-9 ]} $text {} text + set step 2 + } + } + + # Determinate start address + set address [index_to_address left [$left_view index insert]] + + # Iterate over the text and convert each pair/triad of charaters + set len [string length $text] + for {set i 0} {$i < $len} {incr i $value_length} { + # Get character pair/triad + set val [string range $text $i [expr {$i + $step}]] + if {[string is space -strict $val]} { + incr address + if {$address >= $total_capacity} { + break + } + continue + } + set val [string trim $val] + set val [string trimleft $val 0] + + # Convert value to decimal + if {$val == {}} { + set val 0 + } + if {$view_mode == {hex}} { + set val [expr int("0x$val")] + } elseif {$view_mode == {oct}} { + set val [expr int("0$val")] + } + + # Check for allowed range + if {$val < 0 || $val > 255} { + continue + } + + # Set value in editor, simulator and others + setValue $address $val + if {$cell_value_changed_cmd_set} { + eval "$cell_value_changed_cmd $address $val" + } + incr address + incr i + if {$address >= $total_capacity} { + break + } + } + + # Adjust insertion cursor + set address [address_to_index $address] + $left_view mark set insert [lindex $address 0].[lindex $address 2] + $left_view see insert + left_view_adjust_cursor + + # Paste to right view + } elseif {($selected_view == {right}) && $ascii_view} { + # Determinate start address, row and column + scan [$right_view index insert] {%d.%d} row col + incr row -1 + set address [expr {$row * $width + $col}] + + # Iterate over characters in the text + foreach val [split $text {}] { + # Convert to decimal + binary scan $val c val + if {$val < 0 || $val > 126} { + incr address + continue + } + + # Check for valid address + if {$address >= $total_capacity} { + break + } + + # Set value in editor, simulator and others + setValue $address $val + if {$cell_value_changed_cmd_set} { + eval "$cell_value_changed_cmd $address $val" + } + incr address + } + + # Adjust insertion cursor + set address [address_to_index $address] + $right_view mark set insert [lindex $address 0].[lindex $address 1] + $right_view see insert + right_view_adjust_cursor + } + } + + ## Select all text in both views + # @return void + public method text_selall {} { + $left_view tag add sel 1.0 end + } + + ## Left view event handler: <B1-Motion> + # @parm Int x - Relative cursor position + # @parm Int y - Relative cursor position + # @return void + public method left_view_B1_Motion {x y} { + # If x,y overlaps widget area -> abort + set max_x [winfo width $left_view] + incr max_x -3 + set max_y [winfo height $left_view] + incr max_y -3 + if {($x < 3) || ($x > $max_x) || ($y < 3) || ($y > $max_y)} { + return + } + + # If x,y is conresponding to current selection -> abort + set target_idx [$left_view index @$x,$y] + if {[llength [$left_view tag nextrange sel 0.0]]} { + if { + ([$left_view compare $cur_idx == sel.first] + && + [$left_view compare $target_idx == sel.last]) + || + ([$left_view compare $cur_idx == sel.last] + && + [$left_view compare $target_idx == sel.first]) + } then { + return + } + } + + # Adjust selection + $left_view tag remove sel 0.0 end + if {[$left_view compare $cur_idx < $target_idx]} { + $left_view tag add sel $cur_idx $target_idx + } elseif {[$left_view compare $cur_idx > $target_idx]} { + $left_view tag add sel $target_idx $cur_idx + } + + # Adjust cursor + left_view_move_insert $x $y + update + } + + ## Left view event handler: <Button-1> + # @parm Int x - Relative cursor position + # @parm Int y - Relative cursor position + # @return void + public method left_view_B1 {x y} { + $left_view tag remove sel 0.0 end + focus $left_view + left_view_move_insert $x $y + set cur_idx [$left_view index @$x,$y] + } + + ## Set active view + # @parm String side - "left" or "right" + # @return void + public method set_selected_view {side} { + if {$selected_view == $side} { + return + } + set selected_view $side + + # Remove cursor tags + foreach widget [list $left_header $left_view] { + $widget tag remove tag_current_full 0.0 end + $widget tag remove tag_current_half 0.0 end + } + if {$ascii_view} { + foreach widget [list $right_header $right_view] { + $widget tag remove tag_current_full 0.0 end + $widget tag remove tag_current_half 0.0 end + } + } + + # Create new cursor tags + if {$selected_view == {left}} { + set index [address_to_index $cursor_address] + $left_view mark set insert [lindex $index 0].[lindex $index 2] + left_view_adjust_cursor + + } elseif {$ascii_view && $selected_view == {right}} { + set row [expr {($cursor_address / $width) + 1}] + set col [expr {$cursor_address % $width}] + + $right_view mark set insert $row.$col + right_view_adjust_cursor + } + } + + ## Invoke hexeditor popup menu + # @parm String side - "left" or "right" + # @parm Int x - Relative mouse pointer position + # @parm Int y - Relative mouse pointer position + # @parm Int X - Absolute mouse pointer position + # @parm Int Y - Absolute mouse pointer position + # @return void + public method popup_menu {side x y X Y} { + # Set widget to deal with + if {$selected_view == {left}} { + set widget $left_view + left_view_move_insert $x $y + } { + set widget $right_view + } + + # Fucus on that widget and determinate cursor position + focus $widget + set cur_idx [$widget index @$x,$y] + if {$ascii_view && $selected_view == {right}} { + $widget mark set insert $cur_idx + right_view_adjust_cursor + } + + # Configure popup menu + if {[llength [$widget tag nextrange sel 0.0]]} { + $popup_menu entryconfigure [::mc "Copy"] -state normal + } { + $popup_menu entryconfigure [::mc "Copy"] -state disabled + } + if {[catch { + if {[string length [clipboard get]]} { + $popup_menu entryconfigure [::mc "Paste"] -state normal + } { + $popup_menu entryconfigure [::mc "Paste"] -state disabled + } + }]} { + $popup_menu entryconfigure [::mc "Paste"] -state disabled + } + + # Invoke popup menu + tk_popup $popup_menu $X $Y + } + + ## Left view event handler: <Key> + # Unprintable characters, invalid and non 8 bit characters will be ignored + # @parm Char key - Binary code of pressed key + # @return void + public method left_view_key {key} { + if {$disabled} {return} + + # Check if the given value is printable character + if {![string is print -strict $key]} { + return + } + + # Determinate current row and column + scan [$left_view index insert] {%d.%d} row col + if {($row == $height) && ($col >= $left_view_width)} { + return + } + + # Validate the given value + switch -- $view_mode { + {dec} { + if {![string is integer -strict $key]} { + return + } + } + {hex} { + if {![string is xdigit -strict $key]} { + return + } + } + {oct} { + if {![regexp {^[0-7]+$} $key]} { + return + } + } + } + + # Local variables + set boundaries [col_to_start_end $col] ;# Tempotary variable + set col_s [lindex $boundaries 0] ;# Starting column + set col_e [lindex $boundaries 1] ;# End column + set cell [lindex $boundaries 2] ;# Cell number in row + set org_val [$left_view get $row.$col_s $row.$col_e] ;# Original cell value + set org_idx [$left_view index insert] ;# Original insertion index + + # Replace character at current insertion index with the new one + $left_view delete insert {insert+1c} + $left_view insert insert $key + $left_view mark set insert {insert-1c} + $left_view tag add normal_font {insert linestart} {insert lineend} + + # Determinate new cell value + set val [$left_view get $row.$col_s $row.$col_e] + set val [string trim $val] + set val [string trimleft $val 0] + if {$val == {}} { + set val 0 + } + + # Convert new cell value to decimal integer + if {$view_mode == {hex}} { + set val [expr "0x$val"] + } elseif {$view_mode == {oct}} { + set val [expr "0$val"] + } + + # Check for valid value range + if {$val > 255} { + $left_view delete $row.$col_s $row.$col_e + $left_view tag add normal_font [list $row.0 linestart] [list $row.0 lineend] + $left_view insert $row.$col_s $org_val + $left_view mark set insert $org_idx + left_view_adjust_cursor + return + } + + # Invoke pseudo-event <cell_value_changed> + if {$cell_value_changed_cmd_set} { + eval "$cell_value_changed_cmd $cursor_address $val" + } + + # Adjust right view + if {$ascii_view} { + set char [format %c $val] + set cell "$row.$cell" + $right_view delete $cell "$cell+1c" + if {($val < 127) && [string is print -strict $char]} { + $right_view insert $cell $char + $right_view tag remove tag_np $cell "$cell+1c" + } { + $right_view insert $cell {.} + $right_view tag add tag_np $cell "$cell+1c" + } + $right_view tag add normal_font [list $cell linestart] [list $cell lineend] + } + + # Adjust insertion cursor + if {($row == $height) && ($col >= ($left_view_width - 1))} { + left_view_adjust_cursor + } { + left_view_movement 0 Right + } + } + + ## Perform certain movement action on the right (ascii) view + # @parm String key - Action (one of {Left Right End}) + # @return void + public method right_view_movement {key} { + # Remove selection and determinate current column and row + $right_view tag remove sel 0.0 end + scan [$right_view index insert] {%d.%d} row col + + # Determinate correction for insertion cursor + switch -- $key { + {Left} { ;# Move left by one character + if {$row == 1 && $col == 0} { + return + } + + incr col -1 + if {$col < 0} { + set col [expr {$width - 1}] + incr row -1 + } + } + {Right} { ;# Move right by one character + if {($row == $height) && ($col >= ($width - 1))} { + return + } + + incr col + if {$col >= $width} { + set col 0 + incr row + } + } + {End} { ;# Move to the end of the current line + if {$col >= ($width - 1)} { + return + } + + set col [expr {$width - 1}] + } + default { ;# CRITICAL ERROR + error "Unrecognized key: $key" + return + } + } + + # Adjust insertion cursor + $right_view mark set insert $row.$col + $right_view see insert + + # Adjust cursor highlighting tags + right_view_adjust_cursor + } + + ## Perform certain movement action on the left view + # @parm Bool select - Manipulate selection + # @parm String key - Action (one of {Left Right Up Down Home End Prior Next}) + # @return void + public method left_view_movement {select key} { + # Remove selection and determinate current column and row + $left_view tag remove sel 0.0 end + scan [$left_view index insert] {%d.%d} row col + + # Determinate cell boundaries + if {$key == {Left} || $key == {Right}} { + set boundaries [col_to_start_end $col] + set col_s [lindex $boundaries 0] + set col_e [lindex $boundaries 1] + } + + # Determinate correction for insertion cursor + switch -- $key { + {Left} { ;# Move left by one character + if {$row == 1 && $col == 0} { + return + } + if {$col == $col_s} { + set correction {-2c} + } { + set correction {-1c} + } + } + {Right} { ;# Move right by one character + incr col_e -1 + if {($row == $height) && ($col >= ($left_view_width - 1))} { + return + } + if {$col == $col_e} { + set correction {+2c} + } { + set correction {+1c} + } + } + {Up} { ;# Move up by one row + if {!$row} { + return + } + set correction {-1l} + } + {Down} { ;# Move down by one row + if {$row == $height} { + return + } + set correction {+1l} + } + {Home} { ;# Move to the beginning of the current line + if {!$col} { + return + } + set correction {linestart} + } + {End} { ;# Move to the end of the current line + if {$col >= ($left_view_width - 1)} { + return + } + set correction {lineend-1c} + } + {Prior} { ;# Move up by a few lines + set correction {-8l} + } + {Next} { ;# Move up by a few lines + set correction {+8l} + } + default { ;# CRITICAL ERROR + error "Unrecognized key: $key" + return + } + } + + # Adjust insertion cursor + $left_view mark set insert [$left_view index "insert $correction"] + $left_view see insert + + # Adjust selection + if {!$select} { + set cur_idx [$left_view index insert] + } { + if {[$left_view compare $cur_idx <= insert]} { + $left_view tag add sel $cur_idx insert + } { + $left_view tag add sel insert $cur_idx + } + } + + # Adjust cursor highlighting tags + left_view_adjust_cursor + } + + ## Left view event handler: <Leave> + # Manages pseudo-event <cell_leave> + # @return void + public method text_view_leave {} { + if {!$in_cell} { + return + } + + set in_cell 0 + set cell_under_cursor {0.0} + + if {$cell_leave_cmd_set} { + eval $cell_leave_cmd + } + } + + ## Right view event handler: <Motion> + # Manages pseuso-events <cell_motion>, <cell_enter> and <cell_leave> + # @parm Int x - Relative mouse pointer position + # @parm Int y - Relative mouse pointer position + # @parm Int X - Absolute mouse pointer position + # @parm Int Y - Absolute mouse pointer position + # @return void + public method right_view_motion {x y X Y} { + set index [$right_view index @$x,$y] + set dlineinfo [$right_view dlineinfo $index] + if {$y > ([lindex $dlineinfo 1] + [lindex $dlineinfo 3])} { + text_view_leave + return + } + scan $index {%d.%d} row col + + # Motion + if {$cell_under_cursor == $index} { + if {$cell_motion_cmd_set} { + eval "$cell_motion_cmd $X $Y" + } + + # (Leave + ) Enter + } else { + if {$in_cell} { + if {$cell_leave_cmd_set} { + eval $cell_leave_cmd + } + set in_cell 0 + } + if {$cell_enter_cmd_set} { + set address [expr {($row - 1) * $width + $col}] + if {$address >= $total_capacity} { + set address $total_capacity + } + eval "$cell_enter_cmd $address $X $Y" + } + set in_cell 1 + } + + set cell_under_cursor $index + } + + ## Left view event handler: <Motion> + # Manages pseuso-events <cell_motion>, <cell_enter> and <cell_leave> + # @parm Int x - Relative mouse pointer position + # @parm Int y - Relative mouse pointer position + # @parm Int X - Absolute mouse pointer position + # @parm Int Y - Absolute mouse pointer position + # @return void + public method left_view_motion {x y X Y} { + set index [$left_view index @$x,$y] + set dlineinfo [$left_view dlineinfo $index] + if {$y > ([lindex $dlineinfo 1] + [lindex $dlineinfo 3])} { + text_view_leave + return + } + scan $index {%d.%d} row col + if {$view_mode != {hex}} { + set step 4 + } { + set step 3 + } + + # Enter + if {$cell_under_cursor != $index} { + if {$in_cell && $cell_leave_cmd_set} { + eval $cell_leave_cmd + } + if {$cell_enter_cmd_set} { + set address [expr {($row - 1) * $width + ($col / $step)}] + if {$address >= $total_capacity} { + set address $total_capacity + } + eval "$cell_enter_cmd $address $x $y $X $Y" + } + set in_cell 1 + + # Motion + } elseif {$cell_motion_cmd_set} { + eval "$cell_motion_cmd $X $Y" + } + + set cell_under_cursor $index + } + + + # ------------------------------------------------------------------- + # GENERAL PUBLIC INTERFACE + # ------------------------------------------------------------------- + + ## Get editor scroll bar object reference + # @return Widget - Scrolbar + public method get_scrollbar {} { + return $scrollbar + } + + ## Get editor popup menu object reference + # @return Widget - Popup menu + public method get_popup_menu {} { + return $popup_menu + } + + ## Get list of values from hexeditor + # @parm Int start - Start address + # @parm Int end - End address + # @return List - List of decimal values (e.g. {0 226 {} {} 126 {} 6 8}) + public method get_values {start end} { + # Check for allowed address range + if {$DEBUG} { + if {$end >= $total_capacity} { + error "Address out of range" + } + if {$end != [expr {int($end)}]} { + error "Address must be integer" + } + if {$start < 0} { + error "Address out of range" + } + if {$start != [expr {int($start)}]} { + error "Address must be integer" + } + } + + # Determinate text indexes of area to extract + set index [address_to_index $start] + set start_row [lindex $index 0] + set start_col [lindex $index 2] + set index [address_to_index $end] + set end_row [lindex $index 0] + set end_col [lindex $index 3] + incr end_col + + # Determinate cell legth and cell length+space + if {$view_mode != {hex}} { + set step 4 + set len 3 + } { + set step 3 + set len 2 + } + + # Initiate extraction + set result {} + set value {} + for {set row $start_row} {$row <= $end_row} {incr row} { + # Interate over cells withing the row + for {set col_s $start_col} {$col_s < $left_view_width} {incr col_s $step} { + # Determinate cell end index + set col_e $col_s + incr col_e $len + if {($row == $end_row) && ($col_s == $end_col)} { + break + } + + # Determinate cell value + set value [$left_view get $row.$col_s $row.$col_e] + set value [string trim $value] + + # Skip conversion for empty cells + if {$value == {}} { + lappend result {} + continue + } + + # Convert cell value to decimal integer + set value [string trimleft $value 0] + if {$value == {}} { + set value 0 + } + switch -- $view_mode { + {dec} { + lappend result $value + } + {hex} { + lappend result [expr "0x$value"] + } + {oct} { + lappend result [expr "0$value"] + } + } + } + } + + if {$start == $end} { + return [lindex $result 0] + } { + return $result + } + } + + ## Set value of the specified cell + # @parm Int address - Cell address + # @parm Int value - New cell value (must be withing interval [0;255]) + # @return void + public method setValue {address value} { + # Local variables + set index [address_to_index $address] ;# Text index + set row [lindex $index 0] ;# Row in left view + set cell [lindex $index 1] ;# Column in right view / cell number in row + set start_col [lindex $index 2] ;# Starting column in left view + set end_col [lindex $index 3] ;# End column in left view + set index [$left_view index insert] ;# Current insertion index in left view + + # Empty value means clear the cell + if {$value == {}} { + # Clear cell in the left view + $left_view delete $row.$start_col $row.$end_col + if {$view_mode != {hex}} { + $left_view insert $row.$start_col { } + } { + $left_view insert $row.$start_col { } + } + $left_view mark set insert $index + $left_view tag add normal_font [list $row.0 linestart] [list $row.0 lineend] + + # Clear cell in the right view + if {$ascii_view} { + $right_view delete $row.$cell $row.$end_col + $right_view insert $row.$cell { } + $right_view tag add normal_font [list $row.0 linestart] [list $row.0 lineend] + } + + # Restore insertion cursor tags + if {$cursor_address == $address} { + if {$selected_view == {left}} { + left_view_adjust_cursor + } { + right_view_adjust_cursor + } + } + + # Restore cell highlight + restore_cell_highlight $address + + # Abort the rest of procedure + return + } + + # Validate input address and value + if {$DEBUG} { + if {$address >= $total_capacity} { + error "Address out of range" + } + if {$address != [expr {int($address)}]} { + error "Address must be integer" + } + if {$value > 255 || $value < 0} { + error "Value of of range" + } + if {$value != [expr {int($value)}]} { + error "Value must be integer" + } + } + + # Convert the given value to appropriate string + set original_value $value + switch -- $view_mode { + {hex} { + set value [format %X $value] + if {[string length $value] == 1} { + set value "0$value" + } + } + {oct} { + set value [format %o $value] + set len [string length $value] + if {$len != 3} { + set value "[string repeat {0} [expr {3 - $len}]]$value" + } + } + {dec} { + set value [expr $value] + set len [string length $value] + if {$len != 3} { + set value "[string repeat {0} [expr {3 - $len}]]$value" + } + } + } + + # Replace current content of the cell with new value + $left_view delete $row.$start_col $row.$end_col + $left_view insert $row.$start_col $value + $left_view mark set insert $index + $left_view tag add normal_font [list $row.0 linestart] [list $row.0 lineend] + + # Adjust right view + if {$ascii_view} { + set end_col $cell + incr end_col + + # Convert to character + set value [format %c $original_value] + + # Insert value to the text widget + $right_view delete $row.$cell $row.$end_col + if {($original_value < 127) && [string is print -strict $value]} { + $right_view insert $row.$cell $value + $right_view tag remove tag_np $row.$cell "$row.$cell+1c" + } { + $right_view insert $row.$cell {.} + $right_view tag add tag_np $row.$cell $row.$end_col + } + $right_view tag add normal_font [list $row.0 linestart] [list $row.0 lineend] + + # Adjust cursor postion + scan [$right_view index {insert}] {%d.%d} row cell + if {$cell == $width} { + set cell 0 + incr row + } + $right_view mark set insert $row.$cell + } + + # Restore insertion cursor tags + if {$cursor_address == $address} { + if {$selected_view == {left}} { + left_view_adjust_cursor + } { + right_view_adjust_cursor + } + } + + # Restore cell highlight + restore_cell_highlight $address + } + + ## Switch view (from left to right and on the contrary) + # @return void + public method switch_views {} { + if {!$ascii_view} { + return + } + if {$selected_view == {left}} { + focus $right_view + } { + focus $left_view + } + } + + ## Get current view (left or right) + # @return String - "left" or "right" + public method getCurrentView {} { + return $selected_view + } + + ## Focus on the left view + # @return void + public method focus_left_view {} { + focus -force $left_view + } + + ## Focus on the right view + # @return void + public method focus_right_view {} { + if {$ascii_view} { + focus -force $right_view + } + } + + ## Set cell background highlight (as write in progress) + # @parm Int address - Cell address + # @parm Bool bool - 1 == highlight; 0 == clear highlight + # @parm Int type - Type of highlight (color) + # @return void + public method set_bg_hg {address bool type} { + # Validate input address + if {$DEBUG} { + if {$address >= $total_capacity} { + error "Address out of range" + } + if {$address != [expr {int($address)}]} { + error "Address must be integer" + } + if {![string is boolean $bool]} { + error "'$bool' in not booleand value" + } + } + + switch -- $type { + 0 { + set arr {bg_hg} + set tag {tag_bg_hg} + } + 1 { + set arr {bg_hg1} + set tag {tag_bg_hg1} + } + 2 { + set arr {bg_hg2} + set tag {tag_bg_hg2} + } + } + if {[subst "\$${arr}($address)"] == $bool} { + return + } + set ${arr}($address) $bool + + # Local variables + set index [address_to_index $address] ;# (Auxiliary variable) + set row [lindex $index 0] ;# Cell row + set cell [lindex $index 1] ;# Cell number in the row + set start_col [lindex $index 2] ;# Starting column + set end_col [lindex $index 3] ;# End column + + # Create highlight + if {$bool} { + set bool {add} + } { + set bool {remove} + } + $left_view tag $bool $tag $row.$start_col $row.$end_col + if {$ascii_view} { + $right_view tag $bool $tag $row.$cell "$row.$cell+1c" + } + } + + ## Set cell highlight (as changed) + # @parm Int address - Cell address + # @parm Bool bool - 1 == highlight; 0 == clear highlight + # @return void + public method setHighlighted {address bool} { + # Validate input address + if {$DEBUG} { + if {$address >= $total_capacity} { + error "Address out of range" + } + if {$address != [expr {int($address)}]} { + error "Address must be integer" + } + if {![string is boolean $bool]} { + error "'$bool' in not booleand value" + } + } + + if {$highlighted_cells($address) == $bool} { + return + } + set highlighted_cells($address) $bool + + # Local variables + set index [address_to_index $address] ;# (Auxiliary variable) + set row [lindex $index 0] ;# Cell row + set cell [lindex $index 1] ;# Cell number in the row + set start_col [lindex $index 2] ;# Starting column + set end_col [lindex $index 3] ;# End column + + # Create highlight + if {$bool} { + set bool {add} + } { + set bool {remove} + } + $left_view tag $bool tag_hg $row.$start_col $row.$end_col + if {$ascii_view} { + $right_view tag $bool tag_hg $row.$cell "$row.$cell+1c" + } + } + + ## Remove all foreground highlighting tags + # @return void + public method clearHighlighting {} { + for {set i 0} {$i < $total_capacity} {incr i} { + set highlighted_cells($i) 0 + } + $left_view tag remove tag_hg 0.0 end + if {$ascii_view} { + $right_view tag remove tag_hg 0.0 end + } + } + + ## Remove all background highlighting tags + # @parm Int type - Type of highlight (color) + # @return void + public method clearBgHighlighting {type} { + switch -- $type { + 0 { + set arr {bg_hg} + set tag {tag_bg_hg} + } + 1 { + set arr {bg_hg1} + set tag {tag_bg_hg1} + } + 2 { + set arr {bg_hg2} + set tag {tag_bg_hg2} + } + } + for {set i 0} {$i < $total_capacity} {incr i} { + set ${arr}($i) 0 + } + $left_view tag remove $tag 0.0 end + if {$ascii_view} { + $right_view tag remove $tag 0.0 end + } + } + + ## Get address of current cell + # @return Int - Address + public method getCurrentCell {} { + return $cursor_address + } + + ## Set current cell + # @parm Int address - Cell address + # @return void + public method setCurrentCell {address} { + # Check for allowed range + if {$address >= $total_capacity} { + return + } + + # Local variables + set index [address_to_index $address] ;# (Auxiliary variable) + set row [lindex $index 0] ;# Cell row + set cell [lindex $index 1] ;# Cell number in the row + set start_col [lindex $index 2] ;# Cell starting column + + # Adjust cursor + set cursor_address $address + if {$selected_view == {left}} { + $left_view mark set insert $row.$start_col + $left_view see insert + left_view_adjust_cursor + } { + $right_view mark set insert $row.$cell + $right_view see insert + right_view_adjust_cursor + } + } + + ## Scroll to certain cell + # @parm Int address - Cell address + # @return void + public method seeCell {address} { + # Check for allowed range + if {$address >= $total_capacity} { + return + } + + # Local variables + set index [address_to_index $address] ;# (Auxiliary variable) + set row [lindex $index 0] ;# Cell row + set cell [lindex $index 1] ;# Cell number in the row + set start_col [lindex $index 2] ;# Cell starting column + + # Adjust cursor + if {$selected_view == {left}} { + $left_view see $row.$start_col + } { + $right_view see $row.$cell + } + } + + ## Bind command to pseudo-event <current_cell_changed> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd $cursor_address" + # @return void + public method bindCurrentCellChanged {cmd} { + set current_cell_changed_cmd_set 1 + set current_cell_changed_cmd $cmd + } + + ## Bind command to pseudo-event <cell_value_changed> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd $cursor_address $new_value" + # @return void + public method bindCellValueChanged {cmd} { + set cell_value_changed_cmd_set 1 + set cell_value_changed_cmd $cmd + } + + ## Bind command to pseudo-event <cell_enter> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd $cursor_address $X $Y" + # @return void + public method bindCellEnter {cmd} { + set cell_enter_cmd_set 1 + set cell_enter_cmd $cmd + bind_mouse_motions + } + + ## Bind command to pseudo-event <cell_leave> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd" + # @return void + public method bindCellLeave {cmd} { + set cell_leave_cmd_set 1 + set cell_leave_cmd $cmd + bind_mouse_motions + } + + ## Bind command to pseudo-event <cell_motion> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd $X $Y" + # @return void + public method bindCellMotion {cmd} { + set cell_motion_cmd_set 1 + set cell_motion_cmd $cmd + bind_mouse_motions + } + + ## Bind command to pseudo-event <scroll_action> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd" + # @return void + public method bindScrollAction {cmd} { + set scroll_action_cmd_set 1 + set scroll_action_cmd $cmd + } + + ## Bind command to pseudo-event <selection_action> + # @parm String cmd - command to invoke from root namespace + # Command invocation: eval "$cmd bool__something_selected_or_not" + # @return void + public method bindSelectionAction {cmd} { + set selection_action_cmd_set 1 + set selection_action_cmd $cmd + } + + ## + # @return + public method getRangeOfSelection {} { + if {[llength [$left_view tag nextrange sel 0.0]]} { + return [list \ + [index_to_address {left} [$left_view index sel.first+1c]] \ + [index_to_address {left} [$left_view index sel.last-1c]] \ + ] + } { + return {} + } + } + + ## Get number of topmost visible row in both views + # @return Int - Row number (1st row has number 1) + public method getTopRow {} { + return $top_row + } + + ## Switch view mode (HEX, DEC etc.) + # @parm String newmode - New mode for left view (one of {hex oct dec}) + # @return void + public method switch_mode {newmode} { + if {$newmode == $view_mode} { + return + } + set original_mode $view_mode + set view_mode $newmode + switch -- $view_mode { + {hex} { + set new_value_length 2 + set left_view_width [expr {$width * 3 - 1}] + } + {oct} { + set new_value_length 3 + set left_view_width [expr {$width * 4 - 1}] + } + {dec} { + set new_value_length 3 + set left_view_width [expr {$width * 4 - 1}] + } + default { + error "Invalid mode: $view_mode" + } + } + switch -- $original_mode { + {hex} { + set value_length 2 + set skip 3 + } + {oct} { + set value_length 3 + set skip 4 + } + {dec} { + set value_length 3 + set skip 4 + } + } + + $left_view configure -cursor watch + $left_header configure -cursor watch + $left_address_bar configure -cursor watch + if {$ascii_view} { + $right_header configure -cursor watch + $right_view configure -cursor watch + } + update + $left_view configure -width $left_view_width + $left_header configure -width $left_view_width + + # Iterate over rows in left view + for {set row 1} {$row <= $height} {incr row} { + set start 0 + set end $value_length + set values {} + set lineend [$left_view index [list $row.0 lineend]] + + # Save line to list + for {set cell 0} {$cell < $width} {incr cell} { + lappend values [string trim [$left_view get $row.$start $row.$end]] + incr start $skip + incr end $skip + } + $left_view delete $row.0 $lineend + + # Convert list + set first 1 + set space [string repeat { } $new_value_length] + foreach val $values { + if {!$first} { + $left_view insert $lineend { } + } { + set first 0 + } + if {$val == {}} { + $left_view insert $lineend $space + continue + } { + set val [string trimleft $val 0] + if {$val == {}} { + set val 0 + } + switch -- $original_mode { + {hex} { + # HEX -> DEC + if {$view_mode == {dec}} { + set val [expr "0x$val"] + + # HEX -> OCT + } { + set val [expr "0x$val"] + set val [format {%o} $val] + } + } + {dec} { + # DEC -> HEX + if {$view_mode == {hex}} { + set val [format %X $val] + + # DEC -> OCT + } { + set val [format %o $val] + } + } + {oct} { + # OCT -> HEX + if {$view_mode == {hex}} { + set val [expr "0$val"] + set val [format %X $val] + + # OCT -> DEC + } { + set val [expr "0$val"] + } + } + } + } + + set len [string length $val] + if {$len < $new_value_length} { + set len [expr {$new_value_length - $len}] + set val "[string repeat 0 $len]$val" + } + + $left_view insert $lineend $val + $left_view tag add normal_font [list $lineend linestart] [list $lineend lineend] + } + } + + fill_left_header + for {set i 0} {$i < $total_capacity} {incr i} { + if {$highlighted_cells($i)} { + set highlighted_cells($i) 0 + setHighlighted $i 1 + } + if {$bg_hg($i)} { + set bg_hg($i) 0 + set_bg_hg $i 1 + } + } + $left_view configure -cursor xterm + $left_header configure -cursor left_ptr + $left_address_bar configure -cursor left_ptr + if {$ascii_view} { + $right_header configure -cursor left_ptr + $right_view configure -cursor xterm + } + } + + ## Set hexeditor enabled/disabled state + # @parm Bool bool - 1 == enabled; 0 == disabled + # @return void + public method setDisabled {bool} { + set disabled $bool + + # Set state for left view + if {$bool} { + $left_view configure -state disabled + $left_view configure -bg {#F8F8F8} -fg {#999999} ;#DDDDDD + } { + $left_view configure -state normal + $left_view configure -bg {#FFFFFF} -fg {#000000} + } + + # Set state for right view + if {$ascii_view} { + if {$bool} { + $right_view configure -state disabled + $right_view configure -bg {#F8F8F8} -fg {#999999} ;#DDDDDD + } { + $right_view configure -state normal + $right_view configure -bg {#FFFFFF} -fg {#000000} + } + } + } + + ## Get reference of left view text widget + # @return Widget - Text widget + public method getLeftView {} { + return $left_view + } + + ## Get reference of right view text widget + # @return Widget - Text widget or {} + public method getRightView {} { + return $right_view + } + + ## Get configuration list + # @return List - Configuration list + proc get_config {} { + return [list $text_to_find $find_opt(fc) $find_opt(bw)] + } + + ## Load configuration list generated by function `get_config' + # @param List config - Configuration list generated by `get_config' + # @return void + proc load_config_list {config_list} { + # Load configuration + set text_to_find [lindex $config_list 0] + set find_opt(fc) [lindex $config_list 1] + set find_opt(bw) [lindex $config_list 2] + + # Validate loaded configuration + if {![string is boolean -strict $find_opt(fc)]} { + set find_opt(fc) 1 + } + if {![string is boolean -strict $find_opt(bw)]} { + set find_opt(bw) 0 + } + } + + ## Find next occurence of search string + # @return Bool - 0 == Invalid call; 1 == Valid call + public method find_next {} { + if {$last_find_index == {}} { + return 0 + } + if {$find_opt(bw)} { + set result [find_FIND $last_find_index-[string length $text_to_find]c] + } { + set result [find_FIND $last_find_index] + } + return $result + } + + ## Find previous occurence of search string + # @return Bool - 0 == Invalid call; 1 == Valid call + public method find_prev {} { + if {$last_find_index == {}} { + return 0 + } + + set backward_org $find_opt(bw) + set find_opt(bw) [expr {!$find_opt(bw)}] + + if {$find_opt(bw)} { + set result [find_FIND $last_find_index-[string length $text_to_find]c] + } { + set result [find_FIND $last_find_index] + } + + set find_opt(bw) $backward_org + return $result + } + + ## Invoke dialog: Find string + # @return Bool - 1 == string found; 0 == string not found + public method find_dialog {} { + # Create toplevel find_dialog_window + if {[winfo exists $find_dialog_win]} { + destroy $find_dialog_win + } + incr find_dialog_count + set find_dialog_win [toplevel .hex_editor_find_dialog_$find_dialog_count] + + ## Create top frame + set top_frame [frame $find_dialog_win.top_frame] + # Text to find + grid [label $top_frame.string_lbl \ + -text [mc "Text to find"] \ + ] -row 0 -column 0 -columnspan 4 -sticky w + grid [ttk::entry $top_frame.string_entry \ + -textvariable ::HexEditor::text_to_find \ + -width 0 \ + ] -row 1 -column 1 -sticky we -columnspan 3 + # Where + grid [label $top_frame.where_lbl \ + -text [mc "Where"] \ + ] -row 2 -column 0 -columnspan 2 -sticky w + grid [radiobutton $top_frame.radio_0 \ + -variable ::HexEditor::where_to_search \ + -text [mc "Left view"] -value left \ + ] -row 3 -column 1 -sticky w + grid [radiobutton $top_frame.radio_1 \ + -variable ::HexEditor::where_to_search \ + -text [mc "Right view"] -value right \ + ] -row 4 -column 1 -sticky w + set ::HexEditor::where $selected_view + if {!$ascii_view} { + $top_frame.radio_1 configure -state disabled + } + # Options + grid [label $top_frame.options_lbl \ + -text [mc "Options"] \ + ] -row 2 -column 2 -columnspan 2 -sticky w + grid [checkbutton $top_frame.opt_fc_chb \ + -variable ::HexEditor::find_opt(fc) \ + -onvalue 1 -offvalue 0 \ + -text [mc "From cursor"] \ + ] -row 3 -column 3 -sticky w + grid [checkbutton $top_frame.opt_bw_chb \ + -variable ::HexEditor::find_opt(bw) \ + -onvalue 1 -offvalue 0 \ + -text [mc "Backwards"] \ + ] -row 4 -column 3 -sticky w + + # Finalize top frame creation + grid columnconfigure $top_frame 0 -minsize 25 + grid columnconfigure $top_frame 1 -weight 1 + grid columnconfigure $top_frame 2 -minsize 25 + grid columnconfigure $top_frame 3 -weight 1 + + # Create and pack 'OK' and 'CANCEL' buttons + set buttonFrame [frame $find_dialog_win.button_frame] + pack [ttk::button $buttonFrame.ok \ + -text [mc "Ok"] \ + -compound left \ + -image ::ICONS::16::ok \ + -command "$this find_FIND" \ + ] -side left + pack [ttk::button $buttonFrame.cancel \ + -text [mc "Cancel"] \ + -compound left \ + -image ::ICONS::16::button_cancel \ + -command "$this find_CANCEL" \ + ] -side left + + # Events binding (Enter == Find; Escape == Cancel) + bind $find_dialog_win <KeyRelease-Return> "$this find_FIND; break" + bind $find_dialog_win <KeyRelease-KP_Enter> "$this find_FIND; break" + bind $find_dialog_win <KeyRelease-Escape> "$this find_CANCEL; break" + + # Pack dialog frames + pack $top_frame -fill both -anchor nw -padx 5 -pady 5 + pack $buttonFrame -side bottom -anchor e -padx 5 + + # Window manager options -- modal find_dialog_window + wm iconphoto $find_dialog_win ::ICONS::16::find + wm title $find_dialog_win [mc "Find"] + wm minsize $find_dialog_win 260 140 + wm transient $find_dialog_win $main_frame + wm protocol $find_dialog_win WM_DELETE_WINDOW " + grab release $find_dialog_win + destroy $find_dialog_win + " + update + grab $find_dialog_win + $top_frame.string_entry selection range 0 end + focus -force $top_frame.string_entry + + tkwait window $find_dialog_win + if {$last_find_index == {}} { + return 0 + } { + return 1 + } + } + + # ------------------------------------------------------------------- + # HELPER PROCEDURES + # ------------------------------------------------------------------- + + ## Initiate serach + # @return void + public method find_FIND args { + # Determinate search options + set start_index [lindex $args 0] + if {$where_to_search == {left}} { + set widget $left_view + } { + set widget $right_view + } + if {$find_opt(bw)} { + set direction {-backwards} + } { + set direction {-forwards} + } + if {$start_index == {}} { + if {$find_opt(fc)} { + set start_index [$widget index insert] + } { + set start_index 1.0 + } + } + + # Perform search + set last_find_index [$widget search $direction -nocase -- $text_to_find $start_index] + + # String found + if {$last_find_index != {}} { + $popup_menu entryconfigure [::mc "Find next"] -state normal + $popup_menu entryconfigure [::mc "Find previous"] -state normal + catch { + $widget tag remove sel 0.0 end + } + set end_idx $last_find_index+[string length $text_to_find]c + $widget tag add sel $last_find_index $end_idx + $widget mark set insert $end_idx + $widget see $end_idx + set last_find_index $end_idx + set result 1 + + # String not found + } { + $popup_menu entryconfigure [::mc "Find next"] -state disabled + $popup_menu entryconfigure [::mc "Find previous"] -state disabled + + if {[winfo exists $find_dialog_win]} { + set parent $find_dialog_win + } { + set $main_frame + } + tk_messageBox \ + -parent $parent \ + -type ok \ + -icon warning \ + -title [mc "String not found"] \ + -message [mc "Search string '%s' not found !" $text_to_find] + set result 0 + } + + # Close find dialog + if {[winfo exists $find_dialog_win]} { + find_CANCEL + } + + return $result + } + + ## Close find dialog + # @return void + public method find_CANCEL {} { + grab release $find_dialog_win + destroy $find_dialog_win + } +} + +## Initialize NS variables + # Find options +array set ::HexEditor::find_opt { + fc 1 + bw 0 +} diff --git a/lib/lib/ihextools.tcl b/lib/lib/ihextools.tcl new file mode 100755 index 0000000..071b178 --- /dev/null +++ b/lib/lib/ihextools.tcl @@ -0,0 +1,523 @@ +#!/usr/bin/tclsh +# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) + +############################################################################ +# Copyright (C) 2007-2009 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# 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 2 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, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + +# -------------------------------------------------------------------------- +# DESCRIPTION +# Provides some tools for manipulating IHEX8, binary and sim files. +# It's intented for converting between these file types and for +# normalizing hex files. +# -------------------------------------------------------------------------- + +namespace eval IHexTools { + + ## PUBLIC + variable update 0 ;# Bool: Periodicaly update GUI and increment progress + variable abort 0 ;# Bool: Abort currently running procedure + variable progress 1 ;# Int: Variable for progress bars + variable error_count 0 ;# Int: Count of errors + variable error_string {} ;# Error messages + variable highest_addr 0 ;# Int: Highest address in loaded IHEX file + + ## PRIVATE + variable content ;# Array: Currently loaded data (content(0..65535) => 00..FF) + variable INITIALIZED 0 ;# Bool: Namespace variables initialized + variable data_field ;# Auxiliary variable for creating IHEX records + variable data_field_len ;# Auxiliary variable for creating IHEX records + + + # ---------------------------------------------------------------- + # GENERAL PURPOSE PROCEDURES + # ---------------------------------------------------------------- + + ## Compute checksum for the given HEX field (without leading colon) + # @parm String hex_data - HEX field without leading colon + # @return String - resulting hexadecimal checksum + proc getCheckSum {hex_data} { + + set sum 256 ;# Initial checksum + set hex_data [split $hex_data {}] + + # Iterate over hex data + for {set i 0} {1} {incr i} { + + # Gain 1st hex digit + set val [lindex $hex_data $i] + + # If the 1st digit is empty -> return result + if {$val == {}} { + # Handle overflow + if {$sum == 256} {return {00}} + # Convert decimal checksum to hexadecimal + set sum [format "%X" $sum] + if {[string length $sum] == 1} { + set sum "0$sum" + } + return $sum + } + + # Gain 2nd hex digit + incr i + append val [lindex $hex_data $i] + set val [expr "0x$val"] + + # Decrement checksum + incr sum -$val + + # Handle undeflow + if {$sum < 0} {incr sum 256} + } + } + + ## Get maximum value for progressbar when loading hex or sim file + # @parm String data - input sim or hex data + # @return Int - number of iterations divided by 25 + proc get_number_of_iterations {data} { + # Any EOL to LF + regsub -all {\r\n?} $data "\n" data + + # Local variables + set result 0 ;# Resulting number + set index 0 ;# Last search result + + # Get number of LF chracters + while 1 { + set index [string first "\n" $data $index] + if {$index == -1} {break} + incr index + } + + # Return result + return [expr {$result / 25 + 1}] + } + + ## Load IHEX 8 file into internal memory + # @parm String hex_data - Content of IHEX8 file to load + # @return Bool - result + proc load_hex_data {hex_data} { + variable INITIALIZED ;# Bool: Namespace variables initialized + + variable content ;# Array: Currently loaded data + variable update ;# Bool: Periodicaly update GUI and increment progress + variable abort 0 ;# Bool: Abort currently running procedure + variable progress 1 ;# Int: Variable for progress bars + variable error_count 0 ;# Int: Count of errors + variable error_string {} ;# Error messages + variable highest_addr 0 ;# Int: Highest address in loaded IHEX file + + # Initialize array of loaded data + if {!$INITIALIZED} { + free_resources + } + # Convert any EOL to LF + regsub -all {\r\n?} $hex_data "\n" hex_data + + # Local variables + set lineNum 0 ;# Number of the current line + set eof 0 ;# Bool: EOF detected + + # Iterate over HEX records + foreach line [split $hex_data "\n"] { + incr lineNum ;# Increment line number + + # Skip comments + if {[string index $line 0] != {:}} {continue} + + # Check for valid charters + if {![regexp {^:[0-9A-Fa-f]+$} $line]} { + Error $lineNum [mc "Line contains invalid characters"] + continue + } + # Check for odd lenght + set len [string length $line] + if {[expr {$len % 2}] != 1} { + Error $lineNum [mc "Line contains even number of characters"] + continue + } + + # Analize HEX record + set len [ string range $line 1 2 ] ;# Lenght field + set addr [ string range $line 3 6 ] ;# Address field + set type [ string range $line 7 8 ] ;# Type field + set data [ string range $line 9 {end-2} ] ;# Data field + set check [ string range $line {end-1} end ] ;# Checksum field + set line [ string range $line 1 {end-2} ] ;# Record without ':' and checksum + + # Handle record type (01 == EOF; 00 == normal record) + if {$type == {01}} { + set eof 1 + break + } elseif {$type != {00}} { + Error $lineNum [mc "Unknown record type '%s'" $type] + continue + } + + # Check for valid checksum + set new_check [getCheckSum $line] + if {$new_check != $check} { + Error $lineNum [mc "Bad checksum"] + continue + } + + # Check for correct value of the length field + set len [expr "0x$len"] + if {([string length $data] / 2) != $len} { + Error $lineNum [mc "Bad length"] + continue + } + + # Parse and load data field + set addr [expr "0x$addr"] + for {set i 0; set j 1} {$i < ($len * 2)} {incr i 2; incr j 2} { + set content($addr) [string range $data $i $j] + incr addr + } + + # Store highest address + if {$addr > $highest_addr} { + set highest_addr $addr + } + + # Update GUI and progress variable + if {$update} { + if {![expr {$lineNum % 25}]} { + # Conditional abort + if {$abort} {return 0} + # Update progress variable and GUI + incr progress + update + } + } + } + + # If there is no EOF then report that as an error + if {!$eof} { + Error - [mc "Missing EOF"] + } + + # Return result + if {$error_count} { + return 0 + } { + return 1 + } + } + + ## Load binary file into internal memory + # @parm String data - Binary data to load + # @return Bool - result + proc load_bin_data {data} { + variable INITIALIZED ;# Bool: Namespace variables initialized + + variable content ;# Array: Currently loaded data + variable update ;# Bool: Periodicaly update GUI and increment progress + variable abort 0 ;# Bool: Abort currently running procedure + variable progress 1 ;# Int: Variable for progress bars + variable error_count 0 ;# Int: Count of errors + variable error_string {} ;# Error messages + + # Initialize array of loaded data + if {!$INITIALIZED} { + free_resources + } + + # Check for allowed data length + set len [string length $data] + if {$len > 0x10000} { + Error - [mc "Data length exceeding limit 0x10000"] + return 0 + } + + # Load data + set val 0 + for {set i 0} {$i < $len} {incr i} { + binary scan [string index $data $i] c val ;# bin -> dec + set content($i) [string range [format %X $val] end-1 end] ;# dec -> hex + } + return 1 + } + + ## Load simulator file into internal memory + # @parm String data - Content of simulator file to load + # @return Bool - result + proc load_sim_data {data} { + variable INITIALIZED ;# Bool: Namespace variables initialized + + variable content ;# Array: Currently loaded data + variable update ;# Bool: Periodicaly update GUI and increment progress + variable abort 0 ;# Bool: Abort currently running procedure + variable progress 1 ;# Int: Variable for progress bars + variable error_count 0 ;# Int: Count of errors + variable error_string {} ;# Error messages + + # Initialize array of loaded data + if {!$INITIALIZED} { + free_resources + } + + # Adjust input data + regsub -all {\r\n?} $data "\n" data ;# Any EOL to LF + regsub -all -line {\s*#.*$} $data {} data ;# Remove comments + regsub {^[^\n]+\n} $data {} data ;# Discard the first line + + set lineNum 0 ;# Line number + + # Iterate over lines in the given data + foreach line [split $data "\n"] { + incr lineNum ;# Increment line number + + # Skip empty lines + if {$line == {}} {continue} + + # Anylize line + set ln [lindex $line 0] ;# Line number + set addr [lindex $line 1] ;# Address + set line [lreplace $line 0 1] ;# Processor codes + + # Check for validity of line number + if {![string is digit -strict $ln]} { + Error $lineNum [mc "Invalid line number '%s'" $ln] + continue + } + # Check for validity of address + if {![string is digit -strict $addr]} { + Error $lineNum [mc "Invalid address '%s'" $addr] + continue + } + # Check for allowed characters + if {![regexp {^[\d \t]+$} $line] || ![llength $line]} { + Error $lineNum [mc "Invalid data field"] + continue + } + + # Load processor codes + foreach val $line { + set content($addr) [format %X $val] + incr addr + } + + # Update GUI and progress variable + if {$update} { + if {![expr {$lineNum % 25}]} { + # Conditional abort + if {$abort} {return 0} + # Update progress variable and GUI + incr progress + update + } + } + } + + # Return result + if {$error_count} { + return 0 + } { + return 1 + } + } + + ## Get loaded data as binary string + # @return String - Resulting binary data + proc get_bin_data {} { + variable content ;# Array: Currently loaded data + variable update ;# Bool: Periodicaly update GUI and increment progress + variable abort 0 ;# Bool: Abort currently running procedure + variable progress 1 ;# Int: Variable for progress bars + + # Local variables + set addr 0 ;# Current address + set pad {} ;# Padding + set result {} ;# Resulting binary string + + # Load data and convert them (16 x 4096 interations) + for {set j 0} {$j < 16} {incr j} { + for {set i 0} {$i < 4096} {incr i} { + # Get hexadecimal value + set hex $content($addr) + # Convert it to binary value + if {$hex == {}} { + append pad "\0" + } { + if {$pad != {}} { + append result $pad + set pad {} + } + append result [subst "\\x$hex"] + } + # Increment address + incr addr + } + + # Update GUI and progress variable + if {$update} { + # Update progress variable and GUI + incr progress + update + # Conditional abort + if {$abort} { + return {} + } + } + } + + # Return resulting binary string + return $result + } + + ## Get loaded data as IHEX8 + # @return String - Resulting IHEX8 + proc get_hex_data {} { + variable content ;# Array: Currently loaded data + variable update ;# Bool: Periodicaly update GUI and increment progress + variable abort 0 ;# Bool: Abort currently running procedure + variable progress 1 ;# Int: Variable for progress bars + variable data_field ;# Auxiliary variable for creating IHEX records + variable data_field_len ;# Auxiliary variable for creating IHEX records + + # Local variables + set pointer 0 ;# Current address + set data_field_len 0 ;# IHEX8 Data field lenght + set data_field {} ;# IHEX8 Data field + set result {} ;# Resulting IHEX8 + + # Load data (16 x 4096 interations) + for {set j 0} {$j < 16} {incr j} { + for {set i 0} {$i < 4096} {incr i} { + # Determinate HEX value + set hex $content($pointer) + + # If HEX value if empty -> write record + if {$hex == {} && $data_field_len} { + create_hex_record [expr {$pointer - $data_field_len}] + append result $data_field + set data_field {} + + # Append HEX value to the current data field + } elseif {$hex != {}} { + if {[string length $hex] == 1} { + set hex "0$hex" + } + + append data_field $hex + incr data_field_len + } + + # Increment current address + incr pointer + + # If data field length is high -> write record + if {$data_field_len == ${::Compiler::Settings::max_ihex_rec_length}} { + create_hex_record [expr {$pointer - $data_field_len}] + append result $data_field + set data_field {} + } + } + + # Update GUI and progress variable + if {$update} { + # Update progress variable and GUI + incr progress + update + # Conditional abort + if {$abort} { + return {} + } + } + } + + # Append EOF and return result + append result {:00000001FF} + return $result + } + + ## Free used resources + # @return void + proc free_resources {} { + variable content ;# Array: Currently loaded data + variable error_string {} ;# Error messages + variable data_field 0 ;# Auxiliary variable for creating IHEX records + + # Reset array of loaded data + for {set i 0} {$i < 0x10000} {incr i} { + set content($i) {} + } + } + + ## Get value of particular cell in the loaded array + # @parm Int addr - Must be 0..65535 + # @return Int - -1 == Not defined; 0..255 loaded value + proc get_value {addr} { + variable content ;# Array: Currently loaded data + + if {$addr < 0 || $addr > 0xFFFF} { + return -1 + } + set result $content($addr) + if {$result == {}} { + return -1 + } { + return $result + } + } + + + + # ---------------------------------------------------------------- + # INTERNAL AUXILIARY PROCEDURES + # ---------------------------------------------------------------- + + ## Create IHEX8 record (result -> data_field) + # @parm String addr - Content of address firld (decimal number) + # @return void + proc create_hex_record {addr} { + variable data_field ;# Auxiliary variable for creating IHEX records + variable data_field_len ;# Auxiliary variable for creating IHEX records + + # Adjust address + set addr [format %X $addr] + set len [string length $addr] + if {$len != 4} { + set addr "[string repeat 0 [expr {4 - $len}]]$addr" + } + # Adjust lenght + set len [format %X $data_field_len] + if {[string length $len] == 1} { + set len "0$len" + } + + # Create HEX field + set data_field ":${len}${addr}00${data_field}[getCheckSum ${len}${addr}00${data_field}]\n" + set data_field_len 0 + } + + ## Append error message to error_string + # @parm Int line - Number of line where the error occured + # @parm String - Error message + # @return void + proc Error {line string} { + variable error_count ;# Int: Count of errors + variable error_string ;# Error messages + + incr error_count + append error_string [mc "Error at line %s:\t" $line] $string "\n" + } +} diff --git a/lib/lib/innerwindow.tcl b/lib/lib/innerwindow.tcl new file mode 100755 index 0000000..f1d2505 --- /dev/null +++ b/lib/lib/innerwindow.tcl @@ -0,0 +1,360 @@ +#!/usr/bin/tclsh +# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) + +############################################################################ +# Copyright (C) 2007-2009 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# 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 2 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, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + + +# -------------------------------------------------------------------------- +# DESCRIPTION +# Implements tool for creating application inner windows. That means windows +# which are enclosed in the main window. And which are not managed by window +# manager but by their own implementation. These windows are inside the main +# window and cannot be dragged outside. +# +# REQUIREMENTS: +# Librararies: "Incr TCL", "BWidget", "Tk" +# This class also requires this: "namespace import ::itcl::*" +# -------------------------------------------------------------------------- + +class InnerWindow { + ## COMMON + common active_titclr {#AAAAFF} ;# Color: Active background color + common inactive_titclr {#DDDDDD} ;# Color: Inactive background color + common title_bar_height 10 ;# Int: Height of the titlebar in pixels + + # List: Title bar popup menu + common MENU { + {command "Shade/Unshade" "" 0 {collapse_expand} + {}} + {command "Close" "" 0 {close_window} + {}} + } + + ## PRIVATE + private variable win_height + private variable max_X ;# Int: Auxiliary variable for storing max. allowed position + private variable max_Y ;# Int: Auxiliary variable for storing max. allowed position + private variable click_X ;# Int: Auxiliary variable for storing last position + private variable click_Y ;# Int: Auxiliary variable for storing last position + + private variable close_cmd + private variable title_bar ;# Widget: Window title bar + private variable title_label ;# Widget: Label containg text "Scribble notepad" + private variable close_button ;# Widget: Close button + private variable coll_exp_but ;# Widget: Shade button + private variable win + private variable main_frame ;# Widget: Main window frame + private variable minim_flag 0 ;# Bool: Shaded or not + private variable allow_raise_win 1 ;# Bool: Allows to use command + + private variable menu ;# Widget: Title bar popup menu + private variable menu_created 0 ;# Bool: Title bar popup menu created + + private variable close_window_in_progress 0 ;# Bool: Close procedure is in progress + + ## Object constructor + # @parm Widget path - Window path (e.g. ".window_agent_007") + # @parm List geometry - {W H X Y} (Coordinates are raltive to the transient window) + # @parm String title - Window title + # @parm Image icon - Window icon, {} means no icon + # @parm String _close_cmd - Command to execute on close in the root namespace (stack frame #0) + constructor {path geometry title icon _close_cmd} { + + # Configure specific ttk styles + ttk::style configure InnerWindow_Active.TButton \ + -background $active_titclr \ + -padding 0 \ + -borderwidth 1 \ + -relief flat + ttk::style map InnerWindow_Active.TButton \ + -background [list active $active_titclr] \ + -relief [list active raised] + + ttk::style configure InnerWindow_Inactive.TButton \ + -background $inactive_titclr \ + -padding 0 \ + -borderwidth 1 \ + -relief flat + ttk::style map InnerWindow_Inactive.TButton \ + -background [list active $inactive_titclr] \ + -relief [list active raised] + + # Set object variables + set max_X 1000 + set max_Y 1000 + set close_cmd $_close_cmd + + # Create window GUI components + set win [frame $path -bd 1 -relief raised -bg $active_titclr -padx 2 -pady 2] + set main_frame [frame $win.main_frame] + set menu $win.menu + + ## Create title bar + # - Title bar frame + set title_bar [frame $win.title_bar \ + -bg $active_titclr \ + -height $title_bar_height \ + ] + set title_label [label $title_bar.text \ + -bg $active_titclr -pady 0 \ + -compound left -text $title \ + -cursor left_ptr \ + ] + if {$icon != {}} { + $title_label configure -image $icon -padx 5 + } + # - Button "Close" + set close_button [ttk::button $title_bar.close_but \ + -style InnerWindow_Active.TButton \ + -command "$this close_window" \ + -image ::ICONS::16::button_cancel \ + -takefocus 0 \ + ] + DynamicHelp::add $close_button -text [mc "Close"] + setStatusTip -widget $close_button -text [mc "Close"] + # - Button "Shade" + set coll_exp_but [ttk::button $title_bar.col_exp_but \ + -style InnerWindow_Flat.TButton \ + -command "$this collapse_expand" \ + -image ::ICONS::16::_1uparrow \ + -takefocus 0 \ + ] + DynamicHelp::add $coll_exp_but -text [mc "Shade"] + setStatusTip -widget $coll_exp_but -text [mc "Shade"] + # Pack buttons + pack $coll_exp_but -padx 5 -side left -pady 0 -ipady 0 + pack $close_button -side right -pady 0 -ipady 0 -padx 3 + pack $title_label -side left -fill x -pady 0 -ipady 0 -expand 1 + raise $close_button + # Set title bar event bindings + bind $title_label <Double-1> "$this collapse_expand; break" + bind $title_label <Button-1> "$this title_B1 %X %Y" + bind $title_label <B1-Motion> "$this title_B1_motion %X %Y; break" + bind $title_label <ButtonRelease-1> "$this title_B1_release; break" + bind $title_label <ButtonRelease-3> "$this title_B3_release %X %Y; break" + + + pack $title_bar -fill x + pack $main_frame -fill both -expand 1 + + # Show the window + set win_height [lindex $geometry 1] + bind $win <Destroy> "catch {delete object $this}" + bind $main_frame <Destroy> "catch {delete object $this}" + bind $win <Visibility> "$this raise_win" + bind $win <FocusIn> "$this focusin" + bind $win <FocusOut> "$this focusout" + place $win \ + -width [lindex $geometry 0] \ + -height [lindex $geometry 1] \ + -x [lindex $geometry 2] \ + -y [lindex $geometry 3] \ + -anchor nw + raise $win + } + + ## Object destructor + destructor { + close_window + } + + ## Withdraw the window + # Note: Window can be taken back to visible state using method "geometry" + # @see geometry + # @return + public method withdraw {} { + place forget $win + } + + ## Close the window + # @return void + public method close_window {} { + if {$close_window_in_progress} {return} + set close_window_in_progress 1 + + uplevel #0 $close_cmd + destroy $win + } + + ## Get window inner frame where to map widgets in the window + # @return Widget - Inner frame + public method get_frame {} { + return $main_frame + } + + ## Get and/or set window geometry including frame and title bar + # @parm Int = {} - Width + # @parm Int = {} - Height + # @parm Int = {} - Relative position -- X + # @parm Int = {} - Relative position -- Y + # Note: If you want to set only certain attributes then set others as {} + # @return Current window geometry {W H X Y} + public method geometry args { + # Set geometry + if {[llength $args]} { + if {[string length [lindex $args 0]]} { + place $win -width [lindex $args 0] + } + if {[string length [lindex $args 1]]} { + place $win -height [lindex $args 1] + set win_height [lindex $args 1] + } + if {[string length [lindex $args 2]]} { + place $win -x [lindex $args 2] + } + if {[string length [lindex $args 3]]} { + place $win -y [lindex $args 3] + } + update + } + + # Get geometry + return [list \ + [winfo width $win] \ + [winfo height $win] \ + [winfo x $win] \ + [winfo y $win] \ + ] + } + + ## Event handler: window frame <FocusIn> + # @return void + public method focusin {} { + update + foreach widget [list $title_bar $title_label $win] { + $widget configure -bg $active_titclr + } + foreach widget [list $close_button $coll_exp_but] { + $widget configure -style InnerWindow_Active.TButton + } + + update + } + + ## Event handler: window frame <FocusOut> + # @return void + public method focusout {} { + update + foreach widget [list $title_bar $title_label $win] { + $widget configure -bg $inactive_titclr + } + foreach widget [list $close_button $coll_exp_but] { + $widget configure -style InnerWindow_Inactive.TButton + } + + update + } + + ## (Un)Shade window + # @return void + public method collapse_expand {} { + # Object variables + set minim_flag [expr {!$minim_flag}] + + # Shade + if {$minim_flag} { + set image _1downarrow + pack forget $main_frame + place $win -height [expr {[winfo height $win.title_bar] + 4}] + # Unshade + } { + set image _1uparrow + pack $main_frame -fill both -expand 1 + place $win -height $win_height + } + $coll_exp_but configure -image ::ICONS::16::$image + } + + ## Determinate whether the window is shaded or not + # @return Bool - 1 == Shaded; 0 == Not shaded + public method get_minim_flag {} { + return $minim_flag + } + + ## Insure window visibility + # @return void + public method raise_win {} { + if {!$allow_raise_win} {return} + set allow_raise_win 0 + after 1000 "catch {$this set_allow_raise_win}" + raise $win + } + + ## @see raise_win + # @return void + public method set_allow_raise_win {} { + set allow_raise_win 1 + } + + ## Event handler: title bar <Button-1> + # @parm Int x - Absolute X coordinate + # @parm Int y - Absolute Y coordinate + # @return void + public method title_B1 {x y} { + set click_X [expr {[winfo x $win] - $x}] + set click_Y [expr {[winfo y $win] - $y}] + + set max_X [winfo width .] + set max_Y [winfo height .] + incr max_X -70 + incr max_Y -70 + + focus $win + $title_label configure -cursor fleur + } + + ## Event handler: title bar <ButtonRelease-1> + # @return void + public method title_B1_release {} { + $title_label configure -cursor left_ptr + } + + ## Event handler: title bar <ButtonRelease-3> + # @parm Int x - Absolute X coordinate + # @parm Int y - Absolute Y coordinate + # @return void + public method title_B3_release {X Y} { + focus $win + + if {!$menu_created} { + menuFactory $MENU $menu 0 "$this " 0 {} + set menu_created 1 + } + + tk_popup $menu $X $Y + } + + ## Event handler: title bar <B1-Motion> + # @parm Int x - Absolute X coordinate + # @parm Int y - Absolute Y coordinate + # @return void + public method title_B1_motion {x y} { + incr x $click_X + incr y $click_Y + + if {$x > 0 && $x < $max_X} { + place $win -x $x + } + if {$y > 0 && $y < $max_Y} { + place $win -y $y + } + } +} diff --git a/lib/lib/settings.tcl b/lib/lib/settings.tcl new file mode 100755 index 0000000..3c50466 --- /dev/null +++ b/lib/lib/settings.tcl @@ -0,0 +1,293 @@ +#!/usr/bin/tclsh +# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) + +############################################################################ +# Copyright (C) 2007-2009 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# 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 2 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, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + +# -------------------------------------------------------------------------- +# DESCRIPTION +# Implements interface to program settings (which are stored in a file) +# -------------------------------------------------------------------------- + +class Settings { + common dir_sep [file separator] ;# Directory separator (eg. '/') + common count 0 ;# Counter of instances + + private variable isEmpty 1 ;# Is settings array empty + private variable isReady 0 ;# Is interface ready + + private variable directory ;# Path to directory with settings file + private variable filename ;# Name of file with settings related to this instance + private variable fileFullPath ;# Full name of settings file (including directory) + private variable configArray {} ;# Content of settings maneged by this interface + + ## Object contructor + # @parm String configDir - Path to directory with settings file + # @parm String configFileName - Name of file with settings + constructor {configDir configFileName} { + incr count ;# increment instance conter + + # Incalize object variables + set configArray "::Settings::S$count" ;# Array of settings + set directory [string trimright $configDir "/\/"] ;# Path to directory with settings file + set filename [string trimleft $configFileName "/\/"] ;# Name of file with settings + set fileFullPath "${directory}${dir_sep}${filename}" ;# Full name of settings file + + # If specified file does not exist -> create it + if {![file exists $fileFullPath]} { + if {[catch { + file mkdir $directory + close [open $fileFullPath w 420] + }]} then { + return + } else { + set isReady 1 + } + + # Else check if the file is readable and writable + } else { + if {$::MICROSOFT_WINDOWS || ([file readable $fileFullPath] && [file writable $fileFullPath])} { + set isReady 1 + } { + return + } + } + + # Load settings from the file + reLoadConfig + } + + ## Object destructor + destructor { + } + + ## (Re)load settings from config file + # @return result + public method reLoadConfig {} { + + # Check if file is readable + if {!$::MICROSOFT_WINDOWS && ![file readable $fileFullPath]} { + return 0 + } + + # Read content of config file and store it as list of lines into fileData + set configFile [open $fileFullPath r] + set fileData [read $configFile] + set fileData [regsub -all {\r\n} $fileData "\n"] + set fileData [regsub -all {\r} $fileData "\n"] + set fileData [split $fileData "\n"] + close $configFile + + # Parse content of the file + set category {general} + foreach line $fileData { + # Local variables + set line [string trim $line] ;# Line of config file + set key {} ;# Key + set value {} ;# Value for the key + + # Skip empty lines + if {$line == {}} {continue} + + # Handle category declaration + if {[regexp {^\[\s*[\w \t]+\s*\]$} $line]} { + set category [string trim $line "\[\] \t"] + + # Handle key and its value + } elseif {[regexp {^\s*[\w \t:]+\s*\=\s*\".*\"\s*$} $line]} { + # Determinate key + regexp {^\s*[\w \t:]+\s*\=} $line key + set key [string trim $key "=\t "] + # Determinate value + regexp {\s*\".*\"\s*$} $line value + set value [string trim $value] + regsub {^\"} $value {} value + regsub {\"$} $value {} value + regsub -all "\a" $value "\n" value + # Set key and value to array + set "$configArray\($category/$key\)" $value + } + } + + # Set variable isEmpty + if {[array size $configArray] != 0} { + set isEmpty 0 + } { + set isEmpty 1 + } + + # return result + return 1 + } + + ## Save current content of $configArray to config file + # @return result + public method saveConfig {} { + + # Check if file is writable + if {![file writable $fileFullPath]} { + return 0 + } + + # Local variables + set configFile [open $fileFullPath w 420] ;# ID of config file chanel + set categories {general} ;# Name of current category + + # Determinate list of categories + foreach key [array names $configArray] { + # Determinate category + regexp {^.+/} $key category + set category [string trimright $category {/}] + # Append category to the list + if {[lsearch $categories $category] == -1} { + lappend categories $category + } + } + + # Iterate over categories and save them to the file + foreach category $categories { + # Get names of keys in current category + set keys [array names $configArray -regexp "$category/"] + # Save category declaration + puts $configFile "\n\[$category\]" + # Iterate over keys in current category + foreach fullKey $keys { + # Determinate key + regsub {^[^/]*/} $fullKey {} key + # Determinate value + set value [subst "\$$configArray\(\$fullKey\)"] + regsub -all "\n" $value "\a" value + # Save key and value + puts $configFile "$key=\"$value\"" + } + } + + # Done ... + close $configFile + return 1 + } + + ## Return True if config array is empty + # @return Bool - result + public method isEmpty {} { + return $isEmpty + } + + ## Return True if interface is ready + # @return Bool - result + public method isReady {} { + return $isReady + } + + ## Clear all settings + # @return void + public method clear {} { + array unset $configArray + } + + ## Remove specified key from settings + # @parm String key - name of key to remove + # @return Bool - result + public method remove {key} { + regsub -all {_} $key {__} key + regsub -all {\s} $key {_} key + + if {[i_contains $key]} { + unset "$configArray\($key\)" + return 1 + } { + return 0 + } + } + + ## Return True if the specified key is defined + # @parm String key - key to search for + # @return Bool - result + public method contains {key} { + regsub -all {_} $key {__} key + regsub -all {\s} $key {_} key + + return [i_contains $key] + } + + ## Internal key search (Does not peform key name adjusment) + # @parm String key - name of key to search for + # @return Bool - result + private method i_contains {key} { + if {[array names $configArray -exact $key] == {}} { + return 0 + } { + return 1 + } + } + + ## Get value for the given key + # @parm String key - Key + # @parm Mixed default - Default value + # @return Mixed - value for the given key + public method getValue {key default} { + + # Adjust key name + if {![regexp {^.+/} $key]} { + set key "general/$key" + } + regsub -all {_} $key {__} key + regsub -all {\s} $key {_} key + + # Check for valid key format + if {![regexp {^[\w:]+/[\w:]+$} $key]} { + return $default + } + + # Check if the given key is defined + if {[i_contains $key]} { + return [subst "\$$configArray\(\$key\)"] + } { + return $default + } + } + + ## Set value for the given key + # @parm String key - Key + # @parm Mixed value - Value + # @return Bool - result + public method setValue {key value} { + + ## Check for key validity + if {[regexp {[\!\=\$\^\*\+\?\.\[\]\{\}\(\)]} $key]} { + return 0 + } + + regsub -all {_} $key {__} key + regsub -all {\s} $key {_} key + + if {![regexp {^.+/} $key]} { + set key "general/$key" + } + + if {![regexp {^[\w:]+/[\w:]+$} $key]} { + return 0 + } + + ## Set value + set "$configArray\($key\)" $value + return 1 + } +} |