#!/bin/sh

# ncid - Network Caller-ID client

# Copyright (c) 2001-2018
#  John L. Chmielewski <jlc@users.sourceforge.net>
#  Steve Limkemann

# 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 3 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, see <http://www.gnu.org/licenses/>.

# START OF LOCAL MODIFICATION SECTION
# set TCLSH variable, FreeBSD will call it something like tclsh8.4 \
TCLSH=/usr/local/bin/tclsh8.6
# set WISH variable, FreeBSD will call it something like wish8.4 \
WISH=/usr/local/bin/wish8.6
# set BIN to directory where ncid is installed \
BINDIR=/usr/local/bin
# END OF LOCAL MODIFICATION SECTION

# set location of configuration file (it is also set later on for tcl/tk) \
CF=/usr/local/etc/ncid/ncid.conf
# if config file does not exist, set GUI=1 \
[ -f $CF ] || GUI=1
# if $GUI not set, set GUI based on configuration file \
[ -z "$GUI" ] && if grep "set NoGUI" $CF | grep 0 > /dev/null 2>&1; then GUI=1; fi
# if GUI set, look for the --no-gui option, if found set GUI="" \
[ -n "$GUI" ] && for i in $*; do if [ "$i" = "--no-gui" ]; then  GUI=""; fi; done
# if $DISPLAY is not in the environment, set GUI="" \
[ -z "$DISPLAY" ] && GUI=""
# if $GUI is set, look for wish and exec it \
[ -n "$GUI" ] && type $WISH > /dev/null 2>&1 && exec $WISH -f "$0" -- "$@"
# if $GUI is not set, look for tclsh and exec it \
[ -z "$GUI" ] && type $TCLSH > /dev/null 2>&1 && exec $TCLSH "$0" "$@"
# wish not found, look for tclsh and exec it \
type $TCLSH > /dev/null 2>&1 && exec $TCLSH "$0" --no-gui "$@"
# tclsh not found, maybe using a Macintosh \
[ -d /Applications/Wish\ Shell.app ] && \
    /Applications/Wish\ Shell.app/Contents/MacOS/Wish\ Shell -f "$0" -- "$@"
# tcl or tk not found \
echo "wish or tclsh not found in your \$PATH"; exit -1

set ConfigDir   /usr/local/etc/ncid
set ConfigFile  "$ConfigDir/ncid.conf"

### Constants
set Logo        /usr/local/share/ncid/images/ncid.gif
set CygwinBat   /cygwin.bat

### global variables that can be changed by command line options
### or by the configuration file
set Host          127.0.0.1
set Port          3333
set Delay         15
set PIDfile       ""
set PopupTime     1
set Verbose       0
set NoGUI         0
set CallOnRing    0
set HostnameFlag  0
set Ring          999
set NoExit        0
set WakeUp        0
set ExitOn        exit
set AltDate       0
set oldAltDate    0
set preClient_1_0 0
set CallLog       0

###  global variables that only can be changed by the configuration file
set ModDir       /usr/local/share/ncid/modules
set ModName      ""
set Country      "US"
set NoOne        0
set DateSepar   "/"
set oldDateSepar "/"
set YearDot      0
set WrapLines    "word"
set DialPrefix   ""

### global variables that are used as static variables
set display_line_num    0
set awakened            0
set clock               24
set oldClock            24
set autoSave            "off"
set oldAutoSave         "off"
set autoStart           "off"
set oldautoStart        "off"
set NightMode           0
set oldNightMode        0
set Begin               0
set End                 0
set waitMsg             0
set mod_menu            0
set multi               0
set menuDisabled        0
set aliasTypes          "NAMEDEP NMBRDEP NAMEONLY NMBRONLY NMBRNAME LINEONLY"
set aliasList           ""
set labelWidth           4
set dateWidth           10
set timeWidth            5
set lineIDWidth         16
set nmbrWidth           20
set nameWidth           30
set mtypeWidth           7
set fieldseparators      6
set addONE              ""
set dtfile              "/usr/share/applications/ncid.desktop"
set countryCodes        "DE FR HR SE UK US NONE"

# Global Variables
set svrOptions ""
set dial       0
set LineIDS    ""
set svrLID     ""

if {[file exists $ConfigFile]} {
    source $ConfigFile
}

# historyTextWidth must be reduced by 1 character
# $timeWidth will be included after config file sets $clock which can
# cause it to change its value from 5 to 8
set historyTextWidth [ expr $labelWidth + $dateWidth + $lineIDWidth + \
                            $nmbrWidth + $nameWidth +  $mtypeWidth + \
                            $fieldseparators - 1 ]

set linelabel [format "%-${lineIDWidth}.${lineIDWidth}s" "LINE"]
set nmbrlabel [format "%-${nmbrWidth}.${nmbrWidth}s" "NUMBER"]
set namelabel [format "%-${nameWidth}.${nameWidth}s" "NAME"]
set mtypelabel [format "%-${mtypeWidth}.${mtypeWidth}s" "MTYPE"]

# DATE field width is either 10 or 11 characters
# TIME field width is either 5 or 8 characters
# clock 24 & DateSepar .
set lbl1 "TYPE |DATE      |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 24 & DateSepar -
set lbl2 "TYPE |DATE      |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 24 & DateSepar /
set lbl3 "TYPE |DATE      |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar .
set lbl4 "TYPE |DATE      |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar -
set lbl5 "TYPE |DATE      |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar /
set lbl6 "TYPE |DATE      |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 24 & DateSepar . & YearDot 1
set lbl7 "TYPE |DATE       |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar . & YearDot 1
set lbl8 "TYPE |DATE       |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"

if {$ModName != ""} {
    if {[regexp {^.*/} $ModName]} { set Program "$ModName"
    } else {set Program "$ModDir/$ModName"}
} else {set Program ""}

### global variables that are fixed
set Count       0
set ExecSh      0
set Socket      0
set Try         0
set Version     "1.10.1"
set VersionIDENT "client ncid (NCID) $Version"
set Usage       {Usage:   ncid  [OPTS] [ARGS]
         OPTS: [--no-gui]
               [--night-mode]
               [--alt-date              | -A]
               [--call-log              | -c]
               [--country-code <code>   | -C <country code>]
               [--delay <seconds>       | -D <seconds>]
               [--help                  | -h]
               [--hostname-flag         | -H]
               [--noexit                | -X]
               [--pidfile <file>        | -p <file>]
               [--PopupTime <0-5>       | -t <0-5>]
               [--program <module name> | -P <module name>]
               [--ring <0-9|-1|-2|-9>   | -r <0-9|-1|-2|-9>]
               [--verbose <1-9>         | -v <1-9>]
               [--version               | -V]
               [--wakeup                | -W]

         ARGS: [<IP_ADDRESS>            | <HOSTNAME>]
               [<PORT_NUMBER>]}

set loginName   $tcl_platform(user)
set hostname [info hostname]
regsub {([^.]*).*} $hostname {\1/ncid} lineName
set VersionInfo "Client: ncid \[$hostname\] NCID $Version"

set Author \
"
Copyright © 2001-2018
John L. Chmielewski
"
set Website "http://ncid.sourceforge.net"

set labList \
"
BLK:  Blocked           - blacklisted call blocked
CID:  Caller ID         - incoming call
HUP:  Hangup            - blacklisted call hangup
MSG:  Message           - text message from a user or the server
MWI:  Voicemail         - one or more voicemail messages
NOT:  Notice            - a smartphone message notice
OUT:  Out               - outgoing call
PID:  Phone ID          - Caller ID from a smartphone
PUT:  Phone out call    - outgoing Caller ID from a smartphone
RID:  Ring Back         - rings back when called number is available
RLY:  Relay             - send message through an SMS relay
WID:  Call Waiting ID   - Caller ID from call waiting
"

set fieldList \
"
TYPE   - call or message label
DATE   - date of call or message
TIME   - time of call or message
LINE   - telephone line label
NUMBER - caller telephone number
NAME   - caller name
MTYPE  - message type
"

set serverHelp \
"
\"Reload alias, blacklist and whitelist files\" menu entry:
    Server reloads its Alias, Blacklist and Whitelist files.

\"Update current call log\" menu entry:
    Server replaces items in its cidcall.log file with aliases
    in its ncidd.alias file.

\"Update all call logs\" menu entry:
    Server replaces items in its current cidcall.log file and
    previous ones with files with aliases in its ncidd.alias file.

\"Reread call log\" menu entry:
    Server resends the cidcall.log file.

Selecting a line in the history window will enable the alias,
blacklist and whitelist menu entries.  If a modem is active and
the number is all digits, the dial entry will also be enabled.
You'll also be able to copy the current phone number to the clipboard.

You can remove a line selection by clicking below the history
window and outside the message input area.

Once you modify the alias file you must:
    * Reload alias, blacklist and whitelist files
    * Update the current call log or all call logs
    * Reread call log

Once you modify the blacklist or whitelist file, you must:
    * Reload alias, blacklist and whitelist files
"

# display error message and exit
proc exitMsg {code msg} {
    global NoGUI

    if $NoGUI {
        puts stderr $msg
    } else {
        wm withdraw .
        option add *Dialog.msg.wrapLength 9i
        option add *Dialog.msg.font "courier 12"
        tk_messageBox -message $msg -type ok
    }
    exit $code
}

# display the $Try attempt number to connect to ncidd
proc tryCount {msg} {
    global Count
    global Delay
    global Try
    global Txt
    global NoGUI

    # If $Delay == 0, do not try to reconnect
    if (!$Delay) {exit -1}

    if $NoGUI {
        set Once 0
        puts -nonewline stderr $msg
        after [expr $Delay*1000] set Once 1
        vwait Once
    } else {
        set Count $Delay
        while {$Count > 0} {
            if {$Count == 1} {
                set Txt "$msg Try $Try in $Count second."
            } else {
                set Txt "$msg Try $Try in $Count seconds."
            }
            set Once 0
            set Count [expr $Count - 1]
            after [expr 1000] set Once 1
            vwait Once
        }
    }
}

# close connection to NCID server if open, then reconnect
proc Reconnect {} {
    global Socket
    global Count

    if $Count {
        # already waiting to reconnect, force a retry
        set Count 0
        return
    }

    if {$Socket > 0} {
        # close connection to server
        flush $Socket
        fileevent $Socket readable ""
        close $Socket
        set Socket 0
    }

    connectCID
}

# This catches a lot of errors!
proc bgerror {mess} {
    global errorInfo
    global errorCode

    exitMsg 1 "BGError: $mess\n$errorInfo\n$errorCode\n"
}

# Get data from CID server
proc getCID {} {
    global CallOnRing
    global Program
    global cid
    global Host Port
    global NoGUI
    global Ring
    global Socket
    global Try
    global Verbose
    global VersionInfo
    global lineID
    global call dial LineIDS
    global label
    global display_line_num
    global WakeUp
    global wakened targetTime doingLog Begin End remote_status waitMsg
    global mod_menu argument menuDisabled
    global CIDaliasType LineAliasType
    global svrOptions nmbrREQ
    global NightMode hup

    set msg {server connection closed}
    set cnt 0
    while {$cnt != -1} {
        if {[eof $Socket] || [catch {set cnt [gets $Socket dataBlock]} msg]} {
            # remove event handler
            fileevent $Socket readable ""
            close $Socket
            set svrOptions ""
            if !$NoGUI {
                if {$menuDisabled == 0} {
                  set menu .menubar.server
                  $menu entryconfigure Reload* -state disabled
                  $menu entryconfigure Update*current* -state disabled
                  $menu entryconfigure Update*all*call* -state disabled
                  $menu entryconfigure Reread* -state disabled
                  $menu entryconfigure Add/Modify* -state disabled
                  $menu entryconfigure Add*to*Blacklist* -state disabled
                  $menu entryconfigure Remove*from*Blacklist* -state disabled
                  $menu entryconfigure Add*to*Whitelist* -state disabled
                  $menu entryconfigure Remove*from*Whitelist* -state disabled
                  $menu entryconfigure Dial* -state disabled
                  $menu entryconfigure Copy*Phone*Number*to*Clipboard*as*shown* -state disabled
                  $menu entryconfigure Copy*Phone*Number*to*Clipboard*digits*only* -state disabled
                  $menu entryconfigure Copy*Entire*Line*to*Clipboard*as*shown* -state disabled
                  set menuDisabled 1
                }
            }
            set Try [expr $Try + 1]
            tryCount "$Host:$Port - $msg\n"
            connectCID
            return
        }
        set Try 0

        # get rid of non-printable characters at start/end of string
        set dataBlock [string trim $dataBlock]

        if {[string match 200* $dataBlock]} {
            # output NCID server connect message
            doVerbose $dataBlock 1
            set Begin [clock clicks -milliseconds]
            regsub {200 (.*)} $dataBlock {\1} dataBlock
            if $NoGUI { 
               #puts "$VersionInfo\n$dataBlock"
               doVerbose "$VersionInfo\n$dataBlock" 1
               set targetTime 0
            } else {
                set targetTime [expr [clock clicks -milliseconds] + 500]
                .vh configure -state normal
                set doingLog 1
                .vh insert 1.0 "\n\n\t\tReading the call log\n\n"
                update idletasks
                displayCID "$VersionInfo\n$dataBlock" 1
                }
        } elseif {[string match {25[0-3]*} $dataBlock]} {
            # NCID server sent log message
            if !$NoGUI {
                .vh delete 1.0 6.0
                .vh yview moveto 1.0
                .vh configure -state disabled
                if {[lindex [.vh yview] 0] + [lindex [.vh yview] 1] == 1.0} {
                    grid remove .ys
                } else {
                    grid .ys
                }
            }
            set doingLog 0
            if {[regexp {250} $dataBlock]} {
                doVerbose "$dataBlock - $display_line_num lines" 1
            } else {doVerbose "$dataBlock" 1}
            set End [clock clicks -milliseconds]
            set elapsed [expr $End - $Begin]
            doVerbose "$display_line_num entries in $elapsed milliseconds" 4
        } elseif {[string match 300* $dataBlock]} {
            # NCID server sent end of startup message
            doVerbose $dataBlock 1
            continue
        } elseif {[string match 400* $dataBlock]} {
            # NCID server has sent text to be displayed
            doVerbose $dataBlock 1
            toplevel .reply
            wm title .reply "Server's Response"
            grid [text .reply.text -yscrollcommand ".reply.ys set" -setgrid 1 \
                     -font FixedFontP -height 8 -width 70] \
                     -pady 1 -padx 1 -sticky nesw
            grid [ttk::scrollbar .reply.ys -command ".reply.text yview"] \
                    -column 1 -row 0 -sticky ns -pady 1 -padx 1
            grid [ttk::button .reply.btn -text "OK" -command {destroy .reply}] \
                    -pady 10 -columnspan 2
            grid columnconfigure .reply 0 -weight 1
            grid rowconfigure .reply 0 -weight 1
            wm minsize .reply 25 4
            bind .reply <Configure> {
                if {[lindex [.reply.text yview] 0] + [lindex [.reply.text yview] 1] == 1.0} {
                    grid remove .reply.ys
                } else {
                    grid .reply.ys
                }
            }
            modal {.reply}
            continue;
        } elseif {[string match 401* $dataBlock]} {
            # NCID server has sent text to be displayed, must ACCEPT or REJECT
            doVerbose $dataBlock 1
            toplevel .reply
            wm title .reply "Server's Response"
            grid [text .reply.text -yscrollcommand ".reply.ys set" -setgrid 1 \
                    -font FixedFontP -height 8 -width 70] \
                    -pady 10 -padx 10 -sticky nesw
            .reply.text insert 1.0 "\n\n\tUpdating call logs"
            .reply.text configure -state disabled
            grid [ttk::scrollbar .reply.ys -command ".reply.text yview"] \
                    -column 1 -row 0 -sticky ns -pady 10 -padx 5
            grid [ttk::frame .reply.fr]  -pady 10 -padx 10 -columnspan 2 -row 1
            ttk::button .reply.accept_btn -text "Accept" -state disabled -command {
                    global multi

                    if {$multi} {
                        set temp "S"
                    } else {
                        set temp ""
                    }
                    puts $Socket "WRK: ACCEPT LOG$temp"
                    flush $Socket
                    destroy .reply
                    }
            ttk::button .reply.reject_btn -text "Reject" -state disabled -command {
                    global multi

                    if {$multi} {
                        set temp "S"
                    } else {
                        set temp ""
                    }
                    puts $Socket "WRK: REJECT LOG$temp"
                    flush $Socket
                    destroy .reply
                    }
            grid .reply.accept_btn .reply.reject_btn -in .reply.fr -padx 25
            grid columnconfigure .reply 0 -weight 1
            grid rowconfigure .reply 0 -weight 1
            wm minsize .reply 40 5
            bind .reply <Configure> {
                if {[lindex [.reply.text yview] 0] + [lindex [.reply.text yview] 1] == 1.0} {
                    grid remove .reply.ys
                } else {
                    grid .reply.ys
                }
            }
            showBusy "." .reply.text
            modal {.reply}
            continue;
        } elseif {[string match 402* $dataBlock]} {
            doVerbose $dataBlock 1
             set remote_status ""
        } elseif {[string match 403* $dataBlock]} {
            doVerbose $dataBlock 1
            set mod_menu 1
        } elseif {[string match 410* $dataBlock]} {
            doVerbose $dataBlock 1
            .reply.text configure -state normal
            .reply.text delete end-1chars
            .reply.text configure -state disabled
            .reply.text see end
            catch {
                if {[lindex [.reply.text yview] 0] + [lindex [.reply.text yview] 1] == 1.0} {
                    grid remove .reply.ys
                } else {
                    grid .reply.ys
                }
            }
            catch {
                .reply.accept_btn configure -state normal
                .reply.reject_btn configure -state normal
            }
            continue
        } elseif {[string match 411* $dataBlock]} {
            doVerbose $dataBlock 1
            if {$mod_menu} {
                set mod_menu 0
                continue
            }
            if {[string length $remote_status] < 4} {
                set remote_status "Done."
            }
            if {$dial} {
                # $dial == 2 when dial aborted
                if {$dial == 1} {
                    .dial.close configure -state active
                    .dial.abort configure -state active
                }
                set dial 0
            } else {.confirm.close configure -state active}
        } elseif {[string match INFO:* $dataBlock]} {
            doVerbose $dataBlock 1
            if {$mod_menu} {
                set menu .menubar.server
                $menu entryconfigure Copy*Phone*Number*to*Clipboard*as*shown* -state normal
                $menu entryconfigure Copy*Phone*Number*to*Clipboard*digits*only* -state normal
                $menu entryconfigure Copy*Entire*Line*to*Clipboard*as*shown* -state normal
                set temp [split $dataBlock " "]
                set fileType [lindex $temp 1]
                if {$fileType == "dial"} {
                    set dialarg [lindex $temp 2]
                } else {set argument [lindex $temp 2]}
                switch $fileType {
                    alias {
                        set CIDaliasType [lindex $temp 2]
                        set LineAliasType [lindex $temp 3]
                        $menu entryconfigure Add*Alias* -state normal
                    }
                    black {
                        $menu entryconfigure Add*Black* -state disabled
                        $menu entryconfigure Add*White* -state normal
                        $menu entryconfigure Remove*Black* -state normal
                        $menu entryconfigure Remove*White* -state disabled
                    }
                    white {
                        $menu entryconfigure Add*Black* -state disabled
                        $menu entryconfigure Add*White* -state disabled
                        $menu entryconfigure Remove*Black* -state disabled
                        $menu entryconfigure Remove*White* -state normal
                    }
                    neither {
                        $menu entryconfigure Add*Black* -state normal
                        $menu entryconfigure Add*White* -state normal
                        $menu entryconfigure Remove*Black* -state disabled
                        $menu entryconfigure Remove*White* -state disabled
                    }
                    dial {
                        if {$dialarg == "NODIAL"} {
                            $menu entryconfigure Dial* -state disabled
                        } else {
                            $menu entryconfigure Dial* -state normal
                        }
                    }
                }
                continue;
            }
            .reply.text configure -state normal
            if {$waitMsg} {
                set waitMsg 0
                .reply.text delete 1.0 end
            }
            .reply.text insert end [string range [append dataBlock " \n"] 6 end]
            .reply.text configure -state disabled
            continue
        } elseif {[string match RESP:* $dataBlock]} {
            doVerbose $dataBlock 1
            if ($dial) {
                if {[string first "Pickup phone" $dataBlock 0] != -1} {
                    .dial.abort configure -state active
                    .dial.close configure -state active
                } else {
                    regsub {.*Server modem ([\w\d\s]+) dialed.*$} $dataBlock {\1} svrLID
                }
            }
            append remote_status [string range $dataBlock 6 end]
            append remote_status "\n"
            continue
        } elseif {[string match RPLY:* $dataBlock]} {
            doVerbose $dataBlock 1
            set remote_status [string range $dataBlock 6 end]
            append remote_status "\n"
            destroy .dial
            doRPLY
        } elseif {[string match OPT:* $dataBlock]} {
            set svropt [string trim [string range $dataBlock 5 end]]
            doVerbose $dataBlock 1
            if {[string first "LineIDS:" $svropt 0] != -1} {
                regsub {^.*LineIDS: (.*)$} $svropt {\1} LineIDS
            }
            set svrOptions "$svrOptions\n     $svropt"
            doVerbose "option added to svrOption: $svropt" 1
        }
        if {[set label [checkType $dataBlock]]} {
            if {$label == 3} {
                # CIDINFO line
                set ringinfo [getField RING $dataBlock]
                # must use $call($lineinfo) instead of $cid
                set lineinfo [getField LINE $dataBlock]
                if {[array get call $lineinfo] != {}} {
                set CIDtype [lindex $call($lineinfo) 5]
                  if {$ringinfo == -4} {
                      set displine [lindex $hup($lineinfo) 0]
                      # restore color to $CIDtype
                      if $NightMode {
                        set ctcolor #7fff7f
                    } else {
                        set ctcolor purple
                    }
                      .vh configure -state normal
                      .vh replace $displine.0 $displine.0+4c "$CIDtype:" $ctcolor
                      .vh configure -state disabled
                  } elseif {$CallOnRing && $CIDtype == "CID"} {
                    if {$Program != "" && ($Ring == $ringinfo ||
                        ($Ring == -9 && $ringinfo > 1))} {
                      sendCID $call($lineinfo)
                      doVerbose "$dataBlock" 1
                    } else { doVerbose "$dataBlock" 5 }
                  }
                } else {
                    doVerbose "Phone line label \"$lineinfo\" not found" 1
                }
                if {$WakeUp && $ringinfo == 1} {
                    doWakeup
                    set wakened 1
                }
            } elseif {$label == 4 || $label == 5} {
                # MSG (4), NOT (4)
                # MSGLOG (5), NOTLOG (5)
                set msg [formatMSG $dataBlock]
                displayLog $msg 1
                if {$label == 4} {
                    if {!$NoGUI} {
                        displayCID "[lindex $msg 7]\n" 1
                        doPopup
                    }
                    if {$Program != ""} {
                        sendMSG $msg
                    }
                }
            } elseif {$label == 1 || $label == 2} {
                # CID (1), HUP (1) OUT (1), RID (1)
                # BLK (2), MWI (2), PID (2), WID (2)
                if {$WakeUp} {
                    if {!$wakened} {
                        doWakeup
                    } else {set wakened 0}
                }
                set cid [formatCID $dataBlock]
                if {$label == 1} {array set call "{$lineID} [list $cid]"}
                # display log
                displayLog $cid 0
                # display CID
                if {!$NoGUI} {
                    displayCID $cid 0
                    doPopup
                }
                set CIDtype [lindex $cid 5]
                if {(!$CallOnRing  || $CIDtype == "CID" || $Ring == -9) && $Program != ""} {
                    sendCID $cid
                }
            } elseif {$label == 6} {
                # BLKLOG, CIDLOG, HUPLOG, MWILOG, OUTLOG, PIDLOG, RIDLOG, WIDLOG
                set cid [formatCID $dataBlock]
                # display log
                displayLog $cid 0
                if {!$NoGUI && $targetTime && [clock clicks -milliseconds] >= $targetTime} {
                    set targetTime [expr [clock clicks -milliseconds] + 500]
                    .vh insert 3.end "."
                    update idletasks
                }
            }
        }
    }
}

proc showBusy {text widget} {
    global waitMsg
    
    $widget configure -state normal
    $widget insert end $text
    $widget configure -state disabled
    set waitMsg 1
}

proc doWakeup {} {
    global ExecSh
    global ModDir

    if $ExecSh {
        catch {exec sh -c $ModDir/ncid-wakeup} oops
    } else {
        catch {exec $ModDir/ncid-wakeup} oops
    }
}

#   PopupTime = 0:   doPopup is disabled
#   PopupTime = 1-5: Time in seconds window is forced to remain
#                    on top before user is allowed to remove it.
proc doPopup {} {
    global PopupTime

    if {! $PopupTime} { return }

    wm deiconify .
    raise .
    wm attributes . -topmost true
    after [expr $PopupTime*1000] wm attributes . -topmost false
}

proc checkType {dataBlock} {
    set rtn 0
    # Determine label type
    # General classifications:
    #  1 = real time: calls that can trigger WakeUp - CID, HUP, OUT, RID
    #  2 = real time: other calls - BLK, MWI, PID, WID
    #  3 = real time: ring detected - CIDINFO
    #  4 = real time: messages (non-calls)
    #  5 = log file : messages (non-calls)
    #  6 = log file : calls classified as 1 and 2 with suffix LOG
    #  7 = log file : unrecognized line type
    #  8 = real time: relay job - RLY
    #  9 = log file : relay job - RLYLOG
    # 10 = real time: call accounting - END
    # 11 = log file : call accounting - ENDLOG
          if [string match CID:* $dataBlock] {set rtn 1
    } elseif [string match HUP:* $dataBlock] {set rtn 1
    } elseif [string match OUT:* $dataBlock] {set rtn 1
    } elseif [string match RID:* $dataBlock] {set rtn 1
    
    } elseif [string match BLK:* $dataBlock] {set rtn 2
    } elseif [string match MWI:* $dataBlock] {set rtn 2
    } elseif [string match PID:* $dataBlock] {set rtn 2
    } elseif [string match WID:* $dataBlock] {set rtn 2
    
    } elseif [string match CIDINFO:* $dataBlock] {set rtn 3
    
    } elseif [string match MSG:* $dataBlock] {set rtn 4
    } elseif [string match NOT:* $dataBlock] {set rtn 4
    
    } elseif [string match RLY:* $dataBlock] {set rtn 8
    
    } elseif [string match END:* $dataBlock] {set rtn 10
    
    } elseif [string match MSGLOG:* $dataBlock] {set rtn 5
    } elseif [string match NOTLOG:* $dataBlock] {set rtn 5

    } elseif [string match BLKLOG:* $dataBlock] {set rtn 6
    } elseif [string match CIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match HUPLOG:* $dataBlock] {set rtn 6
    } elseif [string match MWILOG:* $dataBlock] {set rtn 6
    } elseif [string match OUTLOG:* $dataBlock] {set rtn 6
    } elseif [string match PIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match RIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match WIDLOG:* $dataBlock] {set rtn 6
    
    } elseif [string match RLYLOG:* $dataBlock] {set rtn 9
    
    } elseif [string match ENDLOG:* $dataBlock] {set rtn 11
    
    } elseif [string match LOG:* $dataBlock] {set rtn 7}
    doVerbose "Assigned type $rtn for $dataBlock" 5
    return $rtn
}

# must be sure the line passed checkType
# returns: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
proc formatCID {dataBlock} {
    global lineID lineIDWidth

    set cidname [formatNAME $dataBlock]
    set cidnumber [formatNMBR $dataBlock]
    set ciddate [formatDATE $dataBlock]
    set cidtime [formatTIME $dataBlock]
    set cidline ""
    if [string match {*\*LINE\**} $dataBlock] {
        set cidline [formatLINE $dataBlock]
    }
    # set default line indicator, should not be needed anymore
    if {$cidline == ""} {
        set cidline "-"
        for {set x 0} {$x < $lineIDWidth} {incr x} {
            set cidline "$cidline "
        }
    }
    # create call line label
    regsub { *$} $cidline {} lineID
    # set type of call
    if {![regsub {(\w+)LOG:.*} $dataBlock {\1} linetype]} {
        regsub {(\w+):.*} $dataBlock {\1} linetype
    }

    return [list $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""]
}

# returns: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $mesgtype $message
proc formatMSG {dataBlock} {

    if {![regsub {(\w+)LOG:.*} $dataBlock {\1} linetype]} {
        regsub {(\w+):.*} $dataBlock {\1} linetype
    }

    if {[regexp {\*\*\*DATE} $dataBlock]} {
        set msgdate [formatDATE $dataBlock]
        set msgtime [formatTIME $dataBlock]
        set msgname [formatNAME $dataBlock]
        set msgnmbr [formatNMBR $dataBlock]
        set msgline [formatLINE $dataBlock]
        set msgtype [formatMTYPE $dataBlock]
        regsub {\w+: (.*)\*\*\*DATE.*} $dataBlock {\1\2} mesg
        set message [list $msgdate $msgtime $msgnmbr $msgname $msgline $linetype $msgtype $mesg]
    } else {
        regsub {\w+: (.*)} $dataBlock {\1} mesg
        set message [list {} {} {} {} {} $linetype {} $mesg]
    }

    return $message
}

proc formatMTYPE {dataBlock} {
    if {[regexp {\*\*\*DATE.*MTYPE} $dataBlock]} {
        set msgtype [getField MTYPE $dataBlock]
    } else {
        set msgtype "-"
    }
    return $msgtype
}

proc formatLINE {dataBlock} {
    set cidline [getField LINE $dataBlock]
    return $cidline
}

proc formatDATE {dataBlock} {
    global AltDate DateSepar YearDot

    set ciddate [getField DATE $dataBlock]
    # slash (/) is the default date separator
    if {$AltDate} {
        # Date format: DDMMYY or DDMM
        if {![regsub {([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])} \
            $ciddate {\2/\1/\3} ciddate]} {
            regsub {([0-9][0-9])([0-9][0-9].*)} $ciddate {\2/\1} ciddate
        }
    } else {
        # Date format: MMDDYY or MMDD
        if {![regsub {([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])} \
            $ciddate {\1/\2/\3} ciddate]} {
            regsub {([0-9][0-9])([0-9][0-9].*)} $ciddate {\1/\2} ciddate
        }
    }
    if {$DateSepar == "-"} {
        # set hyphen (-) as date separator
        regsub -all {/} $ciddate - ciddate
    } elseif {$DateSepar == "."} {
        if $YearDot {
          # set period (.) as date separator and append it to year (ordinal numbers)
          regsub -all {/} $ciddate. . ciddate
        } else {
          # set period (.) as date separator
          regsub -all {/} $ciddate . ciddate
        }
    }
    return $ciddate
}

proc formatTIME {dataBlock} {
    global clock

    set cidtime [getField TIME $dataBlock]
    if ([regexp {(\d{2})(\d{2})} $cidtime time hours minutes]) {
        if {$clock == 24} {
            set cidtime "$hours:$minutes"
        } else {
        set cidtime [convertTo12 $hours $minutes]
        }
    }
    return $cidtime
}

proc formatNAME {dataBlock} {
    set cidname [getField NAME $dataBlock]
    regsub {\*$} $cidname {} cidname
    if {$cidname == "-"} {set cidname "NO NAME"}
    return $cidname
}

proc checkCountry {code} {
    global countryCodes NoGUI

    if {![regexp "$code" $countryCodes]} {
      if {!$NoGUI} {
        doVerbose "Country Code \"$code\" is not supported.\nShould be one of \"$countryCodes\"." 1
     }
     exitMsg 7 "Country Code \"$code\" is not supported.\nShould be one of \"$countryCodes\"."
    }
}

proc formatNMBR {dataBlock} {
    global Country NoOne
    # https://en.wikipedia.org/wiki/National_conventions_for_writing_telephone_numbers

    set cidnumber [getField NMBR $dataBlock]
    if {$cidnumber == "-"} {set cidnumber "NO-NUMBER"}
    switch $Country {
      DE {set cidnumber [formatDE $cidnumber]}
      FR {set cidnumber [formatFR $cidnumber]}
      HR {set cidnumber [formatHR $cidnumber]}
      SE {set cidnumber [formatSE $cidnumber]}
      UK {set cidnumber [formatUK $cidnumber]}
      US {set cidnumber [formatUS $cidnumber]}
      NONE {}
      DEFAULT {
        exitMsg 7 "Country Code \"$Country\" is not supported. Please change it."
      }
    }
    return $cidnumber
}

proc formatUS {cidnumber} {
    global NoOne
    # https://en.wikipedia.org/wiki/North_American_Numbering_Plan
    if {![regsub {(^1)([0-9]+)([0-9]{3})([0-9]{4})} \
      $cidnumber {\1-\2-\3-\4} cidnumber]} {
      if {![regsub {(ob)([0-9]{3})([0-9]{3})([0-9]{3})} \
        $cidnumber {\1-\2-\3-\4} cidnumber]} {
        if {![regsub {^([0-9]{3})([0-9]{3})([0-9]{3})$} \
          $cidnumber {\1-\2-\3} cidnumber]} {
          if {![regsub {([0-9]+)([0-9]{3})([0-9]{4})} \
            $cidnumber {\1-\2-\3} cidnumber]} {
            regsub {([0-9]{3})([0-9]{4})} \
            $cidnumber {\1-\2} cidnumber
          }
        }
      }
    } elseif {$NoOne} {
        regsub {^1-?(.*)} $cidnumber {\1} cidnumber
    }
    return $cidnumber
}

proc formatUK {cidnumber} {
    # https://en.wikipedia.org/wiki/United_Kingdom_area_codes
    if {![regsub {^(011[0-9])([0-9]{3})([0-9]+)} \
      $cidnumber {\1-\2-\3} cidnumber]} {
      if {![regsub {^(01[0-9]1)([0-9]{3})([0-9]+)} \
        $cidnumber {\1-\2-\3} cidnumber]} {
        if {![regsub {^(13873|15242|19467)([0-9]{4,5})} \
          $cidnumber {\1-\2} cidnumber]} {
          if {![regsub {^(153)(94|95|96)([0-9]{4,5})} \
            $cidnumber {\1\2-\3} cidnumber]} {
            if {![regsub {^(169)(73|74|77)([0-9]{4,5})} \
              $cidnumber {\1\2-\3} cidnumber]} {
              if {![regsub {^(176)(83|84|87)([0-9]{4,5})} \
                $cidnumber {\1\2-\3} cidnumber]} {
                if {![regsub {^(01[0-9]{3})([0-9]+)} \
                  $cidnumber {\1-\2} cidnumber]} {
                  if {![regsub {^(02[0-9])([0-9]{4})([0-9]+)} \
                    $cidnumber {\1-\2-\3} cidnumber]} {
                    if {![regsub {^(0[389][0-9]{2})([0-9]{3})([0-9]+)} \
                      $cidnumber {\1-\2-\3} cidnumber]} {
                      if {![regsub {^(07[0-9]{3})([0-9]+)} \
                        $cidnumber {\1-\2} cidnumber]} {
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return $cidnumber
}

proc formatSE {cidnumber} {
    # https://en.wikipedia.org/wiki/Telephone_numbers_in_Sweden#Area_codes
    if {![regsub {^(07[0-9])([0-9]+)} \
        $cidnumber {\1-\2} cidnumber]} {
     if {![regsub {^(08)([0-9]+)} \
         $cidnumber {\1-\2} cidnumber]} {
      if {![regsub {^(01[013689])([0-9]+)} \
          $cidnumber {\1-\2} cidnumber]} {
       if {![regsub {^(0[23][[136])([0-9]+)} \
           $cidnumber {\1-\2} cidnumber]} {
        if {![regsub {^(04[0246])([0-9]+)} \
            $cidnumber {\1-\2} cidnumber]} {
         if {![regsub {^(054)([0-9]+)} \
             $cidnumber {\1-\2} cidnumber]} {
          if {![regsub {^(06[02])([0-9]+)} \
              $cidnumber {\1-\2} cidnumber]} {
           if {![regsub {^(090)([0-9]+)} \
               $cidnumber {\1-\2} cidnumber]} {
            regsub {^([0-9]{4})([0-9]+)} \
                    $cidnumber {\1-\2} cidnumber
           }
          }
         }
        }
       }
      }
     }
    }
    return $cidnumber
}

proc formatDE {cidnumber} {
  # https://en.wikipedia.org/wiki/Area_codes_in_Germany
    
  # format for numbers was broken, removed until it can be done correctly
  return $cidnumber
}

proc formatHR {cidnumber} {
    # https://en.wikipedia.org/wiki/Telephone_numbers_in_Croatia
    if {![regsub {^(01)([0-9]+)} \
      $cidnumber {\1-\2} cidnumber]} {
      if {![regsub {^(02[0123])([0-9]+)} \
        $cidnumber {\1-\2} cidnumber]} {
        if {![regsub {^(03[12345])([0-9]+)} \
          $cidnumber {\1-\2} cidnumber]} {
          if {![regsub {^(04[0234789])([0-9]+)} \
            $cidnumber {\1-\2} cidnumber]} {
            if {![regsub {^(05[123])([0-9]+)} \
              $cidnumber {\1-\2} cidnumber]} {
              if {![regsub {^(09[125789])([0-9]+)} \
                $cidnumber {\1-\2} cidnumber]} {
              }
            }
          }
        }
      }
    }
    return $cidnumber
}

proc formatFR {cidnumber} {
    # http://en.wikipedia.org/wiki/Telephone_numbers_in_France
    set nmbrWidth 20
    #French national calls 
    if {![regsub {^(0[1-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])} \
      $cidnumber {\1 \2 \3 \4 \5} cidnumber]} {
	#international calls (prefix 1* ,2 )
	#formats prefix but doesn't format local number 
      if {![regsub {^(00)(1)([1-9]+)} \
        $cidnumber {(+1) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(2[078])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(2[1234569][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(3[012469])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(3[578][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
	#telemarketing calls with France international prefix 
	#formats prefix and formats local number to french standard 
      if {![regsub {^(00)(33)([1-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9]+)} \
        $cidnumber {(+33) \3 \4 \5 \6 \7} cidnumber]} {
      }
	#other international calls (prefix 3*,4*,5*,6*,7*,8*,9*)
	#formats prefix but doesn't format local number 
      if {![regsub {^(00)(4[013456789])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(4[2][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(5[1345678])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(5[09][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(6[013456])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(6[789][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(7)([1-9]+)} \
        $cidnumber {(+7) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(8[123469])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(8[0578][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(9[0123458])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(9[679][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
    }
    return $cidnumber
}

proc convertTo12 {hours minutes} {
    set AmPm "am"
    if {$hours > 12} {
        set hours [expr $hours - 12]
        set AmPm "pm"
    } elseif {$hours == 12} {
        set AmPm "pm"
    } elseif {$hours == 0} {
        set hours 12
    }
    regsub {^(0|\s|)?(\d)$} $hours { \2} hours
    return "$hours:$minutes $AmPm"
}

proc convertTo24 {hours minutes AmPm} {
    if {$hours == 12 && $AmPm eq "am"} {
        set hours 0
    } elseif {$hours != 12 && $AmPm eq "pm"} {
        set hours [expr $hours + 12]
    }
    regsub {^(0|\s|)?(\d)$} $hours {0\2} hours
    return "$hours:$minutes"
}

# extract field pair where 'dataString' is the field label (NAME, NMBR, etc.)
# and $result is the field data
proc getField {dataString dataBlock} {
    regsub ".*\\*$dataString\\*" $dataBlock {} result
    regsub {(\*?\??[@_!\w\s-]+)\*.*} $result {\1} result
    return $result
}

# send the CID information to an external program
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $cidtype "" ""
proc sendCID {cid} {
  global Program
  global ExecSh
  global ModDir
  global WakeUp

  set modcid "$cid"
  # send DATE\nTIME\nNUMBER\nNAME\nLINE\nTYPE\nMESG\nMTYPE\n
  set modtype "using a module"
  set modin "[lindex $cid 0]\n[lindex $cid 1]\n[lindex $cid 2]\n[lindex $cid 3]\n[lindex $cid 4]\n[lindex $cid 5]\n[lindex $cid 6]\n[lindex $cid 7]"
  if $ExecSh {
    catch {exec sh -c $Program << "$modin" >@stdout &} oops
  } else {
    catch {exec $Program << "$modin" >@stdout &} oops
  }
  doVerbose "$modtype\nSent $Program $modcid" 1
}

# pass the message to an external program
# input: $msgdate $msgtime $msgnumber $msgname $msgline $msgtype $mtype $msg
proc sendMSG {msg} {
  global Program
  global ExecSh
  global preClient_1_0

  set mesg "$msg"
  if $preClient_1_0 {
    # send "\n\n\nMESG\n\nTYPE\n"
    set modtype "using a preClient 1.0 module for a message"
    set modin "[lindex $msg 0]\n[lindex $msg 1]\n[lindex $msg 2]\n[lindex $msg 6]\n[lindex $msg 4]\n[lindex $msg 5]\n[lindex $msg 3]\n"
    set mesg [lreplace $mesg 3 3 [lindex $msg 7]]
    set mesg [lreplace $mesg 7 7 [lindex $msg 3]]
    if $ExecSh {
      catch {exec sh -c $Program << "$modin" >@stdout &} oops
    } else {
      catch {exec $Program << "$modin" >@stdout &} oops
    }
  } else {
    # send "DATE\nTIME\nNMBR\nNAME\nLINE\nTYPE\n\MESG\nMTYPE\n"
    set modtype "using a Client 1.0 type module for a message"
    set modin "[lindex $msg 0]\n[lindex $msg 1]\n[lindex $msg 2]\n[lindex $msg 3]\n[lindex $msg 4]\n[lindex $msg 5]\n[lindex $msg 7]\n[lindex $msg 6]\n"
    set mesg "[list [lindex $msg 0]\n[lindex $msg 1]\n[lindex $msg 2]\n[lindex $msg 3]\n[lindex $msg 4]\n[lindex $msg 5]\n[lindex $msg 7]\n[lindex $msg 6]]\n"
    if $ExecSh {
      catch {exec sh -c $Program << "$modin" >@stdout &} oops
    } else {
      catch {exec $Program << "$modin" >@stdout &} oops
    }
  }
  doVerbose "$modtype\nSent $Program $mesg" 1
}

# display CID information or message
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $type "" ""
#        $msgdate $msgtime $msgnumber $msgname $msgline $type mesage msgio
# ismsg = 0 for CID and 1 for message
proc displayCID {input ismsg} {
    global Txt

    if {$ismsg} {
        set Txt $input
    } else {
        set Txt "[lindex $input 3]\n[lindex $input 2]"
    }
}

# display Call Log
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
# Input: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $msgtype message
proc displayLog {input ismsg} {
    global Program
    global NoGUI
    global display_line_num doingLog
    global nmbrWidth nameWidth lineIDWidth mtypeWidth
    global NightMode hup label

    if $NoGUI {
        if {$Program == ""} {
            if $ismsg {
                if {[lindex $input 1] eq {}} {
                    puts "[lindex $input 6]: [lindex $input 7]"
                } else {
                    puts "[lindex $input 5]: [lindex $input 0]  [lindex $input 1] [lindex $input 4] [lindex $input 2] [lindex $input 3] [lindex $input 6] [lindex $input 7]"
                }
            } else {
                puts "[lindex $input 5]: [lindex $input 0]  [lindex $input 1] [lindex $input 4] [lindex $input 2] [lindex $input 3]"
            }
        }
        incr display_line_num
    } else {
        incr display_line_num
        if {! $doingLog} {.vh configure -state normal}
        if {[lindex $input 1] eq {}} {
          if $NightMode {
            .vh insert end "\n[lindex $input 5]: " #7fff7f [lindex $input 7] black
          } else {
            .vh insert end "\n[lindex $input 5]: " purple [lindex $input 7] black
          }
        } else {
            set ciddate [lindex $input 0]
            set cidtime [lindex $input 1]
            set cidnmbr [format "%${nmbrWidth}.${nmbrWidth}s" [lindex $input 2]]
            set cidname [format "%-${nameWidth}.${nameWidth}s" [lindex $input 3]]
            set cidline [format "%-${lineIDWidth}.${lineIDWidth}s" \
                         [lindex $input 4]]
            set linetype [lindex $input 5]

            if {$label != 6 && $linetype == "HUP"} {
                # set temperory color to $linetype

                # hup array used to restore normal color to $linetype
                array set hup "{$cidline} $display_line_num"

                if $NightMode {
                    set ctcolor white
                } else {
                    set ctcolor black
                }
            } else {
                # set normal color to $linetype
                if $NightMode {
                    set ctcolor #7fff7f
                } else {
                    set ctcolor purple
                }
            }

            if $NightMode {
                .vh insert end "\n$linetype: " $ctcolor "$ciddate " \
                yellow "$cidtime " cyan "$cidline " #7fff7f "$cidnmbr " yellow \
                            "$cidname " cyan
            } else {
                .vh insert end "\n$linetype: " $ctcolor "$ciddate " \
                blue "$cidtime " red "$cidline " purple "$cidnmbr " blue \
                            "$cidname " red
            }

            if $ismsg {
                set msgtype [format "%-${mtypeWidth}.${mtypeWidth}s" \
                             [lindex $input 6]]
                set message [lindex $input 7]
                if $NightMode {
                    .vh insert end "$msgtype " yellow $message black
                } else {
                    .vh insert end "$msgtype " blue $message black
                }
            }
        }
        if {! $doingLog} {
            if {$display_line_num == 1} {
                .vh delete 1.0 2.0
            }
            .vh yview moveto 1.0
            .vh configure -state disabled
            if {[lindex [.vh yview] 0] + [lindex [.vh yview] 1] == 1.0} {
                grid .ys
            }
        }
    }
}

# Open a connection to the NCID server
proc connectCID {} {
    global Host Port
    global Try Delay
    global Socket menuDisabled
    global NoGUI
    global VersionInfo VersionIDENT HostnameFlag hostname
    global Program dtfile 
    global CallLog

    set svrOptions ""
    set Socket 0

    if {!$NoGUI} {
      if {$::tcl_platform(platform) == "unix"} {
        set menu .menubar.file

        # enable or disable autostart menu
        if ![file isfile $dtfile] {
          $menu entryconfigure Auto*Start -state disabled
        } else {
          $menu entryconfigure Auto*Start -state normal
        }
      }
    }

    set menu .menubar.server

    while (1) {
        # open socket to server
        if {[catch {set Socket [socket $Host $Port]} msg]} {
            if {!$NoGUI} {
                if {$menuDisabled == 0} {
                    $menu entryconfigure Reload* -state disabled
                    $menu entryconfigure Update*current* -state disabled
                    $menu entryconfigure Update*all*call* -state disabled
                    $menu entryconfigure Reread* -state disabled
                    set menuDisabled 1

                  # a delay of 1 second causes Reconnect to break ncid
                  if {$Delay == 1} {
                    .menubar.file entryconfigure Reconnect* -state disabled
                  }
                }
            }
            set Try [expr $Try + 1]
            tryCount "$Host:$Port - $msg\n"
        } else {
            # set socket to non-blocking
            fconfigure $Socket -blocking 0 
            # get response from server as an event
            fileevent $Socket readable getCID

            if {!$NoGUI && $menuDisabled} {
                $menu entryconfigure Reload* -state normal
                $menu entryconfigure Update*current* -state normal
                $menu entryconfigure Update*all*call* -state normal
                $menu entryconfigure Reread* -state normal

              if {$Delay == 1} {
                .menubar.file entryconfigure Reconnect* -state normal
              }
            }
            # always make sure it is 0 on connect, even when not in GUI mode
            set menuDisabled 0

            puts $Socket "HELLO: IDENT: $VersionIDENT"
            flush $Socket
            doVerbose "HELLO: IDENT: $VersionIDENT" 1
            if $NoGUI {
                puts "Connected to $Host:$Port"
                if $CallLog {
                    # tell server to send call log
                    puts $Socket "HELLO: CMD: log"
                    flush $Socket
                    doVerbose "Sent: HELLO: CMD: log" 1
                } else {
                    # tell server to not send call log
                    puts $Socket "HELLO: CMD: no_log"
                    flush $Socket
                    doVerbose "Sent: HELLO: CMD: no_log" 1
                }
            } else {
                clearLog
                displayCID "Connected to\n$Host:$Port" 1
            }
        break
        }
    }
}

proc getArg {} {
    global argc
    global argv
    global Host
    global Port
    global Delay
    global Usage
    global NoGUI
    global Verbose
    global Program
    global Ring
    global CallOnRing
    global ModDir
    global HostnameFlag
    global PIDfile
    global PopupTime
    global NoExit
    global AltDate
    global WakeUp
    global Version
    global WrapLines
    global CallLog
    global NightMode Country

    set showUsage 0;
    for {set cnt 0} {$cnt < $argc} {incr cnt} {
        set optarg [lindex $argv [expr $cnt + 1]]
        switch -regexp -- [set opt [lindex $argv $cnt]] {
            {^-r$} -
            {^--ring$} {
                incr cnt
                if {$optarg != ""
                    && [regexp {^-[129]$} $optarg]
                    || [regexp {^[0123456789]$} $optarg]} {
                    set Ring $optarg
                    set CallOnRing 1
                } else {exitMsg 4 "Invalid $opt argument: $optarg\n$Usage\n"}
            }
            {^--no-gui$} {set NoGUI 1}
            {^--night-mode$} {set NightMode 1}
            {^-A$} -
            {^--alt-date$} {set AltDate 1}
            {^-c$} -
            {^--call-log$} {set CallLog 1}
            {^-C$} -
            {^--country-code$} {
                incr cnt
                if {$optarg == ""} {
                    exitMsg 4 "Invalid $opt argument: $optarg\n$Usage\n"}
                set Country $optarg
            }
            {^-D$} -
            {^--delay$} {
                incr cnt
                if {$optarg != ""
                    && [regexp {^[0-9]+$} $optarg]} {
                    set Delay $optarg
                } else {exitMsg 4 "Invalid $opt argument: $optarg\n$Usage\n"}
            }
            {^-h$} -
            {^--help$} {set showUsage 1; set NoGUI 1}
            {^-H$} -
            {^--hostname-flag$} {set HostnameFlag 1}
            {^-M$} -
            {^--message$} {set MsgFlag 1; # obsolete, can be removed}
            {^-P$} -
            {^--program$} {
                incr cnt
                if {$optarg != ""} {
                    if {[regexp {^.*/} $optarg]} {
                        set Program [list $optarg]
                    } else {set Program [list $ModDir/$optarg]}
                } else {exitMsg 6 "Missing $opt argument\n$Usage\n"}
            }
            {^-p$} -
            {^--pidfile$} {
                incr cnt
                set PIDfile $optarg
            }
            {^-t$} -
            {^--PopupTime$} {
                incr cnt
                if {$optarg != ""
                    && [regexp {^[0-5]$} $optarg]} {
                    set PopupTime $optarg
                } else {exitMsg 4 "Invalid $opt argument: $optarg\n$Usage\n"}
            }
            {^-v$} -
            {^--verbose$} {
                incr cnt
                if {$optarg != ""
                    && [regexp {^[1-9]+$} $optarg]} {
                    set Verbose $optarg
                } else {exitMsg 4 "Invalid $opt argument: $optarg\n$Usage\n"}
            }
            {^-V$} -
            {^--version$} {
                puts "ncid (NCID) $Version"
                exit 0
            }
            {^-X} -
            {^--noexit} {set NoExit 1}
            {^-W$} -
            {^--wakeup$} {set WakeUp 1}
            {^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$} {set Host $opt}
            {^[A-Za-z]+[.A-Za-z0-9-]+$} {set Host $opt}
            {^[0-9]+$} {set Port $opt}
            default {exitMsg 5 "Unknown option: $opt\n$Usage\n"}
        }
    }
    if {$showUsage} {
        exitMsg 1 "$Usage\n"
    }
}

proc do_nothing {} {
}

proc do_goodbye {} {
global Socket

if {$Socket > 0} {puts $Socket "GOODBYE"}
}

proc makeWindow {} {
    global ExitOn
    global env
    global rcfile Verbose WrapLines asfile
    global fontList m
    global clock oldClock autoSave oldAutoSave autoStart oldAutoStart
    global AltDate oldAltDate DateSepar oldDateSepar YearDot
    global nameREQ nmbrREQ lineREQ historyTextWidth timeWidth lbl1 lbl2 lbl3 lbl4 lbl5 lbl6 lbl7 lbl8
    global NoGUI
    global NightMode oldNightMode autoStart oldAutoStart

    doVerbose "Platform: $::tcl_platform(platform)\nOS: $::tcl_platform(os)" 1
    switch $::tcl_platform(platform) {
      "unix" {
        set rcfile "$env(HOME)/.ncid"
        set asfile "$env(HOME)/.config/autostart/ncid.desktop"
        set Day "Day"
        set Night "Night"
      }
      "windows" {
        #set rcfile [file join $env(AppData) "ncid.dat"]
        set rcfile ".ncid"
        set Day "Day - must restart"
        set Night "Night - must restart"
      }
    }
    doVerbose "RC File: $rcfile" 1

    if [expr [file exists $rcfile] && [file isfile $rcfile]] {
        set id [open $rcfile]
        set data [read $id]
        close $id
        set lines [split $data "\n"]
        foreach line $lines {
            if [regexp {geometry\s+\S+\s+[0-9x]+} $line] {
                eval $line
            } elseif [regexp {font\s+create} $line] {
                eval $line
            } elseif [regexp {(:?fontList|clock|AltDate|DateSepar|NightMode|autoSave|autoStart)\s+} $line] {
                eval $line
            }
        }
    }
    set oldClock $clock
    set oldAltDate $AltDate
    set oldDateSepar $DateSepar
    set oldNightMode $NightMode
    set oldAutoSave $autoSave
    set oldAutoStart $autoStart
    set auto [expr \"$autoSave\" eq \"off\" ? \"normal\" : \"disabled\"]
    if {![info exists fontList]} {
        scanFonts
    }
    if {[catch {font configure FixedFontH}]} {
        if {[catch {font configure currentFontH}]} {
            set currentFont [lindex $fontList 0]
        }
        font create FixedFontH -family "$currentFont" -size 12
        font create FixedFontM -family "$currentFont" -size 12
        write_rc_file "FixedFontH" \
                "font create FixedFontH [font configure FixedFontH]"
        write_rc_file "FixedFontM" \
                "font create FixedFontM [font configure FixedFontM]"
    }
    if {[catch {font configure FixedFontP}]} {
        set currentFont [font configure FixedFontH -family]
        font create FixedFontP -family "$currentFont" -size 12
        write_rc_file "FixedFontP" \
                "font create FixedFontP [font configure FixedFontP]"
    }

    wm title . "Network Caller ID"
    wm protocol . WM_DELETE_WINDOW $ExitOn

    ttk::style configure TButton -font FixedFontP
    ttk::style configure TCheckbutton -font FixedFontP
    ttk::style configure TRadiobutton -font FixedFontP

    # ttk Widgets: http://wiki.tcl-lang.org/14796
    # Styles and themes: http://www.tkdocs.com/tutorial/styles.html#using
    # Changing ttk Widget Colors: https://wiki.tcl.tk/37973
    #   A collection of all the information on setting the
    #   colors of modern widgets in one place.
    if $NightMode {
      if {$::tcl_platform(platform) == "unix"} {
        ttk::style configure TButton \
                   -background #262626 \
                   -foreground white
        ttk::style map TButton \
                   -background [list active #215d9c]
      }
        ttk::style configure TFrame \
                   -background #262626
        ttk::style configure TLabel \
                   -background #262626 \
                   -foreground white
        ttk::style configure TLabelframe \
                   -background #262626 \
                   -foreground white
        ttk::style configure TSpinbox \
                   -arrowsize 15 \
                   -arrowcolor white \
                   -background #262626 \
                   -foreground white \
                   -selectbackground #215d9c
        ttk::style map TSpinbox \
                   -background [list active #215d9c] \
                   -fieldbackground [list active #215d9c readonly #0f0f00] \
                   -relief [list {pressed !disabled} sunken]
        ttk::style configure TRadiobutton \
                   -indicatorcolor black \
                   -background #262626 \
                   -foreground white
        ttk::style map TRadiobutton \
                   -indicatorcolor [list selected white pressed white] \
                   -background [list active #215d9c]
        ttk::style configure TCheckbutton \
                   -indicatorcolor black \
                   -background #262626 \
                   -foreground white
        ttk::style map TCheckbutton \
                   -indicatorcolor [list selected white pressed white] \
                   -background [list active #215d9c]
        ttk::style configure TScrollbar \
                   -arrowsize 15 \
                   -arrowcolor white \
                   -background #262626 \
                   -troughcolor #3c3c3c
        ttk::style map TScrollbar \
                   -background [list active #215d9c] \
                   -relief [list {pressed !disabled} sunken]
        ttk::style configure TCombobox \
                   -arrowsize 15 \
                   -arrowcolor white \
                   -background #262626 \
                   -foreground white \
                   -fieldbackground #0f0f00 \
                   -selectbackground #215d9c
        ttk::style map TCombobox \
                   -background [list active #215d9c] \
                   -relief [list {pressed !disabled} sunken]
        option add *background #262626
        option add *highlightBackground #262626
        option add *Text.background #0f0f00
        option add *foreground white
        option add *activeBackground #215d9c
        option add *activeForeground white
        option add *selectBackground #215d9c
        option add *selectForeground white
        option add *inactiveSelectBackground #215d9c
        . configure -background #262626
        set menurb "white"
    } else {
      if {$::tcl_platform(platform) == "unix"} {
        ttk::style configure TButton \
                   -background #d9d9d9 \
                   -foreground black
        ttk::style map TButton \
                   -background [list active #4a90d9] \
                   -foreground [list active white]
      }
        ttk::style configure TFrame \
                   -background #d9d9d9
        ttk::style configure TLabel \
                   -background #d9d9d9 -foreground black
        ttk::style configure TLabelframe \
                   -background #d9d9d9 -foreground black
        ttk::style configure TSpinbox \
                   -arrowsize 15 \
                   -selectbackground #4a90d9
        ttk::style map TSpinbox \
                   -arrowcolor [list active white] \
                   -background [list active #4a90d9] \
                   -fieldbackground [list active #4a90d9 readonly #f0f0ff] \
                   -relief [list {pressed !disabled} sunken]
        ttk::style configure TRadiobutton \
                   -indicatorcolor #d9d9d9
        ttk::style map TRadiobutton \
                   -indicatorcolor [list selected black pressed black] \
                   -background [list active #4a90d9] \
                   -foreground [list active white]
        ttk::style configure TCheckbutton \
                   -indicatorcolor #d9d9d9
        ttk::style map TCheckbutton \
                   -indicatorcolor [list selected black pressed black]
        ttk::style configure TCheckbutton \
                   -background #d9d9d9 \
                   -foreground black
        ttk::style map TCheckbutton \
                   -background [list active #4a90d9] \
                   -foreground [list active white]
        ttk::style configure TScrollbar \
                   -arrowsize 15
        ttk::style map TScrollbar \
                   -arrowcolor [list active white] \
                   -background [list active #4a90d9] \
                   -relief [list {pressed !disabled} sunken]
        ttk::style configure TCombobox \
                   -arrowsize 15 \
                   -fieldbackground #f0f0ff \
                   -selectbackground #4a90d9
        ttk::style map TCombobox \
                  -arrowcolor [list active white] \
                  -background [list active #4a90d9] \
                  -relief [list {pressed !disabled} sunken]
        option add *background #d9d9d9
        option add *highlightBackground #d9d9d9
        option add *Text.background #f0f0ff
        option add *activeBackground #4a90d9
        option add *activeForeground white
        option add *selectBackground #4a90d9
        option add *selectForeground white
        option add *inactiveSelectBackground cyan
        . configure -background #d9d9d9
        set menurb "black"
    }
    # menu options: no tearoff and help menu on far right
    option add *tearOff 0
    option add *Menu.useMotifHelp 1
    option add *Text.relief sunken
    option add *Text.borderWidth 2
    option add *highlightThickness 1

    # create menubar
    menu .menubar
    . configure -menu .menubar

    # create and place: column labels
    if {$clock == 24 && $DateSepar == "."} {set lbl $lbl1}
    if {$clock == 24 && $DateSepar == "-"} {set lbl $lbl2}
    if {$clock == 24 && $DateSepar == "/"} {set lbl $lbl3}
    if {$clock == 12 && $DateSepar == "."} {set lbl $lbl4}
    if {$clock == 12 && $DateSepar == "-"} {set lbl $lbl5}
    if {$clock == 12 && $DateSepar == "/"} {set lbl $lbl6}
    if {$clock == 24 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl7}
    if {$clock == 12 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl8}
    if {$NightMode} { set fgcolor "yellow" } else { set fgcolor "blue" }
    ttk::label .la -text $lbl -justify left -font {FixedFontH} -foreground $fgcolor
    grid .la -row 1 -sticky w -columnspan 2

    # create File, Preferences and Help menus
    set m .menubar
    menu $m.file
    menu $m.file.auto
    menu $m.file.startup
    menu $m.server
    menu $m.prefs
    menu $m.theme
    menu $m.help
    $m add cascade -menu $m.file -label File -underline 0 -font FixedFontM
    $m add cascade -menu $m.server -label Server -underline 0 -font FixedFontM
    $m add cascade -menu $m.prefs -label Preferences -underline 0 -font FixedFontM
    $m add cascade -menu $m.theme -label Themes -underline 0 -font FixedFontM
    $m add cascade -menu $m.help -label Help -underline 0 -font FixedFontM

    # create File menu items
    $m.file add command -label "Clear Log" -command clearLog -font FixedFontM
    $m.file add command -label "Reconnect" -command Reconnect -font FixedFontM
    $m.file add separator
  if {$::tcl_platform(platform) == "unix"} {
    $m.file add cascade -menu $m.file.startup -label "Auto Start" -font FixedFontM
    $m.file add separator
  }
    $m.file add cascade -menu $m.file.auto -label "Auto Save" -font FixedFontM
    $m.file add command -label "Save Size" -state $auto -command {saveSize 0} -font FixedFontM
    $m.file add command -label "Save Size and Position" -state $auto -command {saveSize 1} -font FixedFontM
    $m.file add separator
    $m.file add command -label Quit -command {do_goodbye; exit} -font FixedFontM

    $m.file.auto add radiobutton -label "Size" -variable autoSave -value "size" -command {logAuto $m.file} -font FixedFontM -selectcolor $menurb
    $m.file.auto add radiobutton -label "Size and Position" -variable autoSave -value "both" -command {logAuto $m.file} -font FixedFontM -selectcolor $menurb
    $m.file.auto add radiobutton -label "Off" -variable autoSave -value "off" -command {logAuto $m.file} -font FixedFontM -selectcolor $menurb

    $m.file.startup add radiobutton -label "On" -variable autoStart -value "on" -command {logStart} -font FixedFontM -selectcolor $menurb
    $m.file.startup add radiobutton -label "On with desktop notifications" -variable autoStart -value "on+alert" -command {logStart} -font FixedFontM -selectcolor $menurb
    $m.file.startup add radiobutton -label "On only desktop notifications" -variable autoStart -value "alert" -command {logStart} -font FixedFontM -selectcolor $menurb
    $m.file.startup add radiobutton -label "Off" -variable autoStart -value "off" -command {logStart} -font FixedFontM -selectcolor $menurb

    # create Server menu items
    $m.server add command -label "Reload alias, blacklist and whitelist files" -command {
                Disable $m
                puts $Socket "REQ: RELOAD"
                flush $Socket
            } -font FixedFontM
    $m.server add command -label "Update current call log" -command {
                global multi

                Disable $m
                puts $Socket "REQ: UPDATE"
                flush $Socket
                set multi 0
            } -font FixedFontM
    $m.server add command -label "Update all call logs" -command {
                global multi

                Disable $m
                puts $Socket "REQ: UPDATES"
                flush $Socket
                set multi 1
            } -font FixedFontM
    $m.server add command -label "Reread call log" -command {
                global display_line_num

                set display_line_num 0
                .vh configure -state normal
                .vh delete 1.0 end
                set doingLog 1
                .vh insert 1.0 "\n\n\t\tReading the call log\n\n"
                Disable $m
                puts $Socket "REQ: REREAD"
                flush $Socket
            } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Add/Modify/Remove Alias in Alias File" -state disabled -command { DoList alias "" "" } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Add to Blacklist File" -state disabled -command {
                DoList black add ""
            } -font FixedFontM
    $m.server add command -label "Remove from Blacklist File" -state disabled -command {
                global argument
                DoList black remove $argument
            } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Add to Whitelist File" -state disabled -command {
                DoList white add ""
            } -font FixedFontM
    $m.server add command -label "Remove from Whitelist File" -state disabled -command {
                global argument
                DoList white remove $argument
            } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Dial Number" -state disabled -command { doDial } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Copy Phone Number to Clipboard as shown"    -state disabled -command { doClipboard 1 [lindex $dataDump 13] } -font FixedFontM
    $m.server add command -label "Copy Phone Number to Clipboard digits only" -state disabled -command { doClipboard 2 [lindex $dataDump 13] } -font FixedFontM
    $m.server add command -label "Copy Entire Line to Clipboard as shown"     -state disabled -command { doClipboard 3 $dataDump } -font FixedFontM

    # create Preferences menu items
    $m.prefs add command -label "Font" -command {changeFont} -font FixedFontM
    $m.prefs add separator
    $m.prefs add command -label "Date and Time" -command {formatDT} -font FixedFontM

    # create themes menu item
    $m.theme add radiobutton -label $Night \
        -variable NightMode -value 1 \
        -command {logTheme} -font FixedFontP -selectcolor $menurb
    $m.theme add radiobutton -label $Day \
        -variable NightMode -value 0 \
        -command {logTheme} -font FixedFontP -selectcolor $menurb

    # create Help menu item
    $m.help add command -label About -command {helpItem "About" $About} -font FixedFontM
    $m.help add command -label "Online Docs" -command {helpItem "Online Documentation" ""} -font FixedFontM
    $m.help add command -label "Line Labels" -command {helpItem "Line Label Descriptions" $labList} -font FixedFontM
    $m.help add command -label "Field Labels" -command {helpItem "Field Label Descriptions" $fieldList} -font FixedFontM
    $m.help add command -label "Server Menu" -command {helpItem "Server Menu Help" $serverHelp} -font FixedFontM
    $m.help add command -label "Server Options" -command serverOPT -font FixedFontM

    # create and place: CID history scroll window
    if {$clock == 12} {set timeWidth 8}
    set historyTextWidth [expr $historyTextWidth + $timeWidth]
    text .vh -width $historyTextWidth -height 4 -yscrollcommand ".ys set" \
        -state disabled -font {FixedFontH} -setgrid 1 -wrap $WrapLines
    ttk::scrollbar .ys -command ".vh yview"
    grid .vh -row 2 -sticky nsew -padx 2 -pady 2
    grid .ys -row 2 -column 1 -sticky ns -pady 2

    # create and place: user message window with a label
    ttk::frame .fr
    grid .fr -row 3 -columnspan 2
    if $NightMode {
        ttk::label .ml -text "Send Message: " -font {FixedFontM} -foreground yellow
        text .im -width 40 -height 1 -font {FixedFontM} -foreground cyan -insertbackground white
    } else {
        ttk::label .ml -text "Send Message: " -font {FixedFontM} -foreground blue
        text .im -width 40 -height 1 -font {FixedFontM} -foreground red
    }
    grid .ml .im -in .fr

    # create and place: call and server message display
    if {$NightMode} { set fgcolor "yellow" } else { set fgcolor "blue" }
    ttk::label .md -textvariable Txt -font {FixedFontM} -foreground $fgcolor
    grid .md -row 4 -columnspan 2
    
    grid columnconfigure . 0 -weight 1
    grid rowconfigure . 2 -weight 1

    set geometry [wm grid .]
    wm minsize . [lindex $geometry 0] [lindex $geometry 1]
    update

    switch $autoSave {
        "size" {
            $m.file entryconfigure Quit -command {do_goodbye; saveSize 0; exit}
            wm protocol . WM_DELETE_WINDOW {saveSize 0; $ExitOn}
        }
        "both" {
            $m.file entryconfigure Quit -command {do_goodbye; saveSize 1; exit}
            wm protocol . WM_DELETE_WINDOW {saveSize 1; $ExitOn}
        }
    }
    if $NightMode {
        .vh tag configure yellow -foreground yellow
        .vh tag configure cyan -foreground cyan
        .vh tag configure #7fff7f -foreground #7fff7f
    } else {
        .vh tag configure blue -foreground blue
        .vh tag configure red -foreground red
        .vh tag configure purple -foreground purple
    }

    if {$Verbose >= 4} {
        set temp "[font configure FixedFontH]"
        regsub {\s+\-slant.+$} $temp {} temp
        puts "History window font set to: $temp"
        
        set temp "[font configure FixedFontM]"
        regsub {\s+\-slant.+$} $temp "" temp
        puts "Message window and display font set to: $temp"
        
        set temp "[font configure FixedFontP]"
        regsub {\s+\-slant.+$} $temp "" temp
        puts "Help text set to: $temp"

        set temp [wm geometry .]
        regsub {(\d+x\d+)\+(\d+)\+(\d+)} $temp {\1 at x=\2 y=\3} temp
        puts "Window geometry set to: $temp"
    }
    bind . <Configure> {
            if {[lindex [.vh yview] 0] + [lindex [.vh yview] 1] == 1.0} {
                grid remove .ys
            } else {
                grid .ys
            }
    }
    bind .fr  <Button-1> {
        Disable $m
    }
    bind .ml  <Button-1> {
        Disable $m
    }
    bind .md  <Button-1> {
        Disable $m
    }
    bind .vh <ButtonRelease-1> {
        .vh tag remove sel 1.0 end
        set first [.vh index @%x,%ylinestart]
        set last [.vh index @%x,%ylineend]
        .vh tag add sel $first $last
        .vh mark unset anchor
        .vh mark unset tk::anchor1
        .vh mark set insert 1.0
        .vh mark set current 1.0
        set dataDump [.vh dump -text $first $last]
        set dataDump1 [.vh dump $first $last]
        set select_label [string trimright [lindex $dataDump 1]]
        set lineREQ [string trimright [lindex $dataDump 10]]
        set nmbrREQ [string trimright [lindex $dataDump 13]]
        set nmbrREQ [string trimleft $nmbrREQ]
        set nameREQ [string trimright [lindex $dataDump 16]]
        set selected [.vh get $first $last]
        if {[regexp {[0-9]+-} $nmbrREQ]} {
            set nmbrREQ [regsub -all -- {-} $nmbrREQ ""]
        }
        if {$select_label eq "RLY:" || $nameREQ eq ""} {
            doVerbose "$select_label unsupported line label" 1
            set menu .menubar.server
            $menu entryconfigure Add*Alias* -state disabled
            if {!$NoGUI} {
                $menu entryconfigure *Blacklist* -state disabled
                $menu entryconfigure *Whitelist* -state disabled
            }
        } else {
            if {!$Try} {
                puts $Socket "REQ: INFO $nmbrREQ&&$nameREQ&&$lineREQ"
                flush $Socket
                doVerbose "REQ: INFO $nmbrREQ&&$nameREQ&&$lineREQ" 1
            } else { doVerbose "Server not connected for a REQ: INFO" 1}
        }
        break
    }
}

proc Disable {menu} {
    .vh tag remove sel 1.0 end
    set last [$menu.server index last]
    set found 0
    for {set index 0} {$index <= $last} {incr index} {
        set type [$menu.server type $index]
        if {! $found && $type eq "separator"} {
            set found 1
            continue
        }
        if {$found && $type ne "separator"} {
            $menu.server entryconfigure $index -state disabled
        }
    }
}

proc remove {menu block} {
    set last [$menu index last]
    set found [expr $block == 0 ? 1 : 0]
    for {set index 0} {$index <= $last} {incr index} {
        set type [$menu type $index]
        if {$found} {
            $menu delete $index
            set index [expr $index - 1]
            if {$type eq "separator"} {
                break
            }
        } elseif {$type eq "separator"} {
            set block [expr $block - 1]
            set found [expr $block == 0 ? 1 : 0]
            continue
        }
    }
}

proc doRPLY {} {
    global remote_status addONE

    toplevel .rply
    wm title .rply "Dial Reply"
    wm resizable .rply 0 0

    set addONE ""
    grid [ttk::label .rply.mesg -font FixedFontP -justify left -textvariable remote_status ] -columnspan 2 -padx 12

    grid [ttk::button .rply.ok -text "Close" -command { destroy .rply}] \
          -columnspan 2 -pady 10

    modal {.rply}
}

proc doDial {} {
    global nmbrREQ nameREQ lineREQ DialPrefix remote_status
    global LineIDS Country addONE

    toplevel .dial
    wm title .dial "Dial"
    wm resizable .dial 0 0
    if {$DialPrefix == ""} {
        set dprefix "no"
    } else {
        set dprefix $DialPrefix
    }
    set lid "\nIf multiple telephone\nlines, choose one:"
    set row 1
    grid [ttk::label .dial.rd -justify left -font FixedFontP \
          -text "Request the server dial:\n\nDial: $dprefix prefix\nNmbr: $nmbrREQ\nName: $nameREQ\nLine: $lineREQ"] \
         -columnspan 2 -padx 12 -pady 10
    grid [ttk::label .dial.cl -justify left -font FixedFontP -text $lid] -columnspan 2 -padx 12
    grid [listbox .dial.lb -font FixedFontP \
          -selectmode single -height 0 -width 0] -columnspan 2
    foreach lineid_ $LineIDS { .dial.lb insert end $lineid_ }
    .dial.lb selection set 0
    .dial.lb curselection
    grid [ttk::frame .dial.fr] -pady 10 -columnspan 2
    incr row
    if {$Country == "US"} {
        grid [ttk::checkbutton .dial.fr.one -text "Add leading 1 to number" \
              -variable addONE -offvalue ""] -pady 5 -columnspan 2
    }
    incr row
    grid [ttk::label .dial.fr.lab1 -font {FixedFontM} -text "Status:"] -padx 3
    incr row
    grid [ttk::label .dial.fr.lab2 -font {FixedFontM} -textvariable remote_status] -column 0 -padx 3
    incr row
    grid [ttk::button .dial.cancel -text "Cancel" -command {destroy .dial}] -row $row -pady 12
    grid [ttk::button .dial.call -text "Call" -command {doit "" "DIAL" "$DialPrefix$addONE$nmbrREQ" "$nameREQ" 1}] -row $row -column 1
    grid [ttk::button .dial.abort -text "ABORT" -command {doit "" "DIAL_ABORT" "$DialPrefix$addONE$nmbrREQ" "$nameREQ" 1} -state disabled] \
                -pady 10 -row $row -column 1
    grid [ttk::button .dial.close -text "Close" -command {
          Disable .menubar
          destroy .dial} \
          -state disabled] -columnspan 2 -row $row -pady 10
    grid remove .dial.close .dial.abort
    set remote_status "Waiting for user action..."
    modal {.dial}
}

proc doAliasType {} {
    global entry_ action_ list_ remote_status replace_ comment_
    global aliasList aliasTypes CIDaliasType LineAliasType SelAliasType
    global name_ nmbr_ line_ temptext

    switch $SelAliasType {
        NAMEDEP {
            set replace_ $name_
            set temptext "Replace NAME: $name_\nif NMBR: $nmbr_\nwith ALIAS entered below"
            if {$action_ eq "modify" } {
                append temptext ",\n or clear it to remove it"
                incr row
            }
            append temptext ".\n" 
            .confirm.lab configure -text $temptext
        }
        NMBRDEP {
            set replace_ $nmbr_
            set temptext "Replace NMBR: $nmbr_\nif NAME: $name_\nwith ALIAS entered below"
            if {$action_ eq "modify" } {
                append temptext ",\n or clear it to remove it"
                incr row
            }
            append temptext ".\n" 
            .confirm.lab configure -text $temptext
        }
        NAMEONLY {
            set replace_ $name_
            if {$action_ eq "modify" } {
                set temptext "Replace NAME ALIAS: $name_\nwith ALIAS entered below"
                append temptext ",\n or clear it to remove it"
                incr row
            } else {
                set temptext "Replace NAME: $name_\nwith ALIAS entered below"
            }
            append temptext ".\n" 
            .confirm.lab configure -text $temptext
        }
        NMBRONLY {
            set replace_ $nmbr_
            if {$action_ eq "modify" } {
                set temptext "Replace NMBR ALIAS: $nmbr_\nwith ALIAS entered below"
                append temptext ",\n or clear it to remove it"
                incr row
            } else {
                set temptext "Replace NMBR: $nmbr_\nwith ALIAS entered below"
            }
            append temptext ".\n" 
            .confirm.lab configure -text $temptext
        }
        NMBRNAME {
            set replace_ ""
            if {$action_ eq "modify" } {
                set temptext "Replace NMBRNAME ALIAS: $name_\nand NMBR: $nmbr_\nwith ALIAS entered below"
                append temptext ",\n or clear it to remove it"
                incr row
            } else {
                set temptext "Replace NMBRNAME: $name_\nand NMBR: $nmbr_\nwith ALIAS entered below"
            }
            append temptext ".\n" 
            .confirm.lab configure -text $temptext
        }
        LINEONLY {
            set name_ $line_
            set nmbr_ ""
            set replace_ $line_
            if {$action_ eq "modify" } {
                set temptext "Replace LINE: $name_\nwith ALIAS entered below"
            } else {
                set temptext "Replace LINE ALIAS : $name_\nwith ALIAS entered below"
                append temptext ",\n or clear it to remove it"
                incr row
            }
            append temptext ".\n" 
            .confirm.lab configure -text $temptext
        }
    }
}

proc DoList {list action which} {
    global entry_ action_ list_ remote_status replace_ comment_
    global aliasList aliasTypes CIDaliasType LineAliasType SelAliasType
    global nameREQ nmbrREQ lineREQ name_ nmbr_ line_ temptext
    global NightMode

    toplevel .confirm
    wm title .confirm "Confirmation"
    wm resizable .confirm 0 0
    set action_ $action
    set list_ $list
    set comment_ ""
    set name_ $nameREQ
    set nmbr_ $nmbrREQ
    set line_ $lineREQ

    if {$list eq "black" || $list eq "white"} {
        set entry [list "$nmbrREQ" "$nameREQ"]
        set entry_ [lindex $entry 0]
        if {[lindex $entry 1] eq "NO NAME" || $name_ eq $nmbr_} {
            set entry [lreplace $entry 1 1]
        }

        if {$action eq "add"} {
            set _entry [join $entry "\" or \""]
        } elseif {$which eq "name"} {
            set entry_ [set _entry [lindex $entry 1]]
            set entry ""
        } else {
            set entry_ [set _entry [lindex $entry 0]]
            set entry ""
        }
        set _action [string toupper $action 0 0]
        set prep [expr {$action} eq "{add}" ? "{to}" : "{from}"]
        grid [ttk::label .confirm.lab -font FixedFontP -text "$_action \"$_entry\"\n$prep the server's ${list}list"] \
                -columnspan 2 -padx 12 -pady 10
        if {[llength $entry] == 2} {
            grid [ttk::radiobutton .confirm.rb1 -text [lindex $entry 0] \
                  -variable entry_ \
                  -value [lindex $entry 0]] -pady 5 -columnspan 2
            grid [ttk::radiobutton .confirm.rb2 -text [lindex $entry 1] \
                  -variable entry_ \
                  -value [lindex $entry 1]] -pady 5 -columnspan 2
            set row 3
        } else {
            set row 1
        }
        if {$action eq "add"} {
            grid [ttk::label .confirm.lab1 -font FixedFontP -text "Comment:"] -padx 12 -sticky w
            if $NightMode {
                text .confirm.entry -width 40 -height 1 -font FixedFontP -foreground cyan -insertbackground white
            } else {
                text .confirm.entry -width 40 -height 1 -font FixedFontP -foreground red
            }
            grid .confirm.entry -sticky ew -columnspan 2 -padx 8
            set row [expr $row + 2]
        }
    } else {
        grid [ttk::label .confirm.list -justify left -font FixedFontP -text "NAME: $name_\nNMBR: $nmbrREQ\nLINE: $lineREQ\n\nChoose the alias type:"] -columnspan 2 -padx 12 -pady 10
        grid [listbox .confirm.lb -font FixedFontP -listvariable aliasList -selectmode single -height 0 -width 0] -columnspan 2
        if {$CIDaliasType eq "NOALIAS"} {
            set aliasList $aliasTypes
            set action_ "add"
            set replace_ ""
        } else {
            set aliasList "$CIDaliasType LINEONLY"
            set action_ "modify"
            set replace_ $name_
        }
        .confirm.lb selection set 0
        .confirm.lb curselection
        set SelAliasType [.confirm.lb get [.confirm.lb curselection]]
        set temptext ""
         grid [ttk::label .confirm.lab -justify left -font FixedFontP -text $temptext] \
         -columnspan 2 -padx 12 -pady 10
        doAliasType
         append temptext ".\n" 
         if $NightMode {
             text .confirm.entry -width 40 -height 1 -font FixedFontP -foreground cyan -insertbackground white
         } else {
             text .confirm.entry -width 40 -height 1 -font FixedFontP -foreground red
         }
         grid .confirm.entry -columnspan 2 -padx 12 -pady 10
         # .confirm.entry selection range 0 end
         focus .confirm.entry
         set row 4
        set SelAliasType [.confirm.lb get [.confirm.lb curselection]]
    }
    bind all <<ListboxSelect>> {
        if {[.confirm.lb curselection] eq ""} {
            set SelAliasType [.confirm.lb index 1]
        } else {
            set SelAliasType [.confirm.lb get [.confirm.lb curselection]]
        }
        if {$SelAliasType eq "LINEONLY"} {
            if {$LineAliasType eq "NOALIAS"} {
                set replace_ ""
                set action_ "add"
            } else {
                set action_ "modify"
            }
        } else {
            if {$CIDaliasType eq "NOALIAS"} {
                set replace_ ""
                set action_ "add"
            } else {
                set action_ "modify"
            }
        }
    }
    grid [ttk::frame .confirm.fr] -pady 10 -columnspan 2 -row $row
    incr row
    grid [ttk::label .confirm.fr.lab1 -font FixedFontP -text "Status:"] -padx 3
    grid [ttk::label .confirm.fr.lab2 -font FixedFontP -textvariable remote_status] -column 0 -row 1 -padx 3
    grid [ttk::button .confirm.cancel -text "Cancel" -command {destroy .confirm}] -pady 10
    
    if {$list eq "alias"} {
        grid [ttk::button .confirm.ok -text "Apply" -command {
           set replace_ [.confirm.entry get 1.0 end]; \
           set replace_ [string trim $replace_]; \
           doit $action_ $list_ $nmbr_&&$replace_ "$SelAliasType&&$name_" 0}] \
          -pady 10 -row $row -column 1
    } else {
        grid [ttk::button .confirm.ok -text "Apply" -command {
                if {$action_ eq "add"} {
                    set comment_ [.confirm.entry get 1.0 end]; \
                    set comment_ [string trim $comment_]; \
                }; \
                doit $action_ $list_ $entry_ $comment_ 0}] \
                -pady 10 -row $row -column 1
    }
    
    incr row
    grid [ttk::button .confirm.close -text "Close"  -command {
            Disable .menubar
            destroy .confirm} \
             -state disabled ] -columnspan 2 -row $row -pady 10
    grid remove .confirm.close
    set remote_status "Waiting for user action..."
    modal {.confirm}
}

proc doit {action list entry extra wantdial} {
    global Socket  remote_status Try dial

    set dial $wantdial
    if {$Try} {
        set remote_status "Server not connected ..."
        doVerbose "Server not connected for a REQ:" 1
        return
    }
    if {$dial} {
        if {$list == "DIAL_ABORT"} {set dial 2}
        set selLID [.dial.lb get [.dial.lb curselection]]
        grid forget .dial.cancel .dial.call
        grid configure .dial.close -columnspan 1
        grid .dial.abort .dial.close
        puts $Socket "REQ: $list $entry&&$extra&&$selLID"
        flush $Socket
        doVerbose "REQ: $list $entry&&$extra&&$selLID" 1
    } else {
        set remote_status "Working ..."
        grid forget .confirm.cancel .confirm.ok
        grid .confirm.close
        puts $Socket "REQ: $list $action \"$entry\" \"$extra\""
        flush $Socket
        doVerbose "REQ: $list $action \"$entry\" \"$extra\"" 1
    }
}

proc doClipboard {flag passedData} {

    #flag = 1 = copy phone number as shown
    #       2 = copy phone number digits only
    #       3 = copy entire line

    if {$flag != 3} {
       doVerbose "passedData is '$passedData'" 4
       set passedData [string trim $passedData]
    }

    clipboard clear
    if {$flag == 1} {
        clipboard append $passedData
        doVerbose "Copied '$passedData' to clipboard as shown" 1
    } elseif {$flag ==2} {
        regsub -all -line {[^\d]} $passedData {} passedData
        clipboard append $passedData
        doVerbose "Copied '$passedData' to clipboard digits only" 1
    } else {
        set tempLine ""
        for {set x 1} {$x < [ llength $passedData]} {incr x 3} {
            set tempField [ lindex $passedData $x ]
            doVerbose "Parsing $x: $tempField" 4
            set tempLine "$tempLine$tempField"
        }
        set tempLine [string trim $tempLine]
        clipboard append $tempLine
        doVerbose "Copied '$tempLine' to clipboard" 1
    }
}

proc helpItem {title mesg} {
    global Logo command Website

    toplevel .hlp
    wm title .hlp $title
    wm resizable .hlp 0 0

    if [file exists $Logo] {
        image create photo ncid -file $Logo
        grid [ttk::label .hlp.img -image ncid] -columnspan 2 -padx 12 -pady 10
    }

    if {$title == "About"} {set jt "center"} else {set jt "left"}

    if {$mesg == ""} {
      grid [ttk::label .hlp.s1]
      grid [hyperlink .hlp.um -command [list eval exec $command "http://ncid.sourceforge.net/doc/NCID-UserManual.html" &] -text "User Manual"] -columnspan 2
      grid [hyperlink .hlp.mp -command [list eval exec $command "http://ncid.sourceforge.net/man/man.html" &] -text "Manual Pages"] -columnspan 2 -padx 42
      grid [ttk::label .hlp.s2]
    } else {
      grid [ttk::label .hlp.mesg -font FixedFontP -justify $jt -text $mesg ] -columnspan 2 -padx 12
      if { $title == "About" } {
      grid [hyperlink .hlp.web -command [list eval exec $command $Website &] -text "NCID website"] -columnspan 2
    grid [ttk::label .hlp.s3]
      }
    }

    grid [ttk::button .hlp.ok -text "OK" -command {
          Disable .menubar
          destroy .hlp}] \
          -columnspan 2 -pady 10

    modal {.hlp}
}

proc serverOPT {} {
    global Logo svrOptions

    set displayOPT "\nOptions sent to clients\n to indicate enabled:"
    if {$svrOptions == ""} {
        set displayOPT "$displayOPT\n\n         none"
    } else {
        set displayOPT "$displayOPT\n$svrOptions"
    }
    helpItem "Server Options" $displayOPT
}

proc clearLog {} {
    global display_line_num

    set display_line_num 0
    .vh configure -state normal
    .vh delete 1.0 end
    .vh yview moveto 0.0
    .vh configure -state disabled
}

proc saveSize {flag} {
    global Txt
    
    set save $Txt
    set Txt ""
    update
    set geometry [wm geometry .]
    set Txt $save
    if {$flag == 0} {
        regexp {(\d+x\d+)\+} $geometry -> geometry
    }
    write_rc_file "geometry\\s+\\S+\\s+\[0-9x\]+" "wm geometry . $geometry"
}

proc write_rc_file {regexpr command} {
    global rcfile

    if [file exists $rcfile] {
        if [file isdirectory $rcfile] {
            doVerbose "Unable to save data to $rcfile because it is a directory" 1
            return
        }
        set id [open $rcfile]
        set data [read $id]
        close $id
        set lines [lrange [split $data "\n"] 0 end-1]
        set index 0
        foreach line $lines {
            if [regexp $regexpr $line] {
                break
            }
            incr index
        }
        if {$index >= [llength $lines]} {
            lappend lines "$command"
        } else {
            lset lines $index "$command"
        }
        set data [join $lines "\n"]
        set id [open $rcfile w]
        puts $id $data
    } else {
        set id [open $rcfile w]
        puts $id $command
    }
    close $id
}

# Change Font
proc changeFont {} {
    global fontList
    global spinvalH
    global spinvalM
    global spinvalP
    global boldH
    global boldM
    global boldP

    toplevel .f
    wm title .f "Change Fixed Font"
    wm resizable .f 0 0

    eval [concat {font create SelectionFontH} [font configure FixedFontH]]
    eval [concat {font create SelectionFontM} [font configure FixedFontM]]
    eval [concat {font create SelectionFontP} [font configure FixedFontP]]
    
    set spinvalH [font configure FixedFontH -size]
    set boldH    [font configure FixedFontH -weight]

    set spinvalM [font configure FixedFontM -size]
    set boldM    [font configure FixedFontM -weight]

    set spinvalP [font configure FixedFontP -size]
    set boldP    [font configure FixedFontP -weight]

    set currentFont [font configure FixedFontH -family]
    
    label .f.ln -font FixedFontP -text "Font Name"
    grid [ttk::labelframe .f.fn -labelwidget .f.ln -labelanchor "n"] -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::combobox .f.fn.cb -font FixedFontP -values $fontList -textvariable currentFont] -padx 15 -pady 5 -padx 80
    grid [ttk::button .f.fn.btn -text "Re-scan"] -column 0 -row 1 -pady 5 -padx 60
    .f.fn.cb set $currentFont

    label .f.hw -font FixedFontP -text "History Window Font"
    grid [ttk::labelframe .f.fh -labelwidget .f.hw -labelanchor "n"] -column 0 -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.fh.cb -text "Bold" -variable boldH \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontH -weight $boldH}] \
          -pady 5 -padx 5
    grid [ttk::label .f.fh.label -font FixedFontP -text "Size: "] -column 1 -row 0 -pady 5 -padx 5
    grid [ttk::spinbox .f.fh.size -from 8 -to 36 -width 3 -font FixedFontP -textvariable spinvalH \
          -state readonly -command {font configure SelectionFontH -size $spinvalH}] \
          -column 2 -row 0 -pady 5 -padx 5
    grid [ttk::label .f.fh.sample -text "Sample text 0123456789" -font SelectionFontH] -row 1 -columnspan 3 -pady 5 -padx 70

    label .f.mml -font FixedFontP -text "Message, Menu and Label Font"
    grid [ttk::labelframe .f.fm -labelwidget .f.mml -labelanchor "n"] -column 0  -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.fm.cb -text "Bold" -variable boldM \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontM -weight $boldM}] \
          -pady 5 -padx 5
    grid [ttk::label .f.fm.label -font FixedFontP -text "Size: "] -column 1 -row 0 -pady 5 -padx 5
    grid [ttk::spinbox .f.fm.size -from 8 -to 36 -width 3 \
          -font FixedFontP -textvariable spinvalM \
          -state readonly \
          -command {font configure SelectionFontM -size $spinvalM}] \
          -column 2 -row 0 -pady 5
    grid [ttk::label .f.fm.sample -text "Sample text 0123456789" -font SelectionFontM] -row 1 -columnspan 3 -pady 5 -padx 70

    label .f.pw -font FixedFontP -text "Popup Window Font"
    grid [ttk::labelframe .f.fp -labelwidget .f.pw -labelanchor "n"] -column 0  -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.fp.cb -text "Bold" -variable boldP \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontP -weight $boldP}] \
          -pady 5 -padx 5
    grid [ttk::label .f.fp.label -font FixedFontP -text "Size: "] -column 1 -row 0 -pady 5 -padx 5
    grid [ttk::spinbox .f.fp.size -from 8 -to 36 -width 3 \
          -font FixedFontP -textvariable spinvalP \
          -state readonly \
          -command {font configure SelectionFontP -size $spinvalP}] \
          -column 2 -row 0 -pady 5
    grid [ttk::label .f.fp.sample -text "Sample text 0123456789" -font SelectionFontP] -row 1 -columnspan 3 -pady 5 -padx 70

    grid [ttk::frame .f.f]  -column 0 -sticky "ew" -pady 8
    grid [ttk::button .f.f.cancel -text "Cancel"] -padx 12 -pady 6
    grid [ttk::button .f.f.apply -text "Apply"] -column 1 -row 0 -padx 12
    grid [ttk::button .f.f.ok -text "OK"] -column 2 -row 0 -padx 12

    # change font family
    bind all <<ComboboxSelected>> {
        font configure SelectionFontH -family "$currentFont"
        font configure SelectionFontM -family "$currentFont"
        font configure SelectionFontP -family "$currentFont"
    }

    bind TButton <ButtonRelease-1> {+
        switch -regexp %W {
            ".confirm.*" { break }
            ".dial.*" { break }
            ".dt.*" { break }
            ".hlp.*" { break }
            ".rply.*" { break }
            ".reply.*" { break }
            default {
                set temp [%W cget -text]
                switch $temp {
                    "Cancel" {
                        destroy .f
                        break
                    }
                    "OK" -
                    "Apply" {
                        font configure FixedFontH -family "$currentFont" \
                            -size $spinvalH -weight $boldH
                        font configure FixedFontM -family "$currentFont" \
                            -size $spinvalM -weight $boldM
                        font configure FixedFontP -family "$currentFont" \
                            -size $spinvalP -weight $boldP
                        logFont
                        if {$temp eq "OK"} {
                            destroy .f
                        }
                        break
                    }
                    "Re-scan" {
                        .f.fn.cb configure -values {}
                        unset fontList
                        scanFonts
                        .f.fn.cb configure -values $fontList
                        break
                    }
                }
            }
        }
    }

    modal {.f}

    font delete SelectionFontH
    font delete SelectionFontM
    font delete SelectionFontP
}

proc logFont {} {
    set tempH "[font configure FixedFontH]"
    set tempM "[font configure FixedFontM]"
    set tempP "[font configure FixedFontP]"

    write_rc_file "FixedFontH" "font create FixedFontH $tempH"
    write_rc_file "FixedFontM" "font create FixedFontM $tempM"
    write_rc_file "FixedFontP" "font create FixedFontP $tempP"

    doVerbose "history window font set to: $tempH" 1
    doVerbose "message window and display font set to: $tempM" 1
    doVerbose "help text font set to: $tempP" 1
}

proc formatDT {} {

    global NightMode

    toplevel .dt
    wm title .dt "Date and Time Formats"
    wm resizable .dt 0 0

    grid [ttk::label .dt.s1]
    grid [ttk::radiobutton .dt.12 -text "12 hour time" -variable clock -value 12 -command {logClock .vh}] -columnspan 2
    grid [ttk::radiobutton .dt.24 -text "24 hour time" -variable clock -value 24 -command {logClock .vh}] -columnspan 2 -rowspan 2

    grid [ttk::label .dt.s2]
    grid [ttk::radiobutton .dt.mm -text "Date: MM DD YYYY" -variable AltDate -value 0 -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.dd -text "Date: DD MM YYYY" -variable AltDate -value 1 -command {logDate .vh}] -columnspan 2 -padx 12 -rowspan 2

    grid [ttk::label .dt.s3]
    grid [ttk::radiobutton .dt.s -text "Date Separator: /" -variable DateSepar -value "/" -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.d -text "Date Separator: -" -variable DateSepar -value "-" -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.p -text "Date Separator: ." -variable DateSepar -value "." -command {logDate .vh}] -columnspan 2 -padx 12

    grid [ttk::label .dt.s4]
    grid [ttk::button .dt.ok -text "OK" -command {
          Disable .menubar
          destroy .dt}] \
          -columnspan 2 -pady 10

    modal {.dt}
}

proc logDate {widget} {
    global AltDate oldAltDate DateSepar oldDateSepar YearDot clock Socket display_line_num lbl1 lbl2 lbl3 lbl4 lbl5 lbl6 lbl7 lbl8 historyTextWidth

    set dateflag 0
    if {$AltDate != $oldAltDate} {
        set oldAltDate $AltDate
        set dateflag 1
        write_rc_file "set AltDate" "set AltDate $AltDate"
        doVerbose "AltDate has been changed to: $AltDate" 1
    } elseif {$DateSepar != $oldDateSepar} {
        set oldDateSepar $DateSepar
        write_rc_file "set DateSepar" "set DateSepar $DateSepar"
        doVerbose "AltDate has been changed from: $oldDateSepar to: $DateSepar" 1
    } else { return }

    $widget configure -state normal
    for {set line 0} {1} {incr line} {
        set temp [$widget dump -text "1.0 + $line l" "1.0 + $line l lineend"]
        if {$temp eq ""} {break}
        # do not check for END or RLY in next line because these are ignored in this script
        if {![regexp {^(?:BLK|CID|HUP|MSG|MWI|NOT|OUT|PID|RID|WID)} [lindex $temp 1]]} {
            continue
        }
        # MSG lines may have no date or time, if the llength is 8
        if {[llength $temp] < 10} {continue}
        set date [lindex $temp 4]
        set start [lindex $temp 5]
        set stop [lindex $temp 8]
        set f1 [string range $date 0 1]
        set f2 [string range $date 3 4]
        set f3 [string range $date 6 9]
        if {$dateflag} {
            set date "$f2$DateSepar$f1$DateSepar$f3"
        } else {
            set date "$f1$DateSepar$f2$DateSepar$f3"
        }
        $widget insert "$stop - 1 c" "$date"
        $widget delete "$start" "$stop - 1 c"
    }

    if {$clock == 24 && $DateSepar == "."} {set lbl $lbl1}
    if {$clock == 24 && $DateSepar == "-"} {set lbl $lbl2}
    if {$clock == 24 && $DateSepar == "/"} {set lbl $lbl3}
    if {$clock == 12 && $DateSepar == "."} {set lbl $lbl4}
    if {$clock == 12 && $DateSepar == "-"} {set lbl $lbl5}
    if {$clock == 12 && $DateSepar == "/"} {set lbl $lbl6}
    if {$clock == 24 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl7}
    if {$clock == 12 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl8}

    if $YearDot {
      if {$Socket > 0} {
          set display_line_num 0
          .vh delete 1.0 end
          puts $Socket "REQ: REREAD"
          flush $Socket
      }
      .la configure -text $lbl
      doVerbose "History Text Field Width changed to: $historyTextWidth characters" 1
      $widget configure -width $historyTextWidth
      set geometry [wm grid .]
      wm minsize . [lindex $geometry 0] [lindex $geometry 1]
      update
      $widget configure -state disabled
    }
}

proc logTheme {} {
  global NightMode oldNightMode autoSave
  global argv0 Host Port Program

  set themeflag 0
  if {$NightMode != $oldNightMode} {
    set oldNightMode $NightMode
    set themeflag 1
    write_rc_file "set NightMode" "set NightMode $NightMode"
    doVerbose "NightMode has been changed to: $NightMode" 1

    if {$::tcl_platform(platform) != "windows"} {
      # save window position and size if needed
      switch $autoSave {
        "size" { do_goodbye; saveSize 0 }
        "both" { do_goodbye; saveSize 1 }
      }

      # restart to change display mode
      if {$Program != ""} {
        exec sh $argv0 -P $Program $Host $Port &
      } else {
        exec sh $argv0 $Host $Port &
      }
        destroy .
        after 100 exit
    }

  } else { return }
}

proc logClock {widget} {
    global  clock oldClock DateSepar YearDot Socket display_line_num lbl1 lbl2 lbl3 lbl4 lbl5 lbl6 lbl7 lbl8 historyTextWidth

    if {$clock == $oldClock} { return }
    set oldClock $clock
    write_rc_file "set clock" "set clock $clock"
    doVerbose "Time display has been changed to: $clock hours" 1
    $widget configure -state normal
    for {set line 0} {1} {incr line} {
        set temp [$widget dump -text "1.0 + $line l" "1.0 + $line l lineend"]
        if {$temp eq ""} {break}
        # do not check for END or RLY in next line because these are ignored in this script
        if {![regexp {^(?:BLK|CID|HUP|MSG|MWI|NOT|OUT|PID|RID|WID)} [lindex $temp 1]]} {
            continue
        }
        # MSG lines may have no date or time, if so the llength is 8
        if {[llength $temp] < 10} {continue}
        set time [lindex $temp 7]
        set start [lindex $temp 8]
        set stop [lindex $temp 11]
        if {$clock == 12} {
            set hours [string range $time 0 1]
            set minutes [string range $time 3 4]
            set time [convertTo12 $hours $minutes]
        } else {
            set hours [string range $time 0 1]
            set minutes [string range $time 3 4]
            set AmPm [string range $time 6 7]
            set time [convertTo24 $hours $minutes $AmPm]
        }
        $widget insert "$stop - 1 c" "$time"
        $widget delete "$start" "$stop - 1 c"
    }

    if {$clock == 24 && $DateSepar == "."} {set lbl $lbl1}
    if {$clock == 24 && $DateSepar == "-"} {set lbl $lbl2}
    if {$clock == 24 && $DateSepar == "/"} {set lbl $lbl3}
    if {$clock == 12 && $DateSepar == "."} {set lbl $lbl4}
    if {$clock == 12 && $DateSepar == "-"} {set lbl $lbl5}
    if {$clock == 12 && $DateSepar == "/"} {set lbl $lbl6}
    if {$clock == 24 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl7}
    if {$clock == 12 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl8}

    if {$Socket > 0} {
        set display_line_num 0
        .vh delete 1.0 end
        puts $Socket "REQ: REREAD"
        flush $Socket
    }
    .la configure -text $lbl
    doVerbose "History Text Field Width changed to: $historyTextWidth characters" 1
    $widget configure -width $historyTextWidth
    set geometry [wm grid .]
    wm minsize . [lindex $geometry 0] [lindex $geometry 1]
    update
    $widget configure -state disabled
}

proc logAuto {menu} {
    global      ExitOn autoSave oldAutoSave m

    if {$autoSave eq $oldAutoSave} { return }
    set oldAutoSave $autoSave
    write_rc_file "set autoSave" "set autoSave \"$autoSave\""
    switch $autoSave {
        "size" {
            set temp "save size only"
            $menu entryconfigure *Size -state disabled
            $menu entryconfigure *Position -state disabled
            $menu entryconfigure Quit -command {do_goodbye; saveSize 0; exit}
            wm protocol . WM_DELETE_WINDOW {do_goodbye; saveSize 0; $ExitOn}
        }
        "both" {
            set temp "save size and position"
            $menu entryconfigure *Size -state disabled
            $menu entryconfigure *Position -state disabled
            $menu entryconfigure Quit -command {do_goodbye; saveSize 1; exit}
            wm protocol . WM_DELETE_WINDOW {saveSize 1; $ExitOn}
        }
        "off" {
            set temp "off"
            $menu entryconfigure *Size -state normal
            $menu entryconfigure *Position -state normal
            $menu entryconfigure Quit -command {do_goodbye; exit}
            wm protocol . WM_DELETE_WINDOW $ExitOn
        }
    }
    doVerbose "Auto save has been set to $temp" 1
}

proc logStart {} {
    global      autoStart oldAutoStart dtfile asfile

    if {$autoStart eq $oldAutoStart} { return }
    set oldAutoStart $autoStart
    switch $autoStart {
        "on" {
            file copy -force $dtfile $asfile
        }
        "on+alert" {
            set in  [open $dtfile r]
            set out [open $asfile w]
            while {[gets $in line] != -1} {
                regsub {NCID client} $line {NCID client with ncid-alert} line
                regsub {ID\) client} $line {ID) client with desktop notifications} line
                regsub {Exec=ncid} $line {Exec=ncid --program ncid-alert} line
                puts $out $line
            }
            close $in
            close $out
        }
        "alert" {
            set in  [open $dtfile r]
            set out [open $asfile w]
            while {[gets $in line] != -1} {
                regsub {NCID client} $line {NCID Alert} line
                regsub {NCID \(Network Caller ID\) client} $line {Send NCID call or message desktop notifications} line
                regsub {Exec=ncid} $line {Exec=ncid --no-gui --program ncid-alert} line
                puts $out $line
            }
            close $in
            close $out
        }
        "off" {
            file delete $asfile
        }
    }
    write_rc_file "set autoStart" "set autoStart \"$autoStart\""
    doVerbose "autoStart has been set to $autoStart" 1
}

# Handle MSG from GUI
proc handleGUIMSG {} {

  # get MSG and clear text input box
  set line [.im get 1.0 end]
  .im delete 1.0 end
  # get rid of non-printable characters at start/end of string
  set line [string trim $line]
  # send MSG to server, if $line not empty
  if {[string length $line] > 0} {handleMSG $line}
}

# Handle MSG sent to server
proc handleMSG {msg} {
  global Socket loginName lineName

  puts $Socket "MSG: $msg ###NAME*$loginName*LINE*$lineName"
  flush $Socket
}

# Handle verbosity levels
proc doVerbose {msg level} {
    global Verbose
    if {$Verbose >= $level} {puts "$msg"}
}

# handle a PID file, if it can not be created, ignore it
proc doPID {} {
    global PIDfile
    global Verbose

    if {$PIDfile != ""} {
        set activepid ""
        set PIDdir [file dirname $PIDfile]
        if {[file writable $PIDfile]} {
            # get the pid's on the first line of the pidfile
            set chan [open $PIDfile r ]
            gets $chan line
            close $chan
            # save any active pid
            foreach p $line {
                if {[file exists /proc/$p]} {set activepid "$p "}
            }
            # truncate the pidfile
            set chan [open $PIDfile w ]
            if {$activepid == ""} {
                # write current PID into pidfile
                puts $chan [pid]
            } else {
                # write active PID's and current PID into pidfile
                puts $chan "$activepid [pid]"
            }
            close $chan
        } elseif {[file writable $PIDdir]} {
            # create the pidfile
            set chan [open $PIDfile "CREAT WRONLY" 0644]
            puts $chan [pid]
            close $chan
        }
        doVerbose "Using pidfile: $PIDfile" 1
    } else {doVerbose "Not using a PID file" 1}
}

proc scanFonts {} {
    global fontList

    set numberFonts 0
    set numberFixed 0
    # find a fixed-width font and use it
    foreach family [font families] {
        incr numberFonts

        # Next line is for Apple Mac. Microsoft Word font Bauhaus93 triggers
        # an error in Wish:
        #    CoreText: Invalid 'kern' Table In CTFont <name: Bauhaus93....
        if {$family == "Bauhaus 93"} {
            doVerbose "skipping fixed font: $family" 4
            continue
        } 

        # skip Emoji fonts, color ones cause "X Error of failed request"
        if {[regexp {Emoji} $family]} {
            doVerbose "skipping fixed font: $family" 4
            continue
        } 

        # Microsoft has duplicate fonts that start with @
        if {[regexp {^@} $family]} {
            doVerbose "skipping fixed font: $family" 4
            continue
        }

        if {[font metrics \"$family\" -fixed]} {
            incr numberFixed
            doVerbose "detected fixed font $family" 4
            lappend fontList $family
            if {![info exists currentFont]} {
                set currentFont $family
            }
        }
    }
    doVerbose "current font set to: $currentFont" 1
    doVerbose "$numberFixed fixed fonts out of $numberFonts fonts" 1
    set fontList [lsort -dictionary $fontList]
    write_rc_file "fontList " "set fontList \"$fontList\""
}

proc modal {window} {
  wm transient $window .

  # Tk Command: tkwait visibility
  # https://www.tcl.tk/man/tcl/TkCmd/tkwait.htm
  #   Waits for a change in the visibility state of a window as indicated by a <VisibilityNotify> event.
  #   This is typically used to wait for a newly-created window to appear on the screen before taking some action.
  # https://stackoverflow.com/questions/8929031/grabbing-a-new-window-in-tcl-tk
  #   This prevents the error from sometimes appearing:
  #     RGError: grab failed: window not viewable
  #
  # Tk Command: wininfo viewable
  # https://www.tcl.tk/man/tcl/TkCmd/winfo.htm
  # http://wiki.tcl.tk/10013
  #   Needed for (Windows, OSX/Aqua) because <VisibilityNotify> events are never delivered.
  #   On a windows platform, tkwait visibility does not return on a visable vindow.
  if {![winfo viewable $window]} { tkwait visibility $window }

  grab $window

  # Resolves the lack of focus on a newly created window
  focus $window

  wm protocol $window WM_DELETE_$window {grab release $window; destroy $window}
  raise $window
  tkwait window $window
}

# Hyperlink Widget: https://wiki.tcl.tk/36776
proc hyperlink { name args } {
  global NightMode

  if {"Underline-Font" ni [font names]} {
    font create Underline-Font
  }
  # font size may have changed
  font configure Underline-Font {*}[font actual FixedFontP] -underline true

  if { [ dict exists $args -command ] } {
    set command [ dict get $args -command ]
    dict unset args -command
  }

  # add -foreground, -font and -cursor, but only if they are missing
  if {$NightMode} { set fgcolor "yellow" } else { set fgcolor "blue" }
  set args [ dict merge [ dict create -foreground $fgcolor -font Underline-Font -cursor hand2 ] $args ]

  ttk::label $name {*}$args

  if { [ info exists command ] } {
    bind $name <Button-1> $command
  }

  return $name
}

proc setBrowser {} {
  global command browser

  # open is the OS X equivalent to xdg-open on Linux, start is used on Windows
  set commands {xdg-open open start}
  foreach browser $commands {
    if {$browser eq "start"} {
      set command [list {*}[auto_execok start] {}]
    } else {
      set command [auto_execok $browser]
    }
    if {[string length $command]} {
      break
    }
  }

  if {[string length $command] == 0} {
    return -code error "couldn't find browser"
  }
}

if {$nameWidth == ""
    || ![regexp {^[2345]+[0-9]+$} $nameWidth]
    || $nameWidth > 50} {
    exitMsg 4 "nameWidth should be between 20 and 50 but is \"$nameWidth\"\n"
}

# This is the default, except when using freewrap
if {[catch {encoding system utf-8} msg]} {
    doVerbose "$msg" 1
}

getArg

if {$Program != ""} {
    regsub {.*/(.*)} $Program {\1} ModName
    if {$NoGUI} {
        regsub {ncid} $VersionInfo "$ModName" VersionInfo
        regsub {ncid} $VersionIDENT "$ModName" VersionIDENT
    } else {
        regsub {ncid} $VersionInfo "ncid using module $ModName" VersionInfo
        regsub {ncid} $VersionIDENT "ncid using module $ModName" VersionIDENT
    }
}

set About \
"
$VersionInfo
$Author"

if {$HostnameFlag} {
    regsub {ncid} $VersionIDENT "$hostname/ncid" VersionIDENT
} else {
    set lineName ncid
}

doVerbose "$VersionInfo" 1
checkCountry $Country
doVerbose "Server address: $Host:$Port" 1
doVerbose "Verbose Level: $Verbose" 1
doVerbose "Script executed: $argv0" 1
if {[file exists $ConfigFile]} {
    doVerbose "Config file: $ConfigFile" 1
} else {
    doVerbose "*** Config file Missing: $ConfigFile" 1
}
doVerbose "HostnameFlag: $HostnameFlag" 1
doVerbose "lineName: $lineName" 1
doVerbose "loginName: $loginName" 1
doVerbose "Delay between reconnect tries to the server: $Delay (seconds)" 1

if {!$NoGUI} {
    doVerbose "GUI Display" 1
    doVerbose "Popup time: $PopupTime" 1
    if {$NoExit} {
        set ExitOn do_nothing
        doVerbose "The \"Close Window\" ttk::button is disabled" 1
    }
    if {![regexp {^(:?char|word|none)$} $WrapLines]} {
        doVerbose "WrapLines set to invalid value of \"$WrapLines\", using default" 1
        set WrapLines "char"
    } else {
        doVerbose "WrapLines set to \"$WrapLines\"" 1
    }
    if {$DialPrefix != ""} {
        doVerbose "Dial prefix: $DialPrefix" 1
    } else {
        doVerbose "Dial prefix: none" 1
    }
    setBrowser
    makeWindow

    doVerbose "Time Field Width: $timeWidth characters" 1
    doVerbose "Number Field Width: $nmbrWidth characters" 1
    doVerbose "Name Field Width: $nameWidth characters" 1
    doVerbose "Line Label Field Width: $lineIDWidth characters" 1
    doVerbose "Mesg Type Field Width: $mtypeWidth characters" 1
    doVerbose "Calculated History Text Field Width: $historyTextWidth characters" 1
}

if {(
    $Country != "DE" && \
    $Country != "FR" && \
    $Country != "HR" && \
    $Country != "SE" && \
    $Country != "UK" && \
    $Country != "US" && \
    $Country != "NONE")} {
    exitMsg 7 "Country Code \"$Country\" is not supported. Please change it."
}

doVerbose "Country Code: $Country" 1
if {$Country == "US"} {
    if $NoOne {
        doVerbose "Leading digit '1' in phone number will NOT be displayed" 1
    } else {
        doVerbose "Leading digit '1' in phone number WILL be displayed" 1
    }
}

if {$DateSepar != "/" && $DateSepar != "-" && $DateSepar != "."} {
    exitMsg 7 "Date separator \"$DateSepar\" is not supported. Please change it."
}

if $AltDate {
    doVerbose "Date Format: DD${DateSepar}MM${DateSepar}YYYY" 1
} else { doVerbose "Date Format: MM${DateSepar}DD${DateSepar}YYYY" 1 }

if {$WakeUp} {
    if {![file executable $ModDir/ncid-wakeup]} {
        set WakeUp 0
        doVerbose "Module ncid-wakeup not found or not executable, wakeup option removed" 1
    }
}

if {$Program != ""} {
    if {[file exists $Program]} {
        if {![file executable $Program]} {
            # Simple test to see if running under Cygwin
            if {[file exists $CygwinBat]} {
                # The Cygwin TCL cannot execute shell scripts
                set ExecSh 1
            } else {
                exitMsg 2 "Program Not Executable: $Program"
            }
        }
    } else {exitMsg 3 "Program Not Found: $Program"}
    doVerbose "Using output Module: $Program" 1
    # change module name from <path>/ncid-<name> to ncid_<name>
    regsub {.*/(.*)} $Program {\1} ModName
    regsub {\-} $ModName {_} modopt
    # set the module option variable in $$modopt
    if {[catch {eval [subst $$modopt]} oops]} {
        doVerbose "No optional \"$modopt\" variable in ncid.conf" 1
    } else {
        regsub {.*set *(\w+)\s+.*} [eval concat $$modopt] {\1} modvar
        regsub {.*set *(\w+)\s+(\w+).*} [eval concat $$modopt] {\2} modval
        if {$modvar == "Ring"} { set CallOnRing 1 }
        doVerbose "Optional \"$modopt\" variable set \"$modvar\" to \"$modval\" in ncid.conf" 1
    }
    if {$CallOnRing} {
      switch -- $Ring {
        -9 {doVerbose "Will execute $Program every ring after CID" 1}
        -2 {doVerbose "Will execute $Program after hangup after answer" 1}
        -1 {doVerbose "Will execute $Program after hangup with no answer" 1}
         0 {doVerbose "Will execute $Program when ringing stops" 1}
         default {doVerbose "Will execute $Program at Ring $Ring" 1}
      }
    } elseif {$Program != ""} {
       doVerbose "Will execute $Program when CID arrives" 1
    }
}
if {$NoGUI} doPID
connectCID
if {!$NoGUI} {bind .im <KeyPress-Return> handleGUIMSG}

# enter event loop
vwait forever
