#!/usr/bin/bash

# ncid - Network Caller-ID client

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

# 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/>.

# Lines beginning with # and ending in backslash are a trick to allow sh
# to execute the lines and tclsh/wish to ignore them.

# START OF LOCAL MODIFICATION SECTION
# set PATH to include /usr/local/bin \
  PATH=$PATH:/usr/local/bin; export PATH
# END OF LOCAL MODIFICATION SECTION

# eliminate duplicate PATHS https://unix.stackexchange.com/questions/40749/remove-duplicate-path-entries-with-awk-command \
  PATH=$(printf %s "$PATH" | awk -v RS=: '!a[$0]++' | paste -s -d: -)

# determine full path to tclsh and wish binaries
# set TCLSH variable, FreeBSD will call it something like tclsh8.4 \
  TCLSH=`type tclsh | sed 's,.* \/,/,'`
# set WISH variable, FreeBSD will call it something like wish8.4 \
  WISH=`type wish | sed 's,.* \/,/,'`

# configuration file \
  CF=/etc/ncid/ncid.conf

# check for config file and set GUI if not found \
  [ -f $CF ] || GUI=1

# if $GUI not set, set GUI based on configuration file \
  [ "$GUI" == "" ] && GUI=`awk 'BEGIN {GUI = 1} /^ *#/ {next} /^ *set  *NoGUI/ {if ($3 == 1) {GUI = 0}} END {print GUI}' $CF`

# if GUI == 1, look for the --no-gui option, if found set GUI="0" \
  [ "$GUI" == "1" ] && for i in $*; do if [ "$i" = "--no-gui" ]; then  GUI="0"; fi; done

# if DISPLAY is not in the environment, set GUI="0" \
  [ -z "$DISPLAY" ] && GUI="0"

# if $HOME is not in the environment, set GUI="0" \
  [ -z "$HOME" ] && GUI="0"

# if wish found and $GUI == 1, look for wish and exec it \
  [ -n "$WISH" ] && [ "$GUI"  == "1" ] && exec $WISH -f "$0" -- "$@"

# if tclsh found and $GUI == 0, look for tclsh and exec it \
  [ -n "$TCLSH" ] && [ "$GUI" == "0" ] && exec $TCLSH "$0" "$@"

# if tclsh found and wish not found, exec tclsh with the --no-gui option \
  [ -n "$TCLSH" ] && exec $TCLSH "$0" --no-gui "$@"

# tcl or tk not found \
  echo "wish or tclsh not found in your \$PATH"; exit -1

# if we're running in tclsh, set non-graphical, otherwise set graphical
set Interpreter         [info nameofexecutable]
if {[regexp {tclsh} $Interpreter]} {set NoGUI 1} else {set NoGUI 0}
set delayedMsgs         "NoGUI set to $NoGUI"

set DefaultHost         127.0.0.1
set DefaultPort         3333
set ConfigFile          /etc/ncid/ncid.conf
set ImageDir            /usr/share/ncid/images
set Logo                $ImageDir/ncid.gif
set ThemeDir            /usr/share/ncid/themes

### global variables that can be changed by
### command line options, the configuration file, the rcfile
set AltDate             0

### global variables that can be changed by
### command line options, the configuration file
set Hosts               [list]
set Delay               15
set PIDfile             ""
set PopupTime           1
set Verbose             0
set CallOnRing          0
set HostnameFlag        0
set Ring                999
set NoExit              0
set WakeUp              0
set ExitOn              exit
set CallLog             0
set Country             "US"
set LogEnable           0
set WinLogDir           "logs"
set WinRCfile           ".ncid"
set asfile              ""

if {![info exists $::env(HOME)]} {
  set UnixLogDir "$::env(HOME)/NCID/client"
  set UnixRCfile "$::env(HOME)/.ncid"
  set UnixASfile "$::env(HOME)/.config/autostart/ncid.desktop"
} else {
  set UnixLogDir "/tmp/ncid"
}

### global variables that can be changed by
### command line options, the rcfile
set Host                ""
set Port                ""

### global variables that can be changed by
### the configuration file, the rcfile
set DateSepar           "/"
set YearDot             0

###  global variables that can only be changed by
#### command line options
set Module              ""

###  global variables that can only be changed by
#### the configuration file
set ModDir              /usr/share/ncid/modules
set ModName             ""
set NoOne               0
set EndDot              ""
set WrapLines           "word"
set DialPrefix          ""
set preClient_1_0       0
set nameWidth           30
set ClipboardPopup      1
set ClipboardPopupTime  3

### global variables that can only be changed by
### the rcfile
set clock               24
set autoSave            "off"
set autoStart           "off"
set fontList            ""
set wmGeometry          ""
set labelList           [list TYPE 1 DATE 1 TIME 1 LINE 1 NMBR 1 \
                         NAME 1 NTYPE 0 CTRY 0 LOCA 0 CARI 0 MTYPE 1]

### global variables that are used as static variables
set NightMode           0
set ThemeName           "day"
set fgColor             ""
set fg2Color            ""
set bckColor            ""
set LogFile             ""
set LogChan             ""
set LogStatus           "Log File:      disabled"
set LogDirLocation      ""
set oldHost             $Host
set oldPort             $Port
set oldDateSepar        $DateSepar
set oldYearDot          $YearDot
set oldAltDate          $AltDate
set oldAutoSave         $autoSave
set oldAutoStart        $autoStart
set oldClock            $clock
set Leading1            "Leave"
set oldLeading1         $Leading1
set oldLabelList        [list]
set DialLineID          ""
set display_line_num    0
set awakened            0
set Begin               0
set End                 0
set DoingCallLog        0
set waitMsg             0
set mod_menu            0
set multi               0
set menuDisabled        0
set typeWidth           4
set dateWidth           10
set timeWidth           5
set lineIDWidth         16
set nmbrWidth           20
set fnmbrWidth          $nmbrWidth
set ntypeWidth          8
set countryWidth        4
set locationWidth       12
set carrierWidth        13
set mtypeWidth          5
# the alias width must be in the same position as the alias type
set aliasTypes          "NAMEDEP NAMEONLY NMBRDEP NMBRONLY NMBRNAME LINEONLY"
set aliasWidths         "$nameWidth $nameWidth $nmbrWidth $nmbrWidth $nmbrWidth $lineIDWidth"
set aliasList           ""
set dtfile              "/usr/share/applications/ncid.desktop"
array set hup           {}
array set NBRarray      {}
set HostIndex           -1
set SelAliasType        ""
set ChangeHostFlag      0
set hupColor            ""
set ltColor             ""
set dateColor           ""
set timeColor           ""
set lineColor           ""
set nmbrColor           ""
set nameColor           ""
set mtColor             ""
set tvFgColor           ""
set tvBckColor          ""
set vhFgColor           ""
set vhBckColor          ""
set vhInsColor          ""
set exactMatch          0
set posFlag             0

set LEVEL1              1
set LEVEL2              2
set LEVEL3              3
set LEVEL4              4
set LEVEL5              5
set LEVEL6              6
set LEVEL7              7
set LEVEL8              8
set LEVEL9              9

set TypeGroups          [list]
set oldTypeGroups       [list]

set SelectedTypes       {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 0\
                         PUT 0 RID 0 WID 0 MSG 0 NOT 0}
set oldSelectedTypes    [list]
set SelectedAllTypes    {BLK 1 CID 1 HUP 1 MWI 1 OUT 1 PID 1\
                         PUT 1 RID 1 WID 1 MSG 1 NOT 1}
set SelectedCalls       {BLK 1 CID 1 HUP 1 MWI 1 OUT 1 PID 1\
                         PUT 1 RID 1 WID 1 MSG 0 NOT 0}
set SelectedMessages    {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 0\
                         PUT 0 RID 0 WID 0 MSG 1 NOT 1}
set SelectedSmartPhone  {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 1\
                         PUT 1 RID 0 WID 0 MSG 0 NOT 1}

set LineIDGroups         0
set oldLineIDGroups      0
set DiscoveredLineIDs    [list]
set SelectedLineIDs      [list]
set oldSelectedLineIDs   [list]

# Global Variables
set ServerOptions       ""
set Dialed              0
set ServerOptLineIDS    ""
set svrLID              ""
set OptPmsg             ""

set ConfigFileHost      ""
set ConfigFilePort      ""
set ConfigFileoldHost   ""
set ConfigFileoldPort   ""
set ConfigFileHosts     ""
set ConfigFileHostIndex ""

set ArgHost             ""
set ArgPort             ""
set ArgoldHost          ""
set ArgoldPort          ""
set ArgHosts            ""
set ArgHostIndex        ""

set RCfileHost          ""
set RCfilePort          ""
set RCfileoldHost       ""
set RCfileoldPort       ""
set RCfileHosts         ""
set RCfileHostIndex     ""
set RCfileThemeName     ""

set PortableDir         ""
set nmbrREQ             ""
set fnmbrREQ            ""
set nameREQ             ""
set lineREQ             ""

set ScriptDir [file normalize [file dirname [info script]]]
if {[file exists [file join $ScriptDir [file tail $ConfigFile]]]} {
  set PortableDir $ScriptDir
  set ConfigFile [file join $PortableDir [file tail $ConfigFile]]
  set ThemeDir [file join $PortableDir [file tail $ThemeDir]]
}

if {$::tcl_platform(platform) == "unix"} {set LogDir $UnixLogDir
} elseif {$::tcl_platform(platform) == "windows"} {set LogDir $WinLogDir}

if {[file exists $ConfigFile]} {
  # note: debug log has not been opened yet so logMsg is unavailable
  source $ConfigFile
  set delayedMsgs "$delayedMsgs\nProcessed config file: $ConfigFile"
  #deprecated warning will be given once the log file has been opened
  if {$Hosts == ""} {
    if {$Host != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Host: $Host\n***** WARNING: config file option 'Host' is deprecated, use 'Hosts'"
    }
    if {$Port != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Port: $Port\n***** WARNING: config file option 'Port' is deprecated, use 'Hosts'"
    }
    if {$Host != "" || $Port != ""} {
      if {$Host == ""} {
        set newhost $DefaultHost
      } else {set newhost $Host}
      if {$Port == ""} {
        set newport $DefaultPort
      } else {set newport $Port}
      set Hosts "$newhost:$newport"
      set delayedMsgs \
        "$delayedMsgs\n***** Hosts: $newhost:$newport"
    }
  } else {
    if {$Host != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Host: $Host\n***** WARNING: using config file 'Hosts option, remove deprecated 'Host' option."
      set Host ""
    }
    if {$Port != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Port: $Port\n***** WARNING: using config file 'Hosts' option, remove deprecated, 'Port' option."
      set Port ""
    }
  }
} else {set delayedMsgs "$delayedMsgs\n*** Config file Missing: $ConfigFile"}

set ConfigFileHost $Host
set ConfigFilePort $Port
set ConfigFileoldHost $ConfigFileHost
set ConfigFileoldPort $ConfigFilePort
set ConfigFileHosts $Hosts
set ConfigFileHostIndex $HostIndex

### Constants

set CygwinBat     /cygwin.bat

set viewTextWidth 70

# historyTextWidth = $columnlabel -1 (not calculated yet)
set historyTextWidth ""

set histMinRows 4
set historyTextRows $histMinRows

# these are the column labels as displayed to the user
set dispTYPE  "TYPE"
set dispDATE  "DATE"
set dispTIME  "TIME"
set dispLINE  "LINE ID"
set dispNMBR  "NUMBER"
set dispNAME  "NAME"
set dispNTYPE "NTYPE"
set dispCTRY  "CTRY"
set dispLOCA  "LOCATION"
set dispCARI  "CARRIER"
set dispMTYPE "MTYPE"

#... and their corresponding lengths for alignment purposes e.g., $labXXXX in $fieldList
set dlenTYPE  [string length $dispTYPE]
set dlenDATE  [string length $dispDATE]
set dlenTIME  [string length $dispTIME]
set dlenLINE  [string length $dispLINE]
set dlenNMBR  [string length $dispNMBR]
set dlenNAME  [string length $dispNAME]
set dlenNTYPE [string length $dispNTYPE]
set dlenCTRY  [string length $dispCTRY]
set dlenLOCA  [string length $dispLOCA]
set dlenCARI  [string length $dispCARI]
set dlenMTYPE [string length $dispMTYPE]

set dlenAll   [list $dlenTYPE $dlenDATE $dlenTIME  $dlenLINE \
                    $dlenNMBR $dlenNAME $dlenNTYPE $dlenCTRY \
                    $dlenLOCA $dlenCARI $dlenMTYPE           \
              ]

set dlenMax   [lindex [lsort -integer $dlenAll] end]

set typelabel [format "%-${typeWidth}.${typeWidth}s" $dispTYPE]
set linelabel [format "%-${lineIDWidth}.${lineIDWidth}s" $dispLINE]
set fnmbrlabel [format "%-${fnmbrWidth}.${fnmbrWidth}s" $dispNMBR]
set ntypelabel [format "%-${ntypeWidth}.${ntypeWidth}s" $dispNTYPE]
set namelabel [format "%-${nameWidth}.${nameWidth}s" $dispNAME]
set countrylabel [format "%-${countryWidth}.${countryWidth}s" $dispCTRY]
set locationlabel [format "%-${locationWidth}.${locationWidth}s" $dispLOCA]
set carrierlabel [format "%-${carrierWidth}.${carrierWidth}s" $dispCARI]
set mtypelabel [format "%-${mtypeWidth}.${mtypeWidth}s" $dispMTYPE]

# DATE field width is either 10, 11 or 15 characters
# TIME field width is either 5 or 8 characters
set datenum [format "%-10.10s" $dispDATE]
set datenumyd [format "%-11.11s" $dispDATE]
set dateword [format "%-15.15s" $dispDATE]
set time24 [format "%-5.5s" $dispTIME]
set time12 [format "%-8.8s" $dispTIME]

set datelabel   "$datenum"
set timelabel   "$time24"

if {$ModName != ""} {
    if {[file dirname $ModName] ne ""} {
        set ModName [file tail $ModName]
        set Module "$ModDir/$ModName"
    }
} else {set Module ""}

### global variables that are fixed
set Count       0
set ExecSh      0
set Socket      0
set Try         0
set Version     "1.12"
#during development the version# is embedded in the file name
#'tolower' and 'repeat' thwarts sed in Makefile
if {[string tolower $Version] == [string repeat "x" 5]} {set Version [file tail [info script]]}

set VersionIDENT "client ncid (NCID) $Version"
set Usage       {Usage:   ncid  [OPTS] [ARGS]
         OPTS: [--no-gui]
               [--alt-date                  | -A]
               [--call-log                  | -c]
               [--country-code <code>       | -C <country code>]
               [--delay <seconds>           | -D <seconds>]
               [--help                      | -h]
               [--hostname-flag             | -H]
               [--log-enable <0-2>          | -l <0-2>]
               [--log-dir <dirname>         | -L <dirname>]
               [--module <module name>      | -m <module name>]
               [--noexit                    | -X]
               [--pidfile <file>            | -p <file>]
               [--PopupTime <0-5>           | -t <0-5>]
               [--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 hostname [info hostname]
regsub {([^.]*).*} $hostname {\1/ncid} LineName
set VersionInfo "Client: ncid \[$hostname\] (NCID) $Version"

set noServer      "Server: not connected"
set ServerVersion $noServer

set Author \
"
Copyright © 2001-2021
John L. Chmielewski
"

set Website "http://ncid.sourceforge.net"

set labBLK "BLK:  Blocked           - blacklisted call blocked"
set labCID "CID:  Caller ID         - incoming call"
set labHUP "HUP:  Hangup            - blacklisted call hangup"
set labMSG "MSG:  Message           - text message from a user or the server"
set labMWI "MWI:  Voicemail         - one or more voicemail messages"
set labNOT "NOT:  Notice            - a smartphone message notice"
set labOUT "OUT:  Out               - outgoing call"
set labPID "PID:  Phone ID          - Caller ID from a smartphone"
set labPUT "PUT:  Phone out call    - outgoing Caller ID from a smartphone"
set labRID "RID:  Ring Back         - rings back when called number is available"
set labWID "WID:  Call Waiting ID   - Caller ID from call waiting"

set labList \
"
$labBLK
$labCID
$labHUP
$labMSG
$labMWI
$labNOT
$labOUT
$labPID
$labPUT
$labRID
$labWID
"

set labTYPE  "[format "%-${dlenMax}.${dlenMax}s" "$dispTYPE"  ] - call or message label"
set labDATE  "[format "%-${dlenMax}.${dlenMax}s" "$dispDATE"  ] - date of call or message"
set labTIME  "[format "%-${dlenMax}.${dlenMax}s" "$dispTIME"  ] - time of call or message"
set labLINE  "[format "%-${dlenMax}.${dlenMax}s" "$dispLINE"  ] - telephone line label"
set labNMBR  "[format "%-${dlenMax}.${dlenMax}s" "$dispNMBR"  ] - caller phone number"
set labNAME  "[format "%-${dlenMax}.${dlenMax}s" "$dispNAME"  ] - caller name"
set labNTYPE "[format "%-${dlenMax}.${dlenMax}s" "$dispNTYPE" ] - caller phone type (fixed, mobile, pager, etc)"
set labCTRY  "[format "%-${dlenMax}.${dlenMax}s" "$dispCTRY"  ] - Two letter country code of calling number"
set labLOCA  "[format "%-${dlenMax}.${dlenMax}s" "$dispLOCA"  ] - the locality (city/region) of the calling number"
set labCARI  "[format "%-${dlenMax}.${dlenMax}s" "$dispCARI"  ] - the carrier of the calling phone number"
set labMTYPE "[format "%-${dlenMax}.${dlenMax}s" "$dispMTYPE" ] - message type"

set fieldList \
"
$labTYPE
$labDATE
$labTIME
$labLINE
$labNMBR
$labNAME
$labNTYPE
$labCTRY
$labLOCA
$labCARI
$labMTYPE
"

set aliasDesc \
"
NAMEDEP   - change name to an alias if number receivd
NAMEONLY  - change name to an alias
NUMBDEP   - change nmbr to an alias if name receivd
NMBRONLY  - change nmbr to an alias
NMBRNAME  - change both name and number to an alias if same
LINEONLY  - change the line ID to an alias
"

set viewHelp \
"
Types:   Shows all NCID line types.  Those
         not crossed out are those that may
         or may not show up in the history
         window.

LineIDs: Shows all line identifications.
         Those that are not crossed out
         are those that show up in the
         history window.
"

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 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, whitelist and clipboard menu entries.  If a modem
is active, \"Dial Number Manually \" is enabled.  If the line
selected is all digits, \"Dial Number From History\" will also
be enabled.

You can remove a history window line selection by clicking
on \"Send Message:\" or on a text line below it

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
"

########################################################################
#                       PROCEDURE DEFINITIONS                          #
########################################################################

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

    if {$LogChan != ""} {
        set systemTime [clock seconds]
        puts $LogChan "\[[clock format $systemTime -format "%m/%d %H:%M"]\] $msg"
    }

    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 -icon error -type ok
    }
    exit $code
}

# platform, OS, etc.
proc machine {which} {
  # Observed values      platform       os             osgui
  # Windows 10:          windows        Windows NT     win32
  # Mac (native GUI):    unix           Darwin         aqua
  # Mac (XQuartz):       unix           Darwin         x11
  # AndroWish:           unix           Linux          x11
  # Fedora 25 cinnamon:  unix           Linux          x11
  # FreeBSD              unix           FreeBSD        x11

  switch $which {
    platform {
      if {$::sdltk_present && [expr [sdltk android]]} {return "android"}
      if {$::sdltk_present && [expr [sdltk ischromebook]]} {return "chromebook"}
      return $::tcl_platform(platform)
    }
    os {
      if {$::sdltk_present && [expr [sdltk ischromebook]]} {return "Chrome OS"}
      if {$::sdltk_present && [expr [sdltk android]]} {return "Linux"}
      return $::tcl_platform(os)
    }
    osgui {
      return [tk windowingsystem]
    }
    osname {
      set osname [machine os]
      if {$osname eq "Darwin"} {return "OS X"}
      if {$osname eq "Chrome OS"} {return "Chromebook"}
      if {$::sdltk_present && [expr [sdltk android]]} {return "Android"}
      return $osname
    }
    model {
      if {$::borg_present} {
        array set temparray [borg osbuildinfo]
        return $temparray(model)
      } else {return "not available"}
    }
  }
}

# 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
    global ServerVersion noServer

    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
        set ServerVersion $noServer
    }

    connectCID
}

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

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

proc ncidInfo {} {

set sysInfo1 \
"
Windowing System: [string totitle [machine osgui]]
Operating System: [machine osname]
Platform        : [string totitle [machine platform]]
Theme           : [string totitle $::ThemeName]"

if {[file isdirectory $::ThemeDir]} {
    set sysInfo2 "\nAddon Themes Dir: $::ThemeDir/"
} else {set sysInfo2 ""}

set sysInfo3 \
"
Config File     : $::ConfigFile
Preference File : $::rcfile
Wish Executable : $::Interpreter
                  Version [info patchlevel]
[regsub {:} $::LogDirLocation {   :}]
[regsub {:     } [regsub {          } $::LogStatus {             }] {        :}]
"
    if {$sysInfo2 == ""} {
        set sysinfo "$sysInfo1$sysInfo3"
    } else {
        set sysinfo "$sysInfo1$sysInfo2$sysInfo3"
    }
}

proc getWidgetProps {verboseLevel verboseMsg widgetPath whichOptions} {

        #if whichOptions is "all" then all properties are dumped
        #otherwise, only options related to color, relief and text are dumped

        logMsg $verboseLevel "$verboseMsg Current theme reported by ttk::style is: [ttk::style theme use]"

        lmap c [$widgetPath configure ] {
             if {[llength $c] == 2} continue;
             set testvarname [lindex $c 0]
             set doDump 1
             if {$whichOptions != "all"} {
                 set doDump 0
                 if {[string match -nocase "*ground*" $testvarname] || [string match -nocase "*color*" $testvarname] |\
                      [string match -nocase "*relief*" $testvarname] | [string match -nocase "*text*" $testvarname]} {
                      set doDump 1
                 }
            }
            if {$doDump} {
                 set val [$widgetPath cget $testvarname]
                 logMsg $verboseLevel "$verboseMsg $widgetPath [format "%-25.25s %s" "$testvarname" "$val"]"
            }
        }

}

# performs crude checks on host and port
# https://www.appypie.com/faqs/what-characters-are-allowed-in-a-domain-name
#   The characters allowed in a domain name include letters (abc), numbers
#   (123), and dashes/hyphens (---). No spaces are allowed and the domain
#   name can't begin or end with dash/hyphen.
proc checkHosts {} {
    global Hosts

    # Hosts = host:port [host:port] [...]
    foreach hostport $Hosts {
        lassign [split $hostport ":"] host port
        if {[string length $host] < 4} {
            exitMsg 8 "Network address too short: $host"
        } elseif {![regexp {^[\w][\w.-]+$} $host]} {
            exitMsg 8 "Network address has characters not allowed: $host"
        }
        if {[regexp {^.*-$} $host]} {
            exitMsg 8 "Network address must not end in a dash: $host"
        }
        if {![regexp {^\d{4,5}$} $port]} {
            exitMsg 9 "Network port must be 4 or 5 digits: $port"
        }
    }
}

proc getWindowProps {verboseLevel verboseMsg windowPath} {

    foreach i {geometry manager name parent rootx rooty width height screenwidth screenheight vrootwidth vrootheight  \
               vrootx vrooty x y} {
        set val [winfo $i $windowPath]
        logMsg $verboseLevel "$verboseMsg $windowPath [format "%-25.25s %s" "$i" "$val"] "
    }

}

# Add descriotipns to server options other than LineIDS
proc descServerOpt {serveropt} {
    switch -regexp $serveropt {
        "hangup-1|hupmode-1" {set serveropt "$serveropt: Normal Hangup"}
        "hangup-2|hupmode-2" {set serveropt "$serveropt: Fax Hangup"}
        "hangup-3|hupmode-3" {set serveropt "$serveropt: Voice Hangup"}
        "regex-0" {set serveropt "$serveropt: NCID Simple Expressions"}
        "regex-1" {set serveropt "$serveropt: Posix Regular Expressions"}
        "regex-2" {set serveropt "$serveropt: Perl Regular Expressions"}
        "ignore1" {set serveropt "$serveropt: Ignore Leading 1 In Number"}
    }
    return $serveropt
}

proc checkLogDir {} {
    global LogDir

    if {$LogDir eq ""} {
        exitMsg 11 "no log directory name, use --log-dir|-L <dirname>"
    } elseif {![file exists $LogDir]} {
        if {[catch {file mkdir $LogDir} msg]} {
            exitMsg 11 "$LogDir: #mesg"
        }
    }

    if {![file readable $LogDir]} {
        exitMsg 11 "$LogDir: not readable"
    } elseif {![file writable $LogDir]} {
        exitMsg 11 "$LogDir: not writeable"
    }
}

# Get data from CID server
proc getCID {} {
    global Module Host Port Socket NoGUI Try Verbose VersionInfo ServerVersion noServer
    global Ring CallOnRing
    global cid label display_line_num DoingCallLog
    global call Dialed lineIDlabel ServerOptLineIDS Country
    global WakeUp wakened targetTime Begin End ClientJobResult waitMsg
    global mod_menu argument menuDisabled
    global CIDaliasType LineAliasType
    global ServerOptions nmbrREQ
    global hup bckColor
    global TypeGroups SelectedTypes ChangeHostFlag
    global LineIDGroups SelectedLineIDs DiscoveredLineIDs

    # convert list to array
    array set t_array $SelectedTypes

    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 ServerVersion $noServer
            set ServerOptions ""
            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*Number* -state disabled
                  $menu entryconfigure Copy*to*Clipboard* -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
            logMsg $::LEVEL1 $dataBlock
            regsub {200 (.*)} $dataBlock {\1} dataBlock
            set ServerVersion $dataBlock
            if $NoGUI {
               logMsg $::LEVEL1 "$VersionInfo\n$ServerVersion"
               set targetTime 0
            } else {
                set targetTime [expr [clock clicks -milliseconds] + 500]
                displayCID "$VersionInfo\n$ServerVersion" 1
                }
        } elseif {[string match 254* $dataBlock]} {
            # NCID server sent start of call log message
            if {!$NoGUI} {
                set DoingCallLog 1
                .vh configure -state normal
                .vh insert 1.0 "\n\n\t\tReading the call log\n\n"
                set Begin [clock clicks -milliseconds]
            }
        } elseif {[string match {25[0-3]*} $dataBlock]} {
            # NCID server sent call 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 DoingCallLog 0
            if {[regexp {250} $dataBlock]} {
                # NCID server sent end of call log message
                if {!$NoGUI} {
                    set DiscoveredLineIDs [lsort -dictionary $DiscoveredLineIDs]
                    if {$ChangeHostFlag == 1} {
                        logMsg $::LEVEL1 "$dataBlock - $display_line_num lines"
                        set SelectedLineIDs $DiscoveredLineIDs
                        write_rc_file "set SelectedLineIDs" "set SelectedLineIDs \"$SelectedLineIDs\""
                        set ChangeHostFlag 0
                    }
                }
                set End [clock clicks -milliseconds]
                set elapsed [expr $End - $Begin]
                logMsg $::LEVEL2 "$display_line_num call history entries in $elapsed milliseconds"
            } else {logMsg $::LEVEL1 "$dataBlock"}
        } elseif {[string match 300* $dataBlock]} {
            # NCID server sent end of startup message
            if {$ServerOptions == ""} {set ServerOptions "\nnone"}
            logMsg $::LEVEL1 "Country: $Country"
            logMsg $::LEVEL2 "ServerOptions: [split [regsub -all {^\n|  +} $ServerOptions {}] "\n"]"
            if {!$NoGUI} {logMsg $::LEVEL3 "DiscoveredLineIDs: $DiscoveredLineIDs"}
            logMsg $::LEVEL1 $dataBlock
            continue
        } elseif {[string match 400* $dataBlock]} {
            # NCID server has sent text to be displayed
            logMsg $::LEVEL1 $dataBlock
            toplevel .reply -background $bckColor
            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
            logMsg $::LEVEL1 $dataBlock
            toplevel .reply -background $bckColor
            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]} {
            logMsg $::LEVEL1 $dataBlock
             set ClientJobResult ""
        } elseif {[string match 403* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            set mod_menu 1
        } elseif {[string match 410* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            .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]} {
            logMsg $::LEVEL1 $dataBlock
            if {$mod_menu} {
                set mod_menu 0
                continue
            }
            if {[string length $ClientJobResult] < 4} {
                set ClientJobResult "Done."
            }
            if {$Dialed} {
                # $Dialed == 2 when dial aborted
                if {$Dialed == 1} {
                    .dial.close configure -state active
                    .dial.abort configure -state active
                }
                set Dialed 0
            } else {.confirm.close configure -state active}
        } elseif {[string match INFO:* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            if {$mod_menu} {
                set menu .menubar.server
                $menu entryconfigure Copy*to*Clipboard* -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.dial entryconfigure From*History* -state disabled
                        } else {
                            $menu.dial entryconfigure From*History* -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]} {
            logMsg $::LEVEL1 $dataBlock
            if ($Dialed) {
                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 ClientJobResult [string range $dataBlock 6 end]
            append ClientJobResult "\n"
            continue
        } elseif {[string match RPLY:* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            set ClientJobResult [string range $dataBlock 6 end]
            append ClientJobResult "\n"
            destroy .dial
            doRPLY
        } elseif {[string match OPT:* $dataBlock]} {
            set serveropt [string trim [string range $dataBlock 5 end]]
            if {[string first "LineIDS:" $serveropt 0] != -1} {
                regsub {^.*LineIDS: (.*)$} $serveropt {\1} ServerOptLineIDS
                if {!$NoGUI} {
                    .menubar.server entryconfigure Dial*Number* -state normal
                }
            }
            if {[string first "country: " $serveropt 0] != -1} {
                regsub {^.*country: (.*)$} $serveropt {\1} Country
            }
            logMsg $::LEVEL3 "Received Server Option: $serveropt"
            set ServerOptions "$ServerOptions\n[descServerOpt $serveropt]"
        }
        if {[set label [checkType $dataBlock]]} {
            if {$label == 3} {
                # CIDINFO (ring) line
                set ringinfo [getField RING $dataBlock]
                # must use $call($lineinfo) instead of $cid
                set lineinfo [getField LINE $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "$lineinfo"]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                }
                if {[array get call $lineinfo] != {}} {
                  set CIDtype [lindex $call($lineinfo) 5]
                  if {$ringinfo == -4} {
                    if {!$NoGUI } {
                      # get line from hup array and restore HUP color in GUI
                      if {[catch {set displine $hup($lineinfo)} huperr]} {
                        logMsg $::LEVEL1 "huperr"
                      } else {
                        # restore theme color to $CIDtype
                        .vh configure -state normal
                        .vh replace $displine.0 $displine.0+4c "$CIDtype:" lttag
                        .vh configure -state disabled
                      }
                    }
                  } elseif {$CallOnRing && $CIDtype == "CID"} {
                    if {$Module != "" && ($Ring == $ringinfo ||
                        ($Ring == -9 && $ringinfo > 1))} {
                      sendCID $call($lineinfo)
                      logMsg $::LEVEL1 "$dataBlock"
                    } else { logMsg $::LEVEL6 "$dataBlock" }
                  }
                } else {
                    logMsg $::LEVEL3 "No Call Array label \"$lineinfo\" at RING $ringinfo"
                }
                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]
                if {!$NoGUI} {
                    set status [processLineID "[lindex $msg 4]"]
                    set thisTYPE [lindex $msg 5]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                    if {$TypeGroups == 1} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {continue}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {continue}
                }
                displayLog $msg 1
                if {$label == 4} {
                    if {!$NoGUI} {
                        displayCID "[lindex $msg 7]\n" 1
                        doPopup
                    }
                    if {$Module != ""} {
                        sendMSG $msg
                    }
                }
            } elseif {$label == 1 || $label == 2} {
                # CID (1), HUP (1) OUT (1), RID (1)
                # BLK (2), MWI (2), PID (2), PUT(2), WID (2)
                if {$WakeUp} {
                    if {!$wakened} {
                        doWakeup
                    } else {set wakened 0}
                }
                set cid [formatCID $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "[lindex $cid 4]"]
                    set thisTYPE [lindex $cid 5]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                    if {$TypeGroups == 2} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {continue}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {continue}
                }
                if {$label == 1} {array set call "{$lineIDlabel} [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) && $Module != ""} {
                    sendCID $cid
                }
            } elseif {$label == 6} {
                # BLKLOG, CIDLOG, HUPLOG, MWILOG, OUTLOG, PIDLOG, PUTLOG, RIDLOG, WIDLOG
                set cid [formatCID $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "[lindex $cid 4]"]
                    set thisTYPE [lindex $cid 5]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                    if {$TypeGroups == 2} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {continue}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {continue}
                }
                # 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
                }
            }
        }
    }
    if {!$NoGUI && $DiscoveredLineIDs != ""} {updateViewDisplay}
}

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, PUT, 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 PUT:* $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 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 PUTLOG:* $dataBlock] {set rtn 6
    } elseif [string match RIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match WIDLOG:* $dataBlock] {set rtn 6

    } elseif [string match LOG:* $dataBlock] {set rtn 7

    } elseif [string match RLY:* $dataBlock] {set rtn 8

    } elseif [string match RLYLOG:* $dataBlock] {set rtn 9

    } elseif [string match END:* $dataBlock] {set rtn 10

    } elseif [string match ENDLOG:* $dataBlock] {set rtn 11}
    logMsg $::LEVEL6 "Assigned $rtn for $dataBlock"
    return $rtn
}

# must be sure the line passed checkType
# returns: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
#          $cidfnumber $cidntype $cidcountry $cidlocation $cidcarrier
proc formatCID {dataBlock} {
    global lineIDlabel lineIDWidth

    set cidname [formatNAME $dataBlock]
    set cidnumber [formatNMBR $dataBlock]
    set cidfnumber [formatFNMBR $dataBlock]
    set cidntype [formatNTYPE $dataBlock]
    set cidcarrier [formatCARI $dataBlock]
    set cidcountry [formatCTRY $dataBlock]
    set cidlocation [formatLOCA $dataBlock]
    set ciddate [formatDATE $dataBlock]
    set cidtime [formatTIME $dataBlock]
    set cidline ""
    if [string match {*\*LINE\**} $dataBlock] {
        set cidline [formatLINE $dataBlock]
    }
    # if formatted number is empty , we fill it ,with whatever is in
    # unformatted number  (eg : "NO-NUMBER" )
    if { $cidfnumber == "" } {
         set cidfnumber $cidnumber
       }
    # 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 {} lineIDlabel
    # set type of call
    if {![regsub {(\w+)LOG:.*} $dataBlock {\1} linetype]} {
        regsub {(\w+):.*} $dataBlock {\1} linetype
    }

    return [list $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" "" $cidfnumber $cidntype $cidcountry $cidlocation $cidcarrier]
}

# returns: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $mesgtype
#          $message $msgfnmbr $msgntype $msgcountry $msglocation $msgcarrier
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 msgfnmbr [formatFNMBR $dataBlock]
        if { $msgfnmbr == "" } {
            set msgfnmbr $msgnmbr
        }
        set msgntype [formatNTYPE $dataBlock]
        set msgcarrier [formatCARI $dataBlock]
        set msgcountry [formatCTRY $dataBlock]
        set msglocation [formatLOCA $dataBlock]
        set msgline [formatLINE $dataBlock]
        set msgtype [formatMTYPE $dataBlock]
        regsub {\w+:\s+(.*) \*\*\*DATE.*} $dataBlock {\1\2} mesg
        set message [list $msgdate $msgtime $msgnmbr $msgname $msgline $linetype $msgtype $mesg $msgfnmbr $msgntype $msgcountry $msglocation $msgcarrier]
    } else {
        regsub {\w+:\s+(.*)} $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
    switch $AltDate {
        0 {
            # Date format: MM/DD/YYYY or MM/DD
            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
            }
        }
        1 {
            # Date format: DD/MM/YYYY or DD/MM
            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
            }
        }
        2 {
            # Date format: weekday month DD YYYY
            regsub {([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])} \
                $ciddate {\1/\2/\3} ciddate
            set ciddate "[clock format [clock scan $ciddate -format "%m/%d/%Y"] \
                       -format {%a %b %d %Y}]"
        }
        3 {
            # Date format: weekday DD month YYYY
            regsub {([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])} \
                $ciddate {\1/\2/\3} ciddate
            set ciddate "[clock format [clock scan $ciddate -format "%m/%d/%Y"] \
                       -format {%a %d %b %Y}]"
        }
    }
    if {$AltDate < 2} {
        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]
    if {$cidname == "-"} {set cidname "NO NAME"}
    return $cidname
}
proc formatFNMBR {dataBlock} {
    set cidfnumber [getField FNMBR $dataBlock]
    return $cidfnumber
}
proc formatNTYPE {dataBlock} {
    set cidntype [getField NTYPE $dataBlock]
    return $cidntype
}

proc formatCARI {dataBlock} {
    set cidcarrier [getField CARI $dataBlock]
    return $cidcarrier
}
proc formatCTRY {dataBlock} {
    set cidcountry [getField CTRY $dataBlock]
    return $cidcountry
}
proc formatLOCA {dataBlock} {
    set cidlocation [getField LOCA $dataBlock]
    return $cidlocation
}

proc formatNMBR {dataBlock} {
    set cidnumber [getField NMBR $dataBlock]
    if {$cidnumber == "-"} {set cidnumber "NO-NUMBER"}

    if {$cidnumber == ""} {set cidnumber "NO-NUMBER"}
    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} {
  set result= ""
  regsub ".*\\*$dataString\\*" $dataBlock {} result

  if { $result == $dataBlock } {
     return ""
     #field not found
     # (transitional cidcall.log)
  }

  switch $dataString {
    RING -
    DATE -
    TIME {
      regsub {(\d+).*} $result {\1} result
    }
    LINE {
      regsub {([\w\s@!-]+)\*.*} $result {\1} result
    }
    NMBR -
    FNMBR -
    NTYPE -
    CTRY -
    LOCA -
    CARI -
    MTYPE -
    NAME {
    regsub {\*DATE\*.*|\*TIME\*.*$|\*LINE\*.*|\*NMBR\*.*|\*FNMBR\*.*|\*NTYPE\*.*|\*CTRY\*.*|\*LOCA\*.*|\*CARI\*.*|\*MESG\*.*|\*NAME\*.*|\*MTYPE\*.*|\*$} $result "" result
    }
    default {
    logMsg $::LEVEL7 "getField Default clause"
      regsub {([\w-]+)\*} $result {\1} result
    }
  }
  return $result
}

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

  set modcid "$cid"
  # send DATE\nTIME\nNUMBER\nNAME\nLINE\nTYPE\n""\n""\nFNUMBER\nNTYPE\nCTRY\nLOCATION\nCARRIER
  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]\n[lindex $cid 8]\n[lindex $cid 9]\n[lindex $cid 10]\n[lindex $cid 11]\n[lindex $cid 12]\n"
  if $ExecSh {
    catch {exec sh -c $Module << "$modin" >@stdout &} oops
  } else {
    catch {exec $Module << "$modin" >@stdout &} oops
  }
  logMsg $::LEVEL1 "$modtype\nSent $Module $modcid"
}

# pass the message to an external program
# input: $msgdate $msgtime $msgnumber $msgname $msgline $msgtype $mtype $msg $msgfnumber $msgntype $msgcountry $msglocation $msgcarrier
proc sendMSG {msg} {
  global Module ExecSh 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 $Module << "$modin" >@stdout &} oops
    } else {
      catch {exec $Module << "$modin" >@stdout &} oops
    }
  } else {
    # send DATE\nTIME\nNMBR\nNAME\nLINE\nTYPE\n\MESG\nMTYPE\nFNUMBER\nNTYPE\nMSGCOUNTRY\nMSGLOCATIONn\nMSGCARRIER
    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[lindex $msg 8]\n[lindex $msg 9]\n[lindex $msg 10]\n[lindex $msg 11]\n[lindex $msg 12]\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][lindex $msg 8]\n[lindex $msg 9]\n[lindex $msg 10]\n[lindex $msg 11]\n[lindex $msg 12]]\n"
    if $ExecSh {
      catch {exec sh -c $Module << "$modin" >@stdout &} oops
    } else {
      catch {exec $Module << "$modin" >@stdout &} oops
    }
  }
  logMsg $::LEVEL1 "$modtype\nSent $Module $mesg"
}

# display CID information or message
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
#        $cidfnumber $cidntype $cidcountry $cidlocation $cidcarrier
# Input: $msgdate $msgtime $msgnumber $msgname $msgline $msgtype $msgio $message
#        $cidfnumber $cidntype $cidcountry $cidlocation $cidcarrier
# ismsg = 0 for CID and 1 for message
proc displayCID {input ismsg} {
    global Txt

    if {$ismsg} {
       set maxwidth [expr $::historyTextWidth - 30]
       set firstNewline [string first "\n" $input]
       if {($firstNewline == [expr [string length $input] - 1]) && ([string length $input] > $maxwidth)} {
          logMsg $::LEVEL2 "Truncating long message of length [string length $input] to fit within width $maxwidth"
          set Txt "[string range [string trim $input] 0 $maxwidth](truncated)"
       } else {
        # string is not too long OR string has embedded new lines already
        set Txt $input
       }
    } else {
        set Txt "[lindex $input 3]\n[lindex $input 8]"
    }
}

# display Call Log
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
#        $cidfnumber $cidntype $cidcountry $cidlocation $cidcarrier
# Input: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $msgtype $message
#        $cidfnumber $cidntype $cidcountry $cidlocation $cidcarrier
proc displayLog {input ismsg} {
    global Module NoGUI
    global NMBRarray
    global display_line_num DoingCallLog
    global nmbrWidth nameWidth lineIDWidth mtypeWidth fnmbrWidth ntypeWidth countryWidth locationWidth carrierWidth
    global hup label labelList
    global hupColor ltColor dateColor timeColor lineColor nmbrColor fnmbrColor nameColor


    if $NoGUI {
        if {$Module == ""} {
            if $ismsg {
                if {[lindex $input 1] eq {}} {
                    # $msgio $message
                    logMsg $::LEVEL1 "[lindex $input 6]: [lindex $input 7]"
                } else {
                    # $msgtype: $msgdate $msgtime $msgline $msgnumber $msgname $msgio $message
                    logMsg $::LEVEL1 "[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 {
                # $linetype: $ciddate $cidtime $cidline $cidnumber $cidname
                logMsg $::LEVEL1 "[lindex $input 5]: [lindex $input 0]  [lindex $input 1] [lindex $input 4] [lindex $input 2] [lindex $input 3]"
            }
        }
        incr display_line_num
    } else {
        # GUI
        incr display_line_num

        # convert list to array
        array set labelArray $labelList

        if {! $DoingCallLog} {.vh configure -state normal}
        if {[lindex $input 1] eq {}} {
        .vh insert end "\n[lindex $input 5]: " lttag [lindex $input 7] msgtag
        } else {
            set ciddate [lindex $input 0]
            set cidtime [lindex $input 1]
            set cidnmbr [format "%${nmbrWidth}.${nmbrWidth}s" [lindex $input 2]]
            set fnmbr [lindex $input 8]
            set cidname [format "%-${nameWidth}.${nameWidth}s" [lindex $input 3]]
            set cidline [format "%-${lineIDWidth}.${lineIDWidth}s" [lindex $input 4]]
            set linetype [lindex $input 5]
            set cidfnmbr [format "%${fnmbrWidth}.${fnmbrWidth}s" [lindex $input 8]]
            set NMBRarray($fnmbr)  [lindex $input 2]
            set cidntype [format "%-${ntypeWidth}.${ntypeWidth}s" [lindex $input 9]]
            set cidcountry [format "%-${countryWidth}.${countryWidth}s" [lindex $input 10]]
            set cidlocation [format "%-${locationWidth}.${locationWidth}s" [lindex $input 11]]
            set cidcarrier [format "%-${carrierWidth}.${carrierWidth}s" [lindex $input 12]]


            if {$label != 6 && $linetype == "HUP"} {

                # hup array used to restore normal color to $linetype
                set lineid [string trimright $cidline]
                array set hup "{$lineid} $display_line_num"

                # set HUP active color to $linetype
                .vh insert end "\n$linetype: " huptag
            } else {
                # set theme color to $linetype
                .vh insert end "\n$linetype: " lttag
            }

            if {$labelArray(DATE)} {
                .vh insert end "$ciddate "     datetag
            }
            if {$labelArray(TIME)} {
                .vh insert end "$cidtime "     timetag
            }
            if {$labelArray(LINE)} {
                .vh insert end "$cidline "     linetag
            }
            if {$labelArray(NMBR)} {
                .vh insert end "$cidfnmbr "     fnmbrtag
            }
            if {$labelArray(NAME)} {
                .vh insert end "$cidname "     nametag
            }
            if {$labelArray(NTYPE)} {
                .vh insert end "$cidntype "    typetag
            }
            if {$labelArray(CTRY)} {
                .vh insert end "$cidcountry "  countrytag
            }
            if {$labelArray(LOCA)} {
                .vh insert end "$cidlocation " locationtag
            }
            if {$labelArray(CARI)} {
                .vh insert end "$cidcarrier "  carriertag
            }


            if $ismsg {
                if {$labelArray(MTYPE)} {
                    set msgtype [format "%-${mtypeWidth}.${mtypeWidth}s" \
                                [lindex $input 6]]
                    set message [lindex $input 7]
                    .vh insert end "$msgtype " mttag $message msgtag
                } else {
                    set message [lindex $input 7]
                    .vh insert end $message
                }
            }
        }
        if {! $DoingCallLog} {
            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
            }
        }
    }
}

#https://www.rosettacode.org/wiki/Word_wrap#Tcl
#label widgets don't have a -wrap option so use this
proc wrapParagraph {width text} {
    regsub -all {\s+} [string trim $text] " " text
    set RE "^(.{1,$width})(?:\\s+(.*))?$"
    for {set result ""} {[regexp $RE $text -> line text]} {} {
    append result $line "\n"
    }
    return [string trimright $result "\n"]
}

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

    set ServerOptions ""
    set Socket 0
    set ServerVersion $noServer

    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
    }

    logServerAddress
    logMsg $::LEVEL3 "Attempting to connect"

    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
                    $menu entryconfigure Dial*Number* -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
                set menuDisabled 0

              if {$Delay == 1} {
                .menubar.file entryconfigure Reconnect* -state normal
              }
            }

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

# valid option with argument examples: -v 1 | -v1 | --verbose 1 | --verbose=1
# combined options are not handled, for example: -AcHXW
proc handleOptArg {} {
    global Usage Opt OptArg OptCnt

    set gotarg 0
    if {[regexp {^--} $Opt]} {
        set len [string length $Opt]
        set pos [string first = $Opt]
        if {$pos != -1 && $len > $pos} {
            set OptArg [string range $Opt [expr $pos + 1] $len]
            set Opt [string range $Opt 0 [expr $pos - 1]]
        } else {incr OptCnt}
    } else {
        if {[string length $Opt] > 2} {
            # single letter option combined with argument
            set OptArg [string range $Opt 2 [string length $Opt]]
            set Opt [string range $Opt 0 1]
        } else {incr OptCnt}
   }

   if {$OptArg == ""} {exitMsg 6 "Missing $Opt argument\n$Usage\n"}
}

proc getArg {} {
    # note: debug log has not been opened yet so logMsg is unavailable
    global Opt OptArg OptCnt OptPmsg delayedMsgs
    global Host Port Hosts HostIndex oldHost oldPort SelectedLineIDs
    global Delay
    global Usage
    global NoGUI
    global Verbose
    global LogEnable LogDir
    global Module ModDir Ring CallOnRing
    global HostnameFlag
    global PIDfile
    global PopupTime
    global NoExit
    global AltDate
    global WakeUp
    global Version
    global WrapLines
    global CallLog
    global NightMode

    set showUsage 0
    set hostport 0

    for {set OptCnt 0} {$OptCnt < $::argc} {incr OptCnt} {
        set OptArg [lindex $::argv [expr $OptCnt + 1]]
        switch -regexp -- [set Opt [lindex $::argv $OptCnt]] {
            {^-r} -
            {^--ring$|^--ring=} {
                handleOptArg
                if {[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$|^--country-code=} {
                # obsolete can be removed in a future release
                handleOptArg
            }
            {^-D} -
            {^--delay$|^--delay=} {
                handleOptArg
                if {[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}
            {^-l} -
            {^--log-enable$|^--log-enable=} {
                handleOptArg
                if {[regexp {^[0-2]+$} $OptArg]} {
                    set LogEnable $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-L} -
            {^--log-dir$|^--log-dir=} {
                handleOptArg
                if {$OptArg == ""} {
                    exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
                set LogDir $OptArg
                checkLogDir
            }
            {^-m} -
            {^--module$|^--module=} -
            {^-P$} -
            {^--program$} {
                if {$Opt == "-P" || $Opt == "--program"} {
                    set delayedMsgs \
                    "$delayedMsgs\n***** WARNING: option -P|--program is deprecated, use option -m|--module"
                }
                handleOptArg
                if {[regexp {^.*/} $OptArg]} {
                    set Module [list $OptArg]
                } else {set Module [list $ModDir/$OptArg]}
            }
            {^-p} -
            {^--pidfile$|^--pidfile=} {
                handleOptArg
                if {[regexp {^[\w./]+} $OptArg]} {
                    set PIDfile $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-t} -
            {^--PopupTime$^--PopupTime=} {
                handleOptArg
                if {[regexp {^[0-5]$} $OptArg]} {
                    set PopupTime $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-v} -
            {^--verbose$|--verbose=} {
                handleOptArg
                if {[regexp {^[1-9]+$} $OptArg]} {
                    set Verbose $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-V$} -
            {^--version$} {set NoGUI 1; puts "ncid (NCID) $Version"; exit 0}
            {^-X$} -
            {^--noexit} {set NoExit 1}
            {^-W$} -
            {^--wakeup$} {set WakeUp 1}
            {^-.*$} {set NoGUI 1; exitMsg 5 "Unknown option: $Opt\n$Usage\n"}
            {^\d+$} {set Port $Opt; set hostport 1}
            {^.+$} {set Host $Opt; set hostport 1}
            default {set NoGUI 1; exitMsg 5 "Unknown option: $Opt\n$Usage\n"}
        }
    }

    if {$showUsage} {exitMsg 1 "$Usage\n"}

    if {$hostport} {
        set SelectedLineIDs ""
        if {[regexp {:} $Host]} {
            set Hosts $Host
            lassign [split $Hosts ":"] Host Port
        } else {
            set Hosts "$Host:$Port"
        }
        set delayedMsgs \
            "$delayedMsgs\nncidd address temporarily changed to $Host:$Port"
    }
    set oldHost ""
    set oldPort ""
}

# ttk Widgets: http://wiki.tcl-lang.org/14796
# https://tkdocs.com/tutorial/styles.html
# 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.
#
# sets styles for Day, Night, and themes (if needed)
proc setStyles {} {
  global ThemeName fgColor bckColor mtColor bckSelColor fgSelColor
  global hupColor dateColor timeColor lineColor nmbrColor nameColor
  global tvFgColor  tvBckColor \
  global vhFgColor  vhBckColor vhInsColor

  set enabletheme 0
  set m .menubar
  set items "file file.auto file.startup server server.hosts server.dial \
             server.copy prefs prefs.types prefs.lines prefs theme help"

  switch $ThemeName {
    night {
      set fgColor "yellow"
      set fg2Color "white"
      set fg3Color "cyan"
      set fgActColor "white"
      set fgSelColor "red"
      set bckColor "#262626"
      set bckActColor "#215d9c"
      set bckSelColor "#262626"
      set bck2SelColor "blue"
      set bckFldColor "#0f0f00"
      set bckFldActColor "#215d9c"
      set roColor "#0f0f00"
      set arrowColor "white"
      set arrowActColor "black"
      set indColor "black"
      set indSelColor "white"
      set indPressColor "white"
      set truColor "#3c3c3c"
      set selColor "white"
      set insColor "white"

      # text colors
      set tvFgColor  "yellow"
      set tvBckColor "#262626"
      set vhFgColor  "white"
      set vhBckColor "#262626"
      set vhInsColor "white"

      # history window column colors
      .vh tag configure huptag  -foreground white   -selectbackground blue
      .vh tag configure lttag   -foreground #7fff7f -selectbackground blue
      .vh tag configure datetag -foreground yellow  -selectbackground blue
      .vh tag configure ftimetag -foreground cyan    -selectbackground blue
      .vh tag configure timetag -foreground cyan    -selectbackground blue
      .vh tag configure linetag -foreground #7fff7f -selectbackground blue
      .vh tag configure fnmbrtag -foreground yellow -selectbackground blue
      .vh tag configure nametag -foreground cyan   -selectbackground blue
      .vh tag configure typetag -foreground #7fff7f   -selectbackground blue
      .vh tag configure countrytag -foreground yellow   -selectbackground blue
      .vh tag configure locationtag -foreground cyan   -selectbackground blue
      .vh tag configure carriertag -foreground #7fff7f   -selectbackground blue
      .vh tag configure mttag   -foreground yellow -selectbackground blue
      .vh tag configure msgtag  -foreground white  -selectbackground blue
    }
    day {
      set fgColor "blue"
      set fg2Color "black"
      set fg3Color "green"
      set fgActColor "white"
      set fgSelColor "red"
      set bckColor "#d9d9d9"
      set bckActColor "#4a90d9"
      set bckSelColor "#d9d9d9"
      set bck2SelColor "blue"
      set bckFldColor "#f0f0ff"
      set bckFldActColor "#4a90d9"
      set roColor "#f0f0ff"
      set arrowColor "black"
      set arrowActColor "white"
      set indColor "#d9d9d9"
      set indSelColor "black"
      set indPressColor "black"
      set truColor "#c9c9c9"
      set selColor "black"
      set insColor "black"

      #text colors
      set tvBckColor "white"
      set tvFgColor  "blue"
      set vhBckColor "white"
      set vhFgColor  "black"
      set vhInsColor "white"

      # history window column colors
      .vh tag configure huptag  -foreground black   -selectbackground lightgreen
      .vh tag configure lttag   -foreground purple  -selectbackground lightgreen
      .vh tag configure datetag -foreground blue    -selectbackground lightgreen
      .vh tag configure timetag -foreground red     -selectbackground lightgreen
      .vh tag configure linetag -foreground purple  -selectbackground lightgreen
      .vh tag configure fnmbrtag -foreground blue    -selectbackground lightgreen
      .vh tag configure nametag -foreground red     -selectbackground lightgreen
      .vh tag configure typetag -foreground purple   -selectbackground lightgreen
      .vh tag configure countrytag -foreground blue     -selectbackground lightgreen
      .vh tag configure locationtag -foreground red     -selectbackground lightgreen
      .vh tag configure carriertag -foreground purple    -selectbackground lightgreen
      .vh tag configure mttag   -foreground red    -selectbackground lightgreen
      .vh tag configure msgtag  -foreground black   -selectbackground lightgreen
    }
    default {
      set enabletheme 1

      set fgColor "black"
      set fg2Color "black"
      set fg3Color "black"
      set fgActColor "black"
      set fgSelColor "black"
      set bckColor "#d9d9d9"
      set bckActColor "#d9d9d9"
      set bckSelColor "#d9d9d9"
      set bck2SelColor "blue"
      set bckFldColor "#f0f0ff"
      set bckFldActColor "#d9d9d9"
      set roColor "#f0f0ff"
      set arrowColor "black"
      set arrowActColor "black"
      set indColor "#d9d9d9"
      set indSelColor "black"
      set indPressColor "black"
      set truColor "#c9c9c9"
      set selColor "black"
      set insColor "black"

      #text colors
      set tvBckColor "white"
      set tvFgColor  "black"
      set vhBckColor "white"
      set vhFgColor  "black"
      set vhInsColor "white"

      # history window column colors
      .vh tag configure huptag  -foreground black -selectbackground #d9d9d9
      .vh tag configure lttag   -foreground black -selectbackground #d9d9d9
      .vh tag configure datetag -foreground black -selectbackground #d9d9d9
      .vh tag configure timetag -foreground black -selectbackground #d9d9d9
      .vh tag configure linetag -foreground black -selectbackground #d9d9d9
      .vh tag configure fnmbrtag -foreground black -selectbackground #d9d9d9
      .vh tag configure nametag -foreground black -selectbackground #d9d9d9
      .vh tag configure typetag -foreground black -selectbackground #d9d9d9
      .vh tag configure countrytag -foreground black -selectbackground #d9d9d9
      .vh tag configure locationtag -foreground black -selectbackground #d9d9d9
      .vh tag configure carriertag -foreground black -selectbackground #d9d9d9
      .vh tag configure mttag   -foreground black -selectbackground #d9d9d9
      .vh tag configure msgtag  -foreground black -selectbackground #d9d9d9
    }
  }
  .tv configure -background $tvBckColor -foreground $tvFgColor \
                -selectbackground $tvBckColor -selectforeground $tvFgColor
  .vh configure -background $vhBckColor -foreground $vhFgColor \
                -insertbackground $vhInsColor
  $m configure -background $bckColor -foreground $fg2Color
  foreach item $items {
    $m.$item configure -background $bckColor -foreground $fg2Color \
      -selectcolor $selColor
  }
if {$::tcl_platform(platform) == "unix"} {
    ttk::style configure TButton \
      -background $bckColor \
      -foreground $fg2Color
    ttk::style map TButton \
      -background [list active $bckActColor] \
      -foreground [list active $fgActColor]
  }
  ttk::style configure TEntry \
    -foreground $fg3Color \
    -fieldbackground $bckFldColor \
    -selectbackground $bckSelColor \
    -selectforeground $fgSelColor \
    -insertcolor $insColor
  ttk::style configure TFrame \
    -background $bckColor
  ttk::style configure TLabel \
    -background $bckColor \
    -foreground $fgColor
  ttk::style configure TLabelframe \
    -background $bckColor \
    -foreground $fg2Color
  ttk::style configure TSpinbox \
    -arrowsize 15 \
    -arrowcolor $arrowColor \
    -background $bckColor \
    -foreground $fg2Color \
    -selectbackground $bck2SelColor
  ttk::style map TSpinbox \
    -background [list active $bckActColor] \
    -fieldbackground [list active $bckFldColor readonly $roColor] \
    -relief [list {pressed !disabled} sunken]
  ttk::style configure TRadiobutton \
    -indicatorcolor $indColor \
    -background $bckColor \
    -foreground $fgColor
  ttk::style map TRadiobutton \
    -indicatorcolor [list selected $indSelColor pressed $indPressColor] \
    -background [list active $bckActColor] \
    -activeforeground [list active $fgActColor]
  ttk::style configure TCheckbutton \
    -indicatorcolor $indColor \
    -background $bckColor \
    -foreground $fgColor
  ttk::style map TCheckbutton \
    -indicatorcolor [list selected $indSelColor pressed $indPressColor] \
    -background [list active $bckActColor]
  ttk::style configure TScrollbar \
    -arrowsize 15 \
    -arrowcolor $arrowColor \
    -background $bckColor \
    -troughcolor $truColor
  ttk::style map TScrollbar \
    -arrowcolor [list active $arrowActColor] \
    -background [list active $bckActColor] \
    -relief [list {pressed !disabled} sunken]
  ttk::style configure TCombobox \
    -arrowsize 15 \
    -arrowcolor $arrowColor \
    -background $bckColor \
    -foreground $fgColor \
    -fieldbackground $bckColor \
    -selectbackground $bckColor \
    -selectforeground $fgColor
  ttk::style map TCombobox \
    -arrowcolor [list active $arrowActColor] \
    -background [list active $bckActColor] \
    -relief [list {pressed !disabled} sunken]
  . configure -background $bckColor

  if $enabletheme {
    #ttk::style theme use $ThemeName
    ttk::setTheme $ThemeName
  } else {
    # Day and Night styles modify the default theme
    ttk::setTheme "default"
  }
}

proc processRCfile {} {
    global rcfile ExitOn fontList PortableDir wmGeometry
    global exactMatch autoSave oldAutoSave autoStart oldAutoStart
    global clock oldClock AltDate oldAltDate DateSepar oldDateSepar
    global ThemeName oldThemeName delayedMsgs
    global TypeGroups oldTypeGroups SelectedTypes oldSelectedTypes
    global LineIDGroups oldLineIDGroups SelectedLineIDs oldSelectedLineIDs
    global Host Port Hosts HostIndex Leading1 oldLeading1
    global DefaultHost DefaultPort ConfigFileHost ConfigFilePort
    global labelList oldLabelList YearDot oldYearDot posFlag

    if [expr [file exists $rcfile] && [file isfile $rcfile]] {
        set id [open $rcfile]
        set data [read $id]
        close $id
    } else {
        set data "no data"
    }

    set lines [split $data "\n"]

    foreach line $lines {
        if [regexp {geometry\s+\S+\s+[0-9x]+} $line] {
            eval $line
            set wmGeometry [regsub {\w+\s+\w+\s+\S+\s+([\dx+]+)} $line {\1}]
            regsub {\d+\D+(\d+).*} $wmGeometry {\1} ::historyTextRows
            set posFlag [regexp {[+-]\d+[+-]\d+} $wmGeometry]
        } elseif [regexp {font\s+create} $line] {
            eval $line
        } elseif [regexp {(:?fontList|clock|AltDate|DateSepar|NightMode|\
                             autoSave|autoStart|TypeGroups|SelectedTypes|\
                             LineIDGroups|SelectedLineIDs|ThemeName|\
                             YearDot|exactMatch|labelList|Leading1|Host|Port|\
                             )\s+} $line] {
            eval $line
        }
    }

    # initial values, not final values
    set oldHost $Host
    set oldPort $Port

    # determine Host, Port and Hosts values
    if {$ConfigFileHost == ""} {
        if {$Host == ""} {set Host $DefaultHost}
    } else {
        set Host $ConfigFileHost
    }
    if {$ConfigFilePort == ""} {
        if {$Port == ""} {set Port $DefaultPort}
    } else {
        set Port $ConfigFilePort
    }

    # host and port variables may have been replaced by the .ncid RC file
    # make sure there is a match against the list of hosts
    if {$Hosts == ""} {
        set Hosts "$DefaultHost:$DefaultPort"
        set delayedMsgs \
            "$delayedMsgs\nempty Hosts list set to $DefaultHost:$DefaultPort"
        set HostIndex 0
        lassign [split [lindex $Hosts $HostIndex] ":"] Host Port
        write_rc_file "set Host" "set Host $Host"
        write_rc_file "set Port" "set Port $Port"
    } else {
        set HostIndex [lsearch -regexp $Hosts $Host:$Port]
        if {$HostIndex == -1} {
            # the config file changed since the rcfile was last updated
            set delayedMsgs \
                "$delayedMsgs\n$Host:$Port not in list of Hosts: \"$Hosts\""
            set HostIndex 0
            lassign [split [lindex $Hosts $HostIndex] ":"] Host Port
            set delayedMsgs \
                "$delayedMsgs\nUsing first entry in list of hosts: $Host:$Port"
        }
        write_rc_file "set Host" "set Host $Host"
        write_rc_file "set Port" "set Port $Port"
    }

    if {$Host != $oldHost} {
        # the RC file does not contain "set Host"
        write_rc_file "set Host" "set Host $Host"
    }

    if {$Port != $oldPort} {
        # the RC file does not contain "set Port"
        write_rc_file "set Port" "set Port $Port"
    }

    # remove NightMode from rcfile and add ThemeName to rcfile
    if {[info exists NightMode]} {
        if {$NightMode == 1} {set ThemeName "night"} else {set ThemeName "day"}
        write_rc_file "set ThemeName" "set ThemeName \"$ThemeName\""
        write_rc_file "" "set NightMode"
        set delayedMsgs \
            "$delayedMsgs\nNightMode \"$NightMode\" in rc file converted to ThemeName \"$ThemeName\""
    }

    set oldClock $clock
    set oldAltDate $AltDate
    set oldDateSepar $DateSepar
    set oldYearDot $YearDot
    set oldLeading1 $Leading1
    set oldThemeName $ThemeName
    set oldAutoSave $autoSave
    set oldAutoStart $autoStart
    set oldTypeGroups $TypeGroups
    set oldSelectedTypes $SelectedTypes
    set oldLineIDGroups $LineIDGroups
    set oldSelectedLineIDs $SelectedLineIDs
    set oldLabelList "$labelList"
}

proc logRCfileOldNewVarChange {verboseLevel oldVarName newVarName} {

     # prints "rcfile and $newVarName..."
     upvar $oldVarName oldVarValue
     upvar $newVarName newVarValue
     if {$oldVarValue != $newVarValue} {
        if {[string length $oldVarValue] >30 || [string length $newVarValue] >30} {
           logMsg $verboseLevel "rcfile and $newVarName have been changed"
           logMsg $verboseLevel "    from: $oldVarValue"
           logMsg $verboseLevel "      to: $newVarValue"
        } else {
           logMsg $verboseLevel "rcfile and $newVarName have been changed from: $oldVarValue to: $newVarValue"
        }
     }
}

proc do_nothing {} {
}

proc do_goodbye {} {
    global Socket

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

# create selected visual column lables
proc makeColumnLabels {} {
    global datelabel timelabel datenum dateword \
    global datenumyd YearDot AltDate DateSepar clock time24 time12 labelList

    if {$clock == 24} { set timelabel $time24 } else { set timelabel $time12 }
    set timelabel "$timelabel"
    if {$AltDate < 2} {
        if {$DateSepar == "." && $YearDot == 1} {
            set datelabel $datenumyd
        } else {
            set datelabel $datenum
        }
    } else {
        set datelabel $dateword
    }
    set datelabel "$datelabel"

    return [doColumns]
}

proc makeWindow {} {
    global ExitOn
    global Verbose WrapLines viewTextWidth
    global fontList m currentFont
    global clock autoSave autoStart AltDate
    global Hosts HostIndex
    global nameREQ nmbrREQ lineREQ timeWidth dateWidth
    global ThemeName
    global TypeGroups LineIDGroups DiscoveredLineIDs
    global NMBRarray posArray labelList

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

    set auto [expr \"$autoSave\" eq \"off\" ? \"normal\" : \"disabled\"]

    if {$fontList == ""} {scanFonts}

    if {[catch {font configure FixedFontH}]} {
        font create FixedFontH -family "$currentFont" -size 12
        write_rc_file "FixedFontH" \
                "font create FixedFontH [font configure FixedFontH]"
    }
    if {[catch {font configure FixedFontM}]} {
        font create FixedFontM -family "$currentFont" -size 12
        write_rc_file "FixedFontM" \
                "font create FixedFontM [font configure FixedFontM]"
    }
    if {[catch {font configure FixedFontP}]} {
        font create FixedFontP -family "$currentFont" -size 12
                write_rc_file "FixedFontP" \
                "font create FixedFontP [font configure FixedFontP]"
    }

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

    # 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
    set columnlabel [makeColumnLabels]
    ttk::label .la -text $columnlabel -justify left -font {FixedFontH}
    grid .la -row 1 -sticky w -columnspan 2

    # create File, Server, Preferences, Theme and Help menus
    set m .menubar
    menu $m.file
    menu $m.file.auto
    menu $m.file.startup
    menu $m.server
    menu $m.server.hosts
    menu $m.server.dial
    menu $m.server.copy
    menu $m.prefs
    menu $m.prefs.types
    menu $m.prefs.lines
    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
    $m.file.auto add radiobutton -label "Size and Position" -variable autoSave -value "both" -command {logAuto $m.file} -font FixedFontM
    $m.file.auto add radiobutton -label "Off" -variable autoSave -value "off" -command {logAuto $m.file} -font FixedFontM

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

    # create Server menu items
    $m.server add cascade -menu $m.server.hosts -label "Select NCID Server" -state normal   -font FixedFontM
    set x 0
    foreach i $Hosts {
        $m.server.hosts add radiobutton -label "$i" -variable HostIndex \
            -value $x -command {logHosts} -font FixedFontM
        incr x
    }
    $m.server add separator
    $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
                clearLog
                puts $Socket "REQ: REREAD"
                flush $Socket
            } -font FixedFontM
    $m.server add separator
    $m.server add cascade -menu $m.server.dial -label "Dial Number" -state disabled -font FixedFontM
    $m.server.dial add command -label "Manually" -command { doDial "any" } -font FixedFontM
    $m.server.dial add command -label "From History" -state disabled -command { doDial "history" } -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 cascade -menu $m.server.copy -label "Copy to Clipboard" -state disabled -font FixedFontM
    $m.server.copy add command -label "Name" -command { doClipboard 4 [lindex $dataDump 16] } -font FixedFontM
    $m.server.copy add command -label "Number Formatted" -command { doClipboard 1 [lindex $dataDump 13] } -font FixedFontM
    $m.server.copy add command -label "Number Digits" -command { doClipboard 2 [lindex $dataDump 13] } -font FixedFontM
    $m.server.copy add command -label "Entire Line" -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
    $m.prefs add separator
    $m.prefs add cascade -menu $m.prefs.types -label "Line Types" -font FixedFontM
    $m.prefs add cascade -menu $m.prefs.lines -label "LineIDs" -font FixedFontM
    $m.prefs add command -label "Columns..." -command {selectColumns} -font FixedFontM

    $m.prefs.types add radiobutton -label "All" \
        -variable TypeGroups -value 0 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add separator
    $m.prefs.types add radiobutton -label "Calls" \
        -variable TypeGroups -value 1 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add radiobutton -label "Messages" \
        -variable TypeGroups -value 2 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add radiobutton -label "Smart Phone" \
        -variable TypeGroups -value 3 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add separator
    $m.prefs.types add radiobutton -label "Select..." \
        -variable TypeGroups -value 4 \
        -command {doSelectedTypes} -font FixedFontM

    $m.prefs.lines add radiobutton -label "All" \
        -variable LineIDGroups -value 0 \
              -command {logViewLineIDs} -font FixedFontM
    $m.prefs.lines add separator
    $m.prefs.lines add radiobutton -label "Select..." \
        -variable LineIDGroups -value 1 \
        -command {doLineIDs} -font FixedFontM

    array set lArray $labelList
    if {!$lArray(DATE) && !$lArray(TIME)} {
    .menubar.prefs entryconfigure Date*and*Time* -state disabled
    }

    # create themes menu items
    $m.theme add radiobutton -label "Day" \
        -variable ThemeName -value "day" \
        -command {logTheme} -font FixedFontP
    $m.theme add radiobutton -label "Night" \
        -variable ThemeName -value "night" \
        -command {logTheme} -font FixedFontP
    $m.theme add radiobutton -label "Default" \
        -variable ThemeName -value "default" \
        -command {logTheme} -font FixedFontP
    if {$::DistThemes ne "default"} {$m.theme add separator}

    #Observed values
    #Windows 10:         alt clam classic default    vista winnative xpnative
    #Mac:                alt clam classic default    aqua
    #AndroWish:          alt clam classic default    droid
    #Fedora 25 cinnamon: alt clam classic default
    foreach t $::DistThemes {
        if {$t eq "default"} {continue}
        $m.theme add radiobutton -label [join [list [string totitle $t]]] \
        -variable ThemeName -value $t \
        -command {logTheme} -font FixedFontP
    }

    if {$::AddonThemes != ""} {
        $m.theme add separator
        foreach t $::AddonThemes {
            $m.theme add radiobutton -label [join [list [string totitle $t]]] \
            -variable ThemeName -value $t \
            -command {logTheme} -font FixedFontP
        }
    }

    # create Help menu item
    $m.help add command -label About -command {helpItem "About" "$VersionInfo\n$ServerVersion\n$Author\n" [ncidInfo]} -font FixedFontM
    $m.help add command -label "Online Docs" -command {helpItem "Online Documentation" "" ""} -font FixedFontM
    $m.help add command -label "Alias Description" -command {helpItem "Alias Description" $aliasDesc ""} -font FixedFontM
    $m.help add command -label "Field Labels" -command {helpItem "Field Labels" $fieldList ""} -font FixedFontM
    $m.help add command -label "Line Types" -command {helpItem "Line Types" $labList ""} -font FixedFontM
    $m.help add command -label "Views Window" -command {helpItem "Views Window" $viewHelp ""} -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
    set columnlabel [makeColumnLabels]
    set ::historyTextWidth [expr [string length $columnlabel] - 1]
    text .vh -width $::historyTextWidth -height $::historyTextRows \
        -yscrollcommand ".ys set" \
        -state disabled -font {FixedFontH} -setgrid 1 -wrap $WrapLines
    grid [ttk::scrollbar .ys -command ".vh yview"] \
          -column 1 -row 0 -sticky ns -pady 10 -padx 5
    grid .vh -row 2 -sticky nsew -padx 2 -pady 2
    grid .ys -row 2 -column 1 -sticky ns -pady 2

    # create and place: window for view, user message
    ttk::frame .fr
    grid .fr -row 4 -columnspan 2
    text .tv -font {FixedFontM} -height 2 -insertwidth 0 -wrap word \
        -width $viewTextWidth -yscrollcommand {.vsb set}
    .tv tag add strike 1.end
    .tv tag add blank 1.end
    .tv tag configure strike -overstrike 1
    grid .tv -row 3 -columnspan 2
    grid [ttk::scrollbar .vsb -command ".tv yview"] \
          -column 1 -row 0 -sticky ns -pady 10 -padx 5
    grid .vsb -row 3 -column 1
    grid remove .vsb
    bind .tv <KeyPress> break
    if {$DiscoveredLineIDs != ""} {updateViewDisplay}

    ttk::label .ml -text "Send Message: " -font {FixedFontM}
    ttk::entry .im -width 40 -font {FixedFontM}
    grid .ml .im -in .fr

    # create and place: call and server message display
    ttk::label .md -textvariable Txt -font {FixedFontM}
    grid .md -row 5 -columnspan 2

    grid columnconfigure . 0 -weight 1
    grid rowconfigure . 2 -weight 1

    set geometry [wm geometry .]
    wm minsize . [regsub {(\d+).*} $geometry {\1}] $::histMinRows
    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}
        }
    }
    setStyles

    logMsg $::LEVEL4 "History window font: \
        [regsub {\s+\-slant.+$} [font configure FixedFontH] {}]"

    logMsg $::LEVEL4 "Message window and display font: \
        [regsub {\s+\-slant.+$} [font configure FixedFontM] {}]"

    logMsg $::LEVEL4 "Help text: \
        [regsub {\s+\-slant.+$} [font configure FixedFontP] {}]"

    logMsg $::LEVEL4 "Window geometry set to: \
        [regsub {(\d+x\d+)\+(\d+)\+(\d+)} [wm geometry .] {\1 at x=\2 y=\3}]"

    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 select_label [string trimright [lindex $dataDump 1]]
        if {[info exists posArray(LINE)]} {
            set lineREQ [string trimright [lindex $dataDump $posArray(LINE)]]
        }
        if {[info exists posArray(NMBR)]} {
            set fmnmbrREQ [string trimright [lindex $dataDump $posArray(NMBR)]]
            set fmnmbrREQ [string trimleft $fmnmbrREQ]
        }
        # Recover the unformatted phone number sent by the modem using
        # the formatted number in NMBRarray as a key.  This is needed
        # for aliases, whitelist, blacklist, dial from history, etc
        if {[info exists NMBRarray($fmnmbrREQ)]} {
            set nmbrREQ $NMBRarray($fmnmbrREQ)
        }
        if {[info exists posArray(NAME)]} {
            set nameREQ [string trimright [lindex $dataDump $posArray(NAME)]]
        }
        set selected [.vh get $first $last]
        if {$nameREQ eq ""} {
            logMsg $::LEVEL1 "$select_label nameREQ is null"
            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
                logMsg $::LEVEL1 "REQ: INFO $nmbrREQ&&$nameREQ&&$lineREQ"
            } else { logMsg "Server not connected for a REQ: INFO" $::LEVEL1}
        }
        break
    }

    bind all <<ComboboxSelected>> {
        switch -regexp %W {
            ".f.fn.cb" {
                # change font family
                font configure SelectionFontH -family "$currentFont"
                font configure SelectionFontM -family "$currentFont"
                font configure SelectionFontP -family "$currentFont"
            }
            ".confirm.cb" {
                doAliasType
                .confirm.match configure -text "Exactly match $CIDmatch."
                .confirm.entry delete 0 end
                .confirm.entry insert end "$replace_"
            }
        }
    }

    bind TButton <ButtonRelease-1> {+
        switch -regexp %W {
            ".cbp.*" { break }
            ".ctypes.*" { break }
            ".ltypes.*" { break }
            ".clines.*" { break }
            ".confirm.*" { break }
            ".dial.*" { break }
            ".dt.*" { break }
            ".hlp.*" { break }
            ".rply.*" { break }
            ".reply.*" { break }
            ".__tk__messagebox.ok" { 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
                    }
                }
            }
        }
    }
}

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]
        # do not disable menu items before the 3rd separator
        if {$found == 3} {
            if {$type ne "separator"} {
                $menu.server entryconfigure $index -state disabled
            }
        } elseif {$type eq "separator"} { set found [expr $found + 1] }
    }
    # need to disable this submenu from not disabled menu entry "Dial Number"
    .menubar.server.dial entryconfigure From*History* -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 ClientJobResult bckColor

    toplevel .rply -background $bckColor
    wm title .rply "Dial Reply"
    wm resizable .rply 0 0

    grid [ttk::label .rply.mesg -font FixedFontP -justify left -textvariable ClientJobResult ] -columnspan 2 -padx 12

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

    modal {.rply}
}

proc doDial {dialtype} {
  global nmbrREQ nameREQ lineREQ DialPrefix DialLineID ClientJobResult
  global bckColor fgColor bckSelColor fgSelColor
  global Country nmbrWidth wantdial

  toplevel .dial -background $bckColor
  wm title .dial "Dial"
  wm resizable .dial 0 0
  if {$DialPrefix == ""} {set dprefix "no"} else {set dprefix $DialPrefix}
  if {$dialtype == "history"} {
    set wantdial 1
    set ht "Request the server dial:\n\nDial: $dprefix prefix\nNmbr: $nmbrREQ\nName: $nameREQ\nLine: $lineREQ"
  } else {
    set wantdial 2
    set nameREQ "No Name"
    set ht "Request the server dial:\n\nDial: $dprefix prefix\nName: $nameREQ"
  }
  set row 1
  grid [ttk::label .dial.rd -justify left -font FixedFontP \
        -text $ht ] \
        -columnspan 2 -padx 12 -pady 10
  grid [ttk::label .dial.cl -justify left -font FixedFontP \
        -text "Server Dial Line:"] -columnspan 2 -padx 12 -column 0
  set lineid_list [list $::ServerOptLineIDS]
  set DialLineID [lindex $lineid_list 0]
  grid [ttk::combobox .dial.lb -font FixedFontP -values $lineid_list \
        -height 2 -textvariable DialLineID] -columnspan 2 -column 0
  .dial.lb set $DialLineID
  set row [expr $row + 2]
  if {$dialtype == "history"} {
    ttk::label .dial.mml -font FixedFontM -text "Leading 1 in number"
    grid [ttk::labelframe .dial.lf -labelwidget .dial.mml -labelanchor "n"] \
          -columnspan 2 -column 0 -pady 5 -padx 40
    if {$Country == "US"} {
      grid [ttk::radiobutton .dial.lf.one -text "Add" \
            -variable Leading1 -value "Add" -command logOne] \
            -row 0 -column 0 -padx 8
      grid [ttk::radiobutton .dial.lf.noone -text "Remove" \
            -variable Leading1 -value "Remove" -command logOne] \
            -row 0 -column 1 -padx 8
      grid [ttk::radiobutton .dial.lf.leave -text "Leave" \
            -variable Leading1 -value "Leave" -command logOne] \
            -row 0 -column 2 -padx 8
    }
  } else {
    grid [ttk::label .dial.num -justify left -font FixedFontP \
          -text "Dial Number:"] -columnspan 2 -padx 12 -column 0
    ttk::entry .dial.number -width $nmbrWidth -font FixedFontP
    grid .dial.number -sticky ew -columnspan 2 -padx 8
    focus .dial.number
  }
  set row [expr $row + 1]
  grid [ttk::frame .dial.fr] -pady 10 -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 ClientJobResult] -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[logOne]" "$nameREQ" $wantdial}] \
        -row $row -column 1
  grid [ttk::button .dial.abort -text "ABORT" \
      -command {DoIt "" "DIAL_ABORT" "$DialPrefix[logOne]" "$nameREQ" $wantdial} \
      -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 ClientJobResult "Waiting for user action..."
  modal {.dial}
}

proc doAliasType {} {
    global nameREQ nmbrREQ lineREQ AliasText AliasText2 SelAliasType
    global action_ from_ replace_ alias_action line_action CIDmatch

    switch $SelAliasType {
        NAMEDEP {
            set action_ $alias_action
            set replace_ $nameREQ
            set CIDmatch "$nmbrREQ"
            set from_ "$nmbrREQ"
            set AliasText "Replace NAME:\n$nameREQ\n\nif      NMBR:\n$nmbrREQ"
            set AliasText2 "with ALIAS entered below"
            if {$action_ eq "modify" } {
                append AliasText2 ",\n or clear it to remove it"
                incr row
            }
        }
        NMBRDEP {
            set action_ $alias_action
            set replace_ $nmbrREQ
            set CIDmatch "$nameREQ"
            set from_ "$nameREQ"
            set AliasText "Replace NMBR:\n$nmbrREQ\n\nif      NAME:\n$nameREQ"
            set AliasText2 "with ALIAS entered below"
            if {$action_ eq "modify" } {
                append AliasText2 ",\n or clear it to remove it"
                incr row
            }
        }
        NAMEONLY {
            set action_ $alias_action
            set replace_ $nameREQ
            set CIDmatch "$nameREQ"
            set from_ "nameREQ"
            if {$action_ eq "modify" } {
                set AliasText "Replace NAME alias:\n$nameREQ"
                set AliasText2 "with ALIAS entered below"
                append AliasText2 ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace NAME:\n$nameREQ"
                set AliasText2 "with ALIAS entered below"
            }
        }
        NMBRONLY {
            set action_ $alias_action
            set replace_ $nmbrREQ
            set CIDmatch "$nmbrREQ"
            set from_ "$nmbrREQ"
            if {$action_ eq "modify" } {
                set AliasText "Replace NMBR alias:\n$nmbrREQ"
                set AliasText2 "with ALIAS entered below"
                append AliasText2 ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace NMBR:\n$nmbrREQ"
                set AliasText2 "with ALIAS entered below"
            }
        }
        NMBRNAME {
            set action_ $alias_action
            set replace_ $nmbrREQ
            set CIDmatch "$nameREQ"
            set from_ "$nameREQ"
            if {$action_ eq "modify" } {
                set AliasText "Replace NMBR & NAME alias:\n$nameREQ"
                set AliasText2 "with ALIAS entered below"
                append AliasText2 ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace NMBR & NAME:\n$nameREQ"
                set AliasText2 "with ALIAS entered below"
            }
        }
        LINEONLY {
            set action_ $line_action
            set replace_ $lineREQ
            set CIDmatch "$lineREQ"
            set from_ "$lineREQ"
            if {$action_ eq "modify" } {
                set AliasText "Replace LINE alias:\n$lineREQ"
                set AliasText2 "with ALIAS entered below"
                append AliasText2 ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace LINE name:\n$lineREQ"
                set AliasText2 "with ALIAS entered below"
            }
        }
    }
    append AliasText2 "."
    .confirm.lab configure -text $AliasText
    .confirm.lab2 configure -text $AliasText2
}

proc doList {list action which} {
    global entry_ action_ list_ ClientJobResult replace_ comment_ from_
    global aliasList aliasTypes CIDaliasType LineAliasType SelAliasType
    global nameREQ nmbrREQ lineREQ alias_action line_action
    global CIDmatch nameWidth bckColor fgColor bckSelColor fgSelColor
    global AliasText AliasText2

    toplevel .confirm -background $bckColor
    wm title .confirm "Confirmation"
    wm resizable .confirm 0 0
    set action_ $action
    set list_ $list
    set comment_ ""
    set from_ $nameREQ

    if {$list eq "black" || $list eq "white"} {
    # We only put "^" at beginning  and "$" at the end for "strict matching"  when adding an entry to b     lack/white list
    # When removing , we put the entry without "^ or "$" as ncidutil will "eat up"  those characters wh     en searching
    # black/white list for removal
    if {$action eq "add"} {
       set entry [list "$nmbrREQ" "$nameREQ"]
       }
    if {$action eq "remove"} {
         set entry [list "$nmbrREQ" "$nameREQ"]
       }
    set entry_ [lindex $entry 0]
    if {[lindex $entry 1] eq "NO NAME" || $nameREQ eq $nmbrREQ} {
         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
            ttk::entry .confirm.entry -width $nameWidth -font FixedFontP
            grid .confirm.entry -sticky ew -columnspan 2 -padx 8
            focus .confirm.entry
            set row [expr $row + 2]
        }
    } else {
        grid [ttk::label .confirm.list -justify left -font FixedFontP -text "NAME: $nameREQ\nNMBR: $nmbrREQ\nLINE: $lineREQ\n\nChoose the alias type:"] -columnspan 2 -padx 12 -pady 10
        if {$CIDaliasType eq "NOALIAS"} {
            set aliasList $aliasTypes
            set alias_action "add"
            set replace_ ""
        } else {
            set aliasList "$CIDaliasType LINEONLY"
            set alias_action "modify"
        }
        if {$LineAliasType eq "NOALIAS"} {
            set line_action "add"
        } else {
            set line_action "modify"
        }
        set SelAliasType [lindex $aliasList 0]
        grid [ttk::combobox .confirm.cb -font FixedFontP -values $aliasList \
              -textvariable SelAliasType -width 10 -justify center] \
              -pady 5 -padx [list 60 90] -columnspan 2
        set AliasText ""
        set AliasText2 ""
        focus .confirm.cb
        grid [ttk::label .confirm.lab -justify left -font FixedFontP -text $AliasText] \
         -columnspan 2 -padx 12 -pady 10
        grid [ttk::checkbutton .confirm.match \
             -variable exactMatch ] -columnspan 2 -padx 12
        grid [ttk::label .confirm.lab2 -justify left -font FixedFontP -text $AliasText2] \
             -columnspan 2 -padx 12 -pady 10
        ttk::entry .confirm.entry -exportselection 0 -font FixedFontP
        doAliasType
        .confirm.match configure -text "Exactly match: $CIDmatch"
        set aliaswidth [lindex $::aliasWidths \
                       [lsearch $aliasTypes $SelAliasType]]
        .confirm.entry configure -width $aliaswidth
        grid .confirm.entry -columnspan 2 -padx 12
        set row 6
        if {$action_ eq "modify"} {
            .confirm.entry delete 0 end
            .confirm.entry insert 0 "$replace_"
        }
        focus .confirm.entry
    }
    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 ClientJobResult] -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 {
           global ClientJobResult
           set ClientJobResult ""
           set replace_ [.confirm.entry get]
           set replace_ [string trim $replace_]
           if {$action_ eq "add" && $replace_ eq ""} {
              set ClientJobResult "You must enter something\nwhen adding a new alias."
              continue
           }
           set aliaswidth [lindex $::aliasWidths \
                          [lsearch $::aliasTypes $::SelAliasType]]
           .confirm.entry configure -width $aliaswidth
           if {[string length $replace_] > $aliaswidth} {
              .confirm.entry delete $::nameWidth end
              set replace_ [.confirm.entry get]
              set replace_ [string trim $replace_]
              set ClientJobResult "Alias length truncated to fit\nwindow:  edit Cancel or Apply"
              continue
           }
           if {$exactMatch} {set CIDmatch "^$CIDmatch$"}
           write_rc_file "set exactMatch" "set exactMatch $exactMatch"
           DoIt $action_ $list_ $CIDmatch&&$replace_ "$SelAliasType&&$from_" 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]; \
                    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 ClientJobResult "Waiting for user action..."
    modal .confirm
}

proc DoIt {action list entry extra wantdial} {
    global Socket  ClientJobResult Try Dialed DialLineID

    set Dialed $wantdial
    if {$Try} {
        set ClientJobResult "Server not connected ..."
        logMsg $::LEVEL1 "Server not connected for a REQ:"
        return
    }
    if {$Dialed > 0} {
        if {$Dialed == 2} {
            set entry [regsub -all {\D} [.dial.number get] ""]
            if {$entry eq ""} {
                logMsg $::LEVEL1 "Number to dial has no digits or is empty: Not Dialling"
                return
            } else {
                logMsg $::LEVEL1 "Number to dial: $entry"
            }
        }
        if {$list == "DIAL_ABORT"} {set Dialed 2}
        grid forget .dial.cancel .dial.call
        grid configure .dial.close -columnspan 1
        grid .dial.abort .dial.close
        puts $Socket "REQ: $list $entry&&$extra&&$DialLineID"
        flush $Socket
        logMsg $::LEVEL1 "REQ: $list $entry&&$extra&&$DialLineID"
    } else {
        set ClientJobResult "Working ..."
        grid forget .confirm.cancel .confirm.ok
        grid .confirm.close
        puts $Socket "REQ: $list $action \"$entry\" \"$extra\""
        flush $Socket
        logMsg $::LEVEL1 "REQ: $list $action \"$entry\" \"$extra\""
    }
}

proc doClipboardPopup {title text} {

  global ClipboardPopupCount ClipboardPopupButton ClipboardPopupTime
  global bckColor

  toplevel .cbp -background $bckColor
  wm withdraw .cbp

  wm title .cbp $title
  wm resizable .cbp 0 0

  set dismissBegin [clock clicks -milliseconds]
  set ClipboardPopupCount $ClipboardPopupTime

  if {$ClipboardPopupTime > 0} {after 1000 {ClipboardPopupLoop}}
  set wrapwidth [expr $::historyTextWidth - 30]
  if {[string length $text] > $wrapwidth} {
    logMsg $::LEVEL2 "Wrapping text of length [string length $text] to width $wrapwidth"
    set text [wrapParagraph $wrapwidth $text]
  }

  updateClipboardPopupButton

  set row 3
  grid [ttk::label .cbp.icon -image ::tk::icons::information ] \
       -column 1 -padx 12 -pady 10 -row $row
  grid [ttk::label .cbp.text -font FixedFontP  -text $text ] \
       -column 2 -padx 12 -pady 10 -row $row

  incr row 2

  grid [ttk::button .cbp.ok -textvariable ClipboardPopupButton -command {
        destroy .cbp}] \
       -column 2 -pady 10 -pady 10 -row $row

    if {$::tcl_platform(platform) != "unix"} {
      if {[catch {tk::PlaceWindow .cbp widget .} msg]} { logMsg $::LEVEL1 $msg}
    }

  wm deiconify .cbp
  modal {.cbp}

}

proc doClipboard {flag passedData} {

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

    if {$flag != 3} {set passedData [string trim $passedData]}

    clipboard clear
    switch $flag {
        1 -
        4 {
            clipboard append $passedData
        }
        2 {
            regsub -all -line {[^\d]} $passedData {} passedData
            clipboard append $passedData
        }
        3 {
            set templine ""
            for {set x 1} {$x < [ llength $passedData]} {incr x 3} {
                set tempfield [ lindex $passedData $x ]
                set templine "$templine$tempfield"
            }
            set passedData [string trim $templine]
            regsub -all -line {  +} $passedData { } passedData
            clipboard append $passedData
        }
    }
    logMsg $::LEVEL2 "Copied to clipboard: $passedData"
    doClipboardPopup "Selected History Line" "Copied to clipboard: $passedData"
}

proc updateClipboardPopupButton {} {
    global ClipboardPopupCount ClipboardPopupTime ClipboardPopupButton

    if {$ClipboardPopupTime == 0} {
       set ClipboardPopupButton "OK"
    } else {
      set ClipboardPopupButton "Dismiss or wait $ClipboardPopupCount second"
      if {$ClipboardPopupCount != 1} {append ClipboardPopupButton "s" }
    }

}

proc ClipboardPopupLoop {} {
     global ClipboardPopupCount ClipboardPopupButton
     incr ClipboardPopupCount -1
     updateClipboardPopupButton

     if {[winfo exists .cbp]} {
        if {$ClipboardPopupCount <= 0 } {
           .cbp.ok invoke
        } else {
          after 1000 {ClipboardPopupLoop}
        }
     }

}

# select column lables for display
proc selectColumns {} {
    global labTYPE labDATE labTIME labLINE labNMBR labNAME labNTYPE
    global labCTRY labLOCA labCARI labMTYPE  labelList labelArray
    global SelectedColumns oldSelectedColumns TypeGroups oldTypeGroups
    global msgToUser bckColor

    # convert list to array
    array set labelArray $labelList

    set maxwidth [expr [string length $labLOCA] + 2]

    set msgToUser ""

    toplevel .ltypes -background $bckColor
    wm title .ltypes "Select Columns to View"
    wm resizable .ltypes 0 0

    set row 3

    grid [ttk::label .ltypes.labelr -font FixedFontP \
          -text "Required Columns"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row

    grid [ttk::frame .ltypes.fr1] -pady 10 -columnspan 1

    grid [ttk::checkbutton .ltypes.fr1.type \
              -text                $labTYPE \
              -variable        labelArray(TYPE) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    .ltypes.fr1.type          state "selected disabled"
    incr row

    grid [ttk::checkbutton .ltypes.fr1.lineid \
              -text                $labLINE \
              -variable        labelArray(LINE) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    .ltypes.fr1.lineid         state "selected disabled"
    incr row

    grid [ttk::checkbutton .ltypes.fr1.nmbr \
              -text                $labNMBR \
              -variable        labelArray(NMBR) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    .ltypes.fr1.nmbr           state "selected disabled"
    incr row

    grid [ttk::checkbutton .ltypes.fr1.name \
              -text                $labNAME \
              -variable        labelArray(NAME) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    .ltypes.fr1.name           state "selected disabled"
    incr row

    grid [ttk::label .ltypes.labelo -font FixedFontP \
          -text "Optional Columns"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row

    grid [ttk::frame .ltypes.fr2] -pady 10 -columnspan 1

    grid [ttk::checkbutton .ltypes.fr2.date \
              -text                $labDATE \
              -variable        labelArray(DATE) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ltypes.fr2.time \
              -text                $labTIME \
              -variable        labelArray(TIME) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ltypes.fr2.mtype \
              -text                $labMTYPE \
              -variable        labelArray(MTYPE) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::label .ltypes.labele -font FixedFontP \
        -text "Extra Columns (some data may be obsolete)"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row

    grid [ttk::frame .ltypes.fr3] -pady 10 -columnspan 1

    grid [ttk::checkbutton .ltypes.fr3.nbt \
              -text                $labNTYPE \
              -variable        labelArray(NTYPE) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ltypes.fr3.ctr \
              -text                $labCTRY \
              -variable        labelArray(CTRY) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ltypes.fr3.loc \
              -text                $labLOCA \
              -variable        labelArray(LOCA) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ltypes.fr3.car \
              -text                $labCARI \
              -variable        labelArray(CARI) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row 4

    grid [ttk::label .ltypes.msgtouser -font FixedFontP \
          -textvariable msgToUser] \
         -columnspan 2 -padx 12 -pady 10 -row $row

    grid [ttk::frame .ltypes.fr4]  -column 0 -pady 8
    grid [ttk::button .ltypes.fr4.selall -text "Select All" -command {
               array set labelArray {TYPE 1 DATE 1 TIME 1 LINE 1 NMBR 1 \
                          NAME 1 NTYPE 1 CTRY 1 LOCA 1 CARI 1 MTYPE 1}
               }] -padx 12 -pady 10 -row $row

    grid [ttk::button .ltypes.fr4.clrall -text "Clear All" -command {
               array set labelArray {TYPE 1 DATE 0 TIME 0 LINE 1 NMBR 1 \
                     NAME 1 NTYPE 0 CTRY 0 LOCA 0 CARI 0 MTYPE 0}
               }] -column 1 -padx 12 -pady 10 -row $row

    grid [ttk::button .ltypes.fr4.cancel -text "Cancel" -command {
               set labelList $oldLabelList
               destroy .ltypes}] \
          -column 2 -padx 12 -pady 10 -row $row

    grid [ttk::button .ltypes.fr4.apply -text "Apply" -command {
               global labelArray msgToUser
               set sum 0
               foreach {name value} [array get labelArray] {set sum [expr $sum + $value]}
               if {$sum < 3} {
                  set msgToUser "You must select at least three column labels"
                  logMsg $::LEVEL1 $msgToUser
                  continue
               }
               # convert array to list
               set labelList [lsort -stride 2 [array get labelArray]]
               destroy .ltypes
               #logColumnLabels causes the call log to be re-read
               logColumnLabels}] -column 3 -padx 12 -pady 10 -row $row

    modal {.ltypes}
}

proc doColumns {} {
    global typelabel datelabel timelabel linelabel fnmbrlabel ntypelabel
    global namelabel countrylabel locationlabel carrierlabel mtypelabel
    global labelList posArray

    set labels ""

    set pos 1
    array unset posArray
    array set posArray {}

    # convert list to array
    array set labelArray $labelList

    if {$labelArray(TYPE)} {
        set labels "$labels$typelabel |"
        array set posArray "TYPE $pos"
    }
    if {$labelArray(DATE)} {
        set labels "$labels$datelabel|"
        incr pos 3
        array set posArray "DATE $pos"
    }
    if {$labelArray(TIME)} {
        set labels "$labels$timelabel|"
        incr pos 3
        array set posArray "TIME $pos"
    }
    if {$labelArray(LINE)} {
        set labels "$labels$linelabel|"
        incr pos 3
        array set posArray "LINE $pos"
    }
    if {$labelArray(NMBR)} {
        set labels "$labels$fnmbrlabel|"
        incr pos 3
        array set posArray "NMBR $pos"
    }
    if {$labelArray(NAME)} {
        set labels "$labels$namelabel|"
        incr pos 3
        array set posArray "NAME $pos"
    }
    if {$labelArray(NTYPE)} {
        set labels "$labels$ntypelabel|"
        incr pos 3
        array set posArray "NTYPE $pos"
    }
    if {$labelArray(CTRY)} {
        set labels "$labels$countrylabel|"
        incr pos 3
        array set posArray "CTRY $pos"
    }
    if {$labelArray(LOCA)} {
        set labels "$labels$locationlabel|"
        incr pos 3
        array set posArray "LOCA $pos"
    }
    if {$labelArray(CARI)} {
        set labels "$labels$carrierlabel|"
        incr pos 3
        array set posArray "CARI $pos"
    }
    if {$labelArray(MTYPE)} {
        set labels "$labels$mtypelabel|"
        incr pos 3
        array set posArray "MTYPE $pos"
    }

    return $labels

}

proc doSelectedTypes {} {
    global labBLK labCID labHUP labMSG labMWI labNOT labOUT labPID labPUT labRID labWID
    global SelectedTypes oldSelectedTypes TypeGroups oldTypeGroups
    global t_array msgToUser bckColor

    # convert lists to arrays
    array set t_array $SelectedTypes

    set maxwidth [expr [string length $labRID] + 2]

    set msgToUser ""

    toplevel .ctypes -background $bckColor
    wm title .ctypes "Select Call and Message Types to View"
    wm resizable .ctypes 0 0

    set row 3

    grid [ttk::label .ctypes.calls -font FixedFontP \
          -text "Call Types"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    incr row

    grid [ttk::frame .ctypes.fr1] -pady 10 -columnspan 1
    grid [ttk::checkbutton .ctypes.fr1.blk \
              -text                $labBLK \
              -variable        t_array(BLK) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.cid \
              -text                $labCID \
              -variable        t_array(CID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.hup \
              -text                $labHUP \
              -variable        t_array(HUP) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.mwi \
              -text                $labMWI \
              -variable        t_array(MWI) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.out \
              -text                $labOUT \
              -variable        t_array(OUT) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.pid \
              -text                $labPID \
              -variable        t_array(PID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.put \
              -text                $labPUT \
              -variable        t_array(PUT) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.rid \
              -text                $labRID \
              -variable        t_array(RID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.wid \
              -text                $labWID \
              -variable        t_array(WID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row
    incr row

    grid [ttk::label .ctypes.msgs -font FixedFontP \
          -text "Message Types"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    incr row

    grid [ttk::frame .ctypes.fr2] -pady 10 -columnspan 1
    grid [ttk::checkbutton .ctypes.fr2.msg \
              -text                $labMSG \
              -variable        t_array(MSG) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr2.not \
              -text                $labNOT \
              -variable        t_array(NOT) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row
    incr row
    incr row
    incr row

    grid [ttk::label .ctypes.msgtouser -font FixedFontP \
          -textvariable msgToUser] \
         -columnspan 2 -padx 12 -pady 10 -row $row

    grid [ttk::frame .ctypes.fr3]  -column 0 -pady 8
    grid [ttk::button .ctypes.fr3.selall -text "Select All" \
          -command {array set t_array {BLK 1 CID 1 HUP 1 MWI 1 OUT 1 \
                                       PID 1 PUT 1 RID 1 WID 1 MSG 1 NOT 1}
                  set msgToUser ""
               }] -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.fr3.clrall -text "Clear All" -command {
               array set t_array {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 0 \
                                  PUT 0 RID 0 WID 0 MSG 0 NOT 0}
               }] -column 1 -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.fr3.cancel -text "Cancel" -command {
               set TypeGroups $oldTypeGroups
               destroy .ctypes}] \
          -column 2 -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.fr3.apply -text "Apply" -command {
               global t_array msgToUser
               set sum 0
               foreach {name value} [array get t_array] {set sum [expr $sum + $value]}
               if {!$sum} {
                  set msgToUser "You must select at least one type"
                  logMsg $::LEVEL1 $msgToUser
                  continue
               }
               # then convert array to list
               set SelectedTypes [lsort -stride 2 [array get t_array]]
               destroy .ctypes
               #logTypeGroups causes call log to be re-read
               logTypeGroups}] \
          -column 3 -padx 12 -pady 10 -row $row

    modal {.ctypes}
}

proc doLineIDs {} {
  global DiscoveredLineIDs LineIDGroups oldLineIDGroups
  global SelectedLineIDs oldSelectedLineIDs
  global lineREQ
  global SelLINE msgToUser SelHistoryLINE SelHistoryLINEIndex
  global bckColor fgColor bckSelColor fgSelColor

  set msgToUser ""
  set SelLINE   [list]

  toplevel .clines -background $bckColor
  wm title .clines "Select Line Identifiers to View"
  wm resizable .clines 0 0

  set oldSelectedLineIDs $SelectedLineIDs

  set row 3

  set viewselected $SelectedLineIDs
  set SelectedLineIDs ""
  set SelectedLineIDsIndex ""

  foreach vs $viewselected {
    set index [lsearch -exact "$DiscoveredLineIDs" $vs]
    if {$index != -1} {
      lappend SelectedLineIDs $vs
      lappend SelectedLineIDsIndex $index
    }
  }

  if {$SelectedLineIDsIndex == ""} {set SelectedLineIDsIndex -1}

  # see if user selected a line in call history window
  set SelHistoryLINE ""
  set SelHistoryLINEIndex 0
  if {[info exists lineREQ]} {
    if {$lineREQ != "" } {
      set SelHistoryLINEIndex [lsearch -exact $DiscoveredLineIDs $lineREQ]
      if {$SelHistoryLINEIndex == -1} {
        set SelHistoryLINEIndex 0
      } else {set SelHistoryLINE $lineREQ}
    }
  }

  if {[llength $DiscoveredLineIDs] > 0} {
    grid [listbox .clines.lb -font FixedFontP -height 4 -width 0 \
          -listvariable DiscoveredLineIDs -selectmode extended \
          -yscrollcommand {.clines.ys set} \
          -background $bckColor -foreground $fgColor \
          -selectbackground $bckSelColor -selectforeground $fgSelColor] \
          -columnspan 2
    grid [ttk::scrollbar .clines.ys -command {.clines.lb yview}] \
          -column 1 -row 0 -sticky nse -pady 1 -padx 1
    if {[.clines.lb index end] <= [lindex [.clines.lb configure -height] 4]} {
      grid remove .clines.ys
    } else {
      grid .clines.ys
    }
    if {$SelectedLineIDsIndex != -1} {
      foreach x $SelectedLineIDsIndex {
        .clines.lb selection set $x
      }
      set SelLINE {}
      foreach x [.clines.lb curselection] {
        lappend SelLINE [.clines.lb get $x]
      }
    }

    bind all <<ListboxSelect>> {
      if {[info commands .clines.lb] ne ""} {
        set SelLINE {}
        foreach x [.clines.lb curselection] {
          lappend SelLINE [.clines.lb get $x]
        }
        if {[lindex [.clines.lb yview] 0] + \
            [lindex [.clines.lb yview] 1] == 1.0} {
          grid remove .clines.ys
        } else {
          grid .clines.ys
        }
      }
    }
  }
  set row [expr [llength $DiscoveredLineIDs] + 2]

  if {[llength $DiscoveredLineIDs] > 1} {
    grid [ttk::label .clines.help -justify left -font FixedFontP \
          -text "Multiple selections are allowed."] \
          -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
  }

  if {$SelHistoryLINE != ""} {
    incr row
    incr row
    grid [ttk::label .clines.call -justify left -font FixedFontP \
          -text "Last selected call in history window:"] \
          -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    grid [ttk::button .clines.history -text "LineID: $SelHistoryLINE" -command {
          global SelHistoryLINE SelHistoryLINEIndex SelLINE
          .clines.lb selection clear 0 end
          .clines.lb selection set $SelHistoryLINEIndex
          .clines.lb curselection
          set SelLINE $SelHistoryLINE
          logMsg $::LEVEL4 "Selected index $SelHistoryLINEIndex for SelHistoryLINE=$SelHistoryLINE"
          }] \
          -columnspan 2 -pady 10 -row $row
    incr row
    incr row
  }

  grid [ttk::label .clines.msgtouser -font FixedFontP \
        -textvariable msgToUser] \
        -columnspan 2 -padx 12 -pady 10 -row $row
  incr row
  incr row

  grid [ttk::button .clines.cancel -text "Cancel" -command {
        global DiscoveredLineIDs LineIDGroups oldLineIDGroups
        set LineIDGroups $oldLineIDGroups
        logMsg $::LEVEL4 "Cancel: DiscoveredLineIDs contains $DiscoveredLineIDs"
        destroy .clines}] \
        -padx 12 -row $row

  grid [ttk::button .clines.apply -text "Apply" -command {
        global DiscoveredLineIDs LineIDGroups oldLineIDGroups
        global SelectedLineIDs oldSelectedLineIDs
        global SelLINE msgToUser
        set msgToUser "You must select at least one lineid"
        if {$SelLINE == ""} {
          logMsg $::LEVEL1 $msgToUser
          continue
        }
        set msgToUser ""
        set SelectedLineIDs $SelLINE
        destroy .clines
        #logViewLineIDs causes call log to be re-read
        logViewLineIDs}] \
        -column 1 -padx 12 -row $row

  modal {.clines}

}

proc helpItem {title passedMesg1 passedMesg2} {
    global Logo command Website
    global mesg1 mesg2 bckColor

    set mesg1 [encoding convertfrom utf-8 $passedMesg1]
    set mesg2 [encoding convertfrom utf-8 $passedMesg2]

    toplevel .hlp -background $bckColor
    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 {$mesg1 == ""} {
      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.mesg1 -font FixedFontP -justify $jt -text $mesg1 ] -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]
      }
    }

    if { $mesg2 != "" } {
      grid [ttk::label .hlp.mesg2 -font FixedFontP -justify left -text $mesg2 ] -columnspan 2 -padx 12
    }

    if { $title == "About" } {
      grid [ttk::button .hlp.clip -text "Copy to Clipboard" -command {
              clipboard clear
              clipboard append -- "$mesg1 $mesg2"
              doClipboardPopup "About" "Copied to clipboard: About window text"
            }] \
            -columnspan 2 -pady 10
    }

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

    if {$::tcl_platform(platform) != "unix"} {
      if {[catch {tk::PlaceWindow .hlp widget .} msg]} { logMsg $::LEVEL1 $msg}
    }

    modal {.hlp}
}

proc serverOPT {} {
    global ServerOptions

    set displayDesc "\nOptions sent to clients:"
    set displayOPT "$ServerOptions"
    helpItem "Server Options" $displayDesc $displayOPT
}

proc clearLog {} {
    global display_line_num Begin DiscoveredLineIDs SelHistoryLINE

    set DiscoveredLineIDs [list]
    set SelHistoryLINE ""
    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 posFlag

    set save $Txt
    set Txt ""
    update

    set posFlag $flag
    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"
    logMsg $::LEVEL1 "Saved geometry: $geometry"

}

proc write_rc_file {regexpr command} {
  global rcfile

  if [file exists $rcfile] {
    if [file isdirectory $rcfile] {
      logMsg $::LEVEL1 "Unable to save data to $rcfile because it is a directory"
      return
    }
    set id [open $rcfile]
    set data [read $id]
    close $id
    set lines [lrange [split $data "\n"] 0 end-1]
    if {$regexpr == ""} {
      # command from list
      set lines [lsearch -all -inline -not -regexp $lines "$command"]
    } else {
      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 Fonts
proc changeFont {} {
    global fontList bckColor
    global spinvalH spinvalM spinvalP
    global boldH boldM boldP

    toplevel .f -background $bckColor
    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 FixedFontM -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 FixedFontM -values $fontList -textvariable currentFont] -pady 5 -padx [list 60 90]
    grid [ttk::button .f.fn.btn -text "Re-scan"] -column 1 -row 0 -pady 5 -padx 5
    .f.fn.cb set $currentFont

    ttk::label .f.hw -font FixedFontH -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 60
    grid [ttk::label .f.fh.label -font FixedFontH -text "Size: "] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.fh.size -from 8 -to 36 -width 3 -font FixedFontH -textvariable spinvalH \
          -state readonly -command {font configure SelectionFontH -size $spinvalH}] \
          -column 2 -row 0 -pady 5 -padx 5
    grid [ttk::label .f.hw2 -text "Sample text 0123456789" -font SelectionFontH] -column 1 -row 1 -pady [list 35 5] -padx 25 -sticky "w"

    ttk::label .f.mml -font FixedFontM -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 60
    grid [ttk::label .f.fm.label -font FixedFontM -text "Size: "] -column 1 -row 0 -pady 5 -padx 40
    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 -padx 5
    grid [ttk::label .f.mml2 -text "Sample text 0123456789" -font SelectionFontM] -column 1 -row 2 -pady [list 35 5] -padx 25 -sticky "w"

    ttk::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 60
    grid [ttk::label .f.fp.label -font FixedFontP -text "Size: "] -column 1 -row 0 -pady 5 -padx 40
    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 -padx 5
    grid [ttk::label .f.pw2 -text "Sample text 0123456789" -font SelectionFontP] -column 1 -row 3 -pady [list 35 5] -padx 25 -sticky "w"

    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

    modal {.f}

    font delete SelectionFontH
    font delete SelectionFontM
    font delete SelectionFontP
}

proc logFont {} {
    set fonth "[font configure FixedFontH]"
    set fontm "[font configure FixedFontM]"
    set fontp "[font configure FixedFontP]"

    write_rc_file "FixedFontH" "font create FixedFontH $fonth"
    write_rc_file "FixedFontM" "font create FixedFontM $fontm"
    write_rc_file "FixedFontP" "font create FixedFontP $fontp"

    logMsg $::LEVEL1 "history window font (FixedFontH) has been saved after command: $fonth"
    logMsg $::LEVEL1 "message window and display font (FixedFontM) has been saved after command: $fontm"
    logMsg $::LEVEL1 "help text font (FixedFontP) has been saved after command: $fontp"
}

proc logOne {} {
  global Leading1 oldLeading1 nmbrREQ Country

  if {$Leading1 != $oldLeading1} {
    logRCfileOldNewVarChange $::LEVEL2 oldLeading1 Leading1
    set oldLeading1 $Leading1
    write_rc_file "set Leading1" "set Leading1 \"$Leading1\""
  }

    # no need to determine how to handle a leading 1 for a manual number
    # proc DoIt uses the number the user entered
    if {$nmbrREQ eq ""} {return $nmbrREQ}

    set dialnmbr $nmbrREQ

  if {$Country == "US"} {
    switch $Leading1 {
      "Leave" {
      }
      "Add" {
        if {[regexp {^91?} $dialnmbr] && [string length $dialnmbr] >= 11} {
          regsub {^91?} $dialnmbr {91} dialnmbr
        } else {
          regsub {^1?} $dialnmbr {1} dialnmbr
        }
      }
      "Remove" {
        if {[regexp {^91} $dialnmbr] && [string length $dialnmbr] == 12} {
          regsub {^91} $dialnmbr {9} dialnmbr
        } else {
          regsub {^1} $dialnmbr {} dialnmbr
        }
      }
    }
  }
  return $dialnmbr
}

proc formatDT {} {
    global bckColor AltDate DateSepar YearDot labelList

    toplevel .dt -background $bckColor
    wm title .dt "Date and Time Formats"
    wm resizable .dt 0 0

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

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

    grid [ttk::label .dt.s3]
    grid [ttk::radiobutton .dt.s -width 29 -text "Date Separator: /" -variable DateSepar -value "/" -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.d -width 29 -text "Date Separator: -" -variable DateSepar -value "-" -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.p -width 29 -text "Date Separator: ." -variable DateSepar -value "." -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::checkbutton .dt.y -text "Add . at end" -variable YearDot -onvalue 1 -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

    array set tmpArray $labelList
    if {!$tmpArray(DATE)} {
        #jlc grid remove .dt.s3 .dt.s .dt.d .dt.p .dt.y
        .dt.dd state "disabled"
        .dt.mm state "disabled"
        .dt.ddd state "disabled"
        .dt.mmm state "disabled"
        .dt.s state "disabled"
        .dt.d state "disabled"
        .dt.p state "disabled"
        .dt.y state "disabled"
    }
    if {!$tmpArray(TIME)} {
        .dt.12 state "disabled"
        .dt.24 state "disabled"
    }

    if {$DateSepar ne "."} {.dt.y state "disabled"}
    if {$AltDate > 1} {grid remove .dt.s3 .dt.s .dt.d .dt.p .dt.y}
    modal {.dt}
}

proc changeDateFormat {date infmt outfmt} {
  global oldAltDate AltDate

  if {[catch {
    set date "[clock format [clock scan $date -format $infmt] -format $outfmt]"
  } errmsg]} {
    logMsg $::LEVEL1 "$infmt  $outfmt  $date"
    logMsg $::LEVEL1 $errmsg
    set date ""
  }

    return $date
}

proc logDate {widget} {
    global AltDate oldAltDate DateSepar oldDateSepar
    global YearDot oldYearDot EndDot

    set dateflag  0
    set separflag 0
    set yearflag 0
    if {$DateSepar eq "."} {.dt.y state !disabled} else {.dt.y state disabled}
    if {$AltDate != $oldAltDate} {
        set dateflag 1
        if {$AltDate > 1} {
            grid remove .dt.s3 .dt.s .dt.d .dt.p .dt.y
        } else {grid .dt.s3 .dt.s .dt.d .dt.p .dt.y}
        update
    } elseif {$DateSepar != $oldDateSepar} {
        update
        set separflag 1
        if {$DateSepar eq "."} {set EndDot "."} else {set EndDot ""}
    } elseif {$YearDot != $oldYearDot} {
        if {$DateSepar eq "." && $YearDot == 1} {
            set EndDot "."} else {set EndDot ""}
        set yearflag 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}
        # don't need to check for END or RLY, they are ignored in this script
        if {![regexp {^(?:BLK|CID|HUP|MSG|MWI|NOT|OUT|PID|PUT|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} {
          after 3
          switch $oldAltDate {
            0 {
              set infmt "%m$DateSepar%d$DateSepar%Y$EndDot"
              if {$AltDate == 1} {
                set outfmt "%d$DateSepar%m$DateSepar%Y$EndDot"
              } elseif {$AltDate == 2} {
                set outfmt "%a %b %d %Y"
              } else {
                set outfmt "%a %d %b %Y"
              }
            }
            1 {
              set infmt "%d$DateSepar%m$DateSepar%Y$EndDot"
              if {$AltDate == 0} {
                set outfmt "%m$DateSepar%d$DateSepar%Y$EndDot"
              } elseif {$AltDate == 2} {
                set outfmt "%a %b %d %Y"
              } else {
                set outfmt "%a %d %b %Y"
              }
            }
            2 {
              set infmt "%a %b %d %Y"
              if {$AltDate == 0} {
                set outfmt "%m$DateSepar%d$DateSepar%Y$EndDot"
              } elseif {$AltDate == 1} {
                set outfmt "%d$DateSepar%m$DateSepar%Y$EndDot"
              } else {
                set outfmt "%a %d %b %Y"
              }
            }
            3 {
              set infmt "%a %d %b %Y"
              if {$AltDate == 0} {
                set outfmt "%m$DateSepar%d$DateSepar%Y$EndDot"
              } elseif {$AltDate == 1} {
                set outfmt "%d$DateSepar%m$DateSepar%Y$EndDot"
              } else {
                set outfmt "%a %b %d %Y"
              }
            }
          }
          if {$date ne ""} {set date [changeDateFormat $date $infmt $outfmt]}
        }
        if {$date ne ""} {
            if {$separflag || $yearflag} {
                set date "$f1$DateSepar$f2$DateSepar$f3$EndDot"
            }
            $widget insert "$stop - 1 c" "$date"
            $widget delete "$start" "$stop - 1 c"
        }
    }

    if {$date ne ""} {
        if {$dateflag} {
            write_rc_file "set AltDate" "set AltDate $AltDate"
            logRCfileOldNewVarChange $::LEVEL2 oldAltDate AltDate
            set oldAltDate $AltDate
        }
        if {$separflag} {
            write_rc_file "set DateSepar" "set DateSepar $DateSepar"
            logRCfileOldNewVarChange $::LEVEL2 oldDateSepar DateSepar
            set oldDateSepar $DateSepar
        }
        if {$yearflag} {
            write_rc_file "set YearDot" "set YearDot $YearDot"
            logRCfileOldNewVarChange $::LEVEL2 oldYearDot YearDot
            set oldYearDot $YearDot
        }
    }

    set columnlabel [makeColumnLabels]
    .la configure -text $columnlabel
    set ::historyTextWidth [expr [string length $columnlabel] - 1]
    logMsg $::LEVEL1 "History Text Field Width changed to: $::historyTextWidth characters"
    logMsg $::LEVEL1 "History Text Field Rows: $::historyTextRows"
    $widget configure -width $::historyTextWidth
    set geometry [wm geometry .]
    wm minsize . [regsub {(\d+).*} $geometry {\1}] $::histMinRows
    update
    $widget configure -state disabled
}

proc logColumnLabels {} {
    global labelList oldLabelList Socket display_line_num columnlabel
    global posFlag

    set changeflag 0
    if {$labelList ne $oldLabelList} {
        logRCfileOldNewVarChange $::LEVEL2 oldLabelList labelList
        array set tmpArray $labelList
        if {!$tmpArray(DATE) && !$tmpArray(TIME)} {
            .menubar.prefs entryconfigure Date*and*Time* -state disabled
        } else {
            .menubar.prefs entryconfigure Date*and*Time* -state active
        }
        set oldLabelList $labelList
        set changeflag 1
        set columnlabel [makeColumnLabels]
        set ::historyTextWidth [expr [string length $columnlabel] - 1]
        .la configure -text $columnlabel
        .vh configure -width $::historyTextWidth
        fieldLabels
        set geometry [wm geometry .]
        wm minsize . [regsub {(\d+).*} $geometry {\1}] $::histMinRows
        logMsg $::LEVEL1 "Resized History Text Field Width: $::historyTextWidth characters"
        logMsg $::LEVEL1 "window geometry: $geometry"

        # save $labelList
        write_rc_file "set labelList" "set labelList \{$labelList\}"

        # save window geometry, includes position if $posFlag = 1
        saveSize $posFlag
    }

    if {$changeflag && $Socket > 0} {
       set display_line_num 0
       clearLog
       puts $Socket "REQ: REREAD"
       flush $Socket
    }
}

proc logTypeGroups {} {
  global TypeGroups oldTypeGroups SelectedTypes oldSelectedTypes
  global Socket display_line_num

  set changeflag 0

  if {$TypeGroups != $oldTypeGroups} {
    logRCfileOldNewVarChange $::LEVEL2 oldTypeGroups TypeGroups
    set oldTypeGroups $TypeGroups
    set changeflag 1
    updateViewDisplay
    write_rc_file "set TypeGroups" "set TypeGroups $TypeGroups"
  }
  if {$SelectedTypes != $oldSelectedTypes} {
    logRCfileOldNewVarChange $::LEVEL2 oldSelectedTypes SelectedTypes
    set oldSelectedTypes $SelectedTypes
    set changeflag 1
    updateViewDisplay
    write_rc_file "set SelectedTypes" "set SelectedTypes \{$SelectedTypes\}"
  }

  if {$changeflag && $Socket > 0} {
     set display_line_num 0
     clearLog
     puts $Socket "REQ: REREAD"
     flush $Socket
  }
}

proc logViewLineIDs {} {
  global DiscoveredLineIDs LineIDGroups oldLineIDGroups
  global SelectedLineIDs oldSelectedLineIDs
  global Socket display_line_num

  set changeflag 0

  if {$LineIDGroups != $oldLineIDGroups} {
    logRCfileOldNewVarChange $::LEVEL2 oldLineIDGroups LineIDGroups
    set oldLineIDGroups $LineIDGroups
    set changeflag 1
    write_rc_file "set LineIDGroups" "set LineIDGroups $LineIDGroups"
    if {$LineIDGroups == 0 && $SelectedLineIDs != $DiscoveredLineIDs} {
      set SelectedLineIDs $DiscoveredLineIDs
    }
    updateViewDisplay
  }

  if {$SelectedLineIDs != $oldSelectedLineIDs} {
    logRCfileOldNewVarChange $::LEVEL2 oldSelectedLineIDs SelectedLineIDs
    set oldSelectedLineIDs $SelectedLineIDs
    set changeflag 1
    updateViewDisplay
    write_rc_file "set SelectedLineIDs" "set SelectedLineIDs \"$SelectedLineIDs\""
  }

  if {$changeflag && $Socket > 0} {
     set display_line_num 0
     clearLog
     puts $Socket "REQ: REREAD"
     flush $Socket
  }

}

proc logHosts {} {
  global Hosts HostIndex Host Port oldHost oldPort SelHistoryLINE
  global ChangeHostFlag SelectedLineIDs LineIDGroups TypeGroups
  global ServerOptions

  logMsg $::LEVEL2 "logHosts:"

  set ChangeHostFlag 0
  lassign [split [lindex $Hosts $HostIndex] ":"] Host Port
  #logMsg $::LEVEL2 "Setting Host=$Host and Port=$Port by extracting HostIndex=$HostIndex from Hosts=$Hosts"
  logMsg $::LEVEL2 "    HostIndex=$HostIndex hosts=$Hosts"
  logMsg $::LEVEL2 "    host=$Host oldHost=$oldHost Port=$Port oldPort=$oldPort"
  if {$oldHost != $Host || $oldPort != $Port} {
      if {$oldHost != $Host} {
        logRCfileOldNewVarChange $::LEVEL2 oldHost Host
        set oldHost $Host
        set ChangeHostFlag 1
        write_rc_file "set Host" "set Host $Host"
      }
      if {$oldPort != $Port} {
        logRCfileOldNewVarChange $::LEVEL2 oldPort Port
        set oldPort $Port
        set ChangeHostFlag 1
        write_rc_file "set Port" "set Port $Port"
      }
  }

  if {$ChangeHostFlag} {
    set LineIDGroups 0
    write_rc_file "set LineIDGroups" "set LineIDGroups 0"
    set TypeGroups 0
    write_rc_file "set TypeGroups" "set TypeGroups 0"
    set SelectedLineIDs ""
    set ServerOptions ""
    clearLog
    Reconnect
  }
}

# when switching between multiple servers, make it obvious in the log file
proc logServerAddress {} {
  global Host Port

  set bannerFill "="
  set bannerText "$bannerFill Server address: $Host:$Port $bannerFill"
  set bannerStars [string repeat $bannerFill [string length $bannerText]]
  logMsg $::LEVEL1 "$bannerStars"
  logMsg $::LEVEL1 "$bannerText"
  logMsg $::LEVEL1 "$bannerStars"
}

proc processLineID {lineid} {
  global DiscoveredLineIDs SelectedLineIDs

  set ret 0

  # determine if DiscoveredLineIDs needs to be updated
  if {$lineid == ""} {set lineid "No-LineID"}
  if {[lsearch -exact $DiscoveredLineIDs "$lineid"] == -1} {
     lappend DiscoveredLineIDs "$lineid"
  }

  # check if lineid is in SelectedLineIDs
  if {$SelectedLineIDs == ""} {
    set ret 1
  } else {
    foreach id_ $SelectedLineIDs {if {$id_ == $lineid} {set ret 1; break}}
  }

  return $ret
}

proc updateViewDisplay {} {
  global TypeGroups SelectedTypes
  global LineIDGroups SelectedLineIDs DiscoveredLineIDs
  global SelectedAllTypes SelectedCalls SelectedMessages SelectedSmartPhone

  set selectview [list]
  .tv delete 1.0 2.end
  switch $TypeGroups {
    0 {
      set selectview $SelectedAllTypes; .tv insert 1.end "View All Types:"
    }
    1 {
      set selectview $SelectedCalls; .tv insert 1.end "View Call Types:"
    }
    2 {
      set selectview $SelectedMessages; .tv insert 1.end "View Message Types:"
    }
    3 {
      set selectview $SelectedSmartPhone;.tv insert 1.end "View Smart Phone Types:"
    }
    4 {
      set selectview $SelectedTypes; .tv insert 1.end "View Selected Types: "
    }
  }
  if {$selectview == ""} {
    .tv insert 1.end "View All Types: "
    set SelectedTypes "$SelectedAllTypes"
  } else {set SelectedTypes "$selectview"}
  foreach {type flag} $SelectedTypes {
    if {$flag == 1} {
      .tv insert 1.end " $type" "blank"
    } else {
      .tv insert 1.end " " "blank"
      .tv insert 1.end "$type" "strike $type"
    }
  }

  if {$SelectedLineIDs == ""} {
    set SelectedLineIDs $DiscoveredLineIDs
  } else {
    set viewselected $SelectedLineIDs
    set SelectedLineIDs ""
    foreach vs $viewselected {
      set index [lsearch -exact "$DiscoveredLineIDs" $vs]
      if {$index != -1} {
        lappend SelectedLineIDs $vs
      }
    }
  }

  .tv insert 1.end "\n"
  if {$LineIDGroups == 0} {
    .tv insert 2.end "View All LineIDs: $SelectedLineIDs"
  } else {
    .tv insert 2.end "View Selected LineIDs:"
    set lineids [list]

    # Setup array for viewing lineids
    foreach lineid $DiscoveredLineIDs {
      lappend lineids $lineid 0
    }

    # determine which lineids are wanted for viewing
    foreach lineid $SelectedLineIDs {
      if {[set pos [lsearch -exact $lineids $lineid]] != -1} {
        set pos1 [expr $pos + 1]
        set lineids "[lreplace $lineids $pos1 $pos1 1]"
      }
    }

    # view each lineid
    foreach {lineid value} $lineids {
      # need to enclose lineid with {} again
      if {[regexp {\s} $lineid]} {set lineid "{$lineid}"}
      # determine when to strikeout a lineid
      if {$value == 1} {
        .tv insert 2.end " $lineid" "blank"
      } else {
        .tv insert 2.end " " "blank"
        .tv insert 2.end "$lineid" "strike $lineid"
      }
    }
  }
  update
  if {[lindex [.tv yview] 0] + [lindex [.tv yview] 1] != 1.0} {
    grid .vsb} else {grid remove .vsb}
}

proc logTheme {} {
  global oldThemeName ThemeName
  global Socket display_line_num

  if {$ThemeName != $oldThemeName} {
    logRCfileOldNewVarChange $::LEVEL2 oldThemeName ThemeName
    set oldThemeName $ThemeName
    write_rc_file "set ThemeName" "set ThemeName \"$ThemeName\""

    setStyles
    if {$Socket > 0} {
      set display_line_num 0
      clearLog
      puts $Socket "REQ: REREAD"
      flush $Socket
    }
  }
}

proc logClock {widget} {
    global  clock oldClock Socket display_line_num labelList

    if {$clock == $oldClock} { return }
    array set tmpArray $labelList
    #not using logRCfileOldNewVarChange here as next line is more user friendly
    logMsg $::LEVEL2 "rcfile and Time display have been changed from: $oldClock to: $clock hours"
    set oldClock $clock
    write_rc_file "set clock" "set clock $clock"
    $widget configure -state normal
    for {set line 0} {1} {incr line} {
        after 3
        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|PUT|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}
        if {$tmpArray(DATE)} {
            set time [lindex $temp 7]
            set start [lindex $temp 8]
            set stop [lindex $temp 11]
        } else {
            set time [lindex $temp 4]
            set start [lindex $temp 5]
            set stop [lindex $temp 8]
        }
        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"
    }

    set columnlabel [makeColumnLabels]
    .la configure -text $columnlabel
    set ::historyTextWidth [expr [string length $columnlabel] - 1]
    logMsg $::LEVEL1 "History Text Field Width changed to: $::historyTextWidth characters"
    $widget configure -width $::historyTextWidth
    set geometry [wm geometry .]
#jlc:
    wm geometry . ${::historyTextWidth}x${::historyTextRows}[regsub {\d+\D+\d+} $geometry {}]
    wm minsize . [regsub {(\d+).*} $geometry {\1}] $::histMinRows
    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
        }
    }
    logMsg $::LEVEL2 "rcfile and autoSave have been set to $temp"
}

proc logStart {} {
    global autoStart oldAutoStart dtfile asfile ModName ConfigFile

    if {$autoStart eq $oldAutoStart} { return }
    if {![file isdirectory [file dirname $asfile]]} {
        if {[catch {file mkdir [file dirname $asfile]} errmsg]} {
            logMsg $::LEVEL1 $errmsg
            tk_messageBox -message $errmsg -icon error -type ok
        } else {logMsg $::LEVEL1 "created directory: [file dirname $asfile]"}
    }
    if {$autoStart ne "off"} {
        if {$ModName eq "ncid-alert"} {
            set errmsg1 "$ModName started in $ConfigFile"
            set errmsg2 "remove or comment out the $ModName line in"
            set errmsg2 "$errmsg2 [file tail $ConfigFile] and restart ncid"
            set errmsg2 "$errmsg2 to avoid duplicate alerts"
            logMsg $::LEVEL1 "$errmsg1\n$errmsg2"
            tk_messageBox -message "$errmsg1\n\n$errmsg2" -icon error -type ok
            #helpItem "AutoStart Info" $errmsg1 $errmsg2
        }
    }
    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 --module 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 --module ncid-alert} line
                puts $out $line
            }
            close $in
            close $out
        }
        "off" {
            file delete $asfile
        }
    }
    write_rc_file "set autoStart" "set autoStart \"$autoStart\""
    logMsg $::LEVEL1 "rcfile and autoStart have been set to $autoStart"
}

# Handle MSG from GUI
proc handleGUIMSG {} {

  # get MSG and clear text input box
  set line [.im get]
  .im delete 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 logMsg {level msg} {
    global Verbose LogChan

    if {$Verbose >= $level} {
       puts "$msg"
       if {$LogChan != ""} {
           set systemTime [clock seconds]
           puts $LogChan "\[[clock format $systemTime -format "%m/%d %H:%M"]\] $msg"
       }
    }
}

# https://stackoverflow.com/a/34221864
proc logArray {verboseLevel heading a {pattern *}} {
    upvar 1 $a array
    if {![array exists array]} {
        return -code error "\"$a\" isn't an array"
    }
    set maxl 0
    set names [lsort [array names array $pattern]]
    foreach name $names {
        if {[string length $name] > $maxl} {
            set maxl [string length $name]
        }
    }
    set maxl [expr {$maxl + [string length $a] + 2}]
    set indent ""
    if {$heading != ""} {
       logMsg $verboseLevel $heading
       set indent "     "
    }
    foreach name $names {
        set nameString [format %s(%s) $a $name]
        logMsg $verboseLevel [format "%s$%-*s = %s" "$indent" $maxl $nameString $array($name)]
    }
}


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

    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

        }
        logMsg $::LEVEL1 "Using pidfile: $PIDfile"
    } else {logMsg $::LEVEL1 "Not using a PID file"}
}

# handle log file, if it can not be created, ignore it
proc doLogOpen {} {
    global LogEnable LogDir LogChan LogStatus LogFile

  set access "CREAT WRONLY TRUNC"
  switch $LogEnable {
    1 {
      # create embed process ID in file name
      set LogFile [file normalize [file join $LogDir "ncid-client-[pid].log"]]
      set LogStatus "Log File:      $LogFile\n               enabled - process ID"
    }
    2 {
      # create/overwrite, do not embed process ID in file name
      set log_file ncid-client.log
      if {[machine platform] == "windows"} {set log_file ncid-windows.log}
      if {[machine platform] == "unix"} {set log_file "ncid-[info hostname].log"}
      if {[machine platform] == "android"} {set log_file ncid-androwish.log}
      if {[machine os] == "Darwin"} {regsub {^ncid} $log_file {ncid-mac} log_file}
      set LogFile [file normalize [file join $LogDir $log_file]]
      set LogStatus "Log File:      $LogFile\n               enabled - overwrite"
    }
    default {
      # this should never happen
      set LogStatus "LogEnable out of range: $LogEnable"
      set LogEnable 0
    }
  }

  if {$LogEnable > 0} {
    if {[catch {set LogChan [open $LogFile $access "0644"]} failmsg]} {
      # logfile open failed
      set LogStatus "$failmsg"
      set LogEnable 0
    } else {
      fconfigure $LogChan -buffering line
    }
  }
  set LogStatus [regsub {/.*/} $LogStatus ""]
}

proc scanFonts {} {
    global fontList currentFont

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

        # Skip Bauhaus9 on Apple Mac -- triggers wish error:
        # CoreText: Invalid 'kern' Table In CTFont <name: Bauhaus93....
        if {$family == "Bauhaus 93"} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

        # Skip '.LastResort' on Apple Mac -- garbles all text
        if {$family == ".LastResort"} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

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

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

        # skip fixed font Cursor
        if {[regexp {Cursor} $family]} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

        if {[font metrics \"$family\" -fixed]} {
            incr numberFixed
            logMsg $::LEVEL4 "detected fixed font $family"
            lappend fontList $family
        }
    }
    # sort and remove duplicates
    set fontList [lsort -dictionary -unique $fontList]

    set currentFont [lindex $fontList 0]
    logMsg $::LEVEL1 "current font set to: $currentFont"
    logMsg $::LEVEL1 "$numberFixed fixed fonts out of $numberFonts fonts"
    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 fgColor

  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
  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
    }
  }

proc fieldLabels {} {
    global labelList typeWidth datelabel timelabel nmbrWidth nameWidth
    global lineIDWidth ntypeWidth countryWidth carrierWidth locationWidth
    global mtypeWidth

    set labelCount 0
    array set labelArray $labelList
    if $labelArray(TYPE) {
      set msg "  Line Type Field Width: $typeWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(DATE) {
      set msg "$msg  Date Field Width: [string length $datelabel] characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(TIME) {
      set msg "$msg  Time Field Width: [string length $timelabel] characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(NMBR) {
      set msg "$msg  Number Field Width: $nmbrWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(NAME) {
      set msg "$msg  Name Field Width: $nameWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(LINE) {
      set msg "$msg  Line Label Field Width: $lineIDWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(NTYPE) {
      set msg "$msg  Number Type Field Width: $ntypeWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(CTRY) {
      set msg "$msg  Country Field Width: $countryWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(LOCA) {
      set msg "$msg  Location Field Width: $locationWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(CARI) {
      set msg "$msg  Carrier Field Width: $carrierWidth characters\n"
      set labelCount [expr $labelCount + 1]
    }
    if $labelArray(MTYPE) {
      set msg "$msg  Mesg Type Field Width: $mtypeWidth characters"
      set labelCount [expr $labelCount + 1]
    }
    logMsg $::LEVEL1 "Displaying $labelCount out of [expr [llength $labelList] / 2] label fields"
    logMsg $::LEVEL1 "$msg"
}

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

########################################################################
#                      MAIN ROUTINE STARTS HERE                        #
########################################################################

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

if {$ClipboardPopup == 1} {
  if {$ClipboardPopupTime == "" || ![regexp {^\d$} $ClipboardPopupTime]} {
    exitMsg 10 "ClipboardPopupTime should be 0-9 but is \"$ClipboardPopupTime\""
  }
} elseif {$ClipboardPopup != 0} {
  exitMsg 10 "ClipboardPopup should be 0-1 but is \"$ClipboardPopup\""
}

# AndroWish - packages sdltk and borg are included, no need to install
set sdltk_present [expr {[info commands "sdltk"]} ne {""}]
set borg_present [expr {[info commands "borg"]} ne {""}]

if {$NoGUI} {
    if {$Host == ""} {set Host $DefaultHost}
    if {$Port == ""} {set Port $DefaultPort}
} else {
    switch $::tcl_platform(platform) {
      "unix" {
        set rcfile $UnixRCfile
        set asfile $UnixASfile
      }
      "windows" {
        #set rcfile [file join $::env(AppData) "ncid.dat"]
        set rcfile $WinRCfile
      }
    }
    if {$PortableDir != ""} {
      set rcfile [file join $PortableDir ".ncid"]
      if {$asfile == $UnixASfile} {
        regsub {^[\w/]+(.config.*$)} $asfile {\1} asfile
        set asfile [file join $PortableDir/$asfile]
      }
    }

   processRCfile
   set delayedMsgs "$delayedMsgs\nProcessed RC file: $rcfile"
   set RCfileHost $Host
   set RCfilePort $Port
   set RCfileoldHost $oldHost
   set RCfileoldPort $oldPort
   set RCfileHosts $Hosts
   set RCfileHostIndex $HostIndex
   set RCfileThemeName $ThemeName
}

getArg
set delayedMsgs "$delayedMsgs\nProcessed command line arguments"

checkHosts

if {![regexp {^[0-2]+$} $LogEnable]} {
      set LogStatus "Log enable out of range: $LogEnable"
      set LogEnable 0
}

if {$LogEnable > 0} {
    # Make sure LogDir is created or set LogEnable 0
    if {$PortableDir != ""} {set LogDir [file join $PortableDir "logs"]}
    if {[regexp {[/+\w+]$} $LogDir]} {
        if {![file isdirectory $LogDir]} {
            if {[catch {file mkdir $LogDir} msg]} {
                set LogEnable 0; set LogStatus $msg
            }
        }
    } else {set LogEnable 0}
}
set LogDirLocation "Log Directory: [file normalize $LogDir]"
if {$LogEnable > 0} {doLogOpen}

set oldHost $Host
set oldPort $Port
set ArgHost $Host
set ArgPort $Port
set ArgoldHost $oldHost
set ArgoldPort $oldPort
set ArgHosts $Hosts
set ArgHostIndex $HostIndex

if {$NoGUI && $Verbose == 0} {set Verbose 1}

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

if {$Module != ""} {
    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
    }
}

# LoginName on chromebook and android are system-generated user names
# like uX_aYY where X is a user# like 0, 1, 2 and YY seems to be a
# process id for that user instance. It's currently not possible to
# retrieve the gmail address associated with the login user.
if {[machine platform] == "chromebook" } {
   set LoginName "chromebook user"
} elseif {[machine platform] == "android" } {
   set LoginName "android user"
} else {
   set LoginName $tcl_platform(user)
}

# log command line and any options on separate lines
  set cl "Command line: $::argv0"
  for {set cnt 0} {$cnt < $::argc} {incr cnt} {
      set optarg [lindex $::argv [expr $cnt + 1]]
      set opt [lindex $::argv $cnt]
      if {[string index $opt 0] == "-"} {
        logMsg $::LEVEL1 $cl
        set cl "              $opt"
      } else {
        append cl " " $opt;
      }
  }
  logMsg $::LEVEL1 $cl

logMsg $::LEVEL1 "$VersionInfo"
if {$NoGUI} {
    logMsg $::LEVEL1 "        Command line mode"
} else {
    logMsg $::LEVEL1 "        GUI mode"
}
logMsg $::LEVEL1 "Verbose Level: $Verbose"

# bug in AndroWish - as of version 2018-06-30,
# '[info nameofexecutable]' returns null
if {($Interpreter == "") && ([machine platform] == "android")} {
    set Interpreter $::env(PACKAGE_CODE_PATH)/wish
}
logMsg $::LEVEL1 "Interpreter: $Interpreter"
logMsg $::LEVEL1 "Default Host: $DefaultHost"
logMsg $::LEVEL1 "Default Port: $DefaultPort"
if {!$NoGUI && $::tcl_platform(platform) == "unix"} {
    logMsg $::LEVEL1 "AutoStart File: $asfile"
}

# Observed OS encoding systems
# Windows 10:       cp1252
# Mac (native GUI): utf-8
# Mac (XQuartz):    utf-8
# AndroWish:        utf-8
# Linux:            utf-8

logMsg $::LEVEL1 "Operating System Encoding: [encoding system]"

if {$LogDirLocation != ""} {logMsg $::LEVEL1 $LogDirLocation}
logMsg $::LEVEL1 $LogStatus
logMsg $::LEVEL1 "Platform: [machine platform]"
logMsg $::LEVEL1 "OS: [machine os]"
if {[machine platform] == "android"} {
   logMsg $::LEVEL1 "Android device model: [machine model]"
}
logMsg $::LEVEL1 "TCL library: [info library]"
logMsg $::LEVEL1 "TCL version: [info patchlevel]"

if {$PortableDir != ""} {
    logMsg $::LEVEL1 "Using PortableDir: $PortableDir"
}
logMsg $::LEVEL1 $delayedMsgs

if {$OptPmsg != ""} {logMsg $::LEVEL1 "$OptPmsg"}

if {!$NoGUI} {
    set DistThemes [lsort -dictionary [ttk::themes]]

    # only allow default theme for now
    set DistThemes default

    set AddonThemes ""
    if {[file isdirectory $ThemeDir] && [glob -nocomplain -dir $ThemeDir *] != 0} {
        lappend auto_path $ThemeDir

        # This forces an update of the available packages list.
        # It's required for package names to find the themes in
        # <path>/themes/*.tcl
        eval [package unknown] Tcl [package provide Tcl]
        set AddonThemes [lsort -dictionary [ttk::themes]]
        foreach t $DistThemes {
          set AddonThemes [lsearch -all -inline -not -exact $AddonThemes "$t"]
        }
    }

    #on Windows, force dialogs to have readable checkboxes and radiobuttons
    #test by going to View->TYPEs->Select and Preferences->Date and Time
    #acceptable  : alt default classic
    #unacceptable: winnative clam vista
    if {[machine platform] == "windows"} {ttk::style theme use "default"}

    #on AndroWish, any style besides droid is acceptable
    if {[ttk::style theme use] == "droid"} {ttk::style theme use "default"}

}

logMsg $::LEVEL1 "HostnameFlag: $HostnameFlag"
logMsg $::LEVEL1 "LineName: $LineName"
logMsg $::LEVEL1 "LoginName: $LoginName"
logMsg $::LEVEL1 "Delay between reconnect tries to the server: $Delay (seconds)"

# dump environment variables - useful when running in portable mode
logArray $::LEVEL5 "Environment variables:" ::env

#taa set About \
#"
#$VersionInfo
#$Author
#"

if {!$NoGUI} {
    logMsg $::LEVEL1 "Detected windowing system: [machine osgui]"
    logMsg $::LEVEL1 "Distributed Themes: $DistThemes"
    logMsg $::LEVEL1 "Custom Themes: day night"
    if {$AddonThemes != ""} {
        logMsg $::LEVEL1 "Addon Themes Dir: $ThemeDir"
        logMsg $::LEVEL1 "Addon Themes: $AddonThemes"
    }
    logMsg $::LEVEL1 "Current Theme: $ThemeName"
    logMsg $::LEVEL1 "ImageDir: $ImageDir"
    logMsg $::LEVEL1 "Popup time: $PopupTime"
    if {$NoExit} {
        set ExitOn do_nothing
        logMsg $::LEVEL1 "The \"Close Window\" ttk::button is disabled"
    }
    if {![regexp {^(:?char|word|none)$} $WrapLines]} {
        logMsg $::LEVEL1 "WrapLines set to invalid value of \"$WrapLines\", using default"
        set WrapLines "char"
    } else {
        logMsg $::LEVEL1 "WrapLines set to \"$WrapLines\""
    }
    if {$DialPrefix != ""} {
        logMsg $::LEVEL1 "Dial prefix: $DialPrefix"
    } else {
        logMsg $::LEVEL1 "Dial prefix: none"
    }
    setBrowser
    makeWindow

    fieldLabels
    logMsg $::LEVEL1 "Calculated History Text Field Width: $historyTextWidth characters"
    logMsg $::LEVEL1 "History Text Rows: $historyTextRows"
    if {$wmGeometry eq ""} {
    logMsg $::LEVEL1 "window geometry was not previously saved"
    } else {
    logMsg $::LEVEL1 "saved window geometry: $wmGeometry"
    }
    set geometry [wm geometry .]
    logMsg $::LEVEL1 "window geometry: $geometry"
    wm minsize . [regsub {(\d+).*} $geometry {\1}] $::histMinRows
    logMsg $::LEVEL1 "window minimum size: [wm minsize .]"

    if {$ClipboardPopup == 0} {
        set cpt "$ClipboardPopupTime second"
        if {$ClipboardPopupTime != 1} {append cpt "s"}
        logMsg $::LEVEL2 "Clipboard Window Popup Time:  $cpt"
    } else {logMsg $::LEVEL2 "Clipboard Popup Window Time: forever"}

    switch $TypeGroups {
        0 {logMsg $::LEVEL1 "View Types: All"}
        1 {logMsg $::LEVEL1 "View Types: Calls"}
        2 {logMsg $::LEVEL1 "View Types: Messages"}
        3 {logMsg $::LEVEL1 "View Types: Smart Phone"}
        4 {logMsg $::LEVEL1 "View Types: Custom"
           logMsg $::LEVEL3 "            SelectedTypes contains $SelectedTypes"
    }
    }
    switch $LineIDGroups {
        0   {logMsg $::LEVEL1 "View Lines: All"}
        >=1 {logMsg $::LEVEL1 "View Lines: $SelectedLineIDs"}
    }
}

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

if {$YearDot == 1 && $DateSepar eq "."} {set EndDot "."} else {set EndDot ""}
switch $AltDate {
    0 {logMsg $::LEVEL1 "Date Format: MM${DateSepar}DD${DateSepar}YYYY$EndDot"}
    1 {logMsg $::LEVEL1 "Date Format: DD${DateSepar}MM${DateSepar}YYYY$EndDot"}
    2 {logMsg $::LEVEL1 "Date Format: weekday month DD YYYY"}
    3 {logMsg $::LEVEL1 "Date Format: weekday DD month YYYY"}
}

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

if {$Module != ""} {
    if {[file exists $Module]} {
        if {![file executable $Module]} {
            # 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 "Module Not Executable: $Module"
            }
        }
    } else {exitMsg 3 "Module Not Found: $Module"}
    logMsg $::LEVEL1 "Using output Module: $Module"
    # change module name from <path>/ncid-<name> to ncid_<name>
    regsub {\-} $ModName {_} modopt
    # set the module option variable in $$modopt
    if {[catch {eval [subst $$modopt]} oops]} {
        logMsg $::LEVEL1 "No optional \"$modopt\" variable in ncid.conf"
    } 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 }
        logMsg $::LEVEL1 "Optional \"$modopt\" variable set \"$modvar\" to \"$modval\" in ncid.conf"
    }
    if {$CallOnRing} {
      switch -- $Ring {
        -9 {logMsg $::LEVEL1 "Will execute $Module every ring after CID"}
        -2 {logMsg $::LEVEL1 "Will execute $Module after hangup after answer"}
        -1 {logMsg $::LEVEL1 "Will execute $Module after hangup with no answer"}
         0 {logMsg $::LEVEL1 "Will execute $Module when ringing stops"}
         default {logMsg $::LEVEL1 "Will execute $Module at Ring $Ring"}
      }
    } elseif {$Module != ""} {
       logMsg $::LEVEL1 "Will execute $Module when CID arrives"
    }
}

# dump certain variables for troubleshooting
  logMsg $::LEVEL5 ""
  logMsg $::LEVEL5 "Status of variables after processing the config file:"
  logMsg $::LEVEL5 "    Host:      $ConfigFileHost"
  logMsg $::LEVEL5 "    Port:      $ConfigFilePort"
  logMsg $::LEVEL5 "    oldHost:   $ConfigFileoldHost"
  logMsg $::LEVEL5 "    oldPort:   $ConfigFileoldPort"
  logMsg $::LEVEL5 "    Hosts:     $ConfigFileHosts"
  logMsg $::LEVEL5 "    HostIndex: $ConfigFileHostIndex"

  if {!$NoGUI} {
  logMsg $::LEVEL5 ""
  logMsg $::LEVEL5 "Status of variables after processing RC file:"
  logMsg $::LEVEL5 "    Host:      $RCfileHost"
  logMsg $::LEVEL5 "    Port:      $RCfilePort"
  logMsg $::LEVEL5 "    oldHost:   $RCfileoldHost"
  logMsg $::LEVEL5 "    oldPort:   $RCfileoldPort"
  logMsg $::LEVEL5 "    Hosts:     $RCfileHosts"
  logMsg $::LEVEL5 "    HostIndex: $RCfileHostIndex"
  logMsg $::LEVEL5 "    ThemeName: $RCfileThemeName"
  }

  logMsg $::LEVEL5 ""
  logMsg $::LEVEL5 "Status of variables after processing command line arguments:"
  logMsg $::LEVEL5 "    Host:      $ArgHost"
  logMsg $::LEVEL5 "    Port:      $ArgPort"
  logMsg $::LEVEL5 "    oldHost:   $ArgoldHost"
  logMsg $::LEVEL5 "    oldPort:   $ArgoldPort"
  logMsg $::LEVEL5 "    Hosts:     $ArgHosts"
  logMsg $::LEVEL5 "    HostIndex: $ArgHostIndex"
  logMsg $::LEVEL5 ""

if {$NoGUI} doPID
connectCID
if {!$NoGUI} {bind .im <KeyPress-Return> handleGUIMSG}

# enter event loop
vwait forever
