#!/bin/sh
# restart using wish \
  exec wish8.3 "$0" ${1+"$@"}

# $Id: stockwatcher,v 1.25 2000/07/19 01:30:39 lduperva Exp $
# stockwatcher, a stock watcher program
#  Copyright 2000 Laurent Duperval <lduperval@sprint.ca>
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#
#   * Redistributions in binary form must reproduce the above
#     copyright notice, this list of conditions and the following
#     disclaimer in the documentation and/or other materials provided
#     with the distribution.
#
#   * The name of the Copyright holder may not be used to endorse or promote
#     products derived from this software without specific prior written
#     permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Portions of this software are copyrighted by Tom Poindexter, 1999.

package require http 2.0
package require BWidget 1.3
package require BLT 2.4
package require msgcat
namespace import msgcat::*

if {! [info exists env(TCLTICKER_LIB)]} {
	set env(TCLTICKER_LIB) [file join [file dirname $argv0] lib]
}

# Make the path absolute so that the HTML viewer knows how to load the file.
if {[file pathtype $env(TCLTICKER_LIB)] == "relative"} {
	set env(TCLTICKER_LIB) [file join [pwd] $env(TCLTICKER_LIB)]
}

lappend auto_path $env(TCLTICKER_LIB)
msgcat::mcload [file join $env(TCLTICKER_LIB) msgs]

if {! [info exists env(HOME)]} {
if {[info exist env(USER)]} {
	set env(HOME) ~$env(USER)
} else {
	set env(HOME) "/"
}
}

# The about() array contains information identifying the program. Most useful
# in the about window, wouldn't you know.
set about(name) "Stock Watcher"
set about(version) "1.0"
set about(homepage) "http://www.neosoft.com/tcl/ftparchive/sorted/packages-8.0/apps/stockwatcher"
set about(email) "lduperval@sprint.ca"
set about(copyright) " Copyright 2000 Laurent Duperval"
set about(date) {Jul 18, 2000}

# Takes a while to show up. Maybe I should put a splash screen...
wm withdraw .
wm title . "$about(name) $about(version)"

# global variables used
global refresh tick_speed tick_font tick_size tick_ontop tick_round
global ticker_symbols data 
global service_list service
global proxy_host proxy_port
global mail_host  mail_port

global index last_time_stamp

# 'index' is used to cycle through ticker symbols in ticker_symbols array
set index 0

# last_time_stamp is timestamp of last data refresh
set last_time_stamp ""

# 'next_refresh' - after id of next refresh request
global next_refresh
set next_refresh ""

# uiWin contains UI widget which need to be shared between procs. I find it
# more useful to use an array and one "global" than to use the widget name or
# a whole lot of globals
global uiWin

# The folio variable contains the data for all the portfolio information.
# These are the indices used:
#
# $symbol,transactions: Total number of transactions for a given symbol. Each
# transaction corresponds to a purchase of shares. So if, for example, you buy
# 30 stocks of SUNW at 9:30 am, 100 at 10:15 am and 6 at 1:00 pm, that's 3
# transactions for SUNW.
# $symbol,$n,qty: Number of shares for transaction $n on symbol $symbol
# $symbol,$n,value: Current value of transaction $n on symbol $symbol
# $symbol,$n,delta: Delta of transaction $n on symbol $symbol, compared to the
# original purchase price
# $symbol,$n,daily: Delta of transaction $n on symbol $symbol compared to
# previous trade day
# $symbol,$n,date: Purchase date (and time)
# $symbol,$n,price: Purchase price of transaction $n for symbol $symbol
# $symbol,$n,purchase: Total value of purchase of transaction $n on symbol
# $symbol
# $symbol,$n,widget: Widget base name to display the data of transaction $n
# for symbol $symbol
#
global folio

# These are the supported options:
# graphStart: time at which to start gathering data for the graph
# graphStop: time at which to stop gathering data for the graph
# graphHeight: height of the graph in pixels
# graphWidth: width of the graph in pixels
# graphSave: automatically save graph data after this many data points have
# been acquired
# language: language to use for interface
# stockwatcherDir: where the data is stored. Useless, really, at this point.
# graphReset: Reset all graph data this many minutes before the graphStart
# value.
# htmlViewer: External command to view html
global options

# Set some defaults option values
set options(graphStart) "9:30"
set options(graphStop) "17:00"
set options(graphHeight) 480
set options(graphWidth) 640
# Automatically save graph data after this many new transactions are added
set options(graphSave) 5
# Reset graph data this many minutes be fore the start of a new session
set options(graphReset) 15
set options(stockwatcherDir) [file join $env(HOME) .stockwatcher]
set options(language) fr
if {$tcl_platform(platform) == "unix"} {
	set options(htmlViewer) "netscape -remote 'openFile(%s)'"
} else {
	set options(htmlViewer) "$env(COMSPEC) start %s"
}

# Fetch data to be displayed in the ticker
#
# t: The text widget in which the ticker data is displayed.
proc doticker {t} {
    global tick_speed tick_ontop ticker_symbols data tcl_platform
    global index last_time_stamp
	global folio
	global options
    if {! [info exists ticker_symbols(1)] } {
	$t configure -state normal
	$t delete 1.0 end
	$t configure -state disabled
	update idletasks
	get_parms [mc "No symbols entered"]
        after 100 [list doticker $t]
	return
    }
    # find out how much text on the line, change laurent's code to use pixels
    set wid [winfo width $t]
    incr wid 20 	;# make sure new messages get appended before needed
    set len [font measure [$t cget -font] [$t get 1.0 1.end]]
    if {[expr {$len < $wid}]} {
		set msg "  . . .  "
        if {![info exists ticker_symbols($index)]} {
            set index 1
	    set msg "  ([mc "Click here!"])  $last_time_stamp  . . .  "
        } else {
	    set msg "  . . .  "
        }
        set symbol $ticker_symbols($index)
		set sign $data($symbol,sign)
		set message "$symbol $data($symbol,last) $data($symbol,delta)"
		# Updat all transaction information, if there are any transactions
		if {[info exists folio($symbol,transactions)] && [string length $data($symbol,last)] } {
			for {set i 0} {$i < $folio($symbol,transactions)} {incr i} {
				catch {set folio($symbol,$i,value)  \
					[format "%.2f" [expr {$data($symbol,last)*$folio($symbol,$i,qty)}]]}
				catch {set folio($symbol,$i,daily)  \
					[format "%.2f" [expr {$data($symbol,delta)*$folio($symbol,$i,qty)}]]}
				catch {set folio($symbol,$i,delta)  \
					[format "%.2f" [expr {$folio($symbol,$i,value)-$folio($symbol,$i,purchase)}]]}
			}
			# Update the graph
			::tickergraph::addData $symbol [clock seconds] $data($symbol,last)
			::tickergraph::saveData $symbol
		}
        $t configure -state normal
        $t insert end $msg fill
        $t insert end $message tag$sign
        $t configure -state disabled
        incr index
	# windows doesn't support visibility events, so try to force it up if 
	# tick_ontop is wanted, may cause some flicker, or work poorly :-(
	if {$tick_ontop && "$tcl_platform(platform)" == "windows"} {
	    wm withdraw .  ; update
	    raise .        ; update
	    wm deiconify . ; update
	    raise .        ; update
	}
    }
    $t configure -state normal
    $t delete 1.0
    $t configure -state disabled
    after $tick_speed [list doticker $t]
}

# write data in the appropriate font size
proc font_trace {args} {
    global tick_font tick_size
    .t configure -font "$tick_font $tick_size"
}

# Builds the initial interface
proc start_ticker {} {
	global uiWin
	global about
	global env

    set descmenu [list \
        "[mc "&File"]" all file 0 [list  \
            [list command "[mc "E&xit"]" {} "[mc "Exit"] $about(name)" \
			{Ctrl q} -command Exit] \
        ] \
        "[mc "&Graph"]" all graph 0 [list \
            [list command "[mc "&Clear"]" {} "[mc "Clear all graph data"]" {} -command clearGraphData] \
        ] \
        "[mc "&Options"]" all options 0 [list  \
            [list command "[mc "&Ticker"]" {} "[mc "Modify ticker window options"]"\
				{Ctrl e} -command get_parms] \
            [list command "[mc "&Graph"]" {} "[mc "Modify Graph Options"]" \
				{Ctrl g} -command graphOptions] \
            [list command "[mc "&Other"]" {} "[mc "Modify Other Options"]" \
				{Ctrl o} -command otherOptions] \
        ] \
        "[mc "&Help"]" all help 0 [list \
			[list command "[mc Help]" {} "" {None F1} \
			-command [list doHelp [file join $env(TCLTICKER_LIB) help [mc MainHelpPage]]]]\
            [list command "[mc About]..." {} "" {Ctrl a} -command aboutWindow] \
        ] \
    ]
	set uiWin(main) [MainFrame .main -menu $descmenu]
	$uiWin(main) showstatusbar none
	set uiWin(mainframe) [$uiWin(main) getframe]
    global tick_speed tick_font tick_size tick_ontop
    text .t  -relief sunken -bd 0 -wrap none -bg black -state disabled  \
	-exportselection 0 -height 1 -width 60 \
	-font "$tick_font $tick_size" -highlightthickness 0
    bind .t <Button-1> get_parms
    bind .t <Button-2> get_parms
    bind .t <Button-3> get_parms
	# bind . <F1> doHelp
    .t tag configure fill -foreground white
    .t tag configure tag  -foreground white
    .t tag configure tag= -foreground yellow
    .t tag configure tag- -foreground red
    .t tag configure tag+ -foreground green
    pack .t  -fill x -pady 6 -padx 4 -in $uiWin(mainframe)
    .t configure -bg black

    trace variable tick_font w font_trace
    trace variable tick_size w font_trace
    # allow window to be resized in width, but not in height
    #wm resizable . 1 0
    # for unix, make window always on top if desired
    bind . <Visibility> {
	if {$tick_ontop && \
	    ( "%s" == "VisibilityPartiallyObscured" || \
	      "%s" == "VisibilityFullyObscured") } {
	    raise .
	}
    }

    # Create the scrolled window that will display folio information

    create_folio

	pack $uiWin(main) -fill both -expand true

    doticker .t
}

# This shows the graph options dialog
proc graphOptions {} {
	global uiWin
	global options
	set dlg .graphOptionsDlg

	if {![winfo exists $dlg]} {
		set dlg [Dialog $dlg  -title "[mc "Graph Options"]" -modal local -default 0]
		set uiWin(optionsDlg) $dlg
		set frame [$dlg getframe]
		set widgets {}
		set uiWin(graphStart) [LabelEntry $frame.startEntry -label "[mc "Start time"]:" ]
		$uiWin(graphStart) insert 0 $options(graphStart)
		lappend widgets $uiWin(graphStart)
		set uiWin(graphStop) [LabelEntry $frame.endEntry -label "[mc "Stop time"]:" ]
		$uiWin(graphStop) insert 0 $options(graphStop)
		lappend widgets $uiWin(graphStop) 
		set uiWin(graphWidth) [LabelEntry $frame.gwidthEntry -label "[mc "Graph Width"]:" ]
		$uiWin(graphWidth) insert 0 $options(graphWidth)
		lappend widgets $uiWin(graphWidth) 
		set uiWin(graphHeight) [LabelEntry $frame.gheightEntry -label "[mc "Graph Height"]:" ]
		$uiWin(graphHeight) insert 0 $options(graphHeight)
		lappend widgets $uiWin(graphHeight) 
		set uiWin(graphSave) [LabelEntry $frame.gsaveEntry -label "[mc "Save Frequency"]:" ]
		$uiWin(graphSave) insert 0 $options(graphSave)
		lappend widgets $uiWin(graphSave) 
		set uiWin(graphReset) [LabelEntry $frame.gresetEntry -label "[mc "Reset Point"]:" ]
		$uiWin(graphReset) insert 0 $options(graphReset)
		lappend widgets $uiWin(graphReset) 
		foreach w $widgets {
			pack $w
		}
		LabelFrame::align $widgets 
		$dlg add -text "[mc "OK"]" -command saveGraphOptions
		$dlg add -text "[mc "Cancel"]"
	}
	
	$dlg draw
}

proc otherOptions {} {
	global uiWin
	global options
	set dlg .otherOptionsDlg
	
	if {![winfo exists $dlg]} {
		set dlg [Dialog $dlg  -title "[mc "Other Options"]" -modal local -default 0]
		set uiWin(otherOptsDlg) $dlg
		set frame [$dlg getframe]
		set widgets {}
		if {0} {
		set uiWin(language) [LabelEntry $frame.languageEntry -label "[mc "Language"]:" ]
		$uiWin(language) insert 0 $options(language)
		lappend widgets $uiWin(language)
		}
		set uiWin(htmlViewer) [LabelEntry $frame.htmlViewerEntry -label "[mc "HTML Viewer"]:" ]
		$uiWin(htmlViewer) insert 0 $options(htmlViewer)
		lappend widgets $uiWin(htmlViewer) 
		foreach w $widgets {
			pack $w
		}
		LabelFrame::align $widgets 
		$dlg add -text "[mc "OK"]" -command saveOtherOptions
		$dlg add -text "[mc "Cancel"]"
	}
	
	$dlg draw

}

# Saves the other options
proc saveOtherOptions {} {
	global uiWin
	global options

	# The options are not updated autmatically. They must be fetched from the
	# widget holding the information
	foreach opt {language htmlViewer} {
		set options($opt) [$uiWin($opt) get]
	}
	write_options
	$uiWin(otherOptsDlg) withdraw
}

# Saves the graph options
proc saveGraphOptions {} {
	global uiWin
	global options

	# The options are not updated autmatically. They must be fetched from the
	# widget holding the information
	foreach opt {graphHeight graphWidth graphSave graphStart graphStop graphReset} {
		set options($opt) [$uiWin($opt) get]
	}
	::tickergraph::graphConfig
	write_options
	$uiWin(optionsDlg) withdraw
}

# Removes all data stored for the graph display
proc clearGraphData {} {
	::tickergraph::clearData
}

# Adds a page to a notebook widget
#
# nb: Notebook widget name
# symbol: The symbol to add. The notebook's tab will show this string
#
# Returns: The name of the notebook frame into which new pages will be added
proc addNotebookPage {nb symbol} {
    global folio
    global ticker_symbols
	# Remove . from symbol names if any
	regsub -all {\.} $symbol "_" wsymbol
	set wsymbol [string tolower $wsymbol]
	set nbframe [$nb insert end $wsymbol -text "$symbol" ]
    set sw [ScrolledWindow $nbframe.sw -borderwidth 5]
	set swfolio [ScrollableFrame $sw.folio -constrainedwidth 1]

    $sw setwidget $swfolio

    set subf [$swfolio getframe]
	set folio($symbol,nbPage) $subf
	bind . <Control-n> [list newTransaction]
	#pack $add -anchor w

    # Each line of consists of the following:
    # Label:  Qty  Cost TotalCost CurrentValue Delta DailyDelta

    label $subf.labelLabel -text [mc "Symbol"] -anchor w
    label $subf.qtyLabel -text [mc "Quantity"]
    label $subf.costLabel -text "[mc "Purchase\nCost"]"
    label $subf.valueLabel -text "[mc "Current\nValue"]"
    label $subf.deltaLabel -text "[mc "Delta"]"
    label $subf.dailyDeltaLabel -text "[mc "Daily\nDelta"]"

    grid $subf.labelLabel $subf.qtyLabel $subf.costLabel $subf.valueLabel \
        $subf.deltaLabel $subf.dailyDeltaLabel -sticky nsew

	for {set i 0} {$i < [lindex [grid size $subf] 0]} {incr i} {
		grid columnconfigure $subf $i -weight 1
	}

	pack $sw -side bottom -fill both -expand true

	return $subf
}

# Creates the notebook which will contain transaction information
proc create_folio {} {
    global folio
    global ticker_symbols
	global uiWin

    set add [button $uiWin(mainframe).add -text "[mc "New Transaction"]" -command [list newTransaction]]
	pack $add -side top
	set uiWin(nb) [NoteBook $uiWin(mainframe).nb]

	set symbolList {}
	# Put the symbols in alphabetical order
	foreach ix [array names ticker_symbols] {
	    lappend symbolList $ticker_symbols($ix)
	}

	foreach symbol [lsort $symbolList]  {
		if {[info exists folio($symbol,transactions)]} {
			set subf [addNotebookPage $uiWin(nb) $symbol]

			for {set i 0} {$i < $folio($symbol,transactions)} {incr i} {
				insertNewTransaction $symbol $i
			}

			for {set i 0} {$i < [lindex [grid size $subf] 0]} {incr i} {
				grid columnconfigure $subf $i -weight 1
			}
		}

	}

	create_summary $uiWin(nb)

	pack $uiWin(nb) -expand true -fill both
}

# Creates the notebook's summary page. The summary page contains information
# on cumulative transactions.
#
# nb: Notebook widget's name
proc create_summary {nb} {
	global ticker_symbols
    global folio

	set nbframe [$nb insert 0 Summary -text "[mc "Summary"]" ]
    set sw [ScrolledWindow $nbframe.sw -borderwidth 5]
    set swfolio [ScrollableFrame $sw.folio -constrainedwidth 1]

    $sw setwidget $swfolio

    set subf [$swfolio getframe]
	bind . <Control-n> [list newTransaction]
	set folio(summary,widget) $subf

    # Each line of consists of the following:
    # Label:  Qty  Cost TotalCost CurrentValue Delta DailyDelta

    label $subf.labelLabel -text "[mc "Symbol"]" -anchor w
    label $subf.qtyLabel -text "[mc "Quantity"]"
    label $subf.costLabel -text "[mc "Purchase\nCost"]"
    label $subf.valueLabel -text "[mc "Current\nValue"]"
    label $subf.deltaLabel -text "[mc "Delta"]"
    label $subf.dailyDeltaLabel -text "[mc "Daily\nDelta"]"

    grid $subf.labelLabel $subf.qtyLabel $subf.costLabel $subf.valueLabel \
        $subf.deltaLabel $subf.dailyDeltaLabel -sticky nsew
	set symbolList {}
	# Put the symbols in alphabetical order
	foreach ix [array names ticker_symbols] {
	    lappend symbolList $ticker_symbols($ix)
	}
	foreach symbol [lsort $symbolList]  {
		# Remove . from symbol names if any
		regsub -all {\.} $symbol "_" wsymbol
		set wsymbol [string tolower $wsymbol]
		if {[info exists folio($symbol,transactions)]} {
			set folio($symbol,total) 0
			insertNewSummary $symbol
		}
	}
	
	pack $sw -side bottom -fill both -expand true
    for {set i 0} {$i < [lindex [grid size $subf] 0]} {incr i} {
		grid columnconfigure $subf $i -weight 1
    }

	$nb raise Summary
}

# Makes sure a given symbol exists in the list of symbols monitored by the
# ticker
#
# win: the name of the window in which the data is entered
#
# Returns: true if the symbol exists
proc validateSymbol {win} {
	global data
	set symbol [$win get]
	return [info exists data($symbol,last)]
}

# Adds a new transaction to the global transaction data
proc newTransaction {} {
	global ticker_symbols

    set dlg .addDlg
    set now [clock seconds]
    set date [clock format $now -format "%m/%d/%Y"]
    set time [clock format $now -format "%H:%M"]
    
    if {[winfo exists $dlg]} {
	set dlgf [$dlg getframe]
    } else {
	Dialog $dlg -cancel 1 -default 0 -modal local -parent . 
	set dlgf [$dlg getframe]
	set widgets [list]
	lappend widgets [LabelFrame $dlgf.symbolFrame -text "[mc "Symbol"]:"]
	pack [ComboBox $dlgf.symbolEntry -editable 0 ] -in [$dlgf.symbolFrame getframe]
	lappend widgets [LabelEntry $dlgf.qtyEntry -label "[mc "Quantity"]:" \
	    -validate key -validatecommand [list string is double %P]]
	lappend widgets [LabelEntry $dlgf.priceEntry -label "[mc "Purchase Price"]:" \
	    -validate key -validatecommand [list string is double %P]]
	lappend widgets [LabelEntry $dlgf.dateEntry -label "[mc "Date"]:"]
	lappend widgets [LabelEntry $dlgf.timeEntry -label "[mc "Time"]:"]
	eval LabelFrame::align $widgets
	eval pack $widgets
	$dlg add -text "[mc "Ok"]" -command [list createTransactionData $dlg $dlgf]
	$dlg add -text "[mc "Cancel"]"
    }

    $dlgf.dateEntry delete 0 end
    $dlgf.dateEntry insert 0 $date
    $dlgf.timeEntry delete 0 end
    $dlgf.timeEntry insert 0 $time
	foreach ix [array names ticker_symbols] {
	    lappend symbolList $ticker_symbols($ix)
	}
	$dlgf.symbolEntry configure -values [lsort $symbolList]
    $dlg draw
	
}

# Creates the actual transaction data
#
# dlg: dialog window to close after all transactions info is extracted
# win: frame containing the widgets with the transaction data
proc createTransactionData {dlg win} {
    global folio
    global data

    set symbol [lindex [$win.symbolEntry cget -values] [$win.symbolEntry getvalue]]
    set qty [$win.qtyEntry get]
    set price [$win.priceEntry get]
    set date [$win.dateEntry get]
    set time [$win.timeEntry get]

	if {![string length $symbol]} {
		return
	}

	if {![string length $qty]} {
		set qty 0
	}

	if {![string length $price]} {
		set price 0.0
	}

    if {[info exists folio($symbol,transactions)]} {
		set curTsx $folio($symbol,transactions)
		incr folio($symbol,transactions)
		if {[info exists folio($symbol,realtransactions)]} {
			incr folio($symbol,realtransactions)
		}
    } else {
		set curTsx 0
		set folio($symbol,transactions) 1
    }

	set folio($symbol,highestTsx) $curTsx
    set folio($symbol,$curTsx,qty) $qty
    set folio($symbol,$curTsx,price) $price
    set folio($symbol,$curTsx,purchase) [format "%.2f" [expr {$qty*$price}]]
    set folio($symbol,$curTsx,date) [clock scan "$date $time"]
    if {[info exists data($symbol,last)] && \
		[string length $data($symbol,last)]} {
		set folio($symbol,$curTsx,value) \
			[format "%.2f" [expr {$data($symbol,last)*$folio($symbol,$curTsx,qty)}]]
	} else {
		set folio($symbol,$curTsx,value) 0.00
	}

    if {[info exists data($symbol,delta)] && \
		[string length $data($symbol,delta)]} {
		set folio($symbol,$curTsx,daily) \
			[format "%.2f" [expr {$data($symbol,delta)*$folio($symbol,$curTsx,qty)}]]
	} else {
		set folio($symbol,$curTsx,daily) 0.00
	}
	set folio($symbol,$curTsx,delta) \
		[format "%.2f" [expr {$folio($symbol,$curTsx,value)-$folio($symbol,$curTsx,purchase)}]]

	write_folio

    insertNewTransaction $symbol $curTsx

    $dlg withdraw
}

# Removes a transaction from the list of transactions
#
# symbol: symbol for the transaction
# tsx: transaction number
proc deleteTransaction {symbol tsx} {
    global folio
    
    set answer [tk_messageBox -type yesno \
	-message "[mc "Delete transaction"] $symbol-$tsx?"]

    if {[string equal "yes" $answer]} {
	set wbase $folio($symbol,$tsx,widget)
	destroy ${wbase}labelLabel ${wbase}qtyLabel ${wbase}costLabel \
	${wbase}valueLabel ${wbase}deltaLabel ${wbase}dailyDeltaLabel \
	${wbase}editButton ${wbase}delButton
	foreach ix [array names folio "$symbol,$tsx,*"] {
	    unset folio($ix)
	}
	if {[info exists folio($symbol,realtransactions)]} {
	    incr folio($symbol,realtransactions) -1
	} else {
	    set folio($symbol,realtransactions) $folio($symbol,transactions)
	    incr folio($symbol,realtransactions) -1
	}
    }
}

# Transaction editing window
#
# symbol: symbol for the transaction
# tsx: transaction number
proc editTransaction {symbol tsx} {
    global folio

    set dlg .editDlg
    
    if {[winfo exists $dlg]} {
	set dlgf [$dlg getframe]
    } else {
	Dialog $dlg -cancel 1 -default 0 -modal local -parent . 
	set dlgf [$dlg getframe]
	set widgets [list]
	lappend widgets [LabelEntry $dlgf.symbolEntry -label "[mc "Symbol"]:" \
	    -state disabled]
	lappend widgets [LabelEntry $dlgf.qtyEntry -label "[mc "Quantity"]:" \
	    -validate key -validatecommand [list string is double %P]]
	lappend widgets [LabelEntry $dlgf.priceEntry -label "[mc "Purchase Price"]:" \
	    -validate key -validatecommand [list string is double %P]]
	lappend widgets [LabelEntry $dlgf.dateEntry -label "[mc "Date"]" ]
	lappend widgets [LabelEntry $dlgf.timeEntry -label "[mc "Time"]" ]
	eval LabelFrame::align $widgets
	eval pack $widgets
	$dlg add -text "[mc "Ok"]" 
	$dlg add -text "[mc "Cancel"]" -command [list $dlg withdraw]
    }

	$dlg itemconfigure 0 -command [list endEdit $dlg $dlgf $symbol $tsx]
    $dlgf.symbolEntry configure -text $symbol
    $dlgf.qtyEntry configure -text $folio($symbol,$tsx,qty)
    $dlgf.priceEntry configure -text $folio($symbol,$tsx,price)
    $dlgf.dateEntry configure -text [clock format $folio($symbol,$tsx,date) -format "%m/%d/%Y"]
    $dlgf.timeEntry configure -text [clock format $folio($symbol,$tsx,date) -format "%H:%M"]
    $dlg draw

}

# Show daily graph
#
# symbol: symbol for which to display data
proc showGraph {symbol} {
	::tickergraph::showGraph $symbol
}

# Callback after editing transaction info
#
# dlg: dialog window to close after all transactions info is extracted
# win: frame containing the widgets with the transaction data
# symbol: symbol for the transaction
# tsx: transaction number
proc endEdit {dlg win symbol tsx} {
    global folio

    set qty [$win.qtyEntry get]
    set price [$win.priceEntry get]
    set date [$win.dateEntry get]
    set time [$win.timeEntry get]

	if {![string length $qty]} {
		set qty 0
	}

	if {![string length $price]} {
		set price 0.0
	}

    set folio($symbol,$tsx,qty) $qty
    set folio($symbol,$tsx,price) $price
    set folio($symbol,$tsx,purchase) [format "%.2f" [expr {$qty*$price}]]
    set folio($symbol,$tsx,date) [clock scan "$date $time"]

    $dlg withdraw
}

# Insert a new transaction in a notebook page
#
# symbol: Transaction symbol
# tsx: Transaction number
proc insertNewTransaction {symbol tsx } {
    global folio
	global uiWin

	if {![info exists folio($symbol,nbPage)]} {
		addNotebookPage $uiWin(nb) $symbol
	}

	# Don't insert summary information when first building the interface. At
	# startup time, the summary is built after all the portfolio information
	# has been set up.
	if {[info exists folio(summary,widget)] && ![info exists folio($symbol,summary,widget)]} {
		insertNewSummary $symbol
	} else {
	}

	if {[info exists folio($symbol,highestTsx)]} {
		if {$tsx > $folio($symbol,highestTsx)} {
			set folio($symbol,highestTsx) $tsx
		}
	} else {
		set folio($symbol,highestTsx) $tsx
	}
	set subf $folio($symbol,nbPage)

    # Remove . from symbol names if any
    regsub -all {\.} $symbol "_" wsymbol
    set wsymbol [string tolower $wsymbol]
    set folio($symbol,$tsx,widget) $subf.$wsymbol$tsx
    set wbase $folio($symbol,$tsx,widget)
    set widgets ""
    lappend widgets [label ${wbase}labelLabel -anchor w -text $symbol]
    lappend widgets [label ${wbase}qtyLabel -anchor e -textvariable folio($symbol,$tsx,qty)]
    lappend widgets [label ${wbase}costLabel -anchor e -textvariable folio($symbol,$tsx,purchase)]
    lappend widgets [label ${wbase}valueLabel -anchor e -textvariable folio($symbol,$tsx,value)]
    lappend widgets [label ${wbase}deltaLabel -anchor e -textvariable folio($symbol,$tsx,delta)]
    lappend widgets [label ${wbase}dailyDeltaLabel -anchor e -textvariable folio($symbol,$tsx,daily)]
	lappend widgets [button ${wbase}editButton -text "[mc "Edit"]" \
	-command [list editTransaction $symbol $tsx]]
	lappend widgets [button ${wbase}delButton -text "[mc "Delete"]" \
	-command [list deleteTransaction $symbol $tsx]]
    eval grid $widgets -sticky news
}

# Insert a new transaction in the summary page
#
# symbol: Transaction symbol
proc insertNewSummary {symbol} {
    global folio
	set subf $folio(summary,widget)

    # Remove . from symbol names if any
    regsub -all {\.} $symbol "_" wsymbol
    set wsymbol [string tolower $wsymbol]
    set folio($symbol,summary,widget) $subf.$wsymbol
    set wbase $folio($symbol,summary,widget)
    set widgets ""
    lappend widgets [label ${wbase}labelLabel -anchor w -text $symbol]
    lappend widgets [label ${wbase}qtyLabel -anchor e -textvariable folio($symbol,summary,qty)]
    lappend widgets [label ${wbase}costLabel -anchor e -textvariable folio($symbol,summary,purchase)]
    lappend widgets [label ${wbase}valueLabel -anchor e -textvariable folio($symbol,summary,value)]
    lappend widgets [label ${wbase}deltaLabel -anchor e -textvariable folio($symbol,summary,delta)]
    lappend widgets [label ${wbase}dailyDeltaLabel -anchor e -textvariable folio($symbol,summary,daily)]
	lappend widgets [button ${wbase}graphButton -text "[mc "Graph"]" \
		-command [list ::tickergraph::showGraph $symbol]]
	::tickergraph::createGraph $symbol
    eval grid $widgets -sticky news
}

# Refrech ticker info
proc do_refresh {} {
    global service
    if {[catch {$service} svc_rc]} {
        set_last_time "[mc " quote_retrieval_error "]"
    } elseif {$svc_rc} {
        set_last_time
    } else {
        # service module ran, but didn't complete, don't do anyting
    }
}

# Refrech ticker data
proc refresh_data {args} {
    global refresh service next_refresh index
    if {"[lindex $args 0]" == "restart"} {
	catch {after cancel $next_refresh}
    }

    after 0 do_refresh

    set next_refresh [after [expr {$refresh * 60000}] "refresh_data" ]
}


# Acquire parameters for ticker
proc get_parms {{msg {}}} {
    global refresh tick_speed tick_font tick_size tick_ontop tick_round
    global index ticker_symbols data 
    global service_list service
    global proxy_host proxy_port
    global mail_host  mail_port
    global temp_refresh temp_tick_speed 
    global temp_tick_ontop temp_tick_round doParms
    global temp_proxy_host temp_proxy_port
    global temp_mail_host  temp_mail_port
	global about

    catch {
      bind .t <Button-1> {}
      bind .t <Button-2> {}
      bind .t <Button-3> {}
    }
    set temp_refresh $refresh
    set temp_tick_speed $tick_speed
    set temp_tick_ontop $tick_ontop
    set temp_tick_round $tick_round
    set temp_proxy_host $proxy_host
    set temp_proxy_port $proxy_port
    set temp_mail_host  $mail_host
    set temp_mail_port  $mail_port

    # turn off ticker on top during dialog
    set save_tick_ontop $tick_ontop
    set tick_ontop 0

    catch {destroy .p}
    toplevel .p -class Dialog
    wm title .p "$about(name): [mc "Set Parameters"]"
    wm withdraw .p
    update

    if {[string length $msg]} {
	label .p.msg -text $msg -relief raised
	pack .p.msg -side top -fill x
    }

    label .p.l0 -text "[mc "Version"] $about(version) - $about(copyright)"
    pack .p.l0 -side top

    frame .p.q
    label .p.q.l1 -text "[mc "Quote service"]"
    pack .p.q.l1 -side left
    scrollbar .p.q.scr -orient vertical -command ".p.q.service yview"
    listbox .p.q.service -width 30 -height 3 \
		-exportselection 0 \
		-selectmode single -yscrollcommand ".p.q.scr set"
    pack .p.q.service -side left -fill y
    if {[llength $service_list] > 3} {
	pack .p.q.scr -side left -fill y
    }
    pack .p.q -side top -pady 4

    frame .p.p
    label .p.p.l1 -text "[mc "Http proxy host:"]"
    entry .p.p.ph -width 20 -textvariable temp_proxy_host
    label .p.p.l2 -text " [mc "Port:"]"
    entry .p.p.pp -width 4 -textvariable temp_proxy_port
    pack .p.p.pp .p.p.l2 .p.p.ph .p.p.l1 -side right -padx 3 
    pack .p.p -side top -padx 4

    frame .p.m
    label .p.m.l1 -text "[mc "SMTP relay host"]:"
    entry .p.m.mh -width 20 -textvariable temp_mail_host
    label .p.m.l2 -text " [mc "Port"]:"
    entry .p.m.mp -width 4 -textvariable temp_mail_port
    pack .p.m.mp .p.m.l2 .p.m.mh .p.m.l1 -side right -padx 3 
    pack .p.m -side top -pady 4 -padx 4

    frame .p.s
    scale .p.s.tick -label "[mc "Ticker delay (milliseconds)"]" -from 100 -to 500 \
		-orient horizontal -resolution 50 \
		-length 160 -variable temp_tick_speed 
    pack .p.s.tick -side  left
    scale .p.s.refresh -label "[mc "Data refresh (minutes)"]"   -from 1 -to 15 \
		-orient horizontal -length 160 -variable temp_refresh 
    pack .p.s.refresh -side right
    pack .p.s -side top

    frame .p.t
    label .p.t.l1 -text "[mc "Ticker font"]:"
    scrollbar .p.t.fscr -orient horizontal -command ".p.t.fon yview" 
    listbox .p.t.fon -height 1 -width 10 -highlightthickness 0 \
		-selectborderwidth 0 \
		-exportselection 0 -selectmode single \
		-yscrollcommand ".p.t.fscr set" 
    .p.t.fon configure -selectbackground [.p.t.fon cget -background]
    .p.t.fon configure -selectforeground [.p.t.fon cget -foreground]
    label .p.t.l2 -text "    [mc "Size"]:"
    scrollbar .p.t.sscr -orient horizontal -command ".p.t.siz yview"
    listbox .p.t.siz -height 1 -width 3 -highlightthickness 0 \
		-selectborderwidth 0 \
		-exportselection 0 -selectmode single \
		-yscrollcommand ".p.t.sscr set"
    .p.t.siz configure -selectbackground [.p.t.siz cget -background]
    .p.t.siz configure -selectforeground [.p.t.siz cget -foreground]
    pack .p.t.l1 .p.t.fon  .p.t.fscr .p.t.l2 .p.t.siz .p.t.sscr -side left \
		-padx 2
    pack .p.t -side top -pady 4 -padx 4

    frame .p.c
    checkbutton .p.c.on -text "[mc "Always on top"]"     -variable temp_tick_ontop
    checkbutton .p.c.rd -text "[mc "Round to hundreds"]" -variable temp_tick_round
    pack .p.c.on .p.c.rd -side left -padx 10
    pack .p.c -side top

    frame .p.f 
    frame .p.f.f
    label .p.f.f.l2 -text "[mc "Security symbols"]:" 
    button .p.f.f.look -text "[mc "Symbol Lookup"]..." -command symbol_lookup
    pack .p.f.f.l2 .p.f.f.look -side left -padx 20 -fill x
    pack .p.f.f -side top
    scrollbar .p.f.scr -command ".p.f.symbols yview" -orient vertical
    text .p.f.symbols -width 40 -height 4  -wrap word \
		-yscrollcommand ".p.f.scr set"
    pack .p.f.scr -side right -fill y
    pack .p.f.symbols -side left -fill both -expand 1
    pack .p.f -side top -pady 4

    frame .p.b
    button .p.b.ok  -text " [mc "Ok"] "     -command {set doParms 1}
    button .p.b.can -text " [mc "Cancel"] " -command {set doParms 0}
    button .p.b.ex  -text " [mc "Exit"] $about(name) "  -command {Exit}
    pack .p.b.ok .p.b.can .p.b.ex -side left -padx 10 -pady 5
    pack .p.b -side top

    # fill inital services
    eval .p.q.service insert end $service_list
    .p.q.service selection set [lsearch -exact $service_list $service]

    # fill font choices
    .p.t.fon insert end Courier Helvetica Times
    if {[set idx [lsearch -exact "Courier Helvetica Times" $tick_font]] < 0} {
	set idx 1
    }
    .p.t.fon selection set $idx
    .p.t.fon see $idx

    # fill size choices
    .p.t.siz insert end 8 10 12 14 16 18
    if {[set idx [lsearch -exact "8 10 12 14 16 18" $tick_size]] < 0} {
	set idx 2
    }
    .p.t.siz selection set $idx
    .p.t.siz see $idx

    # fill initial symbols
    set i 0
    while {[info exists ticker_symbols([incr i])]} {
	.p.f.symbols insert end "$ticker_symbols($i)   "
    }

    set doParms 0
    wm deiconify .p
    focus .p
    raise .p
    update
    grab .p
    wm protocol .p WM_DELETE_WINDOW {set doParms 0}
    vwait doParms

    if {$doParms == 1} {
	set service [.p.q.service get [.p.q.service curselection]]
	set symbol_list [.p.f.symbols get 1.0 end]
	regsub -all "\[ \n\t\]\[ \n\t\]?" $symbol_list { } symbol_list
	set i 0
	unset ticker_symbols
	array set ticker_symbols {}
	foreach sym [split $symbol_list] {
	    if {[string length $sym] == 0} continue
	    set ticker_symbols([incr i]) $sym
	}
	set tick_speed $temp_tick_speed
	set tick_font  [.p.t.fon get \
	    [expr int(round([lindex [.p.t.fon yview] 0]*[.p.t.fon size]))]]
	set tick_size  [.p.t.siz get \
	    [expr int(round([lindex [.p.t.siz yview] 0]*[.p.t.siz size]))]]
	set tick_ontop $temp_tick_ontop
	set tick_round $temp_tick_round
	if {$tick_ontop} {
	    raise .
	}
	set refresh    $temp_refresh
	set_last_time

	if {[string length [string trim $temp_proxy_host]]} { 
	    set proxy_host [string trim $temp_proxy_host]
	    if {[string length [string trim $temp_proxy_port]] && \
		[scan $temp_proxy_port %d j] == 1} {
	        set proxy_port $j
	    } else {
	        set proxy_port ""
	    }
	} else {
	    set proxy_host ""
	    set proxy_port ""
	}
        http::config -proxyhost "$proxy_host" -proxyport "$proxy_port"

	if {[string length [string trim $temp_mail_host]]} { 
	    set mail_host [string trim $temp_mail_host]
	    if {[string length [string trim $temp_mail_port]] && \
		[scan $temp_mail_port %d j] == 1} {
	        set mail_port $j
	    } else {
	        set mail_port ""
	    }
	} else {
	    set mail_host ""
	    set mail_port ""
	}

	write_defaults
	refresh_data restart
	set index 0

    }  else {
       # restore tick_ontop
       set tick_ontop $save_tick_ontop
    }

    catch {
      bind .t <Button-1> get_parms
      bind .t <Button-2> get_parms
      bind .t <Button-3> get_parms
    }

    destroy .p
    catch {destroy .l}
}


# Symbol lookup dialog
proc symbol_lookup {} {
    global service proxy_host proxy_port
	global about
    catch {destroy .l}
    toplevel .l -class Dialog
    wm title .l "$about(name): [mc "Symbol Lookup"]"

    if {[string length $proxy_host]} {
	set proxy $proxy_host
	if {[string length $proxy_port]} {
	    append proxy : $proxy_port
	}
    } else {
	set proxy [mc "(none)"]
    }

    label .l.l1 -text "[mc "Lookup service"]: $service\n\
	[mc "Using http proxy"]: $proxy"
    pack .l.l1  -side top -fill y
    label .l.msg -text ""
    pack .l.msg -side top -fill y

    frame .l.s
    label .l.s.l1 -text "[mc "Search"]:"
    entry .l.s.sym -width 20
    button .l.s.go -text "[mc "Search"]:" -command do_lookup
    bind .l.s.sym <Key-Return> do_lookup
    pack .l.s.l1 .l.s.sym .l.s.go -side left -padx 4
    pack .l.s -side top -pady 4
    
    label .l.l2 -text "[mc "Results"]"
    pack .l.l2 -side top

    frame .l.r
    scrollbar .l.r.v -orient vertical   -command ".l.r.res yview"
    scrollbar .l.r.h -orient horizontal -command ".l.r.res xview"
    listbox .l.r.res -width 35 -height 10 \
		    -exportselection 0 -selectmode multiple \
		    -yscrollcommand ".l.r.v set" \
		    -xscrollcommand ".l.r.h set" 
    pack .l.r.h -side bottom -fill x
    pack .l.r.v -side right -fill y
    pack .l.r.res -side left -expand 1 
    pack .l.r -side top 

    frame .l.b
    button .l.b.ok -text "[mc "Add selections"]" -command add_symbols
    button .l.b.can -text "[mc "Cancel"]" -command {destroy .l}
    pack .l.b.ok .l.b.can -side left -padx 10
    pack .l.b -side top -pady 4 
    grab .l
    focus .l.s.sym
    update
    tkwait window .l
}


# Perform symbol lookup
proc do_lookup {} {
    global service
    catch {
      .l.msg configure -text "[mc "Searching"]..."
      update idletasks
      set str [string trim [.l.s.sym get]]
      set symco_list ""
      set count 0
      if {! [catch {set symco_list [${service}_lookup $str]} res] } {
	  foreach {sym co} $symco_list {
	      .l.r.res insert end "$sym   $co"
	      incr count
	  }
      }
      if {$count} {
          .l.msg configure -text "[mc "Search complete"]"
      } else {
          .l.msg configure -text "[mc "Nothing found"]"
      }
    }
}

# Add symbol to ticker
proc add_symbols {} {
    catch {
      set sel_list [.l.r.res curselection]
      set count 0
      foreach idx $sel_list {
	  incr count
	  set sym [lindex [split [.l.r.res get $idx]] 0]
	  .p.f.symbols insert end "  $sym "
      }
      .l.r.res selection clear 0 end
      if {$count} {
	  .l.msg configure -text "[mc "Selections added"]"
      }
    }
}

# Callback that is executed when the portfolio information is modified.
#
# var: Name of the array as generated by the trace command
# idx: Index of the array as generated by the trace command
# op: operation performed on var, as generated by trace
proc dataChanged {var idx op} {
	global data
	global folio

	set skipTsx -1
	switch $op {
		w {
			# Match on the *,delta index because it is the last one set in the code.
			# By the time that index value is set, all other calculations will be
			# correct. TODO This is code-style dependent and should be fixed.
			if {[regexp {([^,]+),([0-9]+),delta} $idx idx symbol tsx]} {
				if {$tsx != $folio($symbol,highestTsx)} {
					return
				}
			} else {
				return
			}
		}
		u {
			# Remove the transactions from the list but make sure that the value
			# of the highest transaction is changed accordingly (if needed)
			
			if {[regexp {([^,]+),([0-9]+),delta} $idx idx symbol tsx]} {
				set l [lsort [array names folio "$symbol,*,delta"]]
				set high [lindex [split [lrange $l end end] ,] 1]
				set skipTsx $tsx
				if {$folio($symbol,highestTsx) > $high} {
					set folio($symbol,highestTsx) $high
				}
			} else {
				return
			}
		}
		default {
			return
		}
	}
	
	set qty 0
	set purchase 0
	set value 0
	set delta 0
	set daily 0
	# The catch statements will make sure nothing happens if
	# transactions are removed.
	for {set i 0} {$i <= $folio($symbol,highestTsx)} {incr i} {
		if {$i != $skipTsx} {
			catch {
				set qty [expr {$qty+$folio($symbol,$i,qty)}]
				set purchase [expr {$purchase+$folio($symbol,$i,purchase)}]
				set value [expr {$value+$folio($symbol,$i,qty)*$data($symbol,last)}]
				set delta [expr {$delta+$folio($symbol,$i,value)-$folio($symbol,$i,purchase)}]
				set daily [expr {$daily+$data($symbol,delta)*$folio($symbol,$i,qty)}]
			}
		}
	}
	
	set folio($symbol,summary,qty) $qty
	set folio($symbol,summary,purchase) [format "%.2f" $purchase]
	set folio($symbol,summary,value) [format "%.2f" $value]
	set folio($symbol,summary,delta) [format "%.2f" $delta]
	set folio($symbol,summary,daily) [format "%.2f" $daily]
}

# Cleanup before exiting program
proc Exit {} {
	write_options
	write_folio
	# Force saving of all data
	::tickergraph::saveAllData
	exit
}

# Shows the About window
proc aboutWindow {} {
	global uiWin
	global about
	set uiWin(aboutWindowDlg) .aboutDlg
	set dlg $uiWin(aboutWindowDlg)
	if {![winfo exists $dlg]} {
		Dialog $dlg -title "[mc "About..."]" -modal local
		set frame [$dlg getframe]
		# Calculate the label length at runtime otherwise it looks ugly
		set len [expr {[string length $about(homepage)]+\
			[string length "[mc "Release Date"]: "]}]
		set text [text $frame.t -relief flat -width $len -height 5]
		$text tag config center -justify center
		$text insert end "$about(name) $about(version)\n" center
		$text insert end "$about(copyright)\n" center
		$text insert end "$about(email)\n" center
		$text insert end "[mc "Release Date"]: $about(date)\n" center
		$text insert end "[mc "Home Page"]: $about(homepage)" center
		$text configure -state disabled
		pack $text
		$dlg add -text "[mc "OK"]"
	}
	$dlg draw

}

# start it up!

set_defaults

if {[string match *setup* [string tolower [lindex $argv 0]]]} {
    set setup 1
} else {
    set setup 0
}

if {! [read_defaults] || $setup} {
    # first time, or no tclticker file, get parms, or want 'setup'

    if {! $setup} {
        set msg "[mc "Welcome to Stock Watcher!"]"
    } else {
        set msg ""
    }

    get_parms $msg
}


read_options
read_folio
trace variable folio uw dataChanged
start_ticker
wm deiconify .
raise .
update		;# get ticker going while waiting on inital data
refresh_data

