#!/usr/bin/bash
# ncid - Network Caller-ID client

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

# 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 on most operating systems \
  TCLSH=`type tclsh | sed 's,^.* \/,/,;s, .*$,,'`
# set TCLSH variable on FreeBSD, FreeBSD will call tclsh something like tclsh8.6 \
  [ -z "$TCLSH" ] && TCLSH=`ls /usr/local/bin/tclsh*`
# set WISH variable on most operating systems \
  WISH=`which wish | sed 's,^.* \/,/,;s, .*$,,'`
# set WISH on FreeBSD, FreeBSD will call wish something like wish8.6 \
  [ -z "$WISH" ] && WISH=`ls /usr/local/bin/wish*`

# 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 "$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 Version             "1.18"
set API                 "API: 1.14 Feature Set 1 2 3 4 5"

set TZScript            "phonetz"
set TZFlag              0

set DefaultHost         127.0.0.1
set DefaultPort         3333

# number of minutes to pause ncidd hangup
set pauseValue        0

# NCID libraries
#set TABLELIST           tablelist_tile
set TABLELIST           tablelist
set GETOPT              getopt

set ConfigFile          /etc/ncid/ncid.conf
set ImageDir            /usr/share/ncid/images
set IconDir             /usr/share/ncid/icons
set Logo                $ImageDir/logo.png
set ThemesDir           /usr/share/ncid/themes
set MsgsDir             /usr/share/ncid/msgs
set ncid_library        /usr/share/ncid/lib
set PluginDir           /usr/share/ncid/plugins

### 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             1
set Debug               0
set CallOnRing          0
set HostnameFlag        0
set Ring                999
set NoExit              0
set WakeUp              0
set ExitOn              exit
set CallLogFlag         0
set Country             "US"
set asfile              ""
set LogEnable           2
set RCdir               ".ncid"
set RCfile              "ncidrc"
set RCnoteDir           "notes"

if {[catch {info exists $::env(HOME)} oops]} {
  set delayedMsgs       "$delayedMsgs\n$oops"
  set LogDir            "/tmp/ncid"
} else {
  set LogDir            "$::env(HOME)/NCID/client"
  set UnixRCdir         "$::env(HOME)/$RCdir"
  set UnixASfile        "$::env(HOME)/.config/autostart/ncid.desktop"
}

### 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 preClient_1_0         0
set ClipboardPopup        1
set ClipboardPopupTime    3
set disable_server_dialer 0
set menu_plugins          {}
set context_plugins       {}

### 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 DUR 0 LINE 1 NMBR 1 \
                         NAME 1 NTYPE 0 CTRY 0 LOCA 0 CARI 0 MTYPE 1 MESG 1]
set exactMatch          0
set DurationMode        "C"

### global variables that are used as static variables
set note_text           ""
set NightMode           0
set ncidDir             "$ThemesDir/ncid"
set onlyNCIDthemes      "1"
set ncidThemes          "day night default"
set themeName           "day"
set builtinThemes       "default"
set addonDir            "$ThemesDir/addon"
set addonThemes         ""
set oldThemeName        $themeName
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 oldDurationMode     $DurationMode
set Leading1            "Leave"
set oldLeading1         $Leading1
set oldLabelList        [list]
set oldExactMatch       $exactMatch
set DialPrefixList      {"" 9- 9, 9W 2- 2, 2W}
set DialPrefix          [lindex $DialPrefixList 0]
set oldDialPrefix       ""
set DialNumber          ""
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 do_pause            0
set multi               0
set menuDisabled        0
set ContextMenuDisabled 0
set typeWidth           4
set dateWidth           18
set timeWidth           10
set lineIDWidth         16
set nameWidth           25
set nmbrWidth           16
set fnmbrWidth          $nmbrWidth
set ntypeWidth          6
set countryWidth        4
set locationWidth       12
set carrierWidth        13
set mtypeWidth          5
set mesgWidth           30
set durationWidth       12
set durationKeyWidth    1024
set tooltipWidth        30

### minWidth of fields
set typeMinWidth        12
set dateMinWidth        18
set dateMinWidth_small  15
set dateMinWidth_large  18
set timeMinWidth        13
set timeMinWidth_small  10
set timeMinWidth_large  13
set lineIDMinWidth      16
set nmbrMinWidth        16
set fnmbrMinWidth       $nmbrMinWidth
set nameMinWidth        22
set ntypeMinWidth       5
set countryMinWidth     4
set locationMinWidth    12
set carrierMinWidth     13
set mtypeMinWidth       12
set mesgMinWidth        30
set durationMinWidth    4

### icons
set flagImageSize       16
set NTYPEImageSize      16

set  flagImageList {}
set  NTYPEImageList {}

### 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 noteArray     {}
set HostIndex           -1
set SelAliasType        ""
set ChangeHostFlag      0
set HUP_foreground "black"
set regexDigit          0
set exprText            ""
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]

set hide 0

### 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 typeREQ             ""
set nmbrREQ             ""
set fnmbrREQ            ""
set nameREQ             ""
set lineREQ             ""
set ntypeREQ            ""
set countryREQ          ""
set locationREQ         ""
set carrierREQ          ""
set mtypeREQ            ""
set mesgREQ             ""
set displayedRowREQ     ""
set aliasEntry          ""
set lineEntry           ""
set WhiteEntry          ""
set blackEntry          ""

set tv_scrollbar "off"
set searched_name ""
set searched_name_star ""

set search_last_found_index -1
set ScriptDir [file normalize [file dirname [info script]]]
if {$ScriptDir == [pwd]} {
  file normalize [file dirname [info script]]
  set PortableDir $ScriptDir
  set ConfigFile [file join $PortableDir [file tail $ConfigFile]]
  set ThemesDir [file join $PortableDir [file tail $ThemesDir]]
  set MsgsDir [file join $PortableDir [file tail $MsgsDir]]
  set LogDir [file join $PortableDir [file tail $LogDir]]
  set ncidDir $ThemesDir/ncid
  set addonDir $ThemesDir/addon
}
if {[glob -nocomplain -dir $ThemesDir *] ne ""} {
    logMsg $::LEVEL1 "Themes directories exist"
} else {
    set ::ThemesDir ""
    set ::ncidDir ""
    set ::addonDir ""
}

set auto_path [linsert $auto_path 0 $ncid_library]
package require $GETOPT
package require msgcat

namespace import msgcat::mc
::msgcat::mcload $MsgsDir

if {$::tcl_platform(os) == "Windows NT"} {
    set PluginDir [file join $PortableDir [file tail $PluginDir]]
    set ModDir [file join $PortableDir [file tail $ModDir]]
}

if {!$NoGUI} {
    package require $TABLELIST
    package require BWidget

    # set PATH for optional plugins and context_plugins scripts
    if {$::tcl_platform(os) == "Windows NT"} {
      regsub -all {/} $PluginDir {\\} PluginDir
      set ::env(PATH) "$PluginDir;$::env(PATH)"
    } else {
      set ::env(PATH) "$PluginDir:$::env(PATH)"
    }
}

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 [list "$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"}

array set ntypeArray {
  FIXED      "Land-line Number"
  CELL       "Mobile Number"
  FIX/CELL   "Land-line Or Mobile Number"
  T-FREE     "Toll Free Number"
  PR-RATE    "Premium Rate Number"
  SHR-COST   "Shared Cost Number"
  VOIP       "Voice Over IP number"
  PERS-NUM   "Personal Number"
  PAGER      "Pager Device Number"
  UAN        "Unified Access Number"
  V-MAIL     "Voice Mail (Answering Machine)"
  UNKNOWN    "Unknown"
  S_T-FREE   "short number TOLL_FREE"
  S_STD-RATE "short number STANDARD_RATE"
  S_PR-RATE  "short number PREMIUM_RATE"
  S_UNKNOWN  "Short number Unknown"
  S_SMS      "Short number SMS service"
  S_EMGRCY   "Short number Emergency"
}

array set ntypeImageArray {
  FIXED      "FIXED"
  CELL       "CELL"
  FIX/CELL   "FIX_CELL"
  T-FREE     "T-FREE"
  PR-RATE    "PR-RATE"
  SHR-COST   "SHR-COST"
  VOIP       "VOIP"
  PERS-NUM   "PERS-NUM"
  PAGER      "PAGER"
  UAN        "UAN"
  V-MAIL     "V-MAIL"
  UNKNOWN    "UNKNOWN"
  S_T-FREE   "S_T-FREE"
  S_STD-RATE "S_STD-RATE"
  S_PR-RATE  "S_PR-RATE"
  S_UNKNOWN  "S_UNKNOWN"
  S_SMS      "S_SMS"
  S_EMGRCY   "S_EMGRCY"
}

array set countryName {
  AD     "Andorra"
  AE     "United Arab Emirates"
  AF     "Afghanistan"
  AG     "Antigua and Barbuda"
  AI     "Anguilla"
  AL     "Albania"
  AM     "Armenia"
  AO     "Angola"
  AQ     "Antarctica"
  AR     "Argentina"
  AS     "American Samoa"
  AT     "Austria"
  AU     "Australia"
  AW     "Aruba"
  AX     "Åland Islands"
  AZ     "Azerbaijan"
  BA     "Bosnia and Herzegovina"
  BB     "Barbados"
  BD     "Bangladesh"
  BE     "Belgium"
  BF     "Burkina Faso"
  BG     "Bulgaria"
  BH     "Bahrain"
  BI     "Burundi"
  BJ     "Benin"
  BL     "Saint Barthélemy"
  BM     "Bermuda"
  BN     "Brunei Darussalam"
  BO     "Bolivia, Plurinational State of"
  BQ     "Bonaire, Sint Eustatius and Saba"
  BR     "Brazil"
  BS     "Bahamas"
  BT     "Bhutan"
  BV     "Bouvet Island"
  BW     "Botswana"
  BY     "Belarus"
  BZ     "Belize"
  CA     "Canada"
  CC     "Cocos (Keeling) Islands"
  CD     "Congo, the Democratic Republic of the"
  CF     "Central African Republic"
  CG     "Congo"
  CH     "Switzerland"
  CI     "Côte d'Ivoire"
  CK     "Cook Islands"
  CL     "Chile"
  CM     "Cameroon"
  CN     "China"
  CO     "Colombia"
  CR     "Costa Rica"
  CU     "Cuba"
  CV     "Cape Verde"
  CW     "Curaçao"
  CX     "Christmas Island"
  CY     "Cyprus"
  CZ     "Czech Republic"
  DE     "Germany"
  DJ     "Djibouti"
  DK     "Denmark"
  DM     "Dominica"
  DO     "Dominican Republic"
  DZ     "Algeria"
  EC     "Ecuador"
  EE     "Estonia"
  EG     "Egypt"
  EH     "Western Sahara"
  ER     "Eritrea"
  ES     "Spain"
  ET     "Ethiopia"
  FI     "Finland"
  FJ     "Fiji"
  FK     "Falkland Islands (Malvinas)"
  FM     "Micronesia, Federated States of"
  FO     "Faroe Islands"
  FR     "France"
  GA     "Gabon"
  GB     "United Kingdom"
  GD     "Grenada"
  GE     "Georgia"
  GF     "French Guiana"
  GG     "Guernsey"
  GH     "Ghana"
  GI     "Gibraltar"
  GL     "Greenland"
  GM     "Gambia"
  GN     "Guinea"
  GP     "Guadeloupe"
  GQ     "Equatorial Guinea"
  GR     "Greece"
  GS     "South Georgia and the South Sandwich Islands"
  GT     "Guatemala"
  GU     "Guam"
  GW     "Guinea-Bissau"
  GY     "Guyana"
  HK     "Hong Kong"
  HM     "Heard Island and McDonald Islands"
  HN     "Honduras"
  HR     "Croatia"
  HT     "Haiti"
  HU     "Hungary"
  ID     "Indonesia"
  IE     "Ireland"
  IL     "Israel"
  IM     "Isle of Man"
  IN     "India"
  IO     "British Indian Ocean Territory"
  IQ     "Iraq"
  IR     "Iran, Islamic Republic of"
  IS     "Iceland"
  IT     "Italy"
  JE     "Jersey"
  JM     "Jamaica"
  JO     "Jordan"
  JP     "Japan"
  KE     "Kenya"
  KG     "Kyrgyzstan"
  KH     "Cambodia"
  KI     "Kiribati"
  KM     "Comoros"
  KN     "Saint Kitts and Nevis"
  KP     "Korea, Democratic People's Republic of"
  KR     "Korea, Republic of"
  KW     "Kuwait"
  KY     "Cayman Islands"
  KZ     "Kazakhstan"
  LA     "Lao People's Democratic Republic"
  LB     "Lebanon"
  LC     "Saint Lucia"
  LI     "Liechtenstein"
  LK     "Sri Lanka"
  LR     "Liberia"
  LS     "Lesotho"
  LT     "Lithuania"
  LU     "Luxembourg"
  LV     "Latvia"
  LY     "Libya"
  MA     "Morocco"
  MC     "Monaco"
  MD     "Moldova, Republic of"
  ME     "Montenegro"
  MF     "Saint Martin (French part)"
  MG     "Madagascar"
  MH     "Marshall Islands"
  MK     "Macedonia, the former Yugoslav Republic of"
  ML     "Mali"
  MM     "Myanmar"
  MN     "Mongolia"
  MO     "Macao"
  MP     "Northern Mariana Islands"
  MQ     "Martinique"
  MR     "Mauritania"
  MS     "Montserrat"
  MT     "Malta"
  MU     "Mauritius"
  MV     "Maldives"
  MW     "Malawi"
  MX     "Mexico"
  MY     "Malaysia"
  MZ     "Mozambique"
  NA     "Namibia"
  NC     "New Caledonia"
  NE     "Niger"
  NF     "Norfolk Island"
  NG     "Nigeria"
  NI     "Nicaragua"
  NL     "Netherlands"
  NO     "Norway"
  NP     "Nepal"
  NR     "Nauru"
  NU     "Niue"
  NZ     "New Zealand"
  OM     "Oman"
  PA     "Panama"
  PE     "Peru"
  PF     "French Polynesia"
  PG     "Papua New Guinea"
  PH     "Philippines"
  PK     "Pakistan"
  PL     "Poland"
  PM     "Saint Pierre and Miquelon"
  PN     "Pitcairn"
  PR     "Puerto Rico"
  PS     "Palestine, State of"
  PT     "Portugal"
  PW     "Palau"
  PY     "Paraguay"
  QA     "Qatar"
  RE     "Réunion"
  RO     "Romania"
  RS     "Serbia"
  RU     "Russian Federation"
  RW     "Rwanda"
  SA     "Saudi Arabia"
  SB     "Solomon Islands"
  SC     "Seychelles"
  SD     "Sudan"
  SE     "Sweden"
  SG     "Singapore"
  SH     "Saint Helena, Ascension and Tristan da Cunha"
  SI     "Slovenia"
  SJ     "Svalbard and Jan Mayen"
  SK     "Slovakia"
  SL     "Sierra Leone"
  SM     "San Marino"
  SN     "Senegal"
  SO     "Somalia"
  SR     "Suriname"
  SS     "South Sudan"
  ST     "Sao Tome and Principe"
  SV     "El Salvador"
  SX     "Sint Maarten (Dutch part)"
  SY     "Syrian Arab Republic"
  SZ     "Swaziland"
  TC     "Turks and Caicos Islands"
  TD     "Chad"
  TF     "French Southern Territories"
  TG     "Togo"
  TH     "Thailand"
  TJ     "Tajikistan"
  TK     "Tokelau"
  TL     "Timor-Leste"
  TM     "Turkmenistan"
  TN     "Tunisia"
  TO     "Tonga"
  TR     "Turkey"
  TT     "Trinidad and Tobago"
  TV     "Tuvalu"
  TW     "Taiwan, Province of China"
  TZ     "Tanzania, United Republic of"
  UA     "Ukraine"
  UG     "Uganda"
  UM     "United States Minor Outlying Islands"
  US     "United States"
  UY     "Uruguay"
  UZ     "Uzbekistan"
  VA     "Holy See (Vatican City State)"
  VC     "Saint Vincent and the Grenadines"
  VE     "Venezuela, Bolivarian Republic of"
  VG     "Virgin Islands, British"
  VI     "Virgin Islands, U.S."
  VN     "Viet Nam"
  VU     "Vanuatu"
  WF     "Wallis and Futuna"
  WS     "Samoa"
  YE     "Yemen"
  YT     "Mayotte"
  ZA     "South Africa"
  ZM     "Zambia"
  ZW     "Zimbabwe"
}


array set mtypeArray {
 IN      "message received"
 OUT     "message sent"
 SYS     "server message"
 USER    "user message"
 -       "unknown message type"
}



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

### 4 lines + 1 header = 5
set histMinRows 5
set historyTextRows $histMinRows

### these are the column labels as displayed to the user
set dispTYPE  [::msgcat::mc "TYPE"]
set dispDATE  [::msgcat::mc "DATE"]
set dispTIME  [::msgcat::mc "TIME"]
set dispDUR   [::msgcat::mc "DURATION-$DurationMode"]
set dispLINE  [::msgcat::mc "LINE ID"]
set dispNMBR  [::msgcat::mc "NUMBER"]
set dispNAME  [::msgcat::mc "NAME"]
set dispNTYPE [::msgcat::mc "NTYPE"]
set dispCTRY  [::msgcat::mc "CTRY"]
set dispLOCA  [::msgcat::mc "LOCATION"]
set dispCARI  [::msgcat::mc "CARRIER"]
set dispMTYPE [::msgcat::mc "MTYPE"]
set dispMESG  [::msgcat::mc "MESSAGE"]

### 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 dlenDUR   [string length $dispDUR]
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  $dlenDUR $dlenLINE \
                    $dlenNMBR $dlenNAME $dlenNTYPE $dlenCTRY \
                    $dlenLOCA $dlenCARI $dlenMTYPE           \
              ]

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


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

### 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]
               [--debug                     | -D]
               [--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    [::msgcat::mc  "Server: not connected" ]
set ServerVersion $noServer

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

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

set labBLK [::msgcat::mc  "BLK:  Blocked           - blacklisted call blocked"]
set labCID [::msgcat::mc  "CID:  Caller ID         - incoming call"]
set labHUP [::msgcat::mc  "HUP:  Hangup            - blacklisted call hangup"]
set labMSG [::msgcat::mc  "MSG:  Message           - text message from a user or the server"]
set labMWI [::msgcat::mc  "MWI:  Voicemail         - one or more voicemail messages"]
set labNOT [::msgcat::mc  "NOT:  Notice            - a smartphone message notice"]
set labOUT [::msgcat::mc  "OUT:  Out               - outgoing call"]
set labPID [::msgcat::mc  "PID:  Phone ID          - Caller ID from a smartphone"]
set labPUT [::msgcat::mc  "PUT:  Phone out call    - outgoing Caller ID from a smartphone"]
set labRID [::msgcat::mc  "RID:  Ring Back         - rings back when called number is available"]
set labWID [::msgcat::mc  "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"  ] - [::msgcat::mc "ncidd line type"]"
set labDATE  "[format "%-${dlenMax}.${dlenMax}s" "$dispDATE"  ] - [::msgcat::mc "date of call or message"]"
set labTIME  "[format "%-${dlenMax}.${dlenMax}s" "$dispTIME"  ] - [::msgcat::mc "time of call or message"]"
if {$DurationMode == "C"} {
 set labDUR   "[format "%-${dlenMax}.${dlenMax}s" "$dispDUR"   ] - [::msgcat::mc "call duration"]"
 } else {
 set labDUR   "[format "%-${dlenMax}.${dlenMax}s" "$dispDUR"   ] - [::msgcat::mc "talk duration"]"
}
set labLINE  "[format "%-${dlenMax}.${dlenMax}s" "$dispLINE"  ] - [::msgcat::mc "lineid of incoming or outgoing call"]"
set labNMBR  "[format "%-${dlenMax}.${dlenMax}s" "$dispNMBR"  ] - [::msgcat::mc "phone number"]"
set labNAME  "[format "%-${dlenMax}.${dlenMax}s" "$dispNAME"  ] - [::msgcat::mc "phone number name"]"
set labNTYPE "[format "%-${dlenMax}.${dlenMax}s" "$dispNTYPE" ] - [::msgcat::mc "phone number device type"]"
set labCTRY  "[format "%-${dlenMax}.${dlenMax}s" "$dispCTRY"  ] - [::msgcat::mc "phone number country"]"
set labLOCA  "[format "%-${dlenMax}.${dlenMax}s" "$dispLOCA"  ] - [::msgcat::mc "phone number location or city"]"
set labCARI  "[format "%-${dlenMax}.${dlenMax}s" "$dispCARI"  ] - [::msgcat::mc "phone number carrier"]"
set labMTYPE "[format "%-${dlenMax}.${dlenMax}s" "$dispMTYPE" ] - [::msgcat::mc "message type"]"
set labMESG  "[format "%-${dlenMax}.${dlenMax}s" "$dispMESG"  ] - [::msgcat::mc "message/additional info"]"

set fieldList \
"
$labTYPE
$labDATE
$labTIME
$labDUR
$labLINE
$labNMBR
$labNAME
$labNTYPE
$labCTRY
$labLOCA
$labCARI
$labMTYPE
$labMESG
"
set mIN     [::msgcat::mc "IN     - message received"]
set mOUT    [::msgcat::mc "OUT    - message sent"]
set mSYS    [::msgcat::mc "SYS    - server message"]
set mUSER   [::msgcat::mc "USER   - user message"]
set mNOTYPE [::msgcat::mc "-      - unknown message type"]

set mtypeList \
"
$mIN
$mOUT
$mSYS
$mUSER
$mNOTYPE
"

set historyHelp \
"
[::msgcat::mc "Select a line    - left mouse button"]
[::msgcat::mc "Unselect a line  - <CTRL> left mouse button"]

[::msgcat::mc "You can remove a history window line selection by clicking"]
[::msgcat::mc "on a line below it or on \"Send Message:\"."]

[::msgcat::mc "Context Menu - right mouse button"]
[::msgcat::mc "    Copy to Clipboard"]
[::msgcat::mc "    Add/Edit a Note..."]
[::msgcat::mc "    US Number Info"]
[::msgcat::mc "    (Other Context Menu plugins will appear here if"]
[::msgcat::mc "    defined in ncid.conf)"]
[::msgcat::mc "    Display ncid variables"]
[::msgcat::mc "    Add/Modify/Remove Alias in Alias File..."]
[::msgcat::mc "    Add Entry to or Remove Entry from Whitelist File"]
[::msgcat::mc "    Add Entry to or Remove Entry from Blacklist File"]
[::msgcat::mc "    Dial Number from History..."]

[::msgcat::mc "If a modem is active and the phone number of the line selected"]
[::msgcat::mc "is all digits, \"Dial Number from History...\" will also"]
[::msgcat::mc "be enabled."]

[::msgcat::mc "Once you modify the ALIAS file, you must go to the Server"]
[::msgcat::mc "Menu and:"]
[::msgcat::mc "    * Reload alias, blacklist and whitelist files."]
[::msgcat::mc "    * Update the current call log or all call logs."]
[::msgcat::mc "    * Reread call log."]

[::msgcat::mc "Once you modify the BLACKLIST or WHITELIST file, you must"]
[::msgcat::mc "go to the Server Menu and:"]
[::msgcat::mc "    * Reload alias, blacklist and whitelist files."]
"

set historyLocale \
"
[::msgcat::mc "Supported Languages:"]
[::msgcat::mc "US English (default),"]
[::msgcat::mc "French, German, Japanese"]
"

set aliasDesc \
"[::msgcat::mc "NAMEDEP   - change name to an alias if number received"]
[::msgcat::mc  "NAMEONLY  - change name to an alias"]
[::msgcat::mc  "NUMBDEP   - change nmbr to an alias if name received"]
[::msgcat::mc  "NMBRONLY  - change nmbr to an alias"]
[::msgcat::mc  "NMBRNAME  - change both name and number to an alias if same"]
[::msgcat::mc  "LINEONLY  - Change the line ID to an alias"]
"
set regexDigitExpr \
"[::msgcat::mc "0 - NCID Simple Expressions"]
[::msgcat::mc "1 - Posix Regular Expressions"]
[::msgcat::mc "2 - Perl Regular Expressions"]
"

set prefHelp \
"
[::msgcat::mc  "Font...           : Choose the fonts and fonts size."]
[::msgcat::mc  "Date and Time...  : Choose the date and time display format."]
[::msgcat::mc  "Line Types        : Shows all NCID line types.  Those that"]
[::msgcat::mc  "                    are not crossed out are those that show"]
[::msgcat::mc  "                    up in the history window."]
[::msgcat::mc  "LineIDs           : Shows all line identifications.  Those"]
[::msgcat::mc  "                    that are not crossed out are those that"]
[::msgcat::mc  "                    show up in the history window."]
[::msgcat::mc  "Select Columns... : Choose which columns will be displayed."]
[::msgcat::mc  "Duration Mode     : Choose duration calculation mode"]
[::msgcat::mc  "                    (Talk time or Call time)."]
"
set searchHelp \
"
[::msgcat::mc  "The Search Menu will search for a string in the history window.\n"]
[::msgcat::mc  "The \"Find Name\" sub-menu entry will add a row below \"Send Message:\"."]
[::msgcat::mc  "The added row consists of a place to type the search"]
[::msgcat::mc  "string, an Up button, a Down button and a Close button.\n"]
[::msgcat::mc  "The search string is case-insensitive."]
"

set serverHelp \
"
[::msgcat::mc  "\"Select NCID Server\" menu entry:"]
[::msgcat::mc  "    Presents the list of available servers and ports as"]
[::msgcat::mc  "    defined in ncid.conf."]

[::msgcat::mc  "\"Reload alias, blacklist and whitelist files\" menu entry:"]
[::msgcat::mc  "    Server reloads its Alias, Blacklist and Whitelist files."]

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

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

[::msgcat::mc  "\"Reread call log\" menu entry:"]
[::msgcat::mc  "    Server resends the cidcall.log file."]

[::msgcat::mc  "Once you modify the ALIAS file you should:"]
[::msgcat::mc  "    * Reload alias, blacklist and whitelist files."]
[::msgcat::mc  "    * Update the current call log or all call logs."]
[::msgcat::mc  "    * Reread call log."]

[::msgcat::mc  "Once you modify the BLACKLIST or WHITELIST file, you should:"]
[::msgcat::mc  "    * Reload alias, blacklist and whitelist files"]

[::msgcat::mc  "\"Dial Number Manually...\" menu entry:"]
[::msgcat::mc  "     If a modem is active, \"Dial Number Manually...\" is enabled."]

[::msgcat::mc  "\"Pause Hangup...\" menu entry:"]
[::msgcat::mc  "     If hangup/hupmode settings are configured in the server\'s"]
[::msgcat::mc  "     ncidd.conf file, this option will be enabled to temporarily"]
[::msgcat::mc  "     pause and resume the hangups."]

[::msgcat::mc "      The Query button updates the status each time it is clicked."]

[::msgcat::mc  "     Set the hours and minutes timer for the pause duration."]
[::msgcat::mc  "     Hangups will resume at the end of the pause duration."]

[::msgcat::mc  "     Set the hours and minutes to zero to resume immediately."]
"

set tooltipHelp \
"
[::msgcat::mc "User created tooltips can appear on any of the cells of a row,\nunless that cell has already a help tooltip that takes priority."]
[::msgcat::mc "User notes have tooltips with an Azure color background."]

[::msgcat::mc "Column labels have help tooltips.\nCells in rows can also have help tooltips."]
[::msgcat::mc "Help tooltips have a light yellow color background."]

[::msgcat::mc "COLUMN     TOOLTIP FOR COLUMN IN ROW"]

[::msgcat::mc "TYPE     : explain the type string used for a call or message"]
[::msgcat::mc "DATE     : display a note if there is one"]
[::msgcat::mc "TIME     : display local time of caller phone number if timezone"]
[::msgcat::mc "           is not local"]
[::msgcat::mc "DURATION : display a note if there is one"]
[::msgcat::mc "LINE ID  : display full line id if ellipsized"]
[::msgcat::mc "NUMBER   : display full caller number or text, if ellipsized"]
[::msgcat::mc "NAME     : display full caller name if ellipsized"]
[::msgcat::mc "NTYPE    : explain type of number \(mobile fixed voip, etc\)"]
[::msgcat::mc "CTRY     : display name of country if not the local country"]
[::msgcat::mc "LOCATION : display full location if ellipsized or display local\n           time of caller phone number if timezone is not local"]
[::msgcat::mc "CARRIER  : display full carrier name if ellipsized"]
[::msgcat::mc "MTYPE    : explain type of message string or display full \n           message if message column is hidden"]
[::msgcat::mc "MESSAGE  : display full message if ellipsized"]
"

set dialHelp \
"
[::msgcat::mc "The Server Dial Line section shows the lineid used for"]
[::msgcat::mc "dialing.  If the server is using more than one modem,"]
[::msgcat::mc "select the lineid for dialing.\n"]
[::msgcat::mc "If an outgoing line prefix is needed, select the prefix"]
[::msgcat::mc "and separator.  They can be changed at any time.\n"]
[::msgcat::mc "  The prefix can be 9 or 2.\n"]
[::msgcat::mc "  The separator can be:"]
[::msgcat::mc "    '-' - ignored"]
[::msgcat::mc "    'W' - wait for dialtone"]
[::msgcat::mc "    ',' - brief delay\n"]
[::msgcat::mc "Manual Dial:"]
[::msgcat::mc "  The prefix and separator selected will appear in the"]
[::msgcat::mc "  window to type the phone number.  They cannot be removed,"]
[::msgcat::mc "  but a new prefix and separator can be selected.\n"]
[::msgcat::mc "History Dial:"]
[::msgcat::mc "  The leading one in a number can be added, removed or"]
[::msgcat::mc "  ignored if there or not there."]
"

set pluginHelp \
"
[::msgcat::mc "Plugins are not shown unless they are defined in ncid.conf."]
[::msgcat::mc "Plugins can be in the Plugins Menu in the menu bar or in the"]
[::msgcat::mc "contextual menu for a line in the history window.  Both are"]
[::msgcat::mc "created the same way.\n"]
[::msgcat::mc "Plugin format:"]
[::msgcat::mc "  {\"Menu Name\" \"program/script \[options/arguments\]\"}\n"]
[::msgcat::mc "Menu Plugin Example:"]
[::msgcat::mc "  {\"Hello World Example\" \"zenity --info --text \\\"hello world\\\"\"}\n"]
[::msgcat::mc "Context Menu Plugin Example:"]
[::msgcat::mc "  {\"US Number Info\" \"us_number_info {\$fnmbrREQ} {\$Country}\"}\n"]
[::msgcat::mc "The contextual plugin uses variables from a call or message:"]
[::msgcat::mc "  country     : country where ncidd is running"]
[::msgcat::mc "  typeREQ     : ncidd line type (CID/HUP/WID/OUT/MSG/etc)"]
[::msgcat::mc "  dateREQ     : date of call (MMDDYYYY)"]
[::msgcat::mc "  timeREQ     : time of call (HHMM)"]
[::msgcat::mc "  durationREQ : duration of call or talk portion (HH:MM:SS)"]
[::msgcat::mc "  lineREQ     : lineid of incoming or outgoing call (POTS, VoIP, etc)"]
[::msgcat::mc "  nmbrREQ     : unformatted phone number (all digits)"]
[::msgcat::mc "  fnmbrREQ    : formatted phone number using local country formatting rules"]
[::msgcat::mc "  nameREQ     : phone number name"]
[::msgcat::mc "  ntypeREQ    : phone number device type (Fixed, mobile, etc)"]
[::msgcat::mc "  countryREQ  : phone number country"]
[::msgcat::mc "  locationREQ : phone number location or city"]
[::msgcat::mc "  carrierREQ  : phone number carrier (NTT West, BELLSOUTH, etc)"]
[::msgcat::mc "  mtypeREQ    : message type (IN/OUT/SYS/USER/-)"]
[::msgcat::mc "  mesgREQ     : message"]
"

set autosaveHelp \
"
[::msgcat::mc "The \"Auto Save\" menu item, in the File menu, is used to"]
[::msgcat::mc "automatically save the size, or size and position, when ncid"]
[::msgcat::mc "is terminated.\n"]
[::msgcat::mc "Autosave has 3 modes, the default is off:\n"]
[::msgcat::mc "  Size"]
[::msgcat::mc "  Size and Position"]
[::msgcat::mc "  Off"]
"

set autostartHelp \
"
[::msgcat::mc "The \"Auto Start\" menu item, in the File menu, is used to"]
[::msgcat::mc "automatically start ncid in GUI mode when the user logs"]
[::msgcat::mc "on.  The autostart feature should be supported by most"]
[::msgcat::mc "Unix and Linux desktops, including the Gnome desktop.\n"]
[::msgcat::mc "Autostart can be turned on 3 ways and also be turned off."]
[::msgcat::mc "The default is off:\n"]
[::msgcat::mc "  On"]
[::msgcat::mc "  On with desktop notifications"]
[::msgcat::mc "  On only desktop notifications"]
[::msgcat::mc "  Off\n"]
[::msgcat::mc "The Raspberry Pi OS has another method to configure autostart."]
[::msgcat::mc "It is more complex than simply clicking on an autostart item"]
[::msgcat::mc "in the ncid File menu:"]
"

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

# set the active or dsable state for server menu items
proc serverMenu {state} {
    set menu .menubar.server
    $menu entryconfigure [::msgcat::mc "Reload alias, blacklist and whitelist files"] -state $state
    $menu entryconfigure [::msgcat::mc "Update current call log"] -state $state
    $menu entryconfigure [::msgcat::mc "Update all call logs"] -state $state
    $menu entryconfigure [::msgcat::mc "Reread call log"] -state $state
    if {$state != "active"} {
        # menu is active or disabled by text received from ncidd when it starts
        $menu entryconfigure [::msgcat::mc "Dial Number Manually..."] -state $state
    }
    $menu entryconfigure [::msgcat::mc "Pause Hangup..."] -state $state
}

# 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 $::ThemesDir]} {
    set sysInfo2 "\nAddon Themes Dir: $::ThemesDir/"
} 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 descriptions to server options other than LineIDS
proc descServerOpt {serveropt} {
    switch -regexp $serveropt {
        "hangup-1|hupmode-1" {set serveropt "$serveropt: Normal Hangup"}
        "hangup-2" {set serveropt "$serveropt: Fax Hangup"}
        "hangup-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: $msg"
        }
    }

    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 API
    global Ring CallOnRing regexDigit
    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 contextMenuDisabled
    global CIDaliasType LineAliasType
    global ServerOptions
    global TypeGroups SelectedTypes ChangeHostFlag
    global LineIDGroups SelectedLineIDs DiscoveredLineIDs
    global themeName
    global hide do_pause
    global DurationMode
    global aliasEntry lineEntry whiteEntry blackEntry
    global disable_server_dialer

    # 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 {
                set ContextMenuDisabled 1
                if {$menuDisabled == 0} {
                  # disable server menu items
                  serverMenu {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\n$API"
               set targetTime 0
            } else {
                set targetTime [expr [clock clicks -milliseconds] + 500]
                set apigui [regsub {^API:} $API {API   :}]
                displayCID "$VersionInfo\n$ServerVersion\n$apigui" 1
                }
        } elseif {[string match 254* $dataBlock]} {
            # NCID server sent start of call log message
            if {!$NoGUI} {
                set DoingCallLog 1
                set Begin [clock clicks -milliseconds]
            }
        } elseif {[string match {25[0-3]*} $dataBlock]} {
            # NCID server sent call log message
            set DoingCallLog 0
            if {[regexp {250} $dataBlock]} {
                # NCID server sent end of call log message
                if {!$NoGUI} {
                    if {$ChangeHostFlag == 1} {
                        set DiscoveredLineIDs [lsort -dictionary $DiscoveredLineIDs]
                        set SelectedLineIDs $DiscoveredLineIDs
                        logMsg $::LEVEL1 "$dataBlock - $display_line_num lines"
                        write_rc_file "set SelectedLineIDs" "set SelectedLineIDs \"$SelectedLineIDs\""
                        set ChangeHostFlag 0
                    }
                }
            set End [clock clicks -milliseconds]
            set elapsed [expr $End - $Begin]
            set tsec    [expr $elapsed / 1000]
            set ms      [expr $elapsed % 1000]
            set min     [expr $tsec / 60]
            set sec     [expr $tsec - ($min * 60)]
        .tbl yview moveto  1
        logMsg $::LEVEL2 "[.tbl size] call history entries in $elapsed milliseconds (${min}m ${sec}s ${ms}ms)"
            } 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 $::LEVEL1 "regexDigit: $regexDigit"
            logMsg $::LEVEL2 "ServerOptions: [split [regsub -all {^\n|  +} $ServerOptions {}] "\n"]"
            if {!$NoGUI} {
                logMsg $::LEVEL3 "DiscoveredLineIDs: $DiscoveredLineIDs"
                if {$disable_server_dialer} {
                    .menubar.server entryconfigure [::msgcat::mc "Dial Number Manually..."] -state disabled
                    logMsg $::LEVEL3 "Server Dialer Disabled"
                } else {
                    if {[llength $ServerOptLineIDS] == 1} {
                        logMsg $::LEVEL3 "Server DialLineID: $ServerOptLineIDS"
                    } else {
                        logMsg $::LEVEL3 "Server DialLineID\'s: $ServerOptLineIDS"
                    }
                }
            }
            logMsg $::LEVEL1 $dataBlock
            continue
        } elseif {[string match 400* $dataBlock]} {
            # NCID server has sent text to be displayed
            logMsg $::LEVEL1 $dataBlock
            toplevel .reply
            wm title .reply [::msgcat::mc "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 [::msgcat::mc "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
            wm title .reply [::msgcat::mc "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 [::msgcat::mc "Accept"] -state disabled -command {
                    global multi

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

                    if {$multi} {
                        set temp "S"
                    } else {
                        set temp ""
                    }
                    puts $Socket "WRK: REJECT LOG$temp"
                    flush $Socket
                    logMsg ::LEVEL1 "WRK: ACCEPT LOG$temp"
                    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 {!$do_pause} {
                if {$mod_menu} {
                    set mod_menu 0
                    continue
                }
                if {[string length $ClientJobResult] < 4} {
                    set ClientJobResult "Done."
                }
                if {$Dialed} {
                    set Dialed 0
                } else {.confirm.close configure -state active}
            }
        } elseif {[string match INFO:* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            if {$mod_menu} {
                set popupMenu .popupmenu
                #set temp [split $dataBlock " "]
                set temp [lindex $dataBlock]
                set fileType [lindex $temp 1]
                if {$fileType == "dial"} {
                    set dialarg [lindex $temp 2]
                } else {set argument [lindex $temp 2]}
                switch $fileType {
                    dial {
                        if {$dialarg != "NODIAL" && $disable_server_dialer != 1 } {
                            $popupMenu add command -label [::msgcat::mc "Dial Number from History..."]  -command { doDial "history" } -font FixedFontH
                        }
                    }
                    alias {
                        # INFO: alias NOALIAS "" NOALIAS ""
                        # INFO: alias <alias type> "<entry>" LINEALIAS "<entry>"
                        set CIDaliasType [lindex $temp 2]
                        if {[lindex $temp 3] != "\"\""} {
                            set aliasEntry [lindex $temp 3]
                        }
                        set LineAliasType [lindex $temp 4]
                        if {[lindex $temp 5] != "\"\""} {
                            set lineEntry [lindex $temp 5]
                        }
                        $popupMenu add command -label [::msgcat::mc "Add/Modify/Remove Alias in Alias File..."] -command { doList alias "" "" } -font FixedFontH
                        $popupMenu add separator
                    }
                    black {
                    # INFO: black name|number "<entry>" # no white
                        set blackEntry [lindex $temp 3]
                        $popupMenu add command -label [::msgcat::mc "Add Entry to Blacklist File..."] -command { doList black add "" } -font FixedFontH
                        $popupMenu add command -label [::msgcat::mc "Remove Entry from Blacklist File..."] -command { global argument; doList black remove $argument } -font FixedFontH
                        $popupMenu add separator
                    }
                    white {
                    # INFO: white name|number "<entry>" # no black
                        set whiteEntry [lindex $temp 3]
                        $popupMenu add command -label [::msgcat::mc "Add Entry to Whitelist File..."]  -command { doList white add "" } -font FixedFontH
                        $popupMenu add command -label [::msgcat::mc "Remove Entry from Whitelist File..."] -command { global argument; doList white remove $argument } -font FixedFontH
                        $popupMenu add separator
                    }
                    both {
                        # INFO: both name|number "[<white>]" "[<black>]"
                        set whiteEntry [lindex $temp 3]
                        $popupMenu add command -label [::msgcat::mc "Remove Entry from Whitelist File..."] -command { global argument; doList white remove $argument } -font FixedFontH
                        set blackEntry [lindex $temp 4]
                        $popupMenu add command -label [::msgcat::mc "Remove Entry from Blacklist File..."] -command { global argument; doList black remove $argument } -font FixedFontH
                        $popupMenu add separator
                    }
                    neither {
                        # INFO: neither # black or white
                        $popupMenu add command -label [::msgcat::mc "Add Entry to Blacklist File..."]  -command { doList black add "" } -font FixedFontH
                        $popupMenu add command -label [::msgcat::mc "Add Entry to Whitelist File..."] -command { doList white add "" } -font FixedFontH
                        $popupMenu add separator
                    }
                }
                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]
            if {!$do_pause} { 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} {
                set ServerOptLineIDS [list [regsub {^.*LineIDs: (.*)$} $serveropt {\1}]]
                if {!$NoGUI} {
                    .menubar.server entryconfigure [::msgcat::mc "Dial Number Manually..."] -state active
                }
            }
            if {[string first "country: " $serveropt 0] != -1} {
                regsub {^.*country: (.*)$} $serveropt {\1} Country
            }
            if {[string first "regex-" $serveropt 0] != -1} {
                regsub {regex-(.).*$} $serveropt {\1} regexDigit
            }
            if {[regexp {^hangup|^hupmode} $serveropt 0]} {
                if {!$NoGUI} {
                    .menubar.server entryconfigure [::msgcat::mc "Pause Hangup..."] -state normal
                }
            }
            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 } {
                      # Restore HUP color in GUI
                      logMsg $::LEVEL3  "restore HUP  color to original\n"
                      switch $themeName {
                        night   {
                                                      .tbl configcells end,1 -foreground #7fff7f
                        }
                        day     {
                                                      .tbl configcells end,1 -foreground purple
                        }
                        default {
                                                      .tbl configcells end,1 -foreground black
                        }
                      }
                      # restore theme color to $CIDtype
                    }
                  } 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 hide 0
                    set status [processLineID "[lindex $msg 4]"]
                    set thisTYPE [lindex $msg 5]
                    if {$LineIDGroups == 1 && $status != 1} {set hide 1}
                    if {$TypeGroups == 1} {set hide 1}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {set hide 1}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {set hide 1}
                }
                displayLog $msg 1  $hide
                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 hide 0
                    set status [processLineID "[lindex $cid 4]"]
                    set thisTYPE [lindex $cid 5]
                    if {$LineIDGroups == 1 && $status != 1} {set hide 1}
                    if {$TypeGroups == 2} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {set hide 1}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {set hide 1}
                }
                if {$label == 1} {array set call "{$lineIDlabel} [list $cid]"}
                # display log
                displayLog $cid 0 $hide
                # 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 hide 0
                    set status [processLineID "[lindex $cid 4]"]
                    set thisTYPE [lindex $cid 5]
                    if {$LineIDGroups == 1 && $status != 1} {set hide 1}
                    if {$TypeGroups == 2} {set hide 1}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {set hide 1}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {set hide 1}
                }
                # display log
                displayLog $cid 0 $hide
                if {!$NoGUI && $targetTime && [clock clicks -milliseconds] >= $targetTime} {
                    set targetTime [expr [clock clicks -milliseconds] + 500]
                    update idletasks
                }

            } elseif {$label == 11  ||  $label == 10 } {
              # ENDLOG or END (HTYPE is either BYE or CANCEL)
              if {!$NoGUI} {
                set endBlock [formatEND $dataBlock]
                set matchingKey "*[lindex $endBlock 1]*[lindex $endBlock 2]*[lindex $endBlock 5]*[lindex $endBlock 3]*[lindex $endBlock 9]*"
                # row_name  === *date*time*lineID*number*ctype*
                set row_index [.tbl searchcolumn 14 $matchingKey -backwards]
                if { $row_index != -1 } {
                  if {$DurationMode == "C"} {
                  # Duration mode == "C" (call time ) hence uses SCALL (Block index = 6 )
                  set blk_idx 6
                } else {
                  # Duration mode == "T" (talk time ) hence uses PCALL (Block index = 7 )
                  set blk_idx 7
                }

                if {$DurationMode == "T" && [lindex $endBlock 0]  == "CANCEL" } {
                  # "T" mode and CANCEL --> Duration = 0 no need to calculate , We display "CANCELED"
                  set duration_string [::msgcat::mc "CANCELED"]
                  .tbl cellconfigure $row_index,4 -text $duration_string
                } else {
                  # we calculale duration
                  #  set start_calc 0
                  #  set end_calc 0
                  logMsg $::LEVEL6 "calculation ($DurationMode Mode)  start_time = [lindex $endBlock $blk_idx ] "
                 if {[catch {set start_calc "[clock scan [lindex $endBlock $blk_idx ] -format "%D %T"]"}]} {
                   set start_calc 0
                   logMsg $::LEVEL4 "clock scan: (could not scan start time: [lindex $endBlock $blk_idx ])"
                 }
                 if {[catch {set end_calc "[clock scan [lindex $endBlock 8] -format "%D %T"]"}]} {
                   set end_calc 0
                   logMsg $::LEVEL4 "clock scan: (could not scan end time: [lindex $endBlock 8])"
                 }
                   if { $end_calc != 0 && $start_calc != 0 } {
                     set duration [expr $end_calc - $start_calc]
                     set duration_hour  [expr $duration / 3600]
                     set duration_remainder [expr $duration % 3600]
                     set duration_mins [expr $duration_remainder / 60]
                     set duration_sec [expr $duration_remainder % 60]
                     set duration_string  [format "%02d:%02d:%02d" ${duration_hour} ${duration_mins} ${duration_sec}]
                     .tbl cellconfigure $row_index,4 -text $duration_string
                   } else {
                     # gateway limitations prevented duration calculation
                     logMsg $::LEVEL1 "Duration calculation failed ;gateway limitations prevented duration calculation(?)"
                     set duration_string "[lindex $endBlock 0]"
                     .tbl cellconfigure $row_index,4 -text $duration_string
                   }
                }
              }
            }
          }
        }
    }
    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 $cidnmbr $cidname $cidline $linetype "" ""
#          $cidfnmbr $cidntype $cidcountry $cidlocation $cidcarrier
proc formatCID {dataBlock} {
    global lineIDlabel lineIDWidth

    set cidname [formatNAME $dataBlock]
    set cidnmbr [formatNMBR $dataBlock]
    set cidfnmbr [formatFNMBR $dataBlock]
    set cidntype [formatNTYPE $dataBlock]
    set cidcarrier [formatCARI $dataBlock]
    set cidcountry [formatCTRY $dataBlock]
    set cidlocation [formatLOCA $dataBlock]
    set ciddate [getField DATE $dataBlock]
    set cidtime [getField TIME  $dataBlock]
    set cidmesg [formatMESG $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 { $cidfnmbr == "" } {
         set cidfnmbr $cidnmbr
       }
    # 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
    }
    # changed by BGR , ( even CALL lines can have a cidmesg field )
    return [list $ciddate $cidtime $cidnmbr $cidname $cidline $linetype "" $cidmesg  $cidfnmbr $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]} {
        # changed by BGR : for time and date we take directly from the server
        # (no formatting yet )
        set msgdate [getField DATE $dataBlock]
        set msgtime [getField TIME $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
}



# must be sure the line passed checkType
# returns: $endhtype $enddate $endtime $endnumber $endname $endline $endscall $endpcall $endecall $endctype
proc formatEND {dataBlock} {
    global lineIDlabel lineIDWidth

    set endname   [formatNAME     $dataBlock]
    set endnumber [formatNMBR     $dataBlock]
    set enddate   [getField DATE  $dataBlock]
    set endtime   [getField TIME  $dataBlock]
    set endhtype  [getField HTYPE $dataBlock]
    set endscall  [getField SCALL $dataBlock]
    set endpcall  [getField PCALL $dataBlock]
    set endecall  [getField ECALL $dataBlock]
    set endctype  [getField CTYPE $dataBlock]
    set endline   ""
    if [string match {*\*LINE\**} $dataBlock] {
        set endline [formatLINE   $dataBlock]
    }
    # set default line indicator, should not be needed anymore
    if {$endline == ""} {
        set endline "-"
        for {set x 0} {$x < $lineIDWidth} {incr x} {
            set endline "$endline "
        }
    }
#   older gateways might not have a PCALL field
    if {$endpcall == "" } then {
      set endpcall $endscall
      }
    return [list $endhtype $enddate $endtime $endnumber $endname $endline $endscall $endpcall $endecall $endctype]
}



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 formatNAME {dataBlock} {
    set cidname [getField NAME $dataBlock]
    if {$cidname == "-"} {set cidname "NO NAME"}
    return $cidname
}
proc formatFNMBR {dataBlock} {
    set cidfnmbr [getField FNMBR $dataBlock]
    return $cidfnmbr
}
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 formatMESG {dataBlock} {
    set cidmesg [getField MESG $dataBlock]
    return $cidmesg
}

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

    if {$cidnmbr == ""} {set cidnmbr "NO-NUMBER"}
    return $cidnmbr
}


# 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 } {
     # field not found
     return ""
  }

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

# send the CID information to an external program
# Input: $ciddate $cidtime $cidnmbr $cidname $cidline $cidtype "" "" $cidfnmbr $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 $cidnmbr $cidname $cidline $linetype "" ""
#        $cidfnmbr $cidntype $cidcountry $cidlocation $cidcarrier
# Input: $msgdate $msgtime $msgnumber $msgname $msgline $msgtype $msgio $message
#        $cidfnmbr $cidntype $cidcountry $cidlocation $cidcarrier
# ismsg = 0 for CID and 1 for message
proc displayCID {input ismsg} {
global Txt

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

# display Call Log
# Input: $ciddate $cidtime $cidnmbr $cidname $cidline $linetype "" ""
#        $cidfnmbr $cidntype $cidcountry $cidlocation $cidcarrier
# Input: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $msgtype $message
#        $cidfnmbr $cidntype $cidcountry $cidlocation $cidcarrier
proc displayLog {input ismsg hide} {
    global Module NoGUI
    global NMBRarray
    global display_line_num DoingCallLog
    global nmbrWidth nameWidth lineIDWidth mtypeWidth fnmbrWidth ntypeWidth countryWidth locationWidth carrierWidth mtypeWidth  mesgWidth durationWidth
    global label labelList
    global HUP_foreground
    # always a good practice to initialize variables at the start .....
    set cidnmbr ""
    set linetype ""
    set ciddate ""
    set cidtime ""
    set cidline ""
    set cidfnmbr ""
    set cidname ""
    set cidntype ""
    set cidcountry ""
    set cidlocation ""
    set cidcarrier ""
    set mtype "-"
    set mesg ""
    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 $cidnmbr $cidname
                logMsg $::LEVEL1 "[lindex $input 5]: [lindex $input 0]  [lindex $input 1] [lindex $input 4] [lindex $input 2] [lindex $input 3]"
            }
        }
    } else {
    # GUI == true

        if {[lindex $input 5] eq {}} {
            logMsg $::LEVEL2  " empty type field (1) "
        }
        set ciddate     [lindex $input  0]
        set cidtime     [lindex $input  1]
        set cidnmbr     [lindex $input  2]
        set cidname     [lindex $input  3]
        set cidline     [lindex $input  4]
        set linetype    [lindex $input  5]
        set mtype       [lindex $input  6]
        set mesg        [lindex $input  7]
        set cidfnmbr    [lindex $input  8]
        set cidntype    [lindex $input  9]
        set cidcountry  [lindex $input 10]
        set cidlocation [lindex $input 11]
        set cidcarrier  [lindex $input 12]
        if { $mesg == "NONE"} {
             set mesg  ""
           }
        if {  $mtype == "" } {
             set mtype  "-"
           }
        logMsg $::LEVEL7 "Inserting row in table list :$cidnmbr||$linetype||$ciddate||$cidtime||$cidline||$cidfnmbr||$cidname||$cidntype||$cidcountry||$cidlocation||$cidcarrier||$mtype||$mesg"

        switch $linetype {
                "BLK" -
                "CID" -
                "HUP" -
                "PID" -
		"WID" -
                "RID" {
                    set endctype "IN"
                    set matchingKey "*$ciddate*$cidtime*$cidline*$cidnmbr*$endctype*"
                }
                "PUT" -
                "OUT" {
                    set endctype "OUT"
                    set matchingKey "*$ciddate*$cidtime*$cidline*$cidnmbr*$endctype*"
                }
                default {
                    set matchingKey ""
                    # MWI , NOT , MSG or WID  (we don't process
                    # "WID" as there is no reliable way to know if the call has
                    # been swiched , not taken , or changed to 3 way call )*/
                }
        }

        .tbl insert end  [list  $cidnmbr $linetype $ciddate $cidtime {}  $cidline $cidfnmbr $cidname  $cidntype  $cidcountry $cidlocation $cidcarrier $mtype $mesg $matchingKey ]
        # $matchingKey  will be used to retrieve the row index when later filling up the duration column ...
        putFlag end
        putNTYPE end
        if {   $DoingCallLog  == 0 } {
          if {$label != 6 && $linetype == "HUP"} {
             logMsg $::LEVEL3 "label = $label"
             logMsg $::LEVEL3 "linetype = $linetype"
             logMsg $::LEVEL3 "changing color of last HUP line"
                .tbl configcells end,1 -foreground $HUP_foreground
          }
          logMsg $::LEVEL3 "going to last line"
            .tbl yview moveto  1
        }
        if { $hide  ==  1 } {
          .tbl configrows end -hide 1
        }
  }
}

#https://www.rosettacode.org/wiki/Word_wrap#Tcl
#label widgets don't have a -wrap option so use this
proc wrapParagraph {width text} {
    # first find the length of the longest word in the string
    # we define a word as any non-empty sequence of non-whitespace characters
    set theWords [regexp -all -inline {\S+} $text]
    set max 0
    foreach word $theWords {
      set len [string length $word ]
      if { $max <  $len } {
        set max $len
      }
    }
    logMsg $::LEVEL7 "proc wrapParagraph : Original width: $width "
    if { $max >  $width } {
      set width $max
    }
    logMsg $::LEVEL7 "proc wrapParagraph : length of longest word: $max "
    logMsg $::LEVEL7 "proc wrapParagraph : Final width: $width "
    regsub -all {\s+} [string trim $text] " " text
    set RE "^(.{1,$width})(?:\\s+(.*))?$"
    for {set result ""} {[regexp $RE $text -> line text]} {} {
    append result $line "\n"
    }
    #in case no wrapping was done
    if { $result == "" } {
      return  $text
      logMsg $::LEVEL2 "proc wrapParagraph : Unable to wrap text (probably no spaces) ; returning unmodified text"
      }
    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 CallLogFlag

    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 [::msgcat::mc "Auto Start"] -state disabled
        } else {
          $menu entryconfigure [::msgcat::mc "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} {
                    # disable server menu items
                    serverMenu {disabled}
                    set menuDisabled 1

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

            if {!$NoGUI && $menuDisabled} {
                # activeate server menu items
                serverMenu {active}
                set menuDisabled 0

              if {$Delay == 1} {
                .menubar.file entryconfigure [::msgcat::mc "Reconnect"] -state active
              }
            }

            puts $Socket "HELLO: IDENT: $VersionIDENT"
            flush $Socket
            logMsg $::LEVEL1 "HELLO: IDENT: $VersionIDENT"
            if $NoGUI {
                logMsg $::LEVEL1 "Connected to $Host:$Port"
                if $CallLogFlag {
                    # 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
        }
    }
}

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 Debug
    global LogEnable LogDir
    global Module ModDir Ring CallOnRing
    global HostnameFlag
    global PIDfile
    global PopupTime
    global NoExit
    global AltDate
    global WakeUp
    global Version API
    global WrapLines
    global CallLogFlag
    global NightMode

    getopt arg $::argv {
        --no-gui {
            # command line version, no GUI
            set NoGUI 1
        }
        -A - --alt-date {
            # display the date as dd/mm/yyyy instead of mm/dd/yyyy
            set AltDate 1
        }
        -c - --call-log {
            # configure ncid to have server send the call log, NoGUI mode only
            set CallLogFlag 1
        }
        -C: - --country-code: {
            # obsolete, will be removed, server handles country codes
            set delayedMsgs \
            "$delayedMsgs\n***** WARNING: option -C|--country codes is obsolete, will be removed in a future release"
        }
        -D - --debug {
            # display all log messages sent to the log file
            set Debug 1; set Verbose 3
        }
        -d: - --delay: {
            # if > 0, delay in seconds between server connection attempls
            if {[regexp {^[0-9]+$} $arg]} {
                set Delay $arg
            } else {exitMsg 4 "Invalid argument: $arg\n$Usage\n"}
        }
        -h? - --help {
            # display this help and exit
            help
        }
        -H - --hostname-flag {
            # adds <hostname> to the IDENT string
            set HostnameFlag 1
        }
        -l: - --log-enable: {
            # disable (0) or enable (1) log file
            if {[regexp {^[0-2]+$} $arg]} {
                set LogEnable $arg
            } else {exitMsg 4 "Invalid argument: $arg\n$Usage\n"}
            checkLogDir
        }
        -L: - --log-dir: {
            # set the log directory for the log files
            if {$arg == ""} {
                exitMsg 4 "Invalid argument: $arg\n$Usage\n"
            }
            set LogDir $arg
        }
        -m: - --module: {
            # output the Caller ID information to the external program or output module
            if {[regexp {^.*/} $arg]} {
                set Module [list $arg]
            } else {set Module [list $ModDir/$arg]}
        }
        -P: - --program: {
            # obsolete, will be removed, use -m|--module instead
            set delayedMsgs \
            "$delayedMsgs\n***** WARNING: option -P|--program is deprecated, use option -m|--module"
        }
        -p: - --pidfile: {
            # name of PID file to create as /var/run/ncid.pid
            if {[regexp {^[\w./]+} $arg]} {
                set PIDfile $arg
            } else {exitMsg 4 "Invalid argument: $arg\n$Usage\n"}
        }
        -r: - --ring: {
            # execute output module on specific ring count
            if {[regexp {^-[129]$} $arg]
                || [regexp {^[0123456789]$} $arg]} {
                set Ring $arg
                set CallOnRing 1
            } else {exitMsg 4 "Invalid argument: $arg\n$Usage\n"}
        }
        -t: - --PopupTime: {
            # time for view popup: 1-5 seconds, 0 to disable
            if {[regexp {^[0-5]$} $arg]} {
                set PopupTime $arg
            } else {exitMsg 4 "Invalid argument: $arg\n$Usage\n"}
        }
        -v: - --verbose: {
            # sends log lines, 1-9, higher number for more info, 0 to disable
            if {[regexp {^[1-9]+$} $arg]} {
                set Verbose $arg
            } else {exitMsg 4 "Invalid argument: $arg\n$Usage\n"}
        }
        -V - --version {
            # output version information and exit
            set NoGUI 1
            puts "ncid: (NCID) $Version\n$API"
            exit 0
        }
        -X - --noexit {
            # ignore the close window button
            set NoExit 1
        }
        -W - --wakeup {
            # on X-windows and Gnome, wake monitor on first ring
            set WakeUp 1
        }
        arglist {
            # [port] [host]
            set len [llength $arg]
            if {$len > 1} {
                if {![regexp {^\d+$} [lindex $arg 0]]} {
                    exitMsg 12 "Port must be all digits: $[lindex $arg 0]\n$Usage\n"
                }
                set Port [lindex $arg 0]
                set Host [lindex $arg end]
            } elseif {$len != 0} {
                if {[regexp {^\d+$} $arg]} {
                    set Port $arg
                } else {
                    set Host $arg
                }
            }
        }
    }

    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
  global HUP_foreground
  global menu_plugins
  global context_plugins

  set enabletheme 0
  set m .menubar
  set p .popupmenu
  set items "file file.auto file.startup server server.hosts server.dial search\
             server.copy prefs prefs.types prefs.lines prefs.duration prefs theme help "
    if { $menu_plugins != {} } {
     set items "$items plugins"
     }
  set items_popup "clipboard"
  switch $themeName {
    night {
      set fgColor        "yellow"
      set fg2Color       "white"
      set fg3Color       "cyan"
      set fg4Color       "#7fff7f"
      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"
    }
    day {
      set fgColor        "blue"
      set fg2Color       "black"
      set fg3Color       "red"
      set fg4Color       "purple"
      set fgActColor     "white"
      set fgSelColor     "red"
      set bckColor       "#d9d9d9"
      set bckActColor    "#4a90d9"
      set bckSelColor    "#d9d9d9"
      set bck2SelColor   "lightgreen"
      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"
    }
    default {
      set enabletheme 1

      set fgColor        "black"
      set fg2Color       "black"
      set fg3Color       "black"
      set fg4Color       "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
  .tbl configure   -background $bckColor
  .tbl configure   -labelforeground $fgColor
  .tbl configure   -labelbackground $bckColor
  .tbl configure   -stripebackground ""
  set HUP_foreground $fg2Color
  .tbl configcolumns 1  -foreground $fg4Color
  .tbl configcolumns 1   -selectbackground $bckSelColor
  .tbl configcolumns 2  -foreground $fgColor
  .tbl configcolumns 2   -selectbackground $bckSelColor
  .tbl configcolumns 3  -foreground $fg3Color
  .tbl configcolumns 3   -selectbackground $bckSelColor
  .tbl configcolumns 4  -foreground $fg4Color
  .tbl configcolumns 4   -selectbackground $bckSelColor
  .tbl configcolumns 5  -foreground $fgColor
  .tbl configcolumns 5   -selectbackground $bckSelColor
  .tbl configcolumns 6  -foreground $fg3Color
  .tbl configcolumns 6   -selectbackground $bckSelColor
  .tbl configcolumns 7  -foreground $fg4Color
  .tbl configcolumns 7   -selectbackground $bckSelColor
  .tbl configcolumns 8  -foreground $fgColor
  .tbl configcolumns 8   -selectbackground $bckSelColor
  .tbl configcolumns 9  -foreground $fg3Color
  .tbl configcolumns 9   -selectbackground $bckSelColor
  .tbl configcolumns 10 -foreground $fg4Color
  .tbl configcolumns 10  -selectbackground $bckSelColor
  .tbl configcolumns 11 -foreground $fgColor
  .tbl configcolumns 11  -selectbackground $bckSelColor
  .tbl configcolumns 12 -foreground $fg3Color
  .tbl configcolumns 12  -selectbackground $bckSelColor
  .tbl configcolumns 13 -foreground $fg2Color
  .tbl configcolumns 13  -selectbackground $bckSelColor

  .tv configure -background $tvBckColor -foreground $tvFgColor \
                -selectbackground $tvBckColor -selectforeground $tvFgColor
  .tbl configure -background $vhBckColor
  .tbl configure -foreground $vhFgColor
  .tbl configure -selectbackground $vhInsColor

  $m configure -background $bckColor -foreground $fgColor
  foreach item $items {
    $m.$item configure -background $bckColor -foreground $fgColor \
      -selectcolor $selColor
  }
  $p configure -background $bckColor -foreground $fgColor
  foreach item $items_popup {
    $p.$item configure -background $bckColor -foreground $fgColor \
      -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] \
    -foreground [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
  ttk::style configure Treeview -font FixedFontP -background $bckColor -foreground $fgColor -fieldbackground $bckColor
  ttk::style configure TScrollCommand -background $bckColor -foreground $fgColor

  if $enabletheme {
    ttk::setTheme $themeName
  } else {
    # Day and Night styles modify the default theme
    ttk::setTheme "default"
  }
  logMsg $::LEVEL1 "Current Theme: $themeName"
}

proc processRCfile {} {
    global rcfile ExitOn fontList PortableDir wmGeometry
    global autoSave oldAutoSave autoStart oldAutoStart
    global DurationMode oldDurationMode dispDUR dlenMax labDUR fieldList
    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 DialPrefix oldDialPrefix
    global DefaultHost DefaultPort ConfigFileHost ConfigFilePort
    global labelList oldLabelList YearDot oldYearDot
    global exactMatch oldExactMatch

    if [expr [file exists $rcfile] && [file isfile $rcfile]] {
        set id [open $rcfile]
        fconfigure $id -encoding utf-8
        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}]
        } 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|DialPrefix|\
                             Host|Port|DurationMode|\
                             )\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 [list $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"
        write_rc_file "set SelectedLineIDs" "set SelectedLineIDs \"\""
    } 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"
    }

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

    if { $DurationMode != $oldDurationMode} {
        # Need to update $dispDUR $dlenMax $labDUR $fieldList
        set dispDUR "DURATION-$DurationMode"
        if {$DurationMode == "C"} {
            set labDUR   "[format "%-${dlenMax}.${dlenMax}s" "$dispDUR"   ] - [::msgcat::mc "call duration"]"
        } else {
            set labDUR   "[format "%-${dlenMax}.${dlenMax}s" "$dispDUR"   ] - [::msgcat::mc "talk duration"]"
        }
        set fieldList "[regsub {DURATION-\w\s+-\s+\w+\s+\w+} $fieldList "$labDUR"]"
        set oldDurationMode $DurationMode
    }
}

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 Delay
    global Socket

    if {$Socket > 0} {
        # Delay set to zero to avoid race condition and the
        # reconnect procedure (important and not so obvious)
        set Delay 0
        puts $Socket "GOODBYE"
        flush $Socket
        logMsg $::LEVEL1 "GOODBYE"
    }
}


proc makeWindow {} {
    global ExitOn TZScript TZFlag
    global Verbose API WrapLines viewTextWidth
    global fontList m currentFont flagImageSize NTYPEImageSize
    global clock autoSave autoStart AltDate
    global Hosts HostIndex
    global nameREQ nmbrREQ fnmbrREQ lineREQ timeWidth dateWidth
    global wmGeometry themeName
    global TypeGroups LineIDGroups DiscoveredLineIDs
    global NMBRarray posArray labelArray labelList
    global labTYPE labDATE labTIME labDUR labLINE labNMBR labNAME labNTYPE
    global labCTRY labLOCA labCARI labMTYPE labMESG
    global AltDate DateSepar YearDot labelList
    global clock
    global nmbrWidth typeWidth dateWidth timeWidth nameWidth lineIDWidth mtypeWidth fnmbrWidth ntypeWidth countryWidth locationWidth carrierWidth mtypeWidth mesgWidth durationWidth durationKeyWidth
    global tv_scrollbar
    global DurationMode dispDUR
    global PluginDir menu_plugins context_plugins
    global Logo

    wm title . "Network Caller ID"
    if [file exists $Logo] {
      image create photo Logo -file $Logo
      wm iconphoto . -default  Logo
    }
    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 menu bar menus
    set m .menubar
    menu $m.file
    menu $m.file.auto
    menu $m.file.startup
    menu $m.search
    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.prefs.duration
    menu $m.theme
    array set lArray $labelList
    if { $menu_plugins != {} } {
    menu $m.plugins
    }
    menu $m.help
    $m add cascade -menu $m.file -label [::msgcat::mc "File"] -underline 0 -font FixedFontM
    $m add cascade -menu $m.search -label [::msgcat::mc "Search"] -underline 0 -font FixedFontM
    $m add cascade -menu $m.server -label [::msgcat::mc "Server"] -underline 0 -font FixedFontM
    $m add cascade -menu $m.prefs -label [::msgcat::mc "Preferences"] -underline 0 -font FixedFontM
    $m add cascade -menu $m.theme -label [::msgcat::mc "Themes"] -underline 0 -font FixedFontM
    if { $menu_plugins != {} } {
       $m add cascade -menu $m.plugins -label [::msgcat::mc "Plugins"] -underline 1 -font FixedFontM
    }
    $m add cascade -menu $m.help -label [::msgcat::mc "Help"] -underline 0 -font FixedFontM

    # create Menu Plugins items (if needed)
    if { $menu_plugins != {} } {
      set l  [llength $menu_plugins ]
      set n 0
      for { set i  0 }  { $i < $l } { incr i }  {
        set pluginLabel "[ lindex [lindex $menu_plugins $i ] 0 ]"
        set pluginCommand "[ lindex [lindex $menu_plugins $i ] 1 ]"
        if {[regexp {^\s*#} $pluginLabel]} {
          # unwanted plugin
          incr n
          logMsg $::LEVEL1 "menu_plugins: skipping '$pluginLabel' because it is commented out"
          continue
        }
        logMsg $::LEVEL1 "menu_plugins: detected label: $pluginLabel"
        logMsg $::LEVEL4 "menu_plugins: detected command: $pluginCommand"
        # check that command exists and is executable ...
        set full_path_command ""
        set full_path_command [ auto_execok [ regexp  -inline {\S+} "$pluginCommand"] ]
        if { ${full_path_command}  != "" } {
          set pluginCatchCommand  "if \{ \[catch \{exec ${pluginCommand} &\} result \] \} \{ tk_messageBox -type ok -title  \"$pluginLabel\" -message \"\$result\" \}"
          $m.plugins add command  -label $pluginLabel  -command  ${pluginCatchCommand}  -font FixedFontM
          logMsg $::LEVEL1 "menu_plugins: command full PATH: ${full_path_command}"
          logMsg $::LEVEL5 "menu_plugins: catch command: $pluginCatchCommand"
        } else {
          incr n
          logMsg $::LEVEL1 "menu_plugins: skipping '$pluginLabel' because command '${pluginCommand}' does not exist or is not executable"
        }
      }
      logMsg $::LEVEL1 "menu_plugins: $l total, [expr $l - $n] added, $n rejected"
    }

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

    $m.file.auto add radiobutton -label [::msgcat::mc "Size"] -variable autoSave -value "size" -command {logAuto $m.file} -font FixedFontM
    $m.file.auto add radiobutton -label [::msgcat::mc "Size and Position"] -variable autoSave -value "both" -command {logAuto $m.file} -font FixedFontM
    $m.file.auto add radiobutton -label [::msgcat::mc "Off"] -variable autoSave -value "off" -command {logAuto $m.file} -font FixedFontM

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

    # create Search menu item
    $m.search add command -label [::msgcat::mc "Find Name"] -state normal  -command { find_box }  -font FixedFontM
    # if NAME column is hidden, menu item 1 ("Find Name") is disabled
    if {!$lArray(NAME) } {
        # NAME column is hidden, disable Search->"Find Name"
        .menubar.search entryconfigure [::msgcat::mc  "Find Name"] -state disabled
	remove_find_box
    }

    # create Server menu items
    $m.server add cascade -menu $m.server.hosts -label [::msgcat::mc "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 [::msgcat::mc "Reload alias, blacklist and whitelist files"] -command {
                puts $Socket "REQ: RELOAD"
                flush $Socket
                logMsg $::LEVEL1 "REQ: RELOAD"
            } -font FixedFontM
    $m.server add command -label [::msgcat::mc "Update current call log"] -command {
                global multi

                puts $Socket "REQ: UPDATE"
                flush $Socket
                logMsg $::LEVEL1 "REQ: UPDATE"
                set multi 0
            } -font FixedFontM
    $m.server add command -label [::msgcat::mc "Update all call logs"] -command {
                global multi

                puts $Socket "REQ: UPDATES"
                logMsg $::LEVEL1 "REQ: UPDATES"
                set multi 0
                flush $Socket
                set multi 1
            } -font FixedFontM
    $m.server add command -label [::msgcat::mc "Reread call log"] -command {
                global display_line_num

                set display_line_num 0
                clearLog
                puts $Socket "REQ: REREAD"
                flush $Socket
                logMsg $::LEVEL1 "REQ: REREAD"
            } -font FixedFontM
    $m.server add separator
    $m.server add command -label [::msgcat::mc "Dial Number Manually..."] -command { doDial "manual" } -font FixedFontM -state disabled
    $m.server add separator
    $m.server add command -label [::msgcat::mc "Pause Hangup..."] -state disabled \
            -command {pauseHangup} -font FixedFontM

    set popupMenu [menu .popupmenu]
    set popupMenuclipboard  [menu .popupmenu.clipboard]

    # create Preferences menu items
    $m.prefs add command -label [::msgcat::mc "Font..."] -command {changeFont} -font FixedFontM
    $m.prefs add separator
    $m.prefs add command -label [::msgcat::mc "Date and Time..."] -command {formatDT} -font FixedFontM
    $m.prefs add separator
    $m.prefs add cascade -menu $m.prefs.types -label [::msgcat::mc  "Line Types"] -font FixedFontM
    $m.prefs add cascade -menu $m.prefs.lines -label [::msgcat::mc  "LineIDs"] -font FixedFontM

    $m.prefs add command -label [::msgcat::mc "Select Columns..."] -command {selectColumns} -font FixedFontM
    $m.prefs add separator
    $m.prefs add cascade -menu $m.prefs.duration -label [::msgcat::mc  "Duration Mode"] -font FixedFontM

    $m.prefs.types add radiobutton -label [::msgcat::mc "All"] \
        -variable TypeGroups -value 0 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add separator
    $m.prefs.types add radiobutton -label [::msgcat::mc "Calls"] \
        -variable TypeGroups -value 1 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add radiobutton -label [::msgcat::mc "Messages"] \
        -variable TypeGroups -value 2 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add radiobutton -label [::msgcat::mc "Smartphone"] \
        -variable TypeGroups -value 3 \
        -command {logTypeGroups} -font FixedFontM
    $m.prefs.types add separator
    $m.prefs.types add radiobutton -label [::msgcat::mc "Select..."] \
        -variable TypeGroups -value 4 \
        -command {doSelectedTypes} -font FixedFontM

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

    $m.prefs.duration add radiobutton -label [::msgcat::mc "Talk time"] \
        -variable DurationMode -value "T" \
        -command {logDurationMode} -font FixedFontM
    $m.prefs.duration add separator
    $m.prefs.duration add radiobutton -label [::msgcat::mc "Call time"] \
        -variable DurationMode -value "C" \
        -command {logDurationMode} -font FixedFontM

    # if column DATE and column TIME are hidden ...
    # menu item 2 ("Date and Time") is disabled
    if {!$lArray(DATE) && !$lArray(TIME)} {
        .menubar.prefs entryconfigure [::msgcat::mc "Date and Time..."]  -state disabled
    }

    # if DURATION column is hidden, menu item 8 ("Duration mode") is disabled
    if {!$lArray(DUR) } {
        # DURATION column is hidden, disable "Duration mode"
        .menubar.prefs entryconfigure [::msgcat::mc  "Duration Mode"] -state disabled
    }

  # if NAME column is hidden, menu item 1 ("Find Name") is disabled
    if {!$lArray(NAME) } {
        # NAME column is hidden, disable Search->"Find Name"
        .menubar.search entryconfigure [::msgcat::mc  "Find Name"] -state disabled
        remove_find_box
    }

    # create Help menu item
    $m.help add command -label [::msgcat::mc "About"]              -command {helpItem "About" "$ServerVersion\n\n$VersionInfo\n$API\n\n$Author\n" [ncidInfo]} -font FixedFontM
    $m.help add command -label [::msgcat::mc "Alias Description"]  -command {helpItem "Alias Description"    $aliasDesc     ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Auto Save"]           -command {helpItem "Autosave Help"        $autosaveHelp  ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Auto Start"]          -command {helpItem "Autostart Help"       $autostartHelp "lnk1"} -font FixedFontM
    $m.help add command -label [::msgcat::mc "Dial"]               -command {helpItem "Dial Help"            $dialHelp      ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Field Labels"]       -command {helpItem "Field Labels"         $fieldList     ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "History Window"]     -command {helpItem "History Window"       $historyHelp   ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Line Types"]         -command {helpItem "Line Types"           $labList       ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Locale"]             -command {helpItem "Locale"               $historyLocale ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Message Types"]      -command {helpItem "Message Types"        $mtypeList     ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Online Docs"]        -command {helpItem "Online Documentation" ""             ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Plugins"]            -command {helpItem "Plugin Help"          $pluginHelp    ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Preferences Window"] -command {helpItem "Preferences Window"   $prefHelp      ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Regex Digit Expressions"]  -command {helpItem "Regex Digit Expressions"    $regexDigitExpr ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Search Menu"]        -command {helpItem "Search Menu Help"     $searchHelp    ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Server Menu"]        -command {helpItem "Server Menu Help"     $serverHelp    ""}     -font FixedFontM
    $m.help add command -label [::msgcat::mc "Server Options"]     -command serverOPT                                               -font FixedFontM
    $m.help add command -label [::msgcat::mc "Tooltips"]           -command {helpItem "Tooltip Help"         $tooltipHelp   ""}     -font FixedFontM

    ###########################################################
    # (BGR) tablelist definition and creation
    ###########################################################
    set tbl .tbl
    set vsb_t .vsb_t
    set dispTYPE  [::msgcat::mc "TYPE"]
    set dispDATE  [::msgcat::mc "DATE"]
    set dispTIME  [::msgcat::mc "TIME"]
    set dispDUR   [::msgcat::mc "$dispDUR"]
    set dispLINE  [::msgcat::mc "LINE ID"]
    set dispNMBR  [::msgcat::mc "NUMBER"]
    set dispNAME  [::msgcat::mc "NAME"]
    set dispNTYPE [::msgcat::mc "NTYPE"]
    set dispCTRY  [::msgcat::mc "CTRY"]
    set dispLOCA  [::msgcat::mc "LOCATION"]
    set dispCARI  [::msgcat::mc "CARRIER"]
    set dispMTYPE [::msgcat::mc "MTYPE"]
    set dispMESG  [::msgcat::mc "MESSAGE"]

     set column_list            \
         [ list 0 rawNBR left   \
         0 $dispTYPE     center \
         0 $dispDATE     center \
         0 $dispTIME     center \
         0 $dispDUR      center \
         0 "$dispLINE"   left   \
         0 $dispNMBR     right  \
         0 $dispNAME     left   \
         0 $dispNTYPE    center \
         0 $dispCTRY     center \
         0 $dispLOCA     left   \
         0 $dispCARI     left   \
         0 $dispMTYPE    left   \
         0 $dispMESG     left   \
         0 durationKey   left]

    if {$labelList != "" & [lsearch -exact $labelList MESG] == -1} {
        set labelList [linsert $labelList end MESG 1]
    }

    if {$labelList != "" & [lsearch -exact $labelList DUR] == -1} {
        set labelList [linsert $labelList end DUR 0]
    }

    array set labelArray $labelList
    set tableMinWidth [calculateMinTableWidth ]

    # If for some reasons $TZScript is not installed or fails, Balloon_TZ
    # will not be activated (no "tooltip/balloon" will be displayed)

    if {[catch {exec $TZScript 0256788967 FR} oops]} {
      logMsg $::LEVEL1 "$oops"
      logMsg $::LEVEL1 "not setting timezone balloon"
    } else {
      logMsg $::LEVEL1 "$TZScript available --> setting timezone balloon"
      set TZFlag 1
    }
    # create font for balloon help
    font create BalloonFont  -size 9
    tablelist::tablelist $tbl \
    -tooltipaddcommand  balloon_display \
    -tooltipdelcommand  balloon_delete \
    -columns  $column_list \
    -width $tableMinWidth -height $::historyTextRows \
    -labelfont  {FixedFontH} \
    -font  {FixedFontH} \
    -setgrid 1\
    -yscrollcommand [list $vsb_t set] \
    -movablecolumns no  -showseparators yes  -stretch all

    $tbl configure -activestyle none
    if {[$tbl cget -selectborderwidth] == 0} {
       $tbl configure -spacing 1
       }
    grid [ttk::scrollbar $vsb_t -orient vertical   -command [list $tbl yview]]

    $tbl configcolumns  0 -maxwidth  $nmbrWidth
    $tbl configcolumns  1 -maxwidth  $typeWidth
    $tbl configcolumns  2 -maxwidth  $dateWidth
    $tbl configcolumns  3 -maxwidth  $timeWidth
    $tbl configcolumns  4 -maxwidth  $durationWidth
    $tbl configcolumns  4  -title [::msgcat::mc "$dispDUR"]
    $tbl configcolumns  5 -maxwidth  $lineIDWidth
    $tbl configcolumns  6 -maxwidth  $fnmbrWidth
    $tbl configcolumns  7 -maxwidth  $nameWidth
    $tbl configcolumns  8 -maxwidth  $ntypeWidth
    $tbl configcolumns  9 -maxwidth  $countryWidth
    $tbl configcolumns 10 -maxwidth  $locationWidth
    $tbl configcolumns 11 -maxwidth  $carrierWidth
    $tbl configcolumns 12 -maxwidth  $mtypeWidth
    $tbl configcolumns 13 -maxwidth  $mesgWidth
    # $tbl configcolumns 14 -maxwidth  $durationKeyWidth
    # column 0 is permanently hidden and contains the unformatted phone number
    # (raw number )
    # column 14 is permanently hidden and contains a "duration matching key" or  ""

    $tbl configcolumns 0 -hide true
    $tbl configcolumns 14 -hide true
    #hide/unhide optional columns
    array set labelArray $labelList
    if { ! $labelArray(DATE)}  { $tbl configcolumns  2 -hide true }
    if { ! $labelArray(TIME)}  { $tbl configcolumns  3 -hide true }
    if { ! $labelArray(DUR)}   { $tbl configcolumns  4 -hide true }
    if { ! $labelArray(LINE)}  { $tbl configcolumns  5 -hide true }
    if { ! $labelArray(NMBR)}  { $tbl configcolumns  6 -hide true }
    if { ! $labelArray(NAME)}  { $tbl configcolumns  7 -hide true }
    if { ! $labelArray(NTYPE)} { $tbl configcolumns  8 -hide true }
    if { ! $labelArray(CTRY)}  { $tbl configcolumns  9 -hide true }
    if { ! $labelArray(LOCA)}  { $tbl configcolumns 10 -hide true }
    if { ! $labelArray(CARI)}  { $tbl configcolumns 11 -hide true }
    if { ! $labelArray(MTYPE)} { $tbl configcolumns 12 -hide true }
    if { ! $labelArray(MESG)}  { $tbl configcolumns 13 -hide true }

    # set the display format for date and time according to config file
    if {$DateSepar eq "." && $YearDot == 1} {
            set EndDot "."} else {set EndDot ""}
    .tbl configcolumns 2  -formatcommand FormatDate
    .tbl configcolumns 3  -formatcommand FormatTime
    .tbl configcolumns 8  -formatcommand FormatNTYPE
    .tbl configcolumns 9  -formatcommand FormatCountry
    #set the tooltips on headers
#    DynamicHelp::add .tbl.hdr.t.f.l1 -text $labTYPE
#    DynamicHelp::add .tbl.hdr.t.f.l2 -text  $labDATE
#    DynamicHelp::add .tbl.hdr.t.f.l3 -text  $labTIME
#    DynamicHelp::add .tbl.hdr.t.f.l4 -text  $labDUR
#    DynamicHelp::add .tbl.hdr.t.f.l5 -text  $labLINE
#    DynamicHelp::add .tbl.hdr.t.f.l6 -text  $labNMBR
#    DynamicHelp::add .tbl.hdr.t.f.l7 -text  $labNAME
#    DynamicHelp::add .tbl.hdr.t.f.l8 -text  $labNTYPE
#    DynamicHelp::add .tbl.hdr.t.f.l9 -text  $labCTRY
#    DynamicHelp::add .tbl.hdr.t.f.l10 -text  $labLOCA
#    DynamicHelp::add .tbl.hdr.t.f.l11 -text  $labCARI
#    DynamicHelp::add .tbl.hdr.t.f.l12 -text  $labMTYPE
#    DynamicHelp::add .tbl.hdr.t.f.l13 -text  $labMESG

    ##########################
    #add tablelist + scrollbar
    ##########################
    grid $tbl -row 2 -sticky nsew -padx 2 -pady 2
    grid $vsb_t -row 2 -column 1 -sticky ns -pady 2

    # create and place: window for view, user messagea
    ttk::frame .fr2
    grid .fr2 -row 3 -columnspan 1
    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
    ttk::scrollbar .vsb -command ".tv yview"
    grid .tv .vsb  -in .fr2
    grid remove .vsb
    set tv_scrollbar "off"
    bind .tv <KeyPress> break
    if {$DiscoveredLineIDs != ""} {updateViewDisplay}

    ttk::frame .fr
    grid .fr -row 4 -columnspan 1
    ttk::label .ml -text [::msgcat::mc "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
    wm minsize . [calculateMinTableWidth] $::histMinRows
    logMsg $::LEVEL1 "tablelist min width:  [calculateMinTableWidth]"
    update

    if {$::ThemesDir ne ""} {
        lappend ::auto_path $::ThemesDir

        # 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]
    } else {

        if {$::ncidThemes eq ""} {set ::ncidThemes [glob -nocomplain -directory $::ncidDir  *]}
        if {$::ncidThemes ne ""} {
            # create themes menu items
            $m.theme add separator
            $m.theme add command -label "NCID THEMES" -font FixedFontP
            $m.theme add separator
            foreach t $::ncidThemes {
                if {[lsearch -exact $::ncidThemes $t] == -1} {continue}
                $m.theme add radiobutton -label [join [list [string totitle $t]]] \
                                         -variable themeName -value $t \
                                         -command {logTheme} -font FixedFontP
            }

        }
        set $::addonThemes [ glob -nocomplain -directory $::addonDir  *]
        if {$::addonThemes ne ""} {
            $m.theme add separator
            $m.theme add command -label "ADDON THEMES" -font FixedFontP -state disabled
            foreach t $::addonThemes {
                $m.theme add radiobutton -label [join [list [string totitle $t]]] \
                                         -variable themeName -value $t \
                                         -command {logTheme} -font FixedFontP
            }
        }

        # 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
        if {$::builtinThemes ne "default"} {
            $m.theme add separator
            $m.theme add command -label "BUILTIN THEMES" -font FixedFontP
            $m.theme add separator
            set $::builtinThemes [ttk::style theme names]
            foreach t $::builtinThemes {
                if {$t eq "default" && $::ncidThemes ne ""} {continue}
                $m.theme add radiobutton -label [join [list [string totitle $t]]] \
                                         -variable themeName -value $t \
                                         -command {logTheme} -font FixedFontP
            }
        }
    }

    switch $autoSave {
        "size" {
            $m.file entryconfigure [::msgcat::mc "Quit"] -command {saveSize 0; do_goodbye; $ExitOn}
            wm protocol . WM_DELETE_WINDOW {saveSize 0; $ExitOn}
        }
        "both" {
            $m.file entryconfigure [::msgcat::mc "Quit"] -command {saveSize 1; do_goodbye; $ExitOn}
            wm protocol . WM_DELETE_WINDOW {saveSize 1; $ExitOn}
        }
    }
    setStyles
    #ttk::setTheme $themeName
    #ttk::style theme use $themeName

    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 "GUI 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 .tbl  <<TablelistSelect>>  {
        set selection   [.tbl curselection]
        set selectedRow [.tbl get $selection]
        prepare_history_context_menu $selectedRow
        break
    }

    bind [.tbl bodytag] <Button-3> { DisplayContextMenu %W %x %y}

    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 "[::msgcat::mc "Exactly match"] $from_ ."
                .confirm.entry delete 0 end
                .confirm.entry insert end "$replace_"
            }
            ".dial.lp" {
                if {[winfo exists .dial.number] == 1} {
                    regsub {^\d\D} $DialNumber "" DialNumber
                    .dial.number insert 0 "$DialPrefix"
                    focus .dial.number
                }
            }
        }
    }

    bind TCheckbutton <ButtonRelease-1> {+
        switch %W {
            ".confirm.match" {
                if {$exactMatch == 1} {
                    set from_ $entry_
                    set temp_ $from_
                    .confirm.change delete 0 end
                    .confirm.change insert 0 "^$temp_$"
                    .confirm.change state readonly
                    .confirm.rb1 state disabled
                    .confirm.rb2 state disabled
                    } else {
                        .confirm.change state !readonly
                        .confirm.rb1 state !disabled
                        .confirm.rb2 state !disabled
                        set temp_ [regsub -all {\^(.*)\$} $from_ {\1}]
                        .confirm.change delete 0 end
                        .confirm.change insert 0 "^$temp_$"
                    }
                    .confirm.change delete 0 end
                    .confirm.change insert 0 "$temp_"
            }
        }
    }

    bind TButton <ButtonRelease-1> {+
        switch -regexp %W {
            ".cbp.*"               { break }
            ".ctypes.*"            { break }
            ".ltypes.*"            { break }
            ".clines.*"            { break }
            ".confirm.*"           { break }
            ".dial.*"              { break }
            ".dt.*"                { break }
            ".err.*"               { break }
            ".hlp.*"               { break }
            ".rply.*"              { break }
            ".reply.*"             { break }
            ".__tk__messagebox.ok" { break }
            ".p.f*"                { break }
            default {
              set temp [%W cget -text]
              if { $temp eq [::msgcat::mc  "Cancel"]} {
                destroy .f
              } else {
                if { [ expr { $temp eq [::msgcat::mc  "OK"] } ] }  {
                  if {  [ font configure  FixedFontH -family ]  != $currentFont  ||
                        [ font configure  FixedFontH -size ] != $spinvalH  || \
                        [ font configure  FixedFontH -weight ] != $boldH  } {

                      set geometry [wm geometry .]
                      set current_height [regsub {(\d+)x(\d+).*} $geometry {\2}]

                      font create    newFixedFontH -family "$currentFont"  -size $spinvalH -weight $boldH
                      .tbl configcolumns 0 -font  {newFixedFontH}
                      .tbl configcolumns 1 -font  {newFixedFontH}
                      .tbl configcolumns 2 -font  {newFixedFontH}
                      .tbl configcolumns 3 -font  {newFixedFontH}
                      .tbl configcolumns 4 -font  {newFixedFontH}
                      .tbl configcolumns 5 -font  {newFixedFontH}
                      .tbl configcolumns 6 -font  {newFixedFontH}
                      .tbl configcolumns 7 -font  {newFixedFontH}
                      .tbl configcolumns 8 -font  {newFixedFontH}
                      .tbl configcolumns 9 -font  {newFixedFontH}
                      .tbl configcolumns 10 -font  {newFixedFontH}
                      .tbl configcolumns 11 -font  {newFixedFontH}
                      .tbl configcolumns 12 -font  {newFixedFontH}
                      .tbl configcolumns 13 -font  {newFixedFontH}
                      .tbl configcolumns 14 -font  {newFixedFontH}
                      font configure  FixedFontH -family "$currentFont" \
                                      -size $spinvalH -weight $boldH
                      .tbl configcolumns 0 -font  {FixedFontH}
                      .tbl configcolumns 1 -font  {FixedFontH}
                      .tbl configcolumns 2 -font  {FixedFontH}
                      .tbl configcolumns 3 -font  {FixedFontH}
                      .tbl configcolumns 4 -font  {FixedFontH}
                      .tbl configcolumns 5 -font  {FixedFontH}
                      .tbl configcolumns 6 -font  {FixedFontH}
                      .tbl configcolumns 7 -font  {FixedFontH}
                      .tbl configcolumns 8 -font  {FixedFontH}
                      .tbl configcolumns 9 -font  {FixedFontH}
                      .tbl configcolumns 10 -font  {FixedFontH}
                      .tbl configcolumns 11 -font  {FixedFontH}
                      .tbl configcolumns 12 -font  {FixedFontH}
                      .tbl configcolumns 13 -font  {FixedFontH}
                      .tbl configcolumns 14 -font  {FixedFontH}
                      font delete    newFixedFontH
                      # resize tablelist
                      .tbl configure -width 0
                      .tbl configure -width [calculateMinTableWidth]
                      .tbl configure  -height  $current_height
                      set fontSizeH [ font configure  FixedFontH -size ]
                      if { [expr { $fontSizeH >= 8}] && [expr { $fontSizeH < 14 }]} { set NTYPEImageSize "16" ; set flagImageSize "16"  }
                      if { [expr { $fontSizeH >= 15}] && [expr { $fontSizeH < 21 }]} { set NTYPEImageSize "20" ; set flagImageSize "20"}
                      if { [expr { $fontSizeH >= 22}] && [expr { $fontSizeH < 28 }]} { set NTYPEImageSize "24" ; set flagImageSize "24" }
                      if { [expr { $fontSizeH >= 29}] && [expr { $fontSizeH < 35 }]} { set NTYPEImageSize "28" ;  set flagImageSize "28"}
                      if { [expr { $fontSizeH >= 36}] && [expr { $fontSizeH < 37 }]}  { set NTYPEImageSize "32" ; set flagImageSize "32"}
                      set end [.tbl size]
                      for { set row 1}  {$row < $end } {incr row} {
                        putFlag $row
                        putNTYPE $row
                      }
                  }
                  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
                  }
                } else {
                  if { $temp eq [::msgcat::mc  "Re-scan"]}  {
                    .f.fn.cb configure -values {}
                    unset fontList
                    scanFonts
                    .f.fn.cb configure -values $fontList
                  }
                }
              }
            }
        }
    }
}

proc create_script_popup {display_name1 actual_script1} {
  logMsg $::LEVEL2  "Adding user script  in proc create_script_popup : $display_name1    $actual_script1 "
  set glop::display_name $display_name1
  set glop::actual_script $actual_script1
 .popupmenu add  command -label  "$glop::display_name" -command { eval exec $glop::actual_script  & } -font FixedFontH

}

proc DisplayContextMenu  {w x y} {
    logMsg $::LEVEL7 "event button 3"
    set X $x
    set Y $y
    foreach {tbl x y} [tablelist::convEventFields $w $x $y] {}
    set current_row [$tbl containing $y ]
    logMsg $::LEVEL7  "clicked on Row $current_row"
    set selection [.tbl curselection]
    logMsg $::LEVEL7 "current_row=$current_row selection=$selection   [$tbl get $selection]"
    if { $current_row eq  $selection } {
    logMsg $::LEVEL7 "We can trigger context menu "
    tk_popup .popupmenu [winfo pointerx .] [winfo pointery .]
    }
}


proc editNote {  } {
  global note_text NoteDir noteArray
  global nmbrREQ nameREQ lineREQ dateREQ timeREQ
  global Country nmbrWidth

  if { [info exists  noteArray(${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ})] } {
     set note_text $noteArray(${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ})
    } else {
     set note_text ""
    }
  toplevel .note
  wm title .note [ ::msgcat::mc "Note"]
  wm resizable .note 0 0
  grid [ttk::label .note.edit -justify center  -font FixedFontP \
          -text [::msgcat::mc "Edit Note: (160 characters max)"]]  -columnspan 3 -padx 12 -column 0
  ttk::entry .note.entry -width 60  -textvariable note_text -validate key -validatecommand { expr { [string length $note_text ] <= 160 }}  -font FixedFontP
  grid .note.entry -sticky ew -columnspan 3 -padx 8
  grid [button .note.close -text [::msgcat::mc "Close"] \
        -command {destroy .note } ] -row 3  -column 0 -pady 12 -padx 8
  grid [button .note.delete -text [::msgcat::mc "Delete"] \
        -command {delete_note ;destroy .note } ] -row 3 -column  1 -pady 12 -padx 8
  grid [button .note.save -text [::msgcat::mc "Save"] \
        -command { set noteArray(${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ}) $note_text ; write_note_file $note_text  ;destroy .note }] -row 3 -column  2 -pady 12 -padx 8
  focus .note.entry
  modal {.note}
  destroy {.note}
}


proc calculateMinTableWidth {} {
    global labelArray  nmbrMinWidth typeMinWidth dateMinWidth timeMinWidth durationMinWidth nameMinWidth lineIDMinWidth mtypeMinWidth fnmbrMinWidth ntypeMinWidth countryMinWidth locationMinWidth carrierMinWidth mtypeMinWidth mesgMinWidth
    set fudgedPseudoConstant [ expr { 900 / (  $labelArray(TYPE)  * $typeMinWidth  + \
                         $labelArray(DATE)  * $dateMinWidth     + \
                         $labelArray(TIME)  * $timeMinWidth     + \
                         $labelArray(DUR)   * $durationMinWidth + \
                         $labelArray(LINE)  * $lineIDMinWidth   + \
                         $labelArray(NMBR)  * $fnmbrMinWidth    + \
                         $labelArray(NAME)  * $nameMinWidth     + \
                         $labelArray(NTYPE) * $ntypeMinWidth    + \
                         $labelArray(CTRY)  * $countryMinWidth  + \
                         $labelArray(LOCA)  * $locationMinWidth + \
                         $labelArray(CARI)  * $carrierMinWidth  + \
                         $labelArray(MTYPE) * $mtypeMinWidth    + \
                         $labelArray(MESG)  * $mesgMinWidth ) } ]
    logMsg $::LEVEL2 "fudgedPseudoConstant $fudgedPseudoConstant"

    set tableMinWidth [ expr { $fudgedPseudoConstant + $labelArray(TYPE) * $typeMinWidth + \
                         $labelArray(DATE)  * $dateMinWidth     + \
                         $labelArray(TIME)  * $timeMinWidth     + \
                         $labelArray(DUR)   * $durationMinWidth + \
                         $labelArray(LINE)  * $lineIDMinWidth   + \
                         $labelArray(NMBR)  * $fnmbrMinWidth    + \
                         $labelArray(NAME)  * $nameMinWidth     + \
                         $labelArray(NTYPE) * $ntypeMinWidth    + \
                         $labelArray(CTRY)  * $countryMinWidth  + \
                         $labelArray(LOCA)  * $locationMinWidth + \
                         $labelArray(CARI)  * $carrierMinWidth  + \
                         $labelArray(MTYPE) * $mtypeMinWidth    + \
                         $labelArray(MESG)  * $mesgMinWidth } ]
                         logMsg $::LEVEL2 "tableMinWidth $tableMinWidth"
return $tableMinWidth
}

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

    toplevel .rply
    wm title .rply [::msgcat::mc "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 [::msgcat::mc "Close"] -command { destroy .rply}] \
          -columnspan 2 -pady 10

    modal {.rply}
}

proc doDial {dialtype} {
  global nmbrREQ nameREQ lineREQ ClientJobResult
  global Country nmbrWidth wantdial dialname
  global DialNumber oldDialPrefix DialPrefix DialPrefixList DialLineID

  toplevel .dial
  wm title .dial [::msgcat::mc "Dial"]
  wm resizable .dial 0 0
  if {$oldDialPrefix != $DialPrefix} {set DialPrefix $oldDialPrefix}
  set ht "[::msgcat::mc "Request the server dial:"]"
  if {$dialtype == "history"} {
    set wantdial 1
    set dialname $nameREQ
    set ht2 "[::msgcat::mc "Nmbr:"] $nmbrREQ\n[::msgcat::mc "Name:"] $dialname"
  } else {
    set wantdial 2
    set dialname "No Name"
    set ht2 "[::msgcat::mc "Name:"] [::msgcat::mc $dialname]"
  }
  set row 1
  grid [ttk::label .dial.rd -justify center -font FixedFontP \
        -text "[::msgcat::mc "Request The Server Dial"]" ] \
        -columnspan 2 -padx 12 -pady 10
  grid [ttk::label .dial.rd2 -justify left -font FixedFontP \
        -text $ht2 ] \
        -columnspan 2 -padx 12 -pady 10
  set lineid_list [lindex $::ServerOptLineIDS 0]
  set DialLineID [lindex $lineid_list 0]
  grid [ttk::label .dial.cl -justify left -font FixedFontP \
        -text [::msgcat::mc "Server Dial Line:"]] -columnspan 2 -padx 12 -column 0
  grid [ttk::combobox .dial.lb -font FixedFontP -values $lineid_list \
        -height 5 -textvariable DialLineID -state readonly -justify center] \
        -columnspan 2 -column 0
  .dial.lb set $DialLineID
  grid [ttk::label .dial.p -justify left -font FixedFontP \
        -text [::msgcat::mc "\nOutgoing Line Prefix\n   and separator:"]] -columnspan 2 -padx 12 -column 0
  grid [ttk::combobox .dial.lp -font FixedFontP -values [lindex $DialPrefixList] \
        -height 10 -textvariable DialPrefix -state readonly -width 2 \
        -justify center] -columnspan 2 -column 0
  .dial.lp set $DialPrefix
  set row [expr $row + 6]
  if {$dialtype == "history"} {
    ttk::label .dial.mml -font FixedFontM -text [::msgcat::mc "\nLeading 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 [::msgcat::mc "Add"] \
            -variable Leading1 -value [::msgcat::mc "Add"] -command logOne] \
            -row 0 -column 0 -padx 8
      grid [ttk::radiobutton .dial.lf.noone -text [::msgcat::mc "Remove"] \
            -variable Leading1 -value "Remove" -command logOne] \
            -row 0 -column 1 -padx 8
      grid [ttk::radiobutton .dial.lf.leave -text [::msgcat::mc "Ignore"]\
            -variable Leading1 -value "Leave" -command logOne] \
            -row 0 -column 2 -padx 8
    }
  } else {
    # dial type == "manual"
    grid [ttk::label .dial.num -justify left -font FixedFontP \
          -text [::msgcat::mc "\nDial Number:"]] -columnspan 2 -padx 12 -column 0
    ttk::entry .dial.number -width $nmbrWidth  -font FixedFontP -validate key \
                -textvariable DialNumber -validatecommand {
                  if {$DialPrefix != "" && %i == 0} {
                    # dial prefix changed
                    .dial.number insert %i %S
                    return 0
                  }
                  if { %d == 1  } {
                    # strip anything but digits after "$DialPrefix"
                    .dial.number insert %i "[regsub -all {\D} "%S" "" ]"
                    .dial.number configure -validate key
                  } elseif {[string length $DialNumber] != "" && $DialPrefix != $DialNumber} {
                    .dial.number delete %i
                  }
                  .dial.number configure -validate key
                  return 0
                }

    grid .dial.number -sticky ew -columnspan 2 -padx 8
    set DialNumber ""
    if {$DialPrefix != ""} {
      .dial.number insert 0 "${DialPrefix}"
    }
    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 [::msgcat::mc "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 [::msgcat::mc "Cancel"] \
        -command {destroy .dial}] -row $row -pady 12

  if {$dialtype == "history"} {
    grid [ttk::button .dial.call -text [::msgcat::mc "Call"] \
        -command {logDialPrefix; DoIt "" "DIAL" "[logOne]" "$dialname" $wantdial}] \
        -row $row -column 1
    grid [ttk::button .dial.abort -text [::msgcat::mc "ABORT"] \
      -command {DoIt "" "DIAL_ABORT" "[logOne]" "$dialname" $wantdial} \
      -state disabled] \
      -pady 10 -row $row -column 1
  } else {
    grid [ttk::button .dial.call -text [::msgcat::mc "Call"] \
        -command {logDialPrefix; DoIt "" "DIAL" "$DialNumber" "$dialname" $wantdial}] \
        -row $row -column 1
    grid [ttk::button .dial.abort -text [::msgcat::mc "ABORT"] \
      -command {DoIt "" "DIAL_ABORT" "$DialNumber" "$dialname" $wantdial} \
      -state disabled] \
      -pady 10 -row $row -column 1
  }
  grid [ttk::button .dial.close -text [::msgcat::mc "Close"] -command {
        destroy .dial} \
        -state disabled] -columnspan 2 -row $row -pady 10
  grid remove .dial.close .dial.abort
  set ClientJobResult "[::msgcat::mc "Waiting for user action..."]"
  modal {.dial}
}

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

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

    if {$action_ == "add"} {
        grid .confirm.lab3
        .confirm.match state !disabled
        if {$exactMatch == 1} {
            .confirm.change state !readonly
            set temp_ $from_
            .confirm.change delete 0 end
            .confirm.change insert 0 "^$temp_$"
            .confirm.change state readonly
        } else {
            .confirm.change state !readonly
        }
    } else {
        grid remove .confirm.lab3
        .confirm.match state disabled
        .confirm.change state readonly
    }
}

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 nameWidth
    global AliasText AliasText2 regexDigit exprText
    global exactMatch oldExactMatch
    global aliasEntry lineEntry whiteEntry blackEntry

    toplevel .confirm
    wm title .confirm [::msgcat::mc "Confirmation" ]
    wm resizable .confirm 0 0
    set action_ $action
    set list_ $list
    set comment_ ""
    set from_ $nameREQ
    set exactlyText [::msgcat::mc "Exactly match entry:"]

    switch $regexDigit {
     "0" {set exprText [::msgcat::mc "can edit using NCID simple expressions or"]}
     "1" {set exprText [::msgcat::mc "can edit using Posix regular expressions or"]}
     "2" {set exprText [::msgcat::mc "can edit using Perl regular expressions or"]}
    }

    ttk::label .confirm.lab3 -font FixedFontP -text $exprText
    ttk::checkbutton .confirm.match -variable exactMatch \
      -text $exactlyText
    set entry [list "$nmbrREQ" "$nameREQ"]
    set entry_ [lindex $entry 0]
    ttk::radiobutton .confirm.rb1 -text "NMBR" \
        -variable entry_ -value [lindex $entry 0]
    ttk::radiobutton .confirm.rb2 -text "NAME" \
        -variable entry_ -value [lindex $entry 1]
    if {$list eq "black" || $list eq "white"} {
    if {[lindex $entry 1] eq "NO NAME" || $nameREQ eq $nmbrREQ} {
         set entry [lreplace $entry 1 1]
       }

        if {$action eq "add"} {
            set _entry [list "NMBR" "NAME"]
            set _entry [join $_entry " or "]
            set from_ $entry_
        } elseif {$which eq "name"} {
            set entry_ [set _entry [lindex $entry 1]]
            set entry ""
        } else {
            if {$list eq "black"} {
            set entry_ $blackEntry
        } else {
            set entry_ $whiteEntry
        }
            set _entry "\"$entry_\""
            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 "[::msgcat::mc $_action ] [::msgcat::mc [::msgcat::mc $_entry]] [::msgcat::mc $prep ] [::msgcat::mc "the server's"] ${list}[::msgcat::mc "list"]"] \
                -columnspan 2 -padx 12 -pady 10
        incr row
        if {[llength $entry] == 2} {
            grid .confirm.rb1 .confirm.rb2 -pady 10
            incr row
            grid .confirm.lab3 -columnspan 3 -padx 12
            incr row
            grid .confirm.match -columnspan 3
            incr row
            grid [ttk::entry .confirm.change -textvariable entry_ \
                  -width $nameWidth -font FixedFontP] -columnspan 2 -padx 12
            if {$exactMatch == 1} {
                set entry_ "^$entry_$"
                .confirm.rb1 state disabled
                .confirm.rb2 state disabled
                .confirm.change state readonly
            }
        }
        incr row 2
        if {$action eq "add"} {
            grid [ttk::label .confirm.lab1 -font FixedFontP \
                  -text [::msgcat::mc "Comment:"]] -row $row \
                  -padx 12 -ipady 10 -columnspan 2
            ttk::entry .confirm.entry -width $nameWidth -font FixedFontP
            grid .confirm.entry -sticky ew -columnspan 2 -padx 12
            focus .confirm.entry
            incr row 2
        }
    } else {
        if {[string length $nameREQ] <= $::nameWidth} {
            set name_ $nameREQ
        } else {
            set name_ [concat [string range $nameREQ 0 [expr $nameWidth - 3]] "..."]
        }
        grid [ttk::label .confirm.list -font FixedFontP -text "[::msgcat::mc "NAME:"] $name_\n[::msgcat::mc "NMBR:"] $nmbrREQ\n[::msgcat::mc "LINE:"] $lineREQ"] -columnspan 2 -padx 12 -pady 10 -sticky w
        set row 4
        if {$CIDaliasType eq "NOALIAS"} {
            set aliasList $aliasTypes
            set alias_action "add"
            set replace_ ""
        } else {
            set aliasList "$CIDaliasType LINEONLY"
            set alias_action "modify"
            set from_ $aliasEntry
        }
        if {$LineAliasType eq "NOALIAS"} {
            set line_action "add"
        } else {
            set line_action "modify"
            set from_ $lineEntry
        }
        set SelAliasType [lindex $aliasList 0]
        grid [ttk::label .confirm.choose -justify left -font FixedFontP \
            -text "[::msgcat::mc "Choose the alias type:"]"] \
            -sticky w -row [incr row 6] -padx 12
        grid [ttk::combobox .confirm.cb -font FixedFontP -values $aliasList \
              -textvariable SelAliasType -width 10 -justify left] -column 1 -columnspan 2 -padx 12 -pady 10 -row $row
        set AliasText ""
        set AliasText2 ""
        focus .confirm.cb
        set aliaswidth [lindex $::aliasWidths [lsearch $aliasTypes $SelAliasType]]
        ttk::entry .confirm.entry -exportselection 0 -font FixedFontP
        incr row
        grid [ttk::label .confirm.lab -justify left -font FixedFontP -text $AliasText] \
         -columnspan 2 -padx 12 -pady 10
        grid .confirm.lab3 -columnspan 3 -padx 12
        incr row
        grid .confirm.match -columnspan 3
        incr row
        grid [ttk::entry .confirm.change -textvariable from_ -width $aliaswidth -font FixedFontP] -columnspan 2 -padx 12
        incr row
        grid [ttk::label .confirm.lab2 -justify left -font FixedFontP -text $AliasText2] \
             -columnspan 2 -padx 12 -pady 10
        doAliasType
        set aliaswidth [lindex $::aliasWidths \
                       [lsearch $aliasTypes $SelAliasType]]
        .confirm.entry configure -width $aliaswidth
        grid .confirm.entry -columnspan 2 -padx 12
        incr row
        if {$action_ eq "modify"} {
            .confirm.entry delete 0 end
            .confirm.entry insert 0 "$replace_"
        }
        focus .confirm.entry
        incr row 6
    }
    grid [ttk::frame .confirm.fr] -pady 10 -columnspan 2 -row $row
    incr row
    grid [ttk::label .confirm.fr.lab1 -font FixedFontP -text [::msgcat::mc "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 [::msgcat::mc "Cancel"] -command {destroy .confirm}] -pady 10

    if {$list eq "alias"} {
        grid [ttk::button .confirm.ok -text [::msgcat::mc "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 match $exactMatch $oldExactMatch]} {
               write_rc_file "set exactMatch" "set exactMatch $exactMatch"
               set oldExactMatch $exactMatch
           }
           DoIt $action_ $list_ $from_&&$replace_ "$SelAliasType&&$from_" 0}] \
          -pady 10 -row $row -column 1
    } else {
        grid [ttk::button .confirm.ok -text [::msgcat::mc "Apply"] -command {
           if {$action_ eq "add"} {
               set comment_ [.confirm.entry get]; \
               set comment_ [string trim $comment_]; \
           }
           if {![string match $exactMatch $oldExactMatch]} {
               write_rc_file "set exactMatch" "set exactMatch $exactMatch"
               set oldExactMatch $exactMatch
           }
           DoIt $action_ $list_ $entry_ $comment_ 0}] \
           -pady 10 -row $row -column 1
    }

    incr row
    grid [ttk::button .confirm.close -text [::msgcat::mc "Close"]  -command {
            destroy .confirm} \
             -state disabled ] -columnspan 2 -row $row -pady 10
    grid remove .confirm.close
    set ClientJobResult [::msgcat::mc "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} {
            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

  toplevel .cbp
  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 60
  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 {passedData} {

    clipboard clear
    clipboard append $passedData
    logMsg $::LEVEL2 "Copied to clipboard: $passedData"
    doClipboardPopup [::msgcat::mc "Selected History Line"] "[::msgcat::mc  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 labels for display
proc selectColumns {} {
    global labTYPE labDATE labTIME labDUR labLINE labNMBR labNAME labNTYPE
    global labCTRY labLOCA labCARI labMTYPE labMESG  labelList labelArray
    global SelectedColumns oldSelectedColumns TypeGroups oldTypeGroups
    global msgToUser

    # convert list to array
    array set labelArray $labelList

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

    set msgToUser ""

    toplevel .ltypes
    wm title .ltypes [::msgcat::mc  "Select Columns to View"]
    wm resizable .ltypes 0 0

    set row 3

    grid [ttk::frame .ltypes.ltf]  -column 0 -sticky "ew" -pady 8
    grid [ttk::label .ltypes.ltf.labelr -font FixedFontP \
          -text [::msgcat::mc  "Required Columns"] ] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row

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

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


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

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

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

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

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

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

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

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

    grid [ttk::checkbutton .ltypes.ltf.fr2.mesg \
              -text                $labMESG \
              -variable        labelArray(MESG) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::label .ltypes.ltf.labele -font FixedFontP \
        -text [::msgcat::mc  "Extra Columns"] ] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row

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

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

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

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

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

    grid [ttk::label .ltypes.ltf.labelg -font FixedFontP \
        -text [::msgcat::mc  "Gateway Columns"] ] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row

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

    grid [ttk::checkbutton .ltypes.ltf.fr4.dur \
              -text                $labDUR \
              -variable        labelArray(DUR) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

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

    grid [ttk::frame .ltypes.ltf.fr5]  -column 0 -pady 8
    grid [ttk::button .ltypes.ltf.fr5.select -text [::msgcat::mc "Select Typical"] \
          -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 MESG 1}
               }] -padx 12 -pady 10 -row $row

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

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

    grid [ttk::button .ltypes.ltf.fr5.ok -text [::msgcat::mc "OK"] -command {
               global labelArray msgToUser
               set sum 0
               foreach {name value} [array get labelArray] {set sum [expr $sum + $value]}
               if {$sum < 3} {
                  set msgToUser [::msgcat::mc "You must select at least three column labels" ]
                  logMsg $::LEVEL1 $msgToUser
                  continue
               }
               # convert array to list
               set labelList [lsort -stride 2 [array get labelArray]]
               array set labelArray $labelList
               set geometry [wm geometry .]
               set current_height  [regsub {(\d+)x(\d+).*} $geometry {\2}]
               wm minsize . [calculateMinTableWidth ] $::histMinRows
               if { ! $labelArray(DATE)} {  .tbl configcolumns  2 -hide true
               } else {                     .tbl configcolumns  2 -hide false }
               if { ! $labelArray(TIME)} {  .tbl configcolumns  3 -hide true
               } else {                     .tbl configcolumns  3 -hide false }
               if { ! $labelArray(DUR)} {   .tbl configcolumns  4 -hide true
               } else {                     .tbl configcolumns  4 -hide false }
               if { ! $labelArray(LINE)} {  .tbl configcolumns  5 -hide true
               } else {                     .tbl configcolumns  5 -hide false }
               if { ! $labelArray(NMBR)} {  .tbl configcolumns  6 -hide true
               } else {                     .tbl configcolumns  6 -hide false }
               if { ! $labelArray(NAME)} {  .tbl configcolumns  7 -hide true
               } else {                     .tbl configcolumns  7 -hide false }
               if { ! $labelArray(NTYPE)} { .tbl configcolumns  8 -hide true
               } else {                     .tbl configcolumns  8 -hide false }
               if { ! $labelArray(CTRY)} {  .tbl configcolumns  9 -hide true
               } else {                     .tbl configcolumns  9 -hide false }
               if { ! $labelArray(LOCA)} {  .tbl configcolumns 10 -hide true
               } else {                     .tbl configcolumns 10 -hide false }
               if { ! $labelArray(CARI)} {  .tbl configcolumns 11 -hide true
               } else {                     .tbl configcolumns 11 -hide false }
               if { ! $labelArray(MTYPE)} { .tbl configcolumns 12 -hide true
               } else {                     .tbl configcolumns 12 -hide false }
               if { ! $labelArray(MESG)} {  .tbl configcolumns 13 -hide true
               } else {                     .tbl configcolumns 13 -hide false }
               destroy .ltypes
               # resize tablelist widget
               .tbl configure -width 0
               .tbl configure -width [calculateMinTableWidth ]
               .tbl configure  -height  $current_height
                logColumnLabels
                 }] -column 3 -padx 12 -pady 10 -row $row

    modal {.ltypes}
}


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

    # convert lists to arrays
    array set t_array $SelectedTypes

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

    set msgToUser ""

    toplevel .ctypes
    wm title .ctypes [::msgcat::mc  "Select Call and Message Types to View"]
    wm resizable .ctypes 0 0

    set row 3

    grid [ttk::frame .ctypes.cf]  -column 0 -sticky "ew" -pady 8
    grid [ttk::label .ctypes.cf.calls -font FixedFontP \
          -text [::msgcat::mc  "Call Types"] ] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    incr row

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

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

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

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

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

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

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

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

    grid [ttk::checkbutton .ctypes.cf.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.cf.msgs -font FixedFontP \
          -text [::msgcat::mc  "Message Types"] ] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    incr row

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

    grid [ttk::checkbutton .ctypes.cf.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.cf.msgtouser -font FixedFontP \
          -textvariable msgToUser] \
         -columnspan 2 -padx 12 -pady 10 -row $row

    grid [ttk::frame .ctypes.cf.fr3]  -column 0 -pady 8
    grid [ttk::button .ctypes.cf.fr3.selall -text [::msgcat::mc  "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}
               }] -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.cf.fr3.clrall -text [::msgcat::mc  "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.cf.fr3.cancel -text [::msgcat::mc   "Cancel"] -command {
               set TypeGroups $oldTypeGroups
               destroy .ctypes}] \
          -column 2 -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.cf.fr3.ok -text [::msgcat::mc   "OK"] -command {
               global t_array msgToUser
               set sum 0
               foreach {name value} [array get t_array] {set sum [expr $sum + $value]}
               if {!$sum} {
                  set msgToUser [::msgcat::mc   "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 does not cause 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

  set msgToUser ""
  set SelLINE   [list]

  toplevel .clines
  wm title .clines [::msgcat::mc  "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}
    }
  }

  grid [ttk::frame .clines.cframe]  -sticky "ew" -pady 8
  if {[llength $DiscoveredLineIDs] > 0} {
    ttk::treeview .clines.cframe.lb -height 4 \
                             -yscrollcommand {.clines.cframe.ys set} \
                             -selectmode extended \
                             -column 0 \
                             -show tree
    foreach lid $DiscoveredLineIDs {
        .clines.cframe.lb insert {} end -text "$lid"
    }
    ttk::scrollbar .clines.cframe.ys -command {.clines.cframe.lb yview}
    grid .clines.cframe.lb .clines.cframe.ys -padx {45 0} -sticky nsew
    if {[llength $DiscoveredLineIDs] <= [lindex [.clines.cframe.lb configure -height] 4]} {
      grid remove .clines.cframe.ys
    } else {
      grid .clines.cframe.ys
    }
    set SelLINE {}

    bind all <<TreeviewSelect>> {
      if {[info commands .clines..cframelb] ne ""} {
        set i [.clines.cframe.lb selection]
        set t [.clines.cframe.lb item $i -text]
        if {[lsearch [list $SelLINE] $t] == -1} {lappend SelLINE $t}
        if {[lindex [.clines.cframe.lb yview] 0] + [lindex [.clines.cframe.lb yview] 1] == 1.0} {
          grid remove .clines.cframe.ys
        } else {
          grid .clines.cframe.ys
        }
      }
    }
  }
  set row [expr [llength $DiscoveredLineIDs] + 2]

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

  if {$SelHistoryLINE != ""} {
    incr row
    grid [ttk::label .clines.cframe.call -justify left -font FixedFontP \
          -text [::msgcat::mc "Last selected call in history window:"]] \
          -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    grid [ttk::button .clines.cframe.history -text "[::msgcat::mc "LineID:"] $SelHistoryLINE" -command {
          global SelHistoryLINE SelHistoryLINEIndex SelLINE
          .clines.cframe.lb selection clear 0 end
          .clines.cframe.lb selection set $SelHistoryLINEIndex
          .clines.cframe.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.cframe.msgtouser -font FixedFontP \
        -textvariable msgToUser] \
        -columnspan 2 -padx 12 -pady 10 -row $row
  incr row
  incr row

  grid [ttk::button .clines.cframe.cancel -text [::msgcat::mc "Cancel"] -command {
        global DiscoveredLineIDs LineIDGroups oldLineIDGroups
        set LineIDGroups $oldLineIDGroups
        logMsg $::LEVEL4 "Cancel: DiscoveredLineIDs contains $DiscoveredLineIDs"
        destroy .clines}] -padx {45 0} -row $row -sticky w

  grid [ttk::button .clines.cframe.ok -text [::msgcat::mc "OK"] -command {
        global DiscoveredLineIDs LineIDGroups oldLineIDGroups
        global SelectedLineIDs oldSelectedLineIDs
        global SelLINE msgToUser
        set msgToUser "You must select at least one lineid"
    set msgToUser [::msgcat::mc   "You must select at least one lineid" ]
        if {$SelLINE == ""} {
          logMsg $::LEVEL1 $msgToUser
          continue
        }
        set msgToUser ""
        set SelectedLineIDs $SelLINE
        destroy .clines
        logViewLineIDs}] -column 1 -padx {0 45} -row $row

  modal {.clines}

}

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

    # needed to properly display the copyright symbol for win10 using utf-16
    if {[encoding system] != "utf-8"}  {
        set mesg1 [encoding convertfrom utf-8 $passedMesg1]
        set mesg2 [encoding convertfrom utf-8 $passedMesg2]
    } else {
        set mesg1  "$passedMesg1"
        set mesg2  "$passedMesg2"
    }
    toplevel .hlp
    wm title .hlp $title
    wm resizable .hlp 0 0

    grid [ttk::frame .hlp.hf] -column 0 -sticky "ew" -pady 8
    if [file exists $Logo] {
        grid [ttk::label .hlp.hf.img -image Logo] -columnspan 2 -padx 12 -pady 10
    }

    if {$title ==  [::msgcat::mc "About" ] } {set jt "center"} else {set jt "left"}

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

    if {$mesg2 != ""} {
      if {$mesg2 == "lnk1"} {
        grid [hyperlink .hlp.hf.pi -command [list eval exec $command "https://forums.raspberrypi.com/viewtopic.php?t=294014" &] -text [::msgcat::mc "How to use Autostart - Raspberry Pi OS"]] -columnspan 2
      } else {
        grid [ttk::label .hlp.hf.mesg2 -font FixedFontP -justify left -text $mesg2 ] -columnspan 2 -padx 12
      }
    }

    if { $title == "About" } {
      grid [ttk::button .hlp.hf.clip -text [::msgcat::mc "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.hf.ok -text [::msgcat::mc "OK"] -command {
          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
    .tbl delete 0 last
}

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 create_array_from_note_files {} {
global noteArray NoteDir
 if [file isdirectory $NoteDir] {
    set noteFiles [ glob -nocomplain -directory $NoteDir  *]
    foreach nf $noteFiles {
      logMsg $::LEVEL7 "[file tail $nf]"
      set fileBaseName [file rootname [file tail $nf] ]
      set fp [open "$nf" r]
      fconfigure $fp -encoding utf-8
      set noteArray($fileBaseName) [read $fp 160]
      logMsg $::LEVEL7 "noteArray($fileBaseName) $noteArray($fileBaseName)"
      close $fp
     }
  } else {
    logMsg $::LEVEL1 "$NoteDir does not exit ; creating it ..."
        if {[catch {file mkdir $NoteDir} msg]} {
            exitMsg 11 "$NoteDir: $msg"
          }
  }
}

proc delete_note { } {
  global NoteDir noteArray nmbrREQ lineREQ dateREQ timeREQ
  if { [info exists  noteArray(${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ})] } {
     unset  noteArray(${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ})
     }
  set notefile "${NoteDir}/${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ}.txt"
  file delete $notefile
}

proc write_note_file {note_string} {
  global NoteDir nmbrREQ lineREQ dateREQ timeREQ
  if [file exists $NoteDir] {
    if [file isdirectory $NoteDir] {
      set notefile "${NoteDir}/${nmbrREQ}-${lineREQ}-${dateREQ}-${timeREQ}.txt"
      set id [open $notefile w ]
      fconfigure $id -encoding utf-8
      logMsg $::LEVEL2 "note: nonewline $id $note_string"
      close $id
      logMsg $::LEVEL2 "Saved note to $notefile"
    } else {
      logMsg $::LEVEL1 "Unable to save note in $NoteDir: $NoteDir is not a directory"
      return
    }
} else {
    logMsg $::LEVEL1 "Unable to save note: $NoteDir directory does not exist "
    return
  }
}

proc write_rc_file {regexpr command} {
  global rcfile rcdir delayedMsgs

  if [file exists $rcfile] {
    if [file isfile $rcdir] {
      logMsg $::LEVEL1 "Unable to save data in $rcdir because it is a file"
      return
    }
    set id [open $rcfile]
    fconfigure $id -encoding utf-8
    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]
    fconfigure $id -encoding utf-8
    puts $id $data
  } else {
    set id [open $rcfile w]
    fconfigure $id -encoding utf-8
    puts $id $command
    set delayedMsgs "$delayedMsgs\n*** Created RC file: $rcfile"
  }
  close $id
}

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

    toplevel .f
    wm title .f  [::msgcat::mc "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]

    grid [ttk::frame .f.ff]  -column 0 -sticky "ew" -pady 8
    ttk::label .f.ff.ln -font FixedFontM -text [::msgcat::mc "Font Name"]
    grid [ttk::labelframe .f.ff.fn -labelwidget .f.ff.ln -labelanchor "n"] -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::combobox .f.ff.fn.cb -font FixedFontM -values $fontList -textvariable currentFont] -pady 5 -padx [list 60 90]
    grid [ttk::button .f.ff.fn.btn -text [::msgcat::mc  "Re-scan"]] -column 1 -row 0 -pady 5 -padx 5
    .f.ff.fn.cb set $currentFont

    ttk::label .f.ff.hw -font FixedFontH -text [::msgcat::mc  "History Window Font"]
    grid [ttk::labelframe .f.ff.fh -labelwidget .f.ff.hw -labelanchor "n"] -column 0 -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.ff.fh.cb -text [::msgcat::mc "Bold"] -variable boldH \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontH  -weight $boldH }] \
          -pady 5 -padx 60
    grid [ttk::label .f.ff.fh.label -font FixedFontH -text [::msgcat::mc  "Size: "]] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.ff.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.ff.hw2 -text [::msgcat::mc "Sample text 0123456789"] -font SelectionFontH] -column 1 -row 1 -pady [list 35 5] -padx 25 -sticky "w"

    ttk::label .f.ff.mml -font FixedFontM -text [::msgcat::mc "Message, Menu and Label Font"]
    grid [ttk::labelframe .f.ff.fm -labelwidget .f.ff.mml -labelanchor "n"] -column 0 -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.ff.fm.cb -text [::msgcat::mc "Bold"] -variable boldM \
          -onvalue [::msgcat::mc "bold"] \
          -offvalue [::msgcat::mc "normal"] \
          -command {font configure SelectionFontM -weight $boldM}] \
          -pady 5 -padx 60
    grid [ttk::label .f.ff.fm.label -font FixedFontM -text [::msgcat::mc "Size: "]] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.ff.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.ff.mml2 -text [::msgcat::mc "Sample text 0123456789"] -font SelectionFontM] -column 1 -row 2 -pady [list 35 5] -padx 25 -sticky "w"

    ttk::label .f.ff.pw -font FixedFontP -text [::msgcat::mc "Popup Window Font"]
    grid [ttk::labelframe .f.ff.fp -labelwidget .f.ff.pw -labelanchor "n"] -column 0  -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.ff.fp.cb -text [::msgcat::mc "Bold"] -variable boldP \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontP -weight $boldP}] \
          -pady 5 -padx 60
    grid [ttk::label .f.ff.fp.label -font FixedFontP -text [::msgcat::mc "Size: "]] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.ff.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.ff.pw2 -text [::msgcat::mc "Sample text 0123456789"] -font SelectionFontP] -column 1 -row 3 -pady [list 35 5] -padx 25 -sticky "w"

    grid [ttk::frame .f.ff.f]  -column 0 -sticky "ew" -pady 8
    grid [ttk::button .f.ff.f.cancel -text [::msgcat::mc "Cancel" ] ] -padx 12 -pady 6
    grid [ttk::button .f.ff.f.ok -text [::msgcat::mc "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 DialPrefix Leading1 oldLeading1 wantdial nmbrREQ Country

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

  set dialnmbr ""
  set Dialed $wantdial
  if {$Dialed == 2} {
     # (manual)
     set dialnmbr [.dial.number get]
     # no need to determine how to handle a leading 1 ( US numbers) in case of manual dial
     # proc DoIt uses the number the user entered
     return $dialnmbr
  }
  if {$Dialed == 1} {
     # (History)
     set dialnmbr $nmbrREQ
  }

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

# Save the prefix to get an outside line
proc logDialPrefix {} {
  global DialPrefix oldDialPrefix

  if {$DialPrefix != $oldDialPrefix} {
    logRCfileOldNewVarChange $::LEVEL2 oldDialPrefix DialPrefix
    set oldDialPrefix $DialPrefix
    write_rc_file "set DialPrefix" "set DialPrefix \"$DialPrefix\""
  }
}

proc formatDT {} {
    global AltDate DateSepar YearDot labelList

    toplevel .dt
    wm title .dt [::msgcat::mc  "Date and Time Formats"]
    wm resizable .dt 0 0

    grid [ttk::frame .dt.df]  -column 0 -sticky "ew" -pady 8
    grid [ttk::label .dt.df.s1]
    grid [ttk::radiobutton .dt.df.12 -width 29 -text [::msgcat::mc  "12 hour clock"] -variable clock -value 12 -command {.tbl configcolumns 3  -formatcommand FormatTime ; logClock } ] -columnspan 2
    grid [ttk::radiobutton .dt.df.24 -width 29 -text [::msgcat::mc  "24 hour clock"] -variable clock -value 24 -command {.tbl configcolumns 3  -formatcommand FormatTime ; logClock } ] -columnspan 2 -rowspan 2

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

    grid [ttk::label .dt.df.s3]
    grid [ttk::radiobutton .dt.df.s -width 29 -text [::msgcat::mc  "Date Separator: /"] -variable DateSepar -value "/" -command {logDate}  ] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.df.d -width 29 -text [::msgcat::mc  "Date Separator: -"] -variable DateSepar -value "-" -command {logDate }  ] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.df.p -width 29 -text [::msgcat::mc  "Date Separator: ."] -variable DateSepar -value "." -command {logDate }  ] -columnspan 2 -padx 12
    grid [ttk::checkbutton .dt.df.y -text [::msgcat::mc  "Add . at end"] -variable YearDot -onvalue 1 -command {logDate }  ] -columnspan 2 -padx 12

    grid [ttk::label .dt.df.s4]
    grid [ttk::button .dt.df.ok -text [::msgcat::mc "OK" ] -command { destroy .dt}]  -columnspan 2 -pady 10

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

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

proc FormatDate { date_string } {
    global AltDate oldAltDate DateSepar oldDateSepar
    global YearDot oldYearDot EndDot

    # check that date_string is not empty
    if { $date_string == "" } {
      return ""
    }

    if {[catch {set rawDate  "[clock scan ${date_string} -format  "%m%d%Y"]"} ]} {
       return ""
    }

    switch $AltDate {
           0 {
               set formattedDate "[ clock format $rawDate -format  "%m$DateSepar%d$DateSepar%Y$EndDot"]"
            }
           1 {
               set formattedDate "[ clock format $rawDate -format  "%d$DateSepar%m$DateSepar%Y$EndDot"]"

           }
           2 {
               set formattedDate "[ clock format $rawDate -locale current -format   "%a %b %d %Y"]"

           }
           3 {
               set formattedDate "[ clock format $rawDate -locale current -format "%a %d %b %Y"]"

           }
     }
     return $formattedDate
}

proc FormatTime { time_string } {
    global clock

    # check that time_string is not empty
    if { $time_string == "" } {
      return ""
      }

    set formattedTime ""
    set date_string "01 01 2000 "
    append date_string $time_string
    if {[catch {set rawDate  "[clock scan $date_string -format  "%m %d %Y %H%M"]"} ]} {
       return ""
    }
    #set rawDate  "[clock scan $date_string -format  "%m %d %Y %H%M"]"
    switch $clock {
        12 {
            set formattedTime "[ clock format  $rawDate -format  "%I:%M %p" -locale current ]"
           }
        24 {
            set formattedTime "[ clock  format $rawDate -format  "%H:%M" -locale current ]"
           }
     }
     return $formattedTime
}

proc FormatCountry {country_code} {
  logMsg $::LEVEL7 "country_Code $country_code"
  if { $country_code == "-" } {
    return "-"
  } else {
    return ""
  }
}


proc FormatNTYPE {NTYPE} {
  logMsg $::LEVEL7 "NTYPE $NTYPE"
  if { $NTYPE == "-" } {
    return "-"
  } else {
    return ""
  }
}

proc putFlag {row} {
  global flagImageSize  flagImageList IconDir

  set countryCode [.tbl getcells  $row,9 ]
  logMsg $::LEVEL6 "putflag countryCode $countryCode"
  if {  $countryCode !=  "" } {
    if  { $countryCode !=  "-" }  {
      set flagdir "$IconDir/flags/$flagImageSize/"
      set flagfile [file join $flagdir [string tolower $countryCode].png ]
      if {  "$flagfile" in  $flagImageList } {
        logMsg $::LEVEL7 "image already exist"
        .tbl cellconfigure $row,9 -image "::flag::$countryCode$flagImageSize"
      } else {
        logMsg $::LEVEL7 "(flag) in imagecreate"
        if { [file exists "$flagfile" ] }  {
          image create photo "::flag::$countryCode$flagImageSize" -file "$flagfile"
          lappend flagImageList  "$flagfile"
          .tbl cellconfigure $row,9 -image "::flag::$countryCode$flagImageSize"
        }
      }
    }
  }
}



proc putNTYPE {row} {
  global NTYPEImageSize ntypeImageArray NTYPEImageList IconDir

  set NTYPE [.tbl getcells  $row,8 ]
  if {  $NTYPE !=  "" } {
    if  { $NTYPE !=  "-" }  {
      set NTYPEdir "$IconDir/phones/$NTYPEImageSize/"
      set NTYPEfile [file join $NTYPEdir  $ntypeImageArray(${NTYPE}).png ]
      if {  "$NTYPEfile" in $NTYPEImageList } {
        .tbl cellconfigure $row,8 -image "::NTYPE::$ntypeImageArray($NTYPE)${NTYPEImageSize}"
      } else {
        if { [file exists "$NTYPEfile"  ]  } {
          image create photo "::NTYPE::$ntypeImageArray(${NTYPE})$NTYPEImageSize" -file "$NTYPEfile"
          lappend NTYPEImageList  "$NTYPEfile"
          .tbl cellconfigure $row,8 -image "::NTYPE::$ntypeImageArray($NTYPE)$NTYPEImageSize"
        }
      }
    }
  }
}

proc logClock {} {
  global  clock oldClock

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


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

    set dateflag  0
    set separflag 0
    set yearflag 0
    if {$DateSepar eq "." && $YearDot == 1} {
       set EndDot "."} else {set EndDot ""}
    if {$DateSepar eq "."} {.dt.df.y state !disabled} else {.dt.df.y state disabled}
    if {$AltDate != $oldAltDate} {
        set dateflag 1
        if {$AltDate > 1} {
            grid remove .dt.df.s3 .dt.df.s .dt.df.d .dt.df.p .dt.df.y
        } else {grid .dt.df.s3 .dt.df.s .dt.df.d .dt.df.p .dt.df.y}
        update
    } elseif {$DateSepar != $oldDateSepar} {
        update
        set separflag 1
    } elseif {$YearDot != $oldYearDot} {
        set yearflag 1
    } else { return }


        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
        }
    .tbl configcolumns 2  -formatcommand FormatDate
}



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 entryconfig [::msgcat::mc "Date and Time..."] -state disabled
        } else {
            .menubar.prefs entryconfig [::msgcat::mc "Date and Time..."]  -state active
        }
        if {$tmpArray(DUR) } {
            # DURATION column is active
            .menubar.prefs entryconfigure [::msgcat::mc  "Duration Mode"] -state active
        } else {
            # DURATION column is hidden
            .menubar.prefs entryconfigure [::msgcat::mc  "Duration Mode"] -state disabled
        }
        if {$tmpArray(NAME) } {
            # NAME column is active
            .menubar.search entryconfigure [::msgcat::mc  "Find Name"] -state active
        } else {
            # Name column is hidden
            .menubar.search entryconfigure [::msgcat::mc  "Find Name"] -state disabled
			remove_find_box
        }



        set oldLabelList $labelList
        set changeflag 1
        fieldLabels
        # save $labelList
        write_rc_file "set labelList" "set labelList \{$labelList\}"

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

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
    hide_show_rows_type
    updateViewDisplay
    write_rc_file "set TypeGroups" "set TypeGroups $TypeGroups"
  }
  if {$SelectedTypes != $oldSelectedTypes} {
    logRCfileOldNewVarChange $::LEVEL2 oldSelectedTypes SelectedTypes
    set oldSelectedTypes $SelectedTypes
    set changeflag 1
    hide_show_rows_type
    updateViewDisplay
    write_rc_file "set SelectedTypes" "set SelectedTypes \{$SelectedTypes\}"
  }

}


proc logDurationMode {} {
  global DurationMode oldDurationMode
  global Socket display_line_num
  global dispDUR labDUR fieldList

  if {$DurationMode != $oldDurationMode} {
        logMsg $::LEVEL3 "DurationMode changed to $DurationMode"
        doDurMode
        .tbl columnconfigure  4 -title [::msgcat::mc "$dispDUR"]
        set fieldList "[regsub {DURATION-\w\s+-\s+\w+\s+\w+} $fieldList "$labDUR"]"
        set display_line_num 0
        clearLog
        puts $Socket "REQ: REREAD"
        flush $Socket
        logMsg $::LEVEL1 "REQ: REREAD"
        set oldDurationMode $DurationMode
        write_rc_file "set DurationMode" "set DurationMode $DurationMode"
    }
}


proc hide_show_rows_type {}  {
  global SelectedTypes

  array set t_array $SelectedTypes
  foreach Type [list "BLK"  "CID" "HUP" "MSG" "MWI" "NOT" "OUT" "PID" "PUT" "RID" "WID" ] {
    set  rows_to_hide_show [.tbl searchcolumn 1 $Type -all]
    foreach Row $rows_to_hide_show {
      .tbl configrows $Row -hide [expr {!($t_array($Type))}]
    }
  }

}

proc hide_show_rows_lineID {}  {
  global DiscoveredLineIDs SelectedLineIDs

   foreach Line_id $DiscoveredLineIDs {
    if { [ lsearch -exact $SelectedLineIDs  $Line_id ] == -1 } {
      set LineIds_array($Line_id) 0
    } else {
      set LineIds_array($Line_id) 1
    }
   }

   foreach Line_id $DiscoveredLineIDs {
     set  rows_to_hide_show [.tbl searchcolumn 5 $Line_id -all]
     foreach Row $rows_to_hide_show {
       .tbl configrows $Row -hide [expr {!$LineIds_array($Line_id)}]
     }
   }
}



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

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

}

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"
     set DiscoveredLineIDs [lsort -dictionary $DiscoveredLineIDs]
  }

  # 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
  global tv_scrollbar
  set current_Y_scroll_position 0
  set current_Y_scroll_position [ lindex [.tv yview] 0 ]
  set selectview [list]
  .tv delete 1.0 2.end
  switch $TypeGroups {
    0 {
      set selectview $SelectedAllTypes; .tv insert 1.end [::msgcat::mc  "View All Types:"]
    }
    1 {
      set selectview $SelectedCalls; .tv insert 1.end [::msgcat::mc "View Call Types:"]
    }
    2 {
      set selectview $SelectedMessages; .tv insert 1.end [::msgcat::mc "View Message Types:"]
    }
    3 {
      set selectview $SelectedSmartPhone;.tv insert 1.end [::msgcat::mc "View Smartphone Types:"]
    }
    4 {
      set selectview $SelectedTypes; .tv insert 1.end [::msgcat::mc "View Selected Types: "]
    }
  }
  if {$selectview == ""} {
    .tv insert 1.end [::msgcat::mc "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
    }
    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 "[::msgcat::mc "View All LineIDs: "]$SelectedLineIDs"
  } else {
    .tv insert 2.end [::msgcat::mc "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
  .tv yview moveto $current_Y_scroll_position
  if {[lindex [.tv yview] 0] + [lindex [.tv yview] 1] != 1.0} {
      if {${tv_scrollbar} == "off" } {
        grid .vsb -in .fr2
        set tv_scrollbar "on"
      }
  } else {
      grid remove .vsb
      set tv_scrollbar "off"
  }
}

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
    #ttk::setTheme $themeName
    #ttk::style theme use $themeName

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


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 [::msgcat::mc "Save Size"] -state disabled
            $menu entryconfigure [::msgcat::mc "Save Size and Position"] -state disabled
            $menu entryconfigure [::msgcat::mc "Quit"] -command {saveSize 0; do_goodbye;  $ExitOn}
            wm protocol . WM_DELETE_WINDOW {saveSize 0; do_goodbye;  $ExitOn}
        }
        "both" {
            set temp "save size and position"
            $menu entryconfigure [::msgcat::mc "Save Size"] -state disabled
            $menu entryconfigure [::msgcat::mc "Save Size and Position"] -state disabled
            $menu entryconfigure Quit -command {saveSize 1; do_goodbye;  $Exiton}
            wm protocol . WM_DELETE_WINDOW {saveSize 1; do_goodbye;  $ExitOn}
        }
        "off" {
            set temp "off"
            $menu entryconfigure [::msgcat::mc "Save Size"] -state normal
            $menu entryconfigure [::msgcat::mc "Save Size and Position"] -state normal
            $menu entryconfigure Quit -command {do_goodbye; $exitOn}
            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 Debug

    if {$Verbose >= $level} {
       if {$Debug != 0} {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

        logMsg $::LEVEL4 "checking if this is a fixed font: $family"

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

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

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

        # skip 'goha tibeb zemen' on Apple Mac under X11 windowing system -- it causes "X Error of failed request"
        if {$family == "goha tibeb zemen"} {
            logMsg $::LEVEL4 "skipping problematic font: $family"
            continue
        }

        # skip 'nil' on Apple Mac under X11 windowing system -- it shows text as graphic blocks
        if {$family == "nil"} {
            logMsg $::LEVEL4 "skipping problematic font: $family"
            continue
        }

        # skip 'open look cursor' on Apple Mac under X11 windowing system -- it shows text as greek symbols
        if {$family == "open look cursor"} {
            logMsg $::LEVEL4 "skipping problematic font: $family"
            continue
        }

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

        # skip fixed font Cursor
        if {[regexp {Cursor} $family]} {
            logMsg $::LEVEL4 "skipping problematic 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 } {

  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 -font Underline-Font -cursor hand2 ] $args ]

  ttk::label $name {*}$args

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

  return $name
}

proc setBrowser {} {
  global command browser

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

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


proc fieldLabels {} {
    global labelList typeWidth dateWidth timeWidth nmbrWidth nameWidth
    global lineIDWidth ntypeWidth countryWidth carrierWidth locationWidth
    global mtypeWidth mesgWidth durationWidth

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

################################################################################
#   procedures related to tooltip "local time display"
#   (for phone numbers in another time zone)
#   (mainly "foreign" phone numbers)
################################################################################

proc balloon_display {tbl1 row col} {
   global labelArray Country  countryName noteArray ntypeArray mtypeArray
   global TZFlag tooltipWidth
   global labTYPE labDATE labTIME labDUR labLINE labNMBR labNAME labNTYPE
   global labCTRY labLOCA labCARI labMTYPE labMESG

  DynamicHelp::configure -bg "lightYellow" -padx -3  -pady 2 -font BalloonFont
  if {( $row == -1  ) } {
   switch $col {
    1  { DynamicHelp::add  $tbl1 -text  $labTYPE }
    2  { DynamicHelp::add  $tbl1 -text  $labDATE }
    3  { DynamicHelp::add  $tbl1 -text  $labTIME }
    4  { DynamicHelp::add  $tbl1 -text  $labDUR }
    5  { DynamicHelp::add  $tbl1 -text  $labLINE }
    6  { DynamicHelp::add  $tbl1 -text  $labNMBR }
    7  { DynamicHelp::add  $tbl1 -text  $labNAME }
    8  { DynamicHelp::add  $tbl1 -text  $labNTYPE }
    9  { DynamicHelp::add  $tbl1 -text  $labCTRY }
    10 { DynamicHelp::add  $tbl1 -text  $labLOCA }
    11 { DynamicHelp::add  $tbl1 -text  $labCARI }
    12 { DynamicHelp::add  $tbl1 -text  $labMTYPE }
    13 { DynamicHelp::add  $tbl1 -text  $labMESG }
   }
  }
  if { ( $row >= 0  ) } {
   # DynamicHelp::configure -bg "lightYellow"
    switch $col {

      1  {
        # help on TYPE　column
        set content "[.tbl getcells  $row,1 ]"
        switch "$content"  {
          BLK  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "BLK: Blocked  -> blacklisted call blocked"]"}
          CID  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "CID: Caller ID -> incoming call"]"}
          HUP  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "HUP: Hangup  -> blacklisted call hangup"]"}
          MSG  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "MSG: Message -> text message from a user or the server"]"}
          MWI  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "MWI: Voicemail -> one or more voicemail messages"]"}
          NOT  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "NOT: Notice -> a smartphone message notice"]"}
          OUT  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "OUT: Out  -> outgoing call"]"}
          PID  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "PID: Phone ID -> Caller ID from a smartphone"]"}
          PUT  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "PUT: Phone out call -> outgoing Caller ID from a smartphone"]"}
          RID  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "RID: Ring Back -> rings back when called number is available"]"}
          WID  {  DynamicHelp::add $tbl1 -text "[::msgcat::mc  "WID: Call Waiting ID -> Caller ID from call waiting"]"}
          default {  DynamicHelp::add $tbl1 -text "" }
        }
      }

      3 {
        # "localtime of phone number" tooltip on TIME columns
        # (if  != computer local time )
        if { $TZFlag == 1 } {
          set phoneNumber [ $tbl1 getcells $row,0 ]
          set localtime [ get_localtime_from_phoneNumumber $phoneNumber]
          if { $localtime != "" } {
            DynamicHelp::add $tbl1 -text " [::msgcat::mc "local time:"] $localtime"
          } else {
            if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
              DynamicHelp::configure -bg "Azure"
              DynamicHelp::add $tbl1 -text [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
            } else {
              DynamicHelp::add $tbl1 -text ""
            }
          }
        } else {
          if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
            DynamicHelp::configure -bg "Azure"
            DynamicHelp::add $tbl1 -text [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        }
      }

      # Hovering on DATE or Duration columns, tooltip will
      # display the associated "note" ( if there is one )
      2  -
      4  {
        if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
          DynamicHelp::configure -bg "Azure"
          DynamicHelp::add $tbl1 -text  [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
        } else {
          DynamicHelp::add $tbl1 -text ""
        }
      }

      5 {
        # Tooltip will wrap long lineID content if ellipsized/snipped
        logMsg $::LEVEL7 "(tooltip) message= [ .tbl getcells  $row,5 ]"
        if { [.tbl  iselemsnipped $row,5 full_text_content ] } {
          logMsg $::LEVEL7 "(tooltip) content is snipped: wrapped content = [wrapParagraph $tooltipWidth [ .tbl getcells  $row,5 ] ]"
          logMsg $::LEVEL7 "(tooltip) full_text_content before wrap  = $full_text_content "
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $full_text_content]
          logMsg $::LEVEL7 "(tooltip) full_text_content = $full_text_content "
        } else {
          if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
            DynamicHelp::configure -bg "Azure"
            DynamicHelp::add $tbl1 -text [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        }
      }

      6 {
        # Tooltip will wrap long number content if ellipsized/snipped
        logMsg $::LEVEL7 "(tooltip) message= [ .tbl getcells  $row,6 ]"
        if { [.tbl  iselemsnipped $row,6 full_text_content ] } {
          logMsg $::LEVEL7 "(tooltip) content is snipped: wrapped content = [wrapParagraph $tooltipWidth [ .tbl getcells  $row,6 ] ]"
          logMsg $::LEVEL7 "(tooltip) full_text_content before wrap  = $full_text_content "
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $full_text_content]
          logMsg $::LEVEL7 "(tooltip) full_text_content = $full_text_content "
        } else {
          if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
            DynamicHelp::configure -bg "Azure"
            DynamicHelp::add $tbl1 -text [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        }
      }

      7 {
        # Tooltip will wrap long name content if ellipsized/snipped
        logMsg $::LEVEL7 "(tooltip) message= [ .tbl getcells  $row,7 ]"
        if { [.tbl  iselemsnipped  $row,7 full_text_content ] } {
          logMsg $::LEVEL7 "(tooltip) content is snipped: wrapped content = [wrapParagraph $tooltipWidth [ .tbl getcells  $row,7 ] ]"
          logMsg $::LEVEL7 "(tooltip) full_text_content before wrap  = $full_text_content "
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $full_text_content]
          logMsg $::LEVEL7 "(tooltip) full_text_content = $full_text_content "
        }  else {
           if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
                DynamicHelp::configure -bg "Azure"
                DynamicHelp::add $tbl1 -text  [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
              }  else {
                 DynamicHelp::add $tbl1 -text ""
              }
        }
      }

      8 {
        # Toooltip help on NTYPE column (Number type )
        if { [info exists  ntypeArray([.tbl getcells  $row,8 ])] } {
          DynamicHelp::add $tbl1 -text [::msgcat::mc $ntypeArray([.tbl getcells  $row,8 ]) ]
        }  else {
          DynamicHelp::add $tbl1 -text ""
        }
      }

      9 {
        # country name tooltip help (not for user's residence country) (COUNTRY column)
        set content "[.tbl getcells  $row,9 ]"
        if { $content == $Country } {
              if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
                 DynamicHelp::configure -bg "Azure"
                 DynamicHelp::add $tbl1 -text  [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
                 }  else {
                    DynamicHelp::add $tbl1 -text ""
                 }
        } else {
          switch "$content"  {
            ""      { DynamicHelp::add $tbl1 -text "" }
            "-"     { DynamicHelp::add $tbl1 -text "" }

            default {
              if { [info exists countryName($content)] } {
                DynamicHelp::add $tbl1 -text  [::msgcat::mc "$countryName($content)"]
              } else {
                DynamicHelp::add $tbl1 -text ""
              }
            }
          }
        }
      }

      10 {
        # "localtime of phone number" tooltip on LOCATION columns
        # (if  != computer local time )
        # +   wrap long LOCATION content if ellipsized/snipped
        set localtime ""
        set tooltipContent ""
        if { $TZFlag == 1 } {
          set phoneNumber [ $tbl1 getcells $row,0 ]
          set localtime [ get_localtime_from_phoneNumumber $phoneNumber]
        }
        if { $localtime != "" } {
          set localtime " [::msgcat::mc "local time:"] $localtime"
        }
        if { [.tbl iselemsnipped $row,10 full_text_content ] } {
          set full_text $full_text_content
          logMsg $::LEVEL7 "(tooltip) content is snipped: full_text_content = $full_text_content "
          set tooltipContent $full_text_content
        } else {
          logMsg $::LEVEL7 "(tooltip) content is not snipped: full_text_content = $full_text_content"
        }
        if { $localtime != "" } {
          set tooltipContent "$tooltipContent  $localtime"
        }
        if { $tooltipContent == "" } {
          if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
            DynamicHelp::configure -bg "Azure"
            DynamicHelp::add $tbl1 -text  [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        } else {
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $tooltipContent]
        }
      }

      11 {
        # Tooltip will wrap long CARRIER content if ellipsized/snipped
        logMsg $::LEVEL7 "(tooltip) message= [ .tbl getcells  $row,11 ]"
        if { [.tbl iselemsnipped $row,11 full_text_content ] } {
          logMsg $::LEVEL7 "(tooltip) content is snipped: wrapped content = [wrapParagraph $tooltipWidth [ .tbl getcells  $row,11 ] ]"
          logMsg $::LEVEL7 "(tooltip) full_text_content before wrap = $full_text_content "
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $full_text_content]
          logMsg $::LEVEL7 "(tooltip) full_text_content = $full_text_content "
        } else {
          if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
            DynamicHelp::configure -bg "Azure"
            DynamicHelp::add $tbl1 -text  [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        }
      }

      12 {
        if { ! $labelArray(MESG)} {
          logMsg $::LEVEL7 "(tooltip) message= [ .tbl getcells  $row,13 ]"
          .tbl iselemsnipped $row,13 full_text_content
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $full_text_content]
          logMsg $::LEVEL7 "(tooltip) full_text_content = $full_text_content "
        } else {
          logMsg $::LEVEL7 "Mtype = [.tbl getcells  $row,12 ]"
          if { [info exists mtypeArray([.tbl getcells  $row,12 ])] } {
            DynamicHelp::add $tbl1 -text [::msgcat::mc $mtypeArray([.tbl getcells  $row,12 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        }
      }

      13 {
        # Tooltip will wrap long MESSAGES content if ellipsized/snipped
        logMsg $::LEVEL7 "(tooltip) message= [ .tbl getcells  $row,13 ]"
        if { [.tbl  iselemsnipped $row,13 full_text_content ] } {
          logMsg $::LEVEL7 "(tooltip) content is snipped: wrapped content = [wrapParagraph $tooltipWidth [ .tbl getcells  $row,13 ] ]"
          logMsg $::LEVEL7 "(tooltip) full_text_content before wrap = $full_text_content "
          DynamicHelp::add $tbl1 -text [wrapParagraph $tooltipWidth  $full_text_content]
          logMsg $::LEVEL7 "(tooltip) full_text_content = $full_text_content "
        } else {
          if { [info exists noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ])] } {
            DynamicHelp::configure -bg "Azure"
            DynamicHelp::add $tbl1 -text  [ wrapParagraph 50 $noteArray([.tbl getcells  $row,0 ]-[.tbl getcells  $row,5 ]-[.tbl getcells  $row,2 ]-[.tbl getcells  $row,3 ]) ]
          } else {
            DynamicHelp::add $tbl1 -text ""
          }
        }
      }

      default {
        DynamicHelp::add $tbl1 -text ""
      }

    }
  }
}

proc balloon_delete {tbl1 row col} {
  DynamicHelp::delete  $tbl1
}


# get localime in timezone of phonenumber whose line in history window, is currently "under the mouse pointer"
# if the number is deemed to be in several timezones, it returns the "min<-->max" local time for the phone number
# if timezone of phone number is same or equivalent to local timezone, it returns "" ; (Hence no "tooltip/balloon" will be displayed)
# if timezone can not be found/determined, it returns "" (will not display any "tooltip/balloon")
# this uses an external python helper program : $TZScript
# the return result is used to display a "tooltip/balloon" in history window
proc get_localtime_from_phoneNumumber { phoneNumber } {
  global Country countryName TZScript

  set fp [open "|$TZScript \"$phoneNumber\" \"$Country\"" r]
  set result [read $fp]
  close $fp
  return "$result"
}

# Pause hangup for a specific time
proc pauseHangup {} {
  global pauseValue Socket ClientJobResult do_pause
  global hr min

  toplevel .p
  wm title .p  [::msgcat::mc "Pause Hangup"]
  wm resizable .p 0 0

  set do_pause 1
  set hr  [expr $pauseValue/60]
  set min [expr $pauseValue%60]
  set ClientJobResult "[::msgcat::mc "Waiting for user action..."]"
  puts $Socket "REQ: PAUSE -1"; flush $Socket
  logMsg $::LEVEL1 "REQ: PAUSE -1"

  grid [ttk::frame .p.f1]  -column 0 -sticky "ew" -pady 8
  grid [ttk::label .p.f1.label -font FixedFontH -text [::msgcat::mc "Time to pause ncidd hangups"]] -column 1 -pady 5 -padx 12 -columnspan 2
  grid [ttk::label .p.f1.l1 -font FixedFontH -text [::msgcat::mc  "Hours"]] -row 1 -column 0 -pady 5 -padx 12 -sticky "w"
  grid [ttk::spinbox .p.f1.s1 -from 0 -to 9 -width 1 -font FixedFontH \
        -textvariable hr -state readonly] \
        -row 1 -column 1 -pady 5 -padx 12 -sticky "w"
  grid [ttk::label .p.f1.l2 -font FixedFontH -text [::msgcat::mc "Minutes"]] -row 1 -column 2 -pady 5 -padx 12 -sticky "w"
  grid [ttk::spinbox .p.f1.s2 -from 0 -to 60 -width 2 -font FixedFontH \
        -textvariable min -state readonly] \
        -row 1 -column 3 -pady 5 -padx 12 -sticky "w"

  grid [ttk::frame .p.f2]  -column 0 -sticky "ew" -pady 8
  grid [ttk::label .p.f2.label -font {FixedFontM} -text [::msgcat::mc "Status:"]] -padx 12 -pady 5 -row 1 -column 0 -sticky "w"
  grid [ttk::label .p.f2.lab2 -font {FixedFontM} \
        -textvariable ClientJobResult] -column 1 -columnspan 3 -padx 12 -pady 5 -row 1 -sticky "w"
  grid [ttk::button .p.f2.close -text [::msgcat::mc "Close" ] \
        -command {destroy .p}] \
          -column 0 -padx 12 -pady 5 -row 2 -sticky "w"
  grid [ttk::button .p.f2.query -text [::msgcat::mc "Query"] \
        -command {puts $Socket "REQ: PAUSE -1"; flush $Socket; \
                  logMsg $::LEVEL1 "REQ: PAUSE -1"}] \
        -column 1 -padx 12 -pady 5 -row 2 -sticky "e"
  grid [ttk::button .p.f2.send -text [::msgcat::mc "Send"] \
        -command {puts $Socket "REQ: PAUSE [expr $hr*60 + $min]"; \
                  flush $Socket; \
                  logMsg $::LEVEL1 "REQ: PAUSE [expr $hr*60 + $min]"}] \
        -column 2 -padx 12 -pady 5 -row 2 -sticky "w"

  modal {.p}
}


proc substituteVar {cmd label} {
 global Country typeREQ nmbrREQ dateREQ timeREQ durationREQ  lineREQ fnmbrREQ nameREQ  ntypeREQ  countryREQ locationREQ carrierREQ mtypeREQ mesgREQ
     logMsg $::LEVEL4 "Command supplied: $cmd"
    # Do the substitutions as in "double quotes" *in the caller's context*
    if { [catch  {set substituted_cmd [uplevel 0 [list subst $cmd]]} result ] } {
      tk_messageBox -type ok -title  "context plugin '$label'" -message "$result \nCommand:\n\
$cmd \n\n\
Could not convert all variables.\n\
Valid variables are: \n\
Country typeREQ dateREQ timeREQ durationREQ lineREQ nmbrREQ fnmbrREQ nameREQ ntypeREQ countryREQ locationREQ carrierREQ  mtypeREQ mesgREQ"
      logMsg $::LEVEL5 "Variables substitution in command failed : $cmd"
      return ""
    } else {
      logMsg $::LEVEL5 "command with substituted variables : $substituted_cmd"
      # return a string not a list of words
      return [join  $substituted_cmd ]
    }
}



####################################################################################
# search box related procedures
#  find_name {} find_next {} find_prev {} not_hidden {} find_box{} remove_find_box{}
####################################################################################

proc find_name {} {
  global searched_name , searched_name_star , search_last_found_index

  # does the initial  search ......

  set row_index 0
  if { "$searched_name" == "" } {
    return 0
  }

  set searched_name_star  "*$searched_name*"

  set last_found_index -1
  set row_index [.tbl searchcolumn 7  $searched_name_star -glob -nocase  -check {not_hidden} ]
  if { ${row_index} != -1 }  {
    .tbl  selection clear 0 last
    .tbl selection set  ${row_index}  ${row_index}
    prepare_history_context_menu [.tbl get ${row_index}]
    .tbl see  ${row_index}
    set search_last_found_index $row_index
  } else {
    bell
  }
  return 0
}

proc find_next {} {
  global searched_name_star , searched_name , search_last_found_index

  # Checks if search has been initialised (done once ) or if content of search box has changed .
  # in that case we  calls find_name .
  set searched_name_star_new   "*$searched_name*"
  if { ${search_last_found_index} == -1 || "$searched_name_star_new" != "$searched_name_star" } { find_name ; return }

  # A search is already in progress.....
  set rowCount [.tbl size]
  if {   $search_last_found_index  == [ expr "$rowCount" - 1  ] } {
    set start_index 0
    # at last row
  } else {
    set start_index [ expr "$search_last_found_index"  + 1 ]
    # Not  at last row
  }

  set row_index [.tbl searchcolumn 7  $searched_name_star -start $start_index -glob -nocase -check not_hidden ]
  if { $row_index == -1 } {
    # No match further Down , restarting from beginning
    set row_index [.tbl searchcolumn 7  $searched_name_star -start 0 -glob -nocase -check not_hidden ]
    set search_last_found_index $row_index
  }
  if { ${row_index} != -1 }  {
   .tbl  selection clear 0 last
   .tbl selection set  ${row_index}  ${row_index}
   prepare_history_context_menu [.tbl get ${row_index}]
   .tbl see  ${row_index}
   set search_last_found_index $row_index
  } else {
    bell
    set search_last_found_index  -1
  }
}

proc find_prev {} {
  global searched_name_star , searched_name , search_last_found_index

  # Checks if search has been initialised (done once ) or if content of search box has changed .
  # in that case we  calls find_name .
  set searched_name_star_new   "*${searched_name}*"
  if { ${search_last_found_index} == -1 || "$searched_name_star_new" != "$searched_name_star" } { find_name ; return }

  # A search is already in progress.....
  set rowCount [.tbl size]
  if {   $search_last_found_index  ==  0  } {
    set start_index [ expr "$rowCount" - 1  ]
    # We are  at row 0  (first row)
  } else {
    # Not  at first row
    set start_index [ expr "$search_last_found_index"  - 1 ]
  }

  set row_index [.tbl searchcolumn 7  $searched_name_star -start $start_index -glob -nocase -backward -check not_hidden ]
  if { $row_index == -1 } {
    # No match further Up , restarting from end
    set row_index [.tbl searchcolumn 7  $searched_name_star -start [ expr "$rowCount" - 1  ]  -glob -nocase -backward -check not_hidden ]
    set search_last_found_index $row_index
  }
  if { ${row_index} != -1 }  {
    .tbl  selection clear 0 last
    .tbl selection set  ${row_index}  ${row_index}
    prepare_history_context_menu [.tbl get ${row_index}]
    .tbl see  ${row_index}
    set search_last_found_index $row_index
  } else {
    bell
  }
}

proc not_hidden { tbl row_index column_index cell_content } {
  if { [$tbl viewablerowcount   $row_index  $row_index   ] ==  0 }  {
    return 0
  } else {
    return 1
  }
}

proc prepare_history_context_menu { selectedRow  } {
        global DATE TIME DUR LINE NMBR NAME NTYPE CTRY LOCA CARI MTYPE MESG
        global nmbrREQ typeREQ dateREQ timeREQ durationREQ lineREQ fnmbrREQ nameREQ ntypeREQ countryREQ locationREQ carrierREQ mtypeREQ mesgREQ displayedRowREQ
        global NoGUI  Try labelArray Socket context_plugins
        # Recover the unformatted phone number sent by the modem
        set nmbrREQ     [lindex $selectedRow  0 ]
        set typeREQ     [lindex $selectedRow  1 ]
        set dateREQ     [lindex $selectedRow  2 ]
        set timeREQ     [lindex $selectedRow  3 ]
        set durationREQ [lindex $selectedRow  4 ]
        set lineREQ     [lindex $selectedRow  5 ]
        set fnmbrREQ    [lindex $selectedRow  6 ]
        set nameREQ     [lindex $selectedRow  7 ]
        set ntypeREQ    [lindex $selectedRow  8 ]
        set countryREQ  [lindex $selectedRow  9 ]
        set locationREQ [lindex $selectedRow 10 ]
        set carrierREQ  [lindex $selectedRow 11 ]
        set mtypeREQ    [lindex $selectedRow 12 ]
        set mesgREQ     [lindex $selectedRow 13 ]
        set displayedRowREQ   ""
        set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow 1 ]]
        if {  $labelArray(DATE)}   { set displayedRowREQ [concat $displayedRowREQ  [FormatDate [lindex $selectedRow 2 ]]] }
        if {  $labelArray(TIME)}   { set displayedRowREQ [concat $displayedRowREQ  [FormatTime [lindex $selectedRow 3 ]]] }
        if {  $labelArray(DUR) }   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow  4 ]] }
        if {  $labelArray(LINE)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow  5 ]] }
        if {  $labelArray(NMBR)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow  6 ]] }
        if {  $labelArray(NAME)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow  7 ]] }
        if {  $labelArray(NTYPE)}  { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow  8 ]] }
        if {  $labelArray(CTRY)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow  9 ]] }
        if {  $labelArray(LOCA)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow 10 ]] }
        if {  $labelArray(CARI)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow 11 ]] }
        if {  $labelArray(MTYPE)}  { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow 12 ]] }
        if {  $labelArray(MESG)}   { set displayedRowREQ [concat $displayedRowREQ  [lindex $selectedRow 13 ]] }

        # Handles popup /context menu
        # First reset everything
        # then what is needed is added
        .popupmenu.clipboard delete 0 end
        .popupmenu delete 0 end

        # This is needed for aliases, whitelist, blacklist, dial from history, etc
        if {$nameREQ eq ""} {
            logMsg $::LEVEL1 "Row $selectedRow :  nameREQ is null"
        } 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}
        }
        #handles context/popup menu for clipboard
     if {!$NoGUI} {
         global address

          .popupmenu add cascade -menu .popupmenu.clipboard -label [::msgcat::mc "Copy to Clipboard"] -font FixedFontH
          if {[regexp {http[\w:./]+} ${displayedRowREQ} address]} {
            .popupmenu.clipboard add command                  -label [::msgcat::mc "URL"]        -command { doClipboard ${address} }  -font FixedFontH
          }
          if {$nameREQ != "" } {
            .popupmenu.clipboard add command                -label [::msgcat::mc "Name"]               -command { doClipboard $nameREQ } -font FixedFontH
          }
          if {$fnmbrREQ != "" } {
            .popupmenu.clipboard add command                -label [::msgcat::mc "Number Formatted"]   -command { doClipboard $fnmbrREQ } -font FixedFontH
          }
          if { $nmbrREQ != "" } {
            .popupmenu.clipboard add command                -label [::msgcat::mc "Number Digits"]      -command { doClipboard $nmbrREQ } -font FixedFontH
          }
          if { $mesgREQ != "" } {
            .popupmenu.clipboard add command                -label [::msgcat::mc "Message"]      -command { doClipboard $mesgREQ } -font FixedFontH
          }
          .popupmenu.clipboard add command                  -label [::msgcat::mc "Entire Line"]        -command { doClipboard ${displayedRowREQ} }  -font FixedFontH
          .popupmenu add separator

          # handles context/popup menu for "notes"
          if { [expr { $dateREQ != "" }] && [expr { $timeREQ != "" }] &&   [expr { $nmbrREQ != "" }] }  {
            .popupmenu add command                          -label [::msgcat::mc "Add/Edit a Note..."]    -command { editNote } -font FixedFontH
            .popupmenu add separator
          }

          # handles context/popup menu for optional plugins scripts
          # scripts are list of 2 members : "display name" and "actual script with argument(s)"
          # create optional context menu items scripts (if needed)
          set l 0
          set i 0
          set n 0
          if { $context_plugins != {} } {
            set l  [llength $context_plugins ]
            logMsg $::LEVEL4 "context_plugins: l=$l "
            for { set i  0 }  { $i < $l } { incr i }  {
              set pluginLabel "[ lindex [lindex $context_plugins $i ] 0 ]"
              set pluginCommand [ substituteVar [list [ lindex [lindex $context_plugins $i ] 1 ] ] "${pluginLabel}" ]
              logMsg $::LEVEL4 "context_plugins: i=$i ; pluginLabel = $pluginLabel ; pluginCommand = ${pluginCommand} "
              if { "${pluginCommand}"  == "" } {
			    continue
			  }
              if {[regexp {^\s*#} $pluginLabel]} {
                # unwanted plugin
				incr n
                logMsg $::LEVEL4 "context_plugins: skipping '$pluginLabel' because it is commented out"
                continue
              }
              logMsg $::LEVEL4 "context_plugins: detected label: $pluginLabel"
              logMsg $::LEVEL4 "context_plugins: detected command: $pluginCommand"
              # check that commant exists and is executable ...
              set full_path_command ""
              set full_path_command [ auto_execok [ regexp  -inline {\S+}  "$pluginCommand"] ]
              if { ${full_path_command} != "" } {
                 set pluginCatchCommand "if \{ \[catch \{exec ${pluginCommand} &\} result \] \} \{ tk_messageBox -type ok -title  \"$pluginLabel\" -message \"\$result\" \}"
                 .popupmenu add command -label $pluginLabel -command ${pluginCatchCommand} -font FixedFontM
                 logMsg $::LEVEL5 "context_plugins: command full PATH: ${full_path_command}"
                 logMsg $::LEVEL5 "context_plugins: catch command: $pluginCatchCommand"
              } else {
                 incr n
                 logMsg $::LEVEL1 "context_plugins: skipping '$pluginLabel' because command '${pluginCommand}' does not exist or is not executable"
              }
            }
          }
          logMsg $::LEVEL1 "context_plugins: $l total, [expr $l - $n] added, $n rejected"
          if { $i > 0 } { .popupmenu add  separator }
     }
}





proc find_box {} {
  global searched_name

  if { ![winfo exists .fr3] } {
    ttk::frame .fr3 -relief  raised -height 30
    grid .fr3 -row 6 -column 0 -columnspan 1
    ttk::entry .fr3.find_name_entry -font FixedFontP  -width 20 -textvariable searched_name
    ttk::button .fr3.find_name_up -text [::msgcat::mc "Up"] -command { find_prev }
    ttk::button .fr3.find_name_down -text [::msgcat::mc "Down"] -command { find_next }
    ttk::button .fr3.find_name_close -text [::msgcat::mc "Close"] -command { remove_find_box  }
    grid .fr3.find_name_entry  -in .fr3 -row 0 -column 0 -padx 10 -pady  10
    grid .fr3.find_name_up  -in .fr3  -row 0 -column 1 -padx 10 -pady  10
    grid .fr3.find_name_down -in .fr3  -row 0 -column 2 -padx 10 -pady  10
    grid .fr3.find_name_close -in .fr3 -row 0 -column 3 -padx 10 -pady  10
    set searched_name ""
    set search_last_found_index -1
  } else {
    set searched_name ""
    set search_last_found_index -1
    grid .fr3 -row 6 -column 0 -columnspan 1
  }
}

proc remove_find_box {} {
  global searched_name

  set searched_name ""
  set search_last_found_index -1
  if { [winfo exists .fr3] } {
     grid remove .fr3
  }
}

proc doDurMode {} {
    global DurationMode dispDUR dlenMax labDUR

    set dispDUR "DURATION-$DurationMode"
    if {$DurationMode == "C"} {
        set labDUR   "[format "%-${dlenMax}.${dlenMax}s" "$dispDUR"   ] - [::msgcat::mc "call duration"]"
    } else {
        set labDUR   "[format "%-${dlenMax}.${dlenMax}s" "$dispDUR"   ] - [::msgcat::mc "talk duration"]"
    }
}



########################################################################
#                      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 rcdir   $UnixRCdir
        set rcfile  $rcdir/$RCfile
        set NoteDir $rcdir/$RCnoteDir
        set asfile  $UnixASfile
      }
      "windows" {
        set rcdir   $RCdir
        set rcfile  $rcdir/$RCfile
        set NoteDir $rcdir/$RCnoteDir
      }
    }
    if {$PortableDir != ""} {
      set rcdir   [file join $PortableDir $RCdir]
      set rcfile  [file join $rcdir $RCfile]
      set NoteDir [file join $rcdir $RCnoteDir]
      if {$asfile == $UnixASfile} {
        regsub {^[\w/]+(.config.*$)} $asfile {\1} asfile
        set asfile [file join $PortableDir/$asfile]
      }
    }
    if [file exists $rcdir] {
        if [file isfile $rcdir] {
            # move <path>/.ncid <path>/.ncid.X
            file copy $rcdir "$rcdir.X"
            file delete $rcdir

            # mkdir <path>.ncid, copy .ncid.X to ncidrc
            file mkdir $NoteDir
            file copy "$rcdir.X" $rcfile
        }
    } else {
        file mkdir $NoteDir
        set delayedMsgs "$delayedMsgs\nCreated Directory: $NoteDir"
    }

    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 "LogEnable setting is 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"
logMsg $::LEVEL1 "Debug: $Debug"

# 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} {
  create_array_from_note_files

  # 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 "CallLogFlag:  $CallLogFlag"
if {!$NoGUI && $CallLogFlag} {
    logMsg $::LEVEL1 "  GUI mode - Ignoring CallNameFlag"
}
logMsg $::LEVEL1 "LineName:     $LineName"
logMsg $::LEVEL1 "LoginName:    $LoginName"
logMsg $::LEVEL1 "\nDelay between reconnect tries to the server: $Delay (seconds)"

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

if {!$NoGUI} {
    logMsg $::LEVEL1 "Detected windowing system: [machine osgui]"

    logMsg $::LEVEL1 "\nPATH: $::env(PATH)"
    logMsg $::LEVEL1 "\nauto_path: $::auto_path"
    logMsg $::LEVEL1  "\nLibrary PATH: $auto_path"
    logMsg $::LEVEL1 "\nModule PATH: [::tcl::tm::path list]\n"

    # determine version and location of tcl modules used
    set modpath "[::tcl::tm::path list]"
    foreach path $modpath {
        if {![catch {set module [glob $path/msgcat*]}]} {
            logMsg $::LEVEL1 "TCL module:     [glob $module]"
        }
    }

    logMsg $::LEVEL1 "\nImageDir:       $ImageDir"
    logMsg $::LEVEL1 "IconDir:        $IconDir"
    logMsg $::LEVEL1 "MsgsDir:        $MsgsDir"
    logMsg $::LEVEL1 "PluginDir:      $PluginDir"
    logMsg $::LEVEL1 "LogDir:         $LogDir"
    logMsg $::LEVEL1 "RCfile:         $rcfile"
    logMsg $::LEVEL1 "NoteDir:        $NoteDir\n"

    logMsg $::LEVEL1 "ThemesDir:      $ThemesDir"
    logMsg $::LEVEL1 "ncid themes:    $ncidThemes"
    logMsg $::LEVEL1 "builtin themes: $builtinThemes"
    logMsg $::LEVEL1 "addon themes:   $addonThemes"
    logMsg $::LEVEL1 "current theme:  $themeName\n"

    # determine version and location of tcl libraries used
    foreach path "$auto_path" {
        if {![catch {set libpath [glob $path/tablelist*]}]} {
            logMsg $::LEVEL1 "NCID library: [glob $libpath]"
        }
        if {![catch {set libpath [glob $path/getopt*]}]} {
            logMsg $::LEVEL1 "NCID library: [glob $libpath]"
        }
        if {![catch {set libpath [glob $path/bwidget*]}]} {
            # must be last
            logMsg $::LEVEL1 "TCL  library: [glob $libpath]"
            break
        }
    }

    logMsg $::LEVEL1 "\nPopup 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"
    }
    if {$wmGeometry eq ""} {
        logMsg $::LEVEL1 "GUI geometry was not previously saved"
    } else {
        logMsg $::LEVEL1 "saved GUI geometry: $wmGeometry"
    }

    setBrowser
    makeWindow
    fieldLabels

    set wmgeometry [wm geometry .]
    logMsg $::LEVEL1 "GUI geometry: $wmgeometry"
     set tableMinWidth [calculateMinTableWidth ]
     wm minsize . $tableMinWidth $::histMinRows
     set current_width [regsub {(\d+).*} $wmgeometry {\1}]
     set current_height  [regsub {(\d+)x(\d+).*} $wmgeometry {\2}]
     .tbl configure -width 0
     .tbl configure -width  $current_width
     .tbl configure -height $current_height
     logMsg $::LEVEL1 "Tablelist widget is initialized "
     logMsg $::LEVEL1 "Resized History Tablelist  Width: $current_width characters"
     logMsg $::LEVEL1 "Resized History Tablelist  Height: $current_height rows"
     logMsg $::LEVEL1 "History Tablelist 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: Smartphone"}
        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
