#!/usr/bin/wish
# GpsTrip - GPL software for GPS Trip.
# Authors: Grigory Milev <week@altlinux.org>
set version "1.1.6"

package require Ttk

set NODEBUG 0
set ERROR 1
set WARNING 2
set INFO 3

namespace eval ::common {
    set debug $INFO
    proc log {level data} {
        variable debug
        if {$debug >= $level} {
            if {$level == $::ERROR} {
                puts "Error: $data"
            } elseif {$debug == $::WARNING} {
                puts "Warning: $data"
            } elseif {$debug == $::INFO} {
                puts "Log: $data"
            }
        }
    }
}

namespace eval ::nmea {
    variable gps_gpgga
    variable gps_gprmc
    variable distance_calc_type
    set nmea_pi     3.141592653589793 ;#< PI value
    set nmea_pi180  [expr $nmea_pi / 180] ;#< PI division by 180

    proc parce_incoming_nmea_data {src} {
        set src_list [split $src ,]
        if {[llength $src_list] > 6} {
            set incoming_type [lindex $src_list 0]
            if {$incoming_type == {$GPGGA}} {
                fill_GPGGA $src_list
            } elseif {$incoming_type == {$GPRMC}} {
                fill_GPRMC $src_list
            } else {
                return -1
            }
        } else {
            return -1
        }
        return 1
    }

    proc init {} {
        set_distance_func nmea_distance_quick
        fill_GPGGA []
        fill_GPRMC []
    }

    # GGA - GPS Данные о местоположении
    proc fill_GPGGA {data} {
        variable gps_gpgga
        set gpgga_pack_vars [list null time latitude ns longitude ew gps_ind satelites hdop height height_unit geo_diff geo_diff_unit diff_life_time]
        if {[llength $data] > [expr [llength $gpgga_pack_vars] - 1]} {
            for {set i 1} {$i < [llength $gpgga_pack_vars]} {incr i} {
                set gps_gpgga([lindex $gpgga_pack_vars $i]) [lindex $data $i]
            }
        } else {
            for {set i 1} {$i < [llength $gpgga_pack_vars]} {incr i} {
                set gps_gpgga([lindex $gpgga_pack_vars $i]) 0
            }
        }
    }

    # RMC – pекомендуемый минимум GPS / навигационных данных
    proc fill_GPRMC {data} {
        variable gps_gprmc
        # Время, Состояние (A = данные верны, V = данные не верны) ...
        set gprmc_pack_vars [list null time state latitude ns longitude ew speed course date magnatic_declension]
        if {[llength $data] > [expr [llength $gprmc_pack_vars] - 1]} {
            for {set i 1} {$i < [llength $gprmc_pack_vars]} {incr i} {
                set gps_gprmc([lindex $gprmc_pack_vars $i]) [lindex $data $i]
                #::common::log [list [lindex $gprmc_pack_vars $i] [lindex $data $i]]
            }
        } else {
            for {set i 1} {$i < [llength $gprmc_pack_vars]} {incr i} {
                set gps_gprmc([lindex $gprmc_pack_vars $i]) 0
            }
        }
    }

    proc get_latitude {{ns 0}} {
        variable gps_gprmc
        if {$ns} {
            return $gps_gprmc(ns)
        } elseif {$gps_gprmc(ns) eq "N"} {
            return [expr -$gps_gprmc(latitude)]
        } else {
            return $gps_gprmc(latitude)
        }
    }

    proc get_longitude {{ew 0}} {
        variable gps_gprmc
        if {$ew} {
            return $gps_gprmc(ew)
        } elseif {$gps_gprmc(ew) eq "E"} {
            return [expr -$gps_gprmc(longitude)]
        } else {
            return $gps_gprmc(longitude)
        }
    }

    proc get_time {{time_zone 3}} {
        variable gps_gprmc
        set hour    [expr int($gps_gprmc(time)/10000)]
        set minute  [expr int(($gps_gprmc(time)-int($gps_gprmc(time)/10000)*10000)/100)]
        set seconds [expr int($gps_gprmc(time)-$hour*10000-$minute*100)]
        if {[string is integer $time_zone] && [string length $time_zone] > 0} {
            set hour    [expr $hour+$time_zone]
            if {$hour > 23} {set hour [expr $hour - 24]}
        }
        return [format "%02d:%02d:%02d" $hour $minute $seconds]
    }

    proc get_hdop {} {
        variable gps_gpgga
        return $gps_gpgga(hdop)
    }
    proc get_satelites {} {
        variable gps_gpgga
        return $gps_gpgga(satelites)
    }
    proc get_height {} {
        variable gps_gpgga
        return $gps_gpgga(height)$gps_gpgga(height_unit)
    }

    proc get_state {} {
        variable gps_gprmc
        if {$gps_gprmc(state) == {A}} {return 1}
        return 0
    }

    proc show_array {arr} {
        variable gps_gpgga
        foreach {parm val} [array get $arr] {
            puts "Parm:$parm Val:$val"
        }
    }

    proc get_speed {{fix 1}} {
        variable gps_gprmc
        if {[string is double $gps_gprmc(speed)] && $gps_gprmc(speed) > 0} {
            return [expr $gps_gprmc(speed) * 1.852 * $fix]
        }
        return 0
    }

    # Пересчитываем растояние, исходя из скорости в секунду
    proc distance_from_speed {} {
        variable gps_gprmc
        set speed [get_speed]
        if {$speed > 0} {
            return [expr $speed / 3.6]
        }
        return 0
    }

    ########################################
    # Функции для вычисления расстояния... #
    ########################################
    proc nmea_degree2radian {val} {
        variable nmea_pi180
        return [expr $val * $nmea_pi180]
    }

    proc nmea_ndeg2degree {val} {
        set deg [::tcl::mathfunc::round [expr $val / 100]]
        set val [expr $deg + ($val - $deg * 100) / 60]
        return $val
    }

    proc nmea_ndeg2radian {val} {
        return [nmea_degree2radian [nmea_ndeg2degree $val]]
    }

    proc nmea_info2pos {lat lon} {
        return [list [nmea_ndeg2radian $lat] [nmea_ndeg2radian $lon] ]
    }

    proc distance {prev_lat prev_lon cur_lat cur_lon} {
        variable distance_calc_type
        return [eval [list $distance_calc_type [nmea_info2pos $prev_lat $prev_lon] [nmea_info2pos $cur_lat $cur_lon] ] ]
    }

    proc set_distance_func {func} {
        variable distance_calc_type
        if {$func == {slow}} {
            set distance_calc_type {nmea_distance_slow}
        } else {
            set distance_calc_type {nmea_distance_quick}
        }
    }

    proc nmea_distance_quick {from_pos to_pos} {
        set from_pos_lat [lindex $from_pos 0]
        set from_pos_lon [lindex $from_pos 1]
        set to_pos_lat   [lindex $to_pos 0]
        set to_pos_lon   [lindex $to_pos 1]

        if {$from_pos_lat == $to_pos_lat && $from_pos_lon == $to_pos_lon} {
            set dist 0
        } else {
            set prepared_for_acos [expr sin($to_pos_lat) * sin($from_pos_lat) + \
                                        cos($to_pos_lat) * cos($from_pos_lat) * cos($to_pos_lon - $from_pos_lon)]
            if {-1 <= $prepared_for_acos && $prepared_for_acos <= 1} {
                set dist [expr 6372795 * acos($prepared_for_acos)]
            } else {
                ::common::log $::ERROR "Error when calculate distance, value for acos not in -1..1 range: $prepared_for_acos\nIncoming values are: From:$from_pos To:$to_pos"
                set dist 0
            }
        }
        return $dist
    }

    proc nmea_distance_slow {from_pos to_pos} {
        set from_pos_lat [lindex $from_pos 0]
        set from_pos_lon [lindex $from_pos 1]
        set to_pos_lat   [lindex $to_pos 0]
        set to_pos_lon   [lindex $to_pos 1]

        if {$from_pos_lat == $to_pos_lat && $from_pos_lon == $to_pos_lon} {
            set dist 0
        } else {
            set cl1     [expr cos($from_pos_lat)]
            set cl2     [expr cos($to_pos_lat)]
            set sl1     [expr sin($from_pos_lat)]
            set sl2     [expr sin($to_pos_lat)]
            set delta   [expr $to_pos_lon - $from_pos_lon]
            set cdelta  [expr cos($delta)]
            set sdelta  [expr sin($delta)]

            set p1 [expr ($cl2*$sdelta)**2]
            set p2 [expr (($cl1*$sl2) - ($sl1*$cl2*$cdelta))**2]
            set p3 [expr ($p1 + $p2)**0.5]
            set p4 [expr $sl1*$sl2]
            set p5 [expr $cl1*$cl2*$cdelta]
            set p6 [expr $p4 + $p5]
            set p7 [expr $p3/$p6]
            set anglerad [expr atan($p7)]
            set dist [expr $anglerad * 6372795]
            if {$dist < 0} {set dist [expr -$dist]}
            #'вычисление начального азимута
            #x = (cl1*sl2) - (sl1*cl2*cdelta) 
            #y = sdelta*cl2 
            #z = (-y/x).ATan.AsDegrees 
            #if (x < 0) then z = z+180 end 
            #z = -(z + 180 mod 360 - 180).AsRadians
            #anglerad2 = z - ((2*pi)*((z/(2*pi)).floor))
            #angledeg = (anglerad2*180)/pi
        }
        return $dist
    }

    init
}
#####################################################################################


namespace eval ::gps {
    set gps_state 0
    set gps_receiver_state 0
    variable gps_fd
    set gps_port_parms {4800,n,8,1}
    if {$::tcl_platform(os) == "Windows CE"} {
        set gps_port {COM8:}
    } elseif {[regexp -all -nocase "Windows" $::tcl_platform(os)]} {
    	#if {$::tcl_platform(os) == "Windows"}
        set gps_port {\\.\COM1}
    } else {
        set gps_port {/dev/rfcomm5}
    }
    set prev_latitude {UNSET}
    set prev_longitude {UNSET}

    proc open_gps {} {
        variable gps_port
        variable gps_port_parms
        variable gps_fd
        variable gps_state
        if {$gps_state == 0} {
            if {[catch {set gps_fd [open $gps_port w+]} reason]} {
                common::log $::ERROR "Error when open GPS on port $gps_port, reason: $reason"
                unset -nocomplain gps_fd
                return 1
            }
            fconfigure $gps_fd -mode $gps_port_parms -blocking no -buffering line
            fileevent $gps_fd readable "::gps::read $gps_fd"
            set gps_state 1
            ::common::log $::INFO "GPS port '$gps_port' successfully opened"
        }
        return 0
    }

    proc close_gps {} {
        variable gps_fd
        variable gps_state
        if {$gps_state >= 1} {
            if {[catch {close $gps_fd} reason]} {
                common::log $::ERROR "Error when close GPS on port $gps_port, reason: $reason"
                return 1
            }
            set gps_state 0
            ::common::log $::INFO "GPS port successfully closed"
        }
        return 0
    }


    proc read {fd} {
        variable prev_latitude
        variable prev_longitude
        variable gps_receiver_state

        if {[gets $fd data] > 0 && [regexp {^\$} $data]} {
            nmea::parce_incoming_nmea_data $data
            set gps_receiver_state [nmea::get_state]
            if {$gps_receiver_state} {
                event generate . <<GPS_state_good>> ;# Отсылаем событи о хорошем приеме
                event generate . <<GPS_updated>>
                set cur_latitude [nmea::get_latitude]
                set cur_longitude [nmea::get_longitude]
                if {$prev_latitude == {UNSET}} {set prev_latitude $cur_latitude; set prev_longitude $cur_longitude}

                if {$prev_latitude != $cur_latitude || $prev_longitude != $cur_longitude} {
                    set distance [nmea::distance $prev_latitude $prev_longitude $cur_latitude $cur_longitude]
                    if {$distance != 0} {
                        counters::update $distance [nmea::distance_from_speed] [nmea::get_speed]
                        set prev_latitude $cur_latitude
                        set prev_longitude $cur_longitude
                        event generate . <<GPS_distance_updated>>
                    }
                }
            } else {
                event generate . <<GPS_state_bad>> ;# Отсылаем событие - потерян прием
            }
        }
    }

    proc init {} {
#        open_gps
    }
}

namespace eval ::logger {
    variable data

    proc set_write_distance {distance} {
        variable data
        set data(write_distance) $distance
    }

    proc write_point {latitude longitude} {
        puts "Writed $latitude $longitude"
    }

    proc log_point {} {
        variable data
        set latitude [nmea::get_latitude]
        set longitude [nmea::get_longitude]
        set distance [nmea::distance $data(latitude_last) $data(longitude_last) $latitude $longitude]
        if {$distance >= $data(write_distance)} {
            set data(latitude_last) $latitude
            set data(longitude_last) $longitude
            write_point $latitude $longitude
            puts "Distance: $distance"
        }
    }

    proc init {} {
        variable data
        set data(latitude)  0
        set data(longitude) 0
        set data(latitude_last) 0
        set data(longitude_last) 0
        set_write_distance 0.1
        bind all <<GPS_distance_updated>> +[namespace current]::log_point
    }
}

namespace eval ::counters {
    set cfg(dist_diff) 70
    set cfg(dist_fix) 1
    set cfg(dist_min_limit) 1
    set cfg(max_counters) 6
    set cfg(pause) 0
    variable counter
    variable direction

    proc update {distance distance_from_speed speed} {
        variable cfg
        variable counter
        variable direction

        if {$distance_from_speed != 0 && $distance != 0} {
############################
#::common::log [list "$distance\t$distance_from_speed\t" [::nmea::get_speed]]
############################
            if {$distance > $distance_from_speed} {
                set dist1 $distance_from_speed
                set dist2 $distance
            } else {
                set dist1 $distance
                set dist2 $distance_from_speed
            }
            if {!$cfg(pause) && [expr $dist1/$dist2*100 > $cfg(dist_diff)] && $speed > $cfg(dist_min_limit)} {
                for {set i 0} {$i <= $cfg(max_counters)} {incr i} {
                    if {![cn_pause_get $i]} {
                        set_counter $i [expr [get_counter $i] + $distance * $direction($i) * $cfg(dist_fix)]
                    }
                }
############################
show
############################
            }
        }
    }

    proc cn_pause_on {num} {
        variable cfg
        set cfg(pause$num) 1
    }
    proc cn_pause_off {num} {
        variable cfg
        set cfg(pause$num) 0
    }
    proc cn_pause_get {num} {
        variable cfg
        return $cfg(pause$num)
    }

    proc pause_on {} {
        variable cfg
        set cfg(pause) 1
    }

    proc pause_off {} {
        variable cfg
        set cfg(pause) 0
    }

    proc clean {num} {
        variable counter
        set_counter $num 0
    }

    proc clean_all {} {
        variable cfg
        for {set i 0} {$i <= $cfg(max_counters)} {incr i} {
            clean $i
        }
    }

    proc set_direction {num way} {
        variable direction
        if {$way == {back} || $way == 0 || $way == -1} {
            set direction($num) -1
        } else {
            set direction($num) 1
        }
    }

    proc get_direction {num} {
        variable direction
        return $direction($num)
    }

    proc switch_direction {num} {
        variable direction
        if {[get_direction $num] > 0} {
            set_direction $num -1
        } else {
            set_direction $num 1
        }
    }

    proc get_counter {num} {
        variable counter
        return $counter($num)
    }

    proc set_counter {num value} {
        variable counter
        set counter($num) $value
    }

    proc show {{num 0}} {
        variable cfg
        if {$num == 0} {
            for {set i 0} {$i <= $cfg(max_counters)} {incr i} {
                common::log $::INFO [list $i - [get_counter $i] ]
            }
            common::log $::INFO {}
        } else {
            ::common::log $::INFO [get_counter $num]
        }
    }

    proc init {} {
        variable direction
        variable cfg
        clean_all
        for {set i 0} {$i <= $cfg(max_counters)} {incr i} {
            set direction($i) 1
            cn_pause_off $i
        }
    }

    init
}


namespace eval ::gui {
    set top .
    variable data

    proc init {} {
        variable top
        variable data

        set data(gps_state) 0
        set data(pause)     0
        set data(counters)  3
        set data(line_one)  0
        set data(time_zone) 3
        set data(cfg_file)  {GpsTrip.cfg}

        if {$::tcl_platform(os) == "Linux"} {
            set data(cfg_file)  {~/.GpsTrip.cfg}
        }
        read_config

        # Определяем заголовок окна
        wm title $top "GPSTrip for $::tcl_platform(os)"

        # Если платформа, на которой стартует программа Windows CE (Pocket PC)
        # устанавливаем размеры  Окна в максимально возможные с автоуправлением
        if {$::tcl_platform(os) == "Windows CE"} {
            ::etcl::automanage .
        } else {
            wm geometry $top 240x320
        }

        DrawMenus
        line_coordinates

        # Переопределяем глобальные клавиши
        bind all <g> [namespace current]::gps_switch_state

        # Настраиваем получателей на собственные события
        bind all <<GPS_updated>> +[namespace current]::gps_update_data
        bind all <<GPS_state_bad>> +[namespace current]::set_bad_isp
        bind all <<GPS_on>> +[namespace current]::gps_on
        bind all <<GPS_off>> +[namespace current]::gps_off
        bind all <<GPS_update_state>> +[namespace current]::gps_update_state
        bind all <<GPS_distance_updated>> +[namespace current]::udate_counters

        after 500 {event generate . <<GPS_on>>}
    }

    proc gps_update_data {} {
        line_coordinates
    }

    proc udate_counters {} {
        variable data
        for {set i 0} {$i <= $data(counters)} {incr i} {
            set data(counter$i) [format "%10.1f" [::counters::get_counter $i]]
        }
    }

    proc clean_counter {num {switch_button 0} {pause_button 0}} {
        variable data
        if {$num == {All}} {
            if {[win_yes_no "Clear all counters?" "Clearing."]} {
                for {set i 1} {$i <= $data(counters)} {incr i} {
                    ::counters::clean $i
                }
            }
        } else {
            ::counters::clean $num
            if {$switch_button != 0 && [::counters::get_direction $num] <= 0} {
                gps_set_direction $switch_button $num 1
            }
            if {$pause_button != 0} {
                cn_pause_off $pause_button $num
            }
        }
        udate_counters
    }

    proc pause_on {button} {
        variable data
        if {! $data(pause)} {
            ::counters::pause_on
            $button configure -background red
            set data(pause) 1
        }
    }
    proc pause_off {button} {
        variable data
        if {$data(pause)} {
            ::counters::pause_off
            $button configure -background yellow
            set data(pause) 0
        }
    }
    proc pause_switch {button} {
        variable data
        if {$data(pause)} {
            pause_off $button
        } else {
            pause_on $button
        }
    }

    proc cn_pause_on {button num} {
        variable data
        if {![::counters::cn_pause_get $num]} {
            ::counters::cn_pause_on $num
            set data(pause_button_background_$num) [$button cget -background]
            $button configure -background yellow
        }
    }
    proc cn_pause_off {button num} {
        variable data
        if {[::counters::cn_pause_get $num]} {
            ::counters::cn_pause_off $num
            $button configure -background $data(pause_button_background_$num)
        }
    }
    proc cn_pause_switch {button num} {
        if {[::counters::cn_pause_get $num]} {
            cn_pause_off $button $num
        } else {
            cn_pause_on $button $num
        }
    }

    # Процедура добавляющая счетчики (отрисовка)
    proc add_counters {top_win} {
        variable data

        set cn [frame $top_win.dist]
        pack $cn -side top -fill both -expand true
        for {set i 1} {$i <= $data(counters)} {incr i} {
            add_counter $cn $i
        }
    }
    # Процедура добавляющая счетчик (отрисовка)
    proc add_counter {cn num} {
        variable top
        variable data
        set font {Helvetica 14}
        set font_dist "Helvetica 16 bold"
        #set cn [frame $top.dist$num]
        set data(counter$num) 0
        button $cn.set$num -font $font -text "$num" \
                -command [list [namespace current]::enter_distance 0 $num $cn.switch$num $cn.pause$num]
        button $cn.dist$num -textvariable [namespace current]::data(counter$num) -font $font_dist \
                -command [list [namespace current]::clean_counter $num $cn.switch$num $cn.pause$num]
        button $cn.switch$num -font $font -text " Bk " \
                -command [list [namespace current]::gps_switch_direction $cn.switch$num $num]
        button $cn.pause$num -font $font -text " P " \
                -command [list [namespace current]::cn_pause_switch $cn.pause$num $num]
        # -relief flat 

        grid $cn.set$num $cn.switch$num $cn.dist$num $cn.pause$num -sticky news
        grid columnconfigure $cn {0 1 3} -weight 1 -uniform 1
        grid columnconfigure $cn {2} -weight 3 -uniform 1
        grid rowconfigure $cn {0 1 2} -weight 1 -uniform 1
    }
    # Ввод расстояния
    proc enter_distance {cur counter dist_widget pause_button} {
        variable top
        variable data

        set data(enter_distance) $cur

        append win $top enter_distance
        if {[catch [list toplevel $win -class EnterDistanceWindow]]} return
        wm title $win "Enter Distance for $counter"
        wm transient $win $top

        if {$::tcl_platform(os) == "Windows CE"} {
            ::etcl::automanage $win
        } else {
            wm geometry $win 240x320
        }

        set font {Helvetica 16}
        set dig [frame $win.dig]
        pack $dig -fill both -expand true

        for {set i 0} {$i <= 9} {incr i} {
            button $dig.$i \
                -text "$i" \
                -font $font \
                -command [list [namespace current]::DoAppend $i]
        }
        button $dig.del -text "Del" -font $font -command [list [namespace current]::DoAppend -1]
        button $dig.ok  -text "Ok"  -font $font -command [list [namespace current]::DoSetCounter $win $counter $dist_widget $pause_button]
        button $dig.no  -text "no"  -font $font -command [list destroy $win]

        label $dig.label \
                -text ANY \
                -relief ridge \
                -padx   2 \
                -pady   2 \
                -anchor e \
                -fg     "#004020" \
                -bg     "#d0e0d0" \
                -bd     1 \
                -font {Helvetica 22} \
                -textvariable [namespace current]::data(enter_distance)
        grid $dig.label -row 0 -column 0 -columnspan 2 -sticky "news"
        grid $dig.del   -row 0 -column 2 -sticky "news"

        grid $dig.1  $dig.2  $dig.3  -row 1 -sticky news
        grid $dig.4  $dig.5  $dig.6  -row 2 -sticky news
        grid $dig.7  $dig.8  $dig.9  -row 3 -sticky news
        grid $dig.no $dig.0  $dig.ok -row 4 -sticky news

        grid rowconfigure $dig {0} -weight 2 -uniform 1
        grid rowconfigure $dig {1 2 3 4} -weight 1 -uniform 1
        grid columnconfigure $dig {0 1 2} -weight 1 -uniform 1
    }

    proc DoAppend {what} {
        variable data
        if {$what == -1} {
            set data(enter_distance) [::tcl::mathfunc::round [expr $data(enter_distance) / 10]]
        } else {
            if {$data(enter_distance) <= 999999} {
                set data(enter_distance) [expr $data(enter_distance) * 10 + $what]
            }
        }
    }
    proc DoSetCounter {win num switch_button pause_button} {
        variable data
        clean_counter $num $switch_button $pause_button
        ::counters::set_counter $num $data(enter_distance)
        udate_counters
        if {[::counters::get_direction $num] > 0} {
            gps_set_direction $switch_button $num -1
        }
        cn_pause_on $pause_button $num
        destroy $win
    }

    proc line_coordinates {} {
        variable top
        variable data

        append main_frame $top main_frame

        set tools    $main_frame.tools
        set counters $main_frame.counters
        set ctrl     $main_frame.ctrl

        if {$data(line_one) == 0} {
            set data(line_one) 1

            set main_frame [frame $main_frame]
            set tools    [frame $tools]
            set counters [frame $counters]
            set ctrl     [frame $ctrl]

            pack $main_frame -fill both -expand yes
            pack $tools    -side top -fill x ;#-fill both -expand yes
            pack $counters -side top -fill both -expand yes
            add_counters $counters ;# Добавляем счетчики
            pack $ctrl -side bottom -fill both -expand yes

            label $tools.latitude  -text {Lat}
            label $tools.longitude -text {Lon}
            label $tools.time      -text {Time}
            label $tools.ispos     -text {IsPos}; set data(isp_background) [$tools.ispos cget -background]
            label $tools.hdop      -text {HDOP}
            label $tools.sats      -text {Sats}
            label $tools.distance  -text {Dist} \
                -textvariable [namespace current]::data(counter0) \
                -font {Helvetica 14 bold}
            label $tools.height    -text {Height}
            label $tools.speed     -text {Speed}

            # Добавляем нижнюю строку - Очистка всех счетчиков и Постановка всех счетчиков на паузу.
            button $ctrl.clear_distance -text {Clear All} -command [list [namespace current]::clean_counter {All}]
            button $ctrl.pause          -text {Pause All} -background yellow \
                -command [list [namespace current]::pause_switch $ctrl.pause]

            grid $tools.latitude  -row 0 -column 0 -sticky w
            grid $tools.longitude -row 1 -column 0 -sticky w
            grid $tools.sats      -row 1 -column 1 -sticky news
            grid $tools.distance  -row 1 -column 2 -sticky e
            grid $tools.ispos     -row 0 -column 1 -sticky news
            grid $tools.time      -row 0 -column 2 -sticky e
            grid $tools.height    -row 2 -column 0 -sticky w
            grid $tools.hdop      -row 2 -column 1 -sticky news
            grid $tools.speed     -row 2 -column 2 -sticky e
            grid columnconfigure $tools {0 1 2} -weight 1 -uniform 2
            grid rowconfigure    $tools {0 1 2} -weight 1 -uniform 2

            pack $ctrl.clear_distance $ctrl.pause -side left  -fill both -expand yes
        } else {
            $tools.latitude  configure -text [list [expr abs([::nmea::get_latitude])] [::nmea::get_latitude 1]]
            $tools.longitude configure -text [list [expr abs([::nmea::get_longitude])] [::nmea::get_longitude 1]]
            $tools.time      configure -text [::nmea::get_time $data(time_zone)]
            $tools.ispos     configure -text [list Isp:[::nmea::get_state]] -background $data(isp_background)
            $tools.hdop      configure -text [list HDop:[::nmea::get_hdop]]
            $tools.sats      configure -text [list Sat:[::nmea::get_satelites]]
            $tools.distance  configure -text [list Sat:[::nmea::get_satelites]]
            $tools.height    configure -text [list H:[::nmea::get_height]]
            $tools.speed     configure -text [format "Sp:%5.1fKm/h" [::nmea::get_speed]]
        }
    }

    proc set_bad_isp {} {
        .main_frame.tools.ispos configure -text "BAD SIGNAL" -background red
    }
    proc set_gps_off_isp {} {
        .main_frame.tools.ispos configure -text "GPS is Off" -background yellow
    }

    proc debug_console {parm} {
    	if {[regexp -all -nocase "Windows" $::tcl_platform(os)]} {
        	# if {$::tcl_platform(os) == "Windows CE"}
            console $parm
        }
    }

    # Процедура заполнения менюшки
    proc DrawMenus {} {
        variable top

        append menu $top mb
        catch {destroy $menu}

        menu $menu -tearoff 0

        $menu add cascade -menu $menu.file  -label "File"   -underline 0
        $menu add cascade -menu $menu.help  -label "Help"   -underline 0
        $menu add check -variable [namespace current]::data(gps_state) -label "GPS On" -command {event generate . <<GPS_update_state>>}

        menu $menu.file
        $menu.file add check   -label "Gps On" -under 0 -variable [namespace current]::data(gps_state) -command {event generate . <<GPS_update_state>>}
        $menu.file add command -label "Config" -under 0 -command [namespace current]::gps_config
        $menu.file add cascade -menu $menu.file.debug -label "Debug"  -under 0
        $menu.file add command -label "Save Config" -under 0 -command [namespace current]::write_config
        $menu.file add separator
        $menu.file add command -label "Exit" -under 1 -command [namespace current]::quit

        menu $menu.file.debug
        $menu.file.debug add radio -label "No debug" -variable ::common::debug -value 0 -under 0 -command [list [namespace current]::debug_console hide]
        $menu.file.debug add radio -label "Error"    -variable ::common::debug -value 1 -under 0 -command [list [namespace current]::debug_console show]
        $menu.file.debug add radio -label "Warning"  -variable ::common::debug -value 2 -under 0 -command [list [namespace current]::debug_console show]
        $menu.file.debug add radio -label "info"     -variable ::common::debug -value 3 -under 0 -command [list [namespace current]::debug_console show]

        menu $menu.help
        $menu.help add command -label Help  -under 0 ;#-command exit
        $menu.help add command -label About -under 0 -command [namespace current]::About

        # Workaround eTcl bug - detach then reattach menu
        [winfo toplevel $top] configure -menu $menu
      return
    }

    proc write_config {} {
        variable data
        if {[catch {set fd [open $data(cfg_file)  w+]} reason]} {
                common::log $::ERROR "Error when open config file for writing: $reason"
                unset -nocomplain fd
                return 1
            }
        puts $fd "set ::gps::gps_port_parms $::gps::gps_port_parms"
        puts $fd "set ::gps::gps_port {$::gps::gps_port}"
        puts $fd "set ::gui::data(time_zone) $::gui::data(time_zone)"
        puts $fd "set ::counters::cfg(dist_fix) $::counters::cfg(dist_fix)"
        puts $fd "set ::counters::cfg(dist_diff) $::counters::cfg(dist_diff)"
        puts $fd "set ::common::debug $::common::debug"

        close $fd
        ::common::log $::INFO "Config successuly writed"
    }

    proc read_config {} {
        variable data
        if {[catch {set fd [open $data(cfg_file)  r+]} reason]} {
                common::log $::ERROR "Error when open config file for reading: $reason\n\tUsing default params."
                unset -nocomplain fd
                return 1
            }
        if {[catch {
                    while {![eof $fd]} {
                    gets $fd ln
                    puts $ln
                    eval $ln
                }
            } reason]} {
                [namespace current]::win_msg "$reason"
                [namespace current]::write_config
            }
        close $fd
        ::common::log $::INFO "Config successuly readed"
    }

    proc win_msg {msg {title "Error Message"}} {
        variable top
        append err_top $top err_top
        if {[catch [list toplevel $err_top -class GpsErrorWindow]]} return
        wm title $err_top $title
        wm transient .$err_top $top
        if {$::tcl_platform(os)=="Windows CE"} {
            ::etcl::automanage $err_top
        }
        ttk::labelframe $err_top.frame -text $title
        ttk::label $err_top.frame.text -text $msg
        pack $err_top.frame $err_top.frame.text -side top -pady 5 -padx 10

        button $err_top.close -text {Ok} -command [list destroy $err_top]
        pack $err_top.close -side bottom
    }
    proc win_yes_no {msg {title "Error Message"}} {
        variable top
        set result 0
        append err_top $top err_top
        if {[catch [list toplevel $err_top -class GpsQuestionWindow]]} return
        wm title $err_top $title
        wm transient $err_top $top
        if {$::tcl_platform(os)=="Windows CE"} {
            ::etcl::automanage $err_top
        }
        ttk::labelframe $err_top.frame -text $title
        ttk::label $err_top.frame.text -text $msg
        pack $err_top.frame $err_top.frame.text -side top -pady 5 -padx 10

        set err_btn [frame $err_top.frame_btn]
        pack $err_btn -fill both -expand yes
        button $err_btn.ok     -text {Ok}     -command {set ::result 1}
        button $err_btn.cancel -text {Cancel} -command {set ::result 0}
        pack $err_btn.ok $err_btn.cancel  -side left  -fill both -expand yes

        vwait ::result
        destroy $err_top
        return $::result
    }

    # Поиск последовательных портов в Windows
    proc getSerialInterfaces {} {
        set coms {}
        set res  {}
        set key "HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM"
        if {[catch {
                # The keys only available if the driver is loaded.
                foreach name [registry values $key] {
                        set value [registry get $key $name]
                        lappend coms \\\\.\\$value
                }
        } fault]} {
                # return a empty list in case no serial interface is found
                return $res
        }

        #set key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum
        #foreach el [registry keys $key] {
        #        foreach el1 [registry keys $key\\$el] {
        #                catch {
        #                        foreach el2 [registry keys $key\\$el\\$el1] {
        #                                set port [registry get "$key\\$el\\$el1\\$el2\\Device Parameters" PortName]
        #                                if {[lsearch $coms $port] < 0} {
        #                                        continue
        #                                }
        #                                set friendlyName [registry get "$key\\$el\\$el1\\$el2" FriendlyName]
        #                                lappend res [list $port $friendlyName]
        #                        }
        #                }
        #        }
        #}
        #return [lsort $res]
	return [lsort $coms]
    }

    # Функция для изменения настроек программы. Пока сырая, но уже лучше, чем ничего :)
    proc gps_config {} {
        variable data
        variable top
        global version

        append cfg_top $top gps_config
        if {[catch [list toplevel $cfg_top -class GpsCfgWindow]]} return
        wm title $cfg_top "Configure GPS"
        wm transient $cfg_top $top

        set gps_ports {
            /dev/rfcomm0 /dev/rfcomm1 /dev/rfcomm2 /dev/rfcomm3 /dev/rfcomm4
            /dev/rfcomm5 /dev/rfcomm6 /dev/rfcomm7 /dev/rfcomm8 /dev/rfcomm9
            /dev/ttyACM0 /dev/ttyACM1 /dev/ttyACM2 /dev/ttyACM3 /dev/ttyACM4
            /dev/ttyS0 /dev/ttyS1 /dev/ttyS2 /dev/ttyS3 /dev/ttyS4
            /dev/ttyS5 /dev/ttyS6 /dev/ttyS7 /dev/ttyS8 /dev/ttyS9
            /dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3 /dev/ttyUSB4
            /dev/ttyUSB5 /dev/ttyUSB6 /dev/ttyUSB7 /dev/ttyUSB8 /dev/ttyUSB9
        }
	if {[regexp -all -nocase "Windows" $::tcl_platform(os)]} {
	    	if {$::tcl_platform(os)=="Windows CE"} {
            		::etcl::automanage $cfg_top
        	}
        	set coms {}
        	set gps_ports  {}
        	set key "HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM"
        	if {[catch {
					# The keys only available if the driver is loaded.
                	foreach name [registry values $key] {
                		set value [registry get $key $name]
						lappend coms $value
                	}} fault]} {
                # return a empty list in case no serial interface is found
        	} else {
			set gps_ports [getSerialInterfaces]
		}
            #set gps_ports {
            #    com0:  com1:  com2:  com3:  com4:  com5:  com6:  com7:  com8: com9:
            #    {\\.\com1} {\\.\com2} {\\.\com3} {\\.\com4} {\\.\com5} {\\.\com6} {\\.\com7} {\\.\com8} {\\.\com9}
            #    {\\.\com10} {\\.\com11} {\\.\com12} {\\.\com13} {\\.\com14} {\\.\com15} {\\.\com16} {\\.\com17}
            #}
		}

        set gps_parms {
             "1200,n,8,1" "2400,n,8,1" "4800,n,8,1" "9600,n,8,1" "19200,n,8,1"
            "38400,n,8,1" "57600,n,8,1" "115200,n,8,1" "230400,n,8,1" "460800,n,8,1"
        }

        set gps_cfg $cfg_top.gps_parms
        ttk::labelframe $gps_cfg -text "Select GPS port and speed"
        ttk::combobox $gps_cfg.speed -textvariable ::gps::gps_port       -state readonly -values $gps_ports
        ttk::combobox $gps_cfg.parms -textvariable ::gps::gps_port_parms -state readonly -values $gps_parms
        pack $gps_cfg -side top -pady 5 -padx 10
        pack $gps_cfg.speed $gps_cfg.parms -pady 5 -padx 10

        ttk::labelframe $cfg_top.time_zone_label -text "Enter time zone -12 to +12"
        ttk::entry $cfg_top.time_zone_label.entry -width 3 -textvariable [namespace current]::data(time_zone)
        pack $cfg_top.time_zone_label $cfg_top.time_zone_label.entry

        ttk::labelframe $cfg_top.distance_fix -text "Enter distance/speed fix 0.01-1.99"
        ttk::entry $cfg_top.distance_fix.entry -width 5 -textvariable ::counters::cfg(dist_fix)
        pack $cfg_top.distance_fix $cfg_top.distance_fix.entry

        ttk::labelframe $cfg_top.dist_diff -text "Enter distance differense 0-99"
        ttk::entry $cfg_top.dist_diff.entry -width 2 -textvariable ::counters::cfg(dist_diff)
        pack $cfg_top.dist_diff $cfg_top.dist_diff.entry

        set buttons [frame $cfg_top.buttons]
        button $buttons.ok -text {Ok} -command [list destroy $cfg_top]
        pack $buttons -side bottom -fill both -expand yes
        pack $buttons.ok -side left -fill both -expand yes
    }


    proc gps_set_direction {top num direction} {
        variable data
#        if {[::counters::get_direction $num] != $direction} {
            ::counters::set_direction $num $direction
            if {[::counters::get_direction $num] > 0} {
                $top configure -background $data(background$top)
            } else {
                set data(background$top) [$top cget -background]
                $top configure -background green
            }
#        }
    }

    proc gps_switch_direction {top num} {
        ::counters::switch_direction $num
        gps_set_direction $top $num [::counters::get_direction $num]
    }

    proc gps_state_get {} {
        variable data
        return $data(gps_state)
    }
    proc gps_state_set {state} {
        variable data
        if {$state} {
            set data(gps_state) 1
        } else {
            set data(gps_state) 0
        }
    }

    proc gps_on {} {
        if {[gps_state_get] == 0} {
            gps_state_set 1
            gps_update_state
        }
    }

    proc gps_off {} {
        if {[gps_state_get] == 1} {
            gps_state_set 0
            gps_update_state
        }
    }

    proc gps_switch_state {} {
        if {[gps_state_get] == 1} {
            gps_off
        }  else {
            gps_on
        }
    }

    proc gps_update_state {} {
        if {[gps_state_get] == 1} {
            if {[::gps::open_gps] != 0} {
                ::common::log $::ERROR "GPS open Error"
                gps_state_set 0
            }
        } else {
            ::gps::close_gps
            set_gps_off_isp
        }
    }

    # About :)
    proc About {} {
        global version
        variable top

        append about $top about
        if {[catch [list toplevel $about -class AboutWindow]]} return
        wm title $about "About"
        wm transient $about $top

        if {$::tcl_platform(os)=="Windows CE"} {
            ::etcl::automanage $about
        }

        ttk::labelframe $about.about -text "GpsTrip v.$version"
        ttk::label $about.about.text -text "(c) by Grigory Milev\nunder GPL license\n\nIf you wish to help, email\nme: week@altlinux.org"
        pack $about.about $about.about.text -side top -pady 5 -padx 10

        button $about.close -text {Ok} -command [list destroy $about]
        pack $about.close -side bottom
    }

    proc quit {} {
        gps_off
        exit
    }
}

::gps::init
::gui::init
::logger::init
