#!/usr/bin/env wish
#
# Boinctray - A system tray application for monitoring the BOINC client
#
# Copyright Esko Arajärvi 2006-2009
# edu@iki.fi
# http://www.iki.fi/edu/programs/boinctray/
#
# 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
####

package require Tcl 8.5
package require Tk 8.5

# Use gettext for i18n
package require msgcat
proc _ {s} {return [::msgcat::mc $s]}

# load language files, stored in msgs subdirectory
::msgcat::mcload [file join [file dirname [info script]] msgs]

# Format seconds to days, hours, minutes and seconds
proc time_format {seconds} {
    set d [expr round($seconds/86400)]
    set h [expr round(($seconds%86400)/3600)]
    set m [expr round(($seconds%3600)/60)]
    set s [expr $seconds%60]
    set new_time [format " %3d %02.2d:%02.2d:%02.2d" $d $h $m $s]
}

# Reads given client state file and updates variables if the data has changed.
# Otherwise estimates progress based on earlier progress.
proc update_data {client_state_file} {
    variable active_data
    variable project_data
    variable completed_results
    variable last_mtime
    variable config
    variable window_visible
    variable icon_on
    variable icon_off

    # Update data variables if the client_state_file has updated
    set err [catch {set new_mtime [file mtime $client_state_file]}]

    if {!$err && $new_mtime>$last_mtime} {
        set last_mtime $new_mtime

        set fh [open $client_state_file r]
        set orig_data [read $fh [file size $client_state_file]]
        close $fh

        set orig_data [split $orig_data "\n"]

        set project_data [lsearch -all -inline -regexp $orig_data \<master_url(?!_)\>*|\<project_name(?!_)\>*|\<user_total_credit(?!_)\>*|\<host_total_credit(?!_)\>*]
        set project_data [regsub -all {<.*?>} $project_data ""]

        set active_data [lsearch -all -inline -regexp $orig_data \<project_master_url(?!_)\>*|\<active_task_state(?!_)\>*|\<fraction_done(?!_)\>*|\<current_cpu_time(?!_)\>*]
        set active_data [regsub -all {<.*?>} $active_data ""]

        set results_data [lsearch -all -inline -regexp $orig_data \<project_name(?!_)\>*|\<final_cpu_time(?!_)\>*|\<state(?!_)\>*]
        set i 0
        set completed_results [list]
        foreach line $results_data {
            if {[string match "*<state>*" $line]} {
                # If the state is some completed state, add it to list, otherwise continue
                switch -- [string trim $line] {
                    <state>3</state> {
                        # One of the possible states for completed work units.
                        lappend completed_results [_ "Error"]
                    }
                    <state>4</state> {
                        # One of the possible states for completed work units.
                        lappend completed_results [_ "Uploading"]
                    }
                    <state>5</state> {
                        # One of the possible states for completed work units.
                        lappend completed_results [_ "Uploaded"]
                    }
                    <state>6</state> {
                        # One of the possible states for completed work units.
                        lappend completed_results [_ "Aborted"]
                    }
                    default {
                        incr i
                        continue
                    }
                }
                # Search for previous <project_name> line
                for {set j $i} {$j>-1} {incr j -1} {
                    if {[string match "*<project_name>*" [lindex $results_data $j]]} {
                        lappend completed_results [lindex $results_data $j]
                        break
                    }
                }
                lappend completed_results [lindex $results_data [expr $i-1]]
            }
            incr i
        }
        set completed_results [regsub -all {<.*?>} $completed_results ""]

        if {$window_visible} {
            hide_window
            show_window
        }

        # Update the tray icon
        set computing 0
        foreach {url task_state fraction_done cpu_time} $active_data {
            if {$task_state==1} {
                incr computing
            }
        }
        if {$computing} {
            catch {.tray.icon configure -image $icon_on}
        } else {
            catch {.tray.icon configure -image $icon_off}
        }
    }

    set row 0

    if {$config(show_credits)} {
        # Headers for credits
        if {$config(show_headers)} {
            incr row
        }

        # Total Credit
        if {$config(show_total_credit)} {
            variable total_credit
            variable host_total_credit
            set total_credit 0
            set host_total_credit 0
            foreach {url proj_name proj_credit host_proj_credit} $project_data {
                set total_credit [format "%.0f" [expr $total_credit+$proj_credit]]
                set host_total_credit [format "%.0f" [expr $host_total_credit+$host_proj_credit]]
            }
            incr row
        }
        if {$config(show_project_credits)} {
            foreach {url proj_name proj_credit host_proj_credit} $project_data {
                variable project_name$row
                variable host_credit$row
                variable project_credit$row
                set project_name$row [string trim "$proj_name:"]
                set host_credit$row [format "%.0f" $host_proj_credit]
                set project_credit$row [format "%.0f" $proj_credit]

                incr row
            }
        }

        # Separator
        incr row
    }

    # Loop through work unit rows and update values
    if {$config(show_work_units)} {

        if {$config(show_completed)} {
            # Headers for completed units
            if {$config(show_headers)} {
                incr row
            }

            foreach {state proj_name result_time} $completed_results {
                variable completed_name$row
                variable completed_state$row
                variable completed_time$row
                set completed_name$row [string trim "$proj_name:"]
                set completed_state$row [string trim $state]
                set completed_time$row [time_format [expr round($result_time)]]

                incr row
            }

            # Separator
            incr row
        }

        # Headers for active units
        if {$config(show_headers)} {
            incr row
        }

        foreach {url task_state fraction_done cpu_time} $active_data {
            set project_index [lsearch $project_data *[string trim $url]*]
            set project_name [string trim "[lindex $project_data [expr $project_index+1]]"]
            if {$task_state==1} {
                set state_active "*"
            } else {
                set state_active ""
            }

            if {$fraction_done>0} {
                set time_remaining [expr round($cpu_time/$fraction_done-$cpu_time)]
            } else {
                set time_remaining 0
            }

            # Update active tasks if so configured. Work done per second is
            # estimated by dividing amount of work done previously with the
            # time spent earlier.
            if {$task_state==1 && $config(estimate_work_done_between_updates)} {
                if {$cpu_time>0} {
                    set work_per_second [expr $fraction_done/$cpu_time]
                } else {
                    set work_per_second 0
                }
                set seconds_since_update [expr [clock seconds]-$last_mtime]
                set cpu_time [expr $cpu_time+$seconds_since_update]

                set fraction_done [expr $fraction_done+$work_per_second*$seconds_since_update]
                if {$fraction_done>0} {
                    set time_remaining [expr round($cpu_time/$fraction_done-$cpu_time)]
                }

                if {$fraction_done>=1} {
                    set fraction_done 1
                    set time_remaining 0
                }
            }

            variable project$row
            variable fraction$row
            variable time_remaining$row
            set project$row [string trim "$state_active$project_name:"]
            set fraction$row "[format "%8.3f" [expr 100*$fraction_done]]"
            set time_remaining$row [time_format $time_remaining]
            incr row
        }
    }

    # Call this proc again after configured time to keep updating
    after 1000 [list update_data $client_state_file]
}

# Populate the window according to the config file preferences and show it
proc show_window {} {
    variable config
    variable active_data
    variable project_data
    variable completed_results

    frame .f -borderwidth 4 -relief ridge
    grid .f

    # Variable tells how many rows there will be in the window
    set row 0

    if {$config(show_credits)} {
        if {$config(show_headers)} {
            # Credits = Points given for completing work units
            grid [label .f.credits_headers0 -text [_ "Credits"]] -column 0 -row $row
            grid [label .f.credits_headers1 -text [_ "Host"]] -column 1 -row $row -columnspan 2
            grid [label .f.credits_headers2 -text [_ "Total"]] -column 3 -row $row
            incr row
        }

        if {$config(show_total_credit)} {
            # Credits = Points given for completing work units
            grid [label .f.total_label -text [_ "Total credit:"]] -column 0 -row $row -sticky e
            grid [label .f.host_total_credit -textvariable host_total_credit] -column 1 -row $row -sticky e
            grid [label .f.total_credit -textvariable total_credit] -column 3 -row $row -sticky e
            incr row
        }

        if {$config(show_project_credits)} {
            foreach {url proj_name proj_credit host_proj_credit} $project_data {
                grid [label .f.project_name$row -textvariable project_name$row] -column 0 -row $row -sticky e
                grid [label .f.host_credit$row -textvariable host_credit$row] -column 1 -row $row -sticky e
                grid [label .f.project_credit$row -textvariable project_credit$row] -column 3 -row $row -sticky e
                incr row
            }
        }

        # Separator
        grid [frame .f.sep -relief groove -borderwidth 2 -width 2 -height 2] -column 0 -columnspan 4 -row $row -sticky ew
        incr row
    }

    # Create labels for stats which are to be shown. First check if any stats are to be shown
    if {$config(show_work_units)} {

        if {$config(show_completed)} {
            # Headers
            if {$config(show_headers)} {
                grid [label .f.completed_headers0 -text [_ "Project"]] -column 0 -row $row
                grid [label .f.completed_headers1 -text [_ "State"]] -column 1 -row $row -columnspan 2
                grid [label .f.completed_headers2 -text [_ "Time used"]] -column 3 -row $row
                incr row
            }

            foreach {state proj_name result_time} $completed_results {
                grid [label .f.completed_name$row -textvariable completed_name$row] -column 0 -row $row -sticky e
                grid [label .f.completed_fraction$row -textvariable completed_state$row] -column 1 -columnspan 2 -row $row -sticky e
                grid [label .f.completed_time$row -textvariable completed_time$row] -column 3 -row $row -sticky e
                incr row
            }

            # Separator
            grid [frame .f.completed_sep -relief groove -borderwidth 2 -width 2 -height 2] -column 0 -columnspan 4 -row $row -sticky ew
            incr row
        }

        if {$config(show_headers)} {
            grid [label .f.work_unit_headers0 -text [_ "Project"]] -column 0 -row $row
            # As in "X percent of work unit done"
            grid [label .f.work_unit_headers1 -text [_ "Done"]] -column 1 -row $row -columnspan 2
            if {$config(estimate_remaining_time)} {
                # As in "X minutes remaining"
                grid [label .f.work_unit_headers2 -text [_ "Remaining"]] -column 3 -row $row
            }
            incr row
        }

        foreach {url task_state fraction_done cpu_time} $active_data {
            # Create label always for active work units and for others
            # according to the config
            if {$task_state==1 || !$config(show_only_active_work_units)} {
                grid [label .f.project$row -textvariable project$row] -column 0 -row $row -sticky e
                grid [label .f.fraction$row -textvariable fraction$row] -column 1 -row $row -sticky e
                grid [label .f.percent$row -text "%"] -column 2 -row $row -sticky e

                if {$config(estimate_remaining_time)} {
                    grid [label .f.time_remaining$row -textvariable time_remaining$row] -column 3 -row $row -sticky e
                }
            }
            incr row
        }
    }
    wm deiconify .
}

# Unpopulate the window and hide it
proc hide_window {} {
    destroy .f
    wm withdraw .
}

# Figure out what the config file name should be in current operating system
# From http://wiki.tcl.tk/886
proc name_config_file {app} {
    global env
    switch $::tcl_platform(platform) "macintosh" {
        set rcfile [file join $env(PREF_FOLDER) "$app Preferences"]
    } "windows" {
        set rcfile [file join $env(HOME) "$app.cfg"]
    } "unix" {
        if {[info exists env(DOTDIR)]} {
            set rcfile "$env(DOTDIR)/.${app}rc"
        } else {
            set rcfile "$env(HOME)/.${app}rc"
        }
    } else {
        set rcfile "${app}.rc"
    }
    return $rcfile
}

# Set configuration defaults and read the configuration file if it exists
proc read_config {} {
    variable config

    # Default values
    set config(show_headers) true

    set config(show_credits) true
    set config(show_total_credit) true
    set config(show_project_credits) false

    set config(show_work_units) true
    set config(show_only_active_work_units) false
    set config(show_completed) true
    set config(estimate_work_done_between_updates) true
    set config(estimate_remaining_time) true
    set config(window_moved) false
    set config(window_x) 100
    set config(window_y) 100
    set config(font_size) 10

    set config(client_state_file) /var/lib/boinc-client/client_state.xml

    set config_file [name_config_file "boinctray"]

    # Parse config file.
    catch {
        set fh [open $config_file r]
        set configs [read $fh]
        foreach line [split $configs "\n"] {
            set parts [split $line "="]
            set config([string trim [lindex $parts 0]]) [string trim [lindex $parts 1]]
        }

        # Check that the font size is a valid digit value and use it.
        set config(font_size) [regsub -all {[^0-9]} $config(font_size) ""]
        if {$config(font_size)<4 || $config(font_size)>20} {
            set config(font_size) 10
        }
        font config DefFont -size $config(font_size)

        return 0
    }
}

# Try to write the configuration file and give a warning if it didn't work
proc write_config {} {
    variable config

    set config_file [name_config_file "boinctray"]

    set err [catch {
        set fh [open $config_file w]
        foreach {key value} [array get config] {
            if {$key!=""} {
                puts $fh "$key = $value"
            }
        }
        close $fh
    }]
    if {$err} {
        tk_messageBox -title [_ "Write error"] \
            -message "Couldn't write to the config file $config_file" \
            -type ok \
            -icon warning
    }
}

# Create a dialog for configuring the application behaviour
proc configuration_dialog {} {
    variable config_temp
    variable config_update

    toplevel .conf
    wm title .conf [_ "Boinctray configuration"]

    grid [checkbutton .conf.headers -offvalue false -onvalue true \
            -text [_ "Show headers"] -variable config_temp(show_headers)] -row 0 -column 0 -sticky w -pady 2

    grid [frame .conf.headers_sep -relief groove -borderwidth 2 -width 2 -height 2] -column 0 -row 1 -sticky ew -pady 2

    # Credits = Points given for completing work units
    grid [checkbutton .conf.show_credits -offvalue false -onvalue true \
            -text [_ "Show credits"] -variable config_temp(show_credits) \
            -command {
                if {$config_temp(show_credits)} {
                    .conf.total_credit configure -state normal
                    .conf.project_credits configure -state normal
                } else {
                    .conf.total_credit configure -state disabled
                    .conf.project_credits configure -state disabled
                }
            }] -row 2 -column 0 -sticky w -pady 2

    # Credits = Points given for completing work units
    grid [checkbutton .conf.total_credit -offvalue false -onvalue true \
            -text [_ "Show total credit"] -variable config_temp(show_total_credit)] -row 3 -column 0 -sticky w -padx 20 -pady 2

    # Credits = Points given for completing work units
    grid [checkbutton .conf.project_credits -offvalue false -onvalue true \
            -text [_ "Show credits of projects"] -variable config_temp(show_project_credits)] -row 4 -column 0 -sticky w -padx 20 -pady 2

    grid [frame .conf.sep -relief groove -borderwidth 2 -width 2 -height 2] -column 0 -row 5 -sticky ew -pady 2

    grid [checkbutton .conf.show_work_units -offvalue false -onvalue true \
            -text [_ "Show work units"] -variable config_temp(show_work_units) \
            -command {
                if {$config_temp(show_work_units)} {
                    .conf.only_active_units configure -state normal
                    .conf.time_remaining configure -state normal
                    .conf.estimate_work_done configure -state normal
                    .conf.completed configure -state normal
                } else {
                    .conf.only_active_units configure -state disabled;
                    .conf.time_remaining configure -state disabled;
                    .conf.estimate_work_done configure -state disabled
                    .conf.completed configure -state disabled
                }
            }] -row 6 -column 0 -sticky w -pady 2

    grid [checkbutton .conf.only_active_units -offvalue false -onvalue true -padx 20 \
            -text [_ "Show only active work units"] \
            -variable config_temp(show_only_active_work_units)] -row 7 -column 0 -sticky w -pady 2

    grid [checkbutton .conf.time_remaining -offvalue false -onvalue true -padx 20\
            -text [_ "Estimate remaining time"] \
            -variable config_temp(estimate_remaining_time)] -row 8 -column 0 -sticky w -pady 2

    grid [checkbutton .conf.estimate_work_done -offvalue false -onvalue true -padx 20 \
            -text [_ "Estimate work done between updates"] \
            -variable config_temp(estimate_work_done_between_updates)] -row 9 -column 0 -sticky w -pady 2

    grid [checkbutton .conf.completed -offvalue false -onvalue true -padx 20 \
            -text [_ "Show completed work units"] -variable config_temp(show_completed)] -row 10 -column 0 -sticky w -pady 2

    grid [frame .conf.sep2 -relief groove -borderwidth 2 -width 2 -height 2] -column 0 -row 11 -sticky ew -pady 2

    grid [spinbox .conf.font_size -from 4 -to 30 -increment 1 -state normal -width 2 -textvariable config_temp(font_size)] -column 0 -row 12 -padx 10 -sticky w -pady 2

    grid [label .conf.font_size_label -text [_ "Font size"]] -row 12 -column 0 -padx [expr ($config_temp(font_size)+8)*3] -sticky w -pady 2

    grid [frame .conf.sep3 -relief groove -borderwidth 2 -width 2 -height 2] -column 0 -row 13 -sticky ew -pady 2

    grid [button .conf.ok -text [_ "Ok"] -command {set config_update 1;destroy .conf}] -row 14 -column 0 -sticky w -pady 2 -padx 9
    grid [button .conf.cancel -text [_ "Cancel"] -command {set config_update 0;destroy .conf}] -row 14 -column 0 -sticky e -pady 2 -padx 9

    # Remember old config settings
    if {!$config_temp(show_credits)} {
        .conf.total_credit configure -state disabled
        .conf.project_credits configure -state disabled
    }
    if {!$config_temp(show_work_units)} {
        .conf.only_active_units configure -state disabled;
        .conf.time_remaining configure -state disabled;
        .conf.estimate_work_done configure -state disabled
        .conf.completed configure -state disabled
    }
}

# Create the About box.
# Hide the main window (because it doesn't work otherwise).
proc about {} {
    wm withdraw .

    set about_text "Boinctray - "
    append about_text [format [_ "Version %s"] "2.4"]
    append about_text "\n\n"
    append about_text [_ "System tray application for monitoring the BOINC client"]
    append about_text "\n\n\Esko Arajärvi, edu@iki.fi, 2006-2009\n"
    append about_text "http://edu.iki.fi/programs/boinctray/\n\n"
    # Translators: Add your name here
    append about_text [_ "Translation: "]
    append about_text "\n\n"
    append about_text [_ "Boinctray licence: GPL"]
    append about_text "\n"
    append about_text [_ "See boinctray.licence"]
    append about_text "\n\n"
    append about_text [_ "Tktray licence: BSDL"]
    append about_text "\n"
    append about_text [_ "See tktray.licence"]
    tk_messageBox \
        -type ok \
        -title [_ "About Boinctray"] \
        -message $about_text
    wm deiconify .
}

# Change path to the application executable folder
cd [file dirname $argv0]

set last_mtime 0
set window_visible false
set keep_window_visible 0

font create DefFont -family Helvetica -size 10
option add *font DefFont

read_config

# Prevents user from modifying the window
wm overrideredirect . 1
wm withdraw .

# Create the tray icon or a normal window is tray icon fails.
# Try to load different versions of the libtktray.
set err [catch {
    load [file join [pwd] libtktray1.1_64.so]
}]
if {$err} {
    set err [catch {
        load [file join [pwd] /usr/lib/tcl/tktray1.2/libtktray1.2.so]
    }]
}
if {$err} {
    set err [catch {
        load [file join [pwd] /usr/lib64/tcl/tktray1.2/libtktray1.2.so]
    }]
}
if {$err} {
    puts stderr [_ "Couldn't load the TkTray library file."]
    puts stderr [_ "You can try to compile your own version with instructions of the README file."]
}

set icon_on [image create photo -file "/usr/share/boinctray/images/boinctray_22x22_on.gif"]
set icon_off [image create photo -file "/usr/share/boinctray/images/boinctray_22x22_off.gif"]
set err [catch {
    tktray::icon .tray -image $icon_on
    set tray true
}]
if {$err} {
    puts stderr [_ "Couldn't create a tray icon."]
    toplevel .tray -height 22 -width 22
    wm title .tray "Boinctray"
    set tray false
}

grid [label .tray.icon -state normal -image $icon_on -height 22 -width 22]
wm iconphoto . -default [image create photo -file "/usr/share/boinctray/images/boinctray_32x32.gif"]
wm iconphoto . -default [image create photo -file "/usr/share/boinctray/images/boinctray_16x16.gif"]

# Create right button menu
menu .menu -tearoff false
.menu add command -label [_ "About"] -command {about}

.menu add separator
.menu add command -label [_ "Select BOINC directory"] -command {
    if {$keep_window_visible} {
        # Hide the window to update row counts etc. and not block the selection dialog-
        hide_window
    }
    set dir "[tk_chooseDirectory \
                -title [_ "Select BOINC directory containing client_state.xml"] \
                -mustexist true]"
    if {$dir!=""} {
        set config(client_state_file) "$dir/client_state.xml"
        if {[file exists "$dir/client_state.xml"]} {
            set config(client_state_file) "$dir/client_state.xml"
            write_config
            if {$keep_window_visible} {
                update_data $config(client_state_file)
                show_window
            }
        } else {
            tk_messageBox -type ok \
                -title [_ "Couldn't find client_state.xml"] \
                -type ok \
                -icon warning \
                -message [format [_ "Couldn't find client_state.xml from the directory\n%s"] $dir]
            if {$keep_window_visible} {
                frame .f -borderwidth 4 -relief ridge
                grid .f

                grid [label .f.error -text [_ "Couldn't find the file client_state.xml."]]
                wm deiconify .
            }
        }
    }
}

.menu add separator
.menu add command -label [_ "Configure"] -command {
    array set config_temp [array get config]
    configuration_dialog
    set x [expr {([winfo screenwidth .]-[winfo width .conf])/3}]
    set y [expr {([winfo screenheight .]-[winfo height .conf])/3}]
    wm geometry .conf +$x+$y

    tkwait window .conf
    if {$config_update} {
        set old_font_size $config(font_size)

        array set config [array get config_temp]

        # Check that the font size is a valid digit value.
        set config(font_size) [regsub -all {[^0-9]} $config(font_size) ""]
        if {$config(font_size)<4 || $config(font_size)>20} {
            set config(font_size) $old_font_size
        }
        unset old_font_size
        font config DefFont -size $config(font_size)

        write_config
    }
    unset config_update

    if {$keep_window_visible && [file exists $config(client_state_file)]} {
        # Hide and show the window to update row counts etc.
        hide_window
        show_window
    }
}
.menu add command -label [_ "Reset window position"] -command {
    set config(window_moved) false
    set config(window_x) 100
    set config(window_y) 100
    event generate . <Configure>
}

.menu add separator
.menu add command -label [_ "Quit"] -command {
    write_config
    exit 0
}

# Show the window if mouse cursor is on top of the tray icon.
bind .tray.icon <Enter> {
    set window_visible true

    # If the data file is not found, show an error message
    if {[file exists $config(client_state_file)]} {
        update_data $config(client_state_file)
        if {!$keep_window_visible} {
            # Hide first to (possibly) destroy elements created in show_
            # window script. They might exists if we have lost the window
            # without user leaving the tray icon.
            hide_window
            # Move window away from the tray icon. It will be moved back
            # again when user points mouse over the tray icon. This fixes
            # problems which arise when changed configuration makes the
            # window larger and therefore the window would cover the tray icon.
            wm geometry . -1000-1000
            show_window
        }
    } else {
        hide_window
        frame .f -borderwidth 4 -relief ridge
        grid .f

        grid [label .f.error -text [_ "Couldn't find the file client_state.xml."]]
        wm deiconify .
    }
}

# Hide the window when moving mouse cursor away
# if the user has not clicked the tray icon
bind .tray.icon <Leave> {
    if {!$keep_window_visible} {
        set window_visible false
        hide_window
    }
}

# Toggle whether the window is shown all the time
bind .tray.icon <Button-1> {
    set keep_window_visible [expr abs($keep_window_visible-1)]
}

# Open a popup menu
bind .tray.icon <ButtonPress-3> {
    if {![winfo exists .conf]} {
        tk_popup .menu [expr [winfo rootx .tray.icon] +%x -[winfo width .menu]] [expr [winfo rooty .tray.icon] +%y -[winfo height .menu]]
    }
}

# Hide the popup menu
bind .tray.icon <ButtonRelease-3> {
    .menu unpost
}

# Update the location where the window is drawn (just next to the tray icon)
bind . <Configure> {
    if {$tray} {
        set iconx [lindex [.tray bbox] 0]
        set icony [lindex [.tray bbox] 1]
        set icon_width [expr [lindex [.tray bbox] 2]-[lindex [.tray bbox] 0]]
        set icon_height [expr [lindex [.tray bbox] 3]-[lindex [.tray bbox] 1]]
    } else {
        set iconx [winfo rootx .tray.icon]
        set icony [winfo rooty .tray.icon]
        set icon_width [winfo width .tray.icon]
        set icon_height [winfo height .tray.icon]
    }
    set screen_width [winfo screenwidth .]
    set screen_height [winfo screenheight .]
    set window_width [winfo width .]
    set window_height [winfo height .]

    # Align left edge of the data window with the left edge of the icon,
    # but try to make sure that window doesn't cover the taskbar.
    if {$config(window_moved)} {
        set windowx $config(window_x)
    } elseif {$iconx>$screen_width/2} {
        if {[expr $iconx+$window_width]>[expr $screen_width-$icon_width]} {
            set windowx [expr $screen_width-$window_width-$icon_width-9]
        } else {
            set windowx [expr $iconx]
        }
    } else {
        if {$iconx<$icon_width} {
            set windowx [expr $iconx+$icon_width+9]
        } else {
            set windowx [expr $iconx]
        }
    }

    # Align bottom of the data window with the bottom of the icon,
    # but try to make sure that window doesn't cover the taskbar.
    if {$config(window_moved)} {
        set windowy $config(window_y)
    } elseif {$icony>$screen_height/2} {
        if {[expr $icony+$window_height]>[expr $screen_height-$icon_height]} {
            set windowy [expr $icony-$window_height-9]
        } else {
            set windowy [expr $icony+$icon_width+9]
        }
    } else {
        set windowy [expr $icony+$icon_width+9]
    }

    wm geometry . +$windowx+$windowy
}

bind . <ButtonPress-1> {
    set windowx0 [expr %X-[winfo rootx .]]
    set windowy0 [expr %Y-[winfo rooty .]]
}

bind . <B1-Motion> {
    wm geometry . +[expr %X-$windowx0]+[expr %Y-$windowy0]
    set config(window_moved) true
}

bind . <ButtonRelease-1> {
    if {$config(window_moved)} {
        set config(window_x) [winfo rootx .]
        set config(window_y) [winfo rooty .]
    }
}

if {[file exists $config(client_state_file)]} {
    update_data $config(client_state_file)
} else {
    catch {.tray.icon configure -image $icon_off}
}
