#
# This file is part of:
#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2013 Miguel Filgueiras migfilg@t-online.de
#
#    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.
#
#  File: know.tcl
#  Last change:  6 October 2013
#
# Includes contributions by Brian Baulch (baulchb_AT_onthenet.com.au)
#  marked "BSB contribution"
#

proc NewItem {wh} {
    # open window for defining a new item
    #  $wh in $TYPES
    global CREATIONDATE Proc DataDefault

    set opts {create revert cancel}
    switch $wh {
	WP  {
	    if { $CREATIONDATE } {
		GMWPoint -1 $opts [FormData WP Date [list [Now]]]
	    } else {
		GMWPoint -1 $opts [FormData WP Commt [list [DateCommt [Now]]]]
	    }
	}
	default {
	    $Proc($wh) -1 $opts $DataDefault($wh)
	}
    }
    return
}

proc CreateItem {wh data} {
    # create a new item of given type and with given data
    # return index of new item
    global Index Number WPRoute

    set ix $Index($wh)
    incr Index($wh) ; incr Number($wh) ; incr Number(Data)
    SetItem $wh $ix $data ; ListAdd $wh $ix
    if { $Number(Data) == 1 } { ChangeOnState datastate normal }
    if { $wh == "WP" } { set WPRoute($ix) "" }
    return $ix
}

proc CreateGRFor {iname obs lp} {
    # create a GR
    #  $iname is index of TXT to use as prefix for GR name, or is a literal
    #    prefix if given as =PREFIX, or has the form @NAME for a
    #    literal name (if in use, the existing GR will be replaced)
    #  $obs is GR remark
    #  $lp is GR contents
    # return index of new GR
    global TXT GRDispl

    if { [regsub {^@} $iname "" grname] } {
	set ix [IndexNamed GR $grname]
	set data [FormData GR "Name Obs Conts" [list $grname $obs $lp]]
	if { $ix != -1 } {
	    if { $GRDispl($ix) } { UnMapGR $ix }
	    SetItem GR $ix $data
	    UpdateItemWindows GR $ix
	} else { set ix [CreateItem GR $data] }
	return $ix
    }
    if { ! [regsub {^=} $iname "" pre] } { set pre $TXT($iname) }
    set n 0
    while 1 {
	set grname [format "$pre %d" $n]
	if { [IndexNamed GR $grname] == -1 } {
	    set data [FormData GR "Name Obs Conts" [list $grname $obs $lp]]
	    return [CreateItem GR $data]
	    break
	}
	incr n
    }
    # not used
    return
}

proc ItemData {wh index} {
    # find data for item with given index
    #  $wh in $TYPES or LAP
    # return list of values in the order given by $Storage($wh)
    # see GMStart (setup.tcl) for the description of data arrays
    global Storage

    set l ""
    foreach s $Storage($wh) {
	global $s

	set l [lappend l [set [set s]($index)]]
    }
    return $l
}

proc FormData {wh names vals} {
    # create a data list for an item of type $wh (in $TYPES, or LAP, TP, LP)
    #  $names is a list of data array names without the prefix $wh
    #  $vals is a list of values aligned with $names
    # return list of values in the order given by $DataIndex($wh)
    #  (if $wh in $TYPES or LAP that is the order of $Storage($wh)) using
    #  default values for those not given in $vals
    # see GMStart (setup.tcl) for the description of data arrays
    global DataDefault DataIndex

    set l $DataDefault($wh)
    foreach n $names v $vals {
	set i $DataIndex(${wh}$n)
	set l [lreplace $l $i $i $v]
    }
    return $l
}

proc SetItem {wh index data} {
    # set data for item with given index
    #  $wh in $TYPES or LAP
    # see GMStart (setup.tcl) for description of data arrays
    global Storage IndexOf

    set ids [lindex $Storage($wh) 0]
    global $ids

    set name [lindex $data 0]
    if { ! [catch {set oldname [set [set ids]($index)]}] && \
	    $oldname != $name } {
	unset IndexOf($wh,$oldname)
    }
    set IndexOf($wh,$name) $index
    foreach val $data field $Storage($wh) {
	global $field

	set [set field]($index) $val
    }
    return
}

proc UnsetItem {wh index} {
    # destroy data for item with given index
    #  $wh in $TYPES or LAP
    # see GMStart (setup.tcl) for description of data arrays
    global Storage IndexOf

    set ids [lindex $Storage($wh) 0]
    global $ids

    unset IndexOf($wh,[set [set ids]($index)])
    foreach field $Storage($wh) {
	global $field

	unset [set field]($index)
    }
    return
}

proc UnsetSeveral {wh ixs} {
    # destroy data for items with given indices
    #  $wh in $TYPES or LAP
    # see GMStart (setup.tcl) for description of data arrays
    global Storage IndexOf

    set ids [lindex $Storage($wh) 0]
    global $ids

    foreach ix $ixs {
	unset IndexOf($wh,[set [set ids]($ix)])
    }
    foreach field $Storage($wh) {
	global $field

	foreach ix $ixs {
	    unset [set field]($ix)
	}
    }
    return
}

proc UnsetAll {wh} {
    # destroy data for all items with given type
    #  $wh in $TYPES or LAP
    # see GMStart (setup.tcl) for description of data arrays
    global Storage IndexOf

    array unset IndexOf $wh,*
    foreach arr $Storage($wh) {
	global $arr

	unset $arr
    }
    return
}

proc Forget {wh ix} {
    # forget an item with given index; $wh in $TYPES or LAP
    global ${wh}Displ RTIdNumber RTWPoints Number MESS TXT
    # BSB contribution
    global MYGPS WPName WPNum UnusedICInx UnusedWPInx

    if { [set ${wh}Displ($ix)] && $wh != "LAP" && ! [UnMap $wh $ix] && \
	     $wh != "GR" } {
	GMMessage [format $MESS(cantfgt) $TXT(name$wh)]
	return 0
    }
    switch $wh {
	WP {
	    # BSB contribution
	    if { $MYGPS == "Lowrance" } {
		if { [string match "ICON*" $WPName($ix)] } {
		    lappend UnusedICInx $WPNum($ix)
		} else {
		    lappend UnusedWPInx $WPNum($ix)
		}
	    }
	}
	RT {
	    UnsetWPRoute $RTIdNumber($ix) $RTWPoints($ix)
	}
    }
    ListDelete $wh $ix ; UnsetItem $wh $ix
    incr Number($wh) -1 ; incr Number(Data) -1
    if { $Number(Data) == 0 } { ChangeOnState datastate disabled }
    return 1
}

proc ForgetSeveral {wh ixs} {
    # forget several items with given indices; $wh in $TYPES or LAP
    #  $ixs has the same order of $ListInds($wh) although with some
    #    elements missing
    # proc based on proc Forget
    global ${wh}Displ RTIdNumber RTWPoints Number MESS TXT \
	    MYGPS WPName WPNum UnusedICInx UnusedWPInx

    if { $wh == "GR" || $wh == "LAP" } {
	set fs $ixs ; set cf [expr -[llength $ixs]]
    } else {
	set fs "" ; set cf 0; set nf 0
	foreach ix $ixs {
	    if { [set ${wh}Displ($ix)] && ! [UnMap $wh $ix] } {
		set nf 1
		continue
	    }
	    switch $wh {
		WP {
		    if { $MYGPS == "Lowrance" } {
			if { [string match "ICON*" $WPName($ix)] } {
			    lappend UnusedICInx $WPNum($ix)
			} else {
			    lappend UnusedWPInx $WPNum($ix)
			}
		    }
		}
		RT {
		    UnsetWPRoute $RTIdNumber($ix) $RTWPoints($ix)
		}
	    }
	    lappend fs $ix
	    incr cf -1
	}
	if { $nf } { GMMessage [format $MESS(cantfgt) $TXT(name$wh)] }
    }
    incr Number($wh) $cf ; incr Number(Data) $cf
    if { $Number($wh) == 0 } {
	if { $Number(Data) == 0 } { ChangeOnState datastate disabled }
	ChangeOnStateList $wh disabled
	UnsetAll $wh ; ListDeleteAll $wh
    } else {
	UnsetSeveral $wh $fs ; ListDeleteSeveral $wh $fs
    }
    return
}

proc AllIndicesForType {wh types} {
    # return list of pairs with type and list of indices for all items
    #  of either all $types if $wh==Data, or for type $wh
    # in the former case, the order of the list is that imposed by $types
    #  and this may be important when writing to files in a format that
    #  imposes a specific order in the data
    global Storage Number

    if { $wh != "Data" } {
	set ids [lindex $Storage($wh) 0]
	global $ids
	set ixs [array names $ids]
	return [list [list $wh $ixs]]
    }
    set lp ""
    foreach wh $types {
	if { $Number($wh) > 0 } {
	    set ids [lindex $Storage($wh) 0]
	    global $ids
	    set ixs [array names $ids]
	    lappend lp [list $wh $ixs]
	}
    }
    return $lp
}

proc IndexNamed {wh name} {
    # find index for item with given name; $wh in $TYPES or LAP
    global IndexOf

    if { [catch {set ix $IndexOf($wh,$name)}] } {
	return -1
    }
    return $ix
}

proc NameOf {wh ix} {
    # return name of item with given index; $wh in $TYPES or LAP
    global Storage

    set ids [lindex $Storage($wh) 0]
    global $ids
    return [set [set ids]($ix)]
}

proc NewName {wh args} {
    # return an unused valid name for an item of type $wh in $TYPES
    #  $args may be the previous name if $wh==WP
    #    that may be used as prefix of new name if formed of acceptable chars
    # in other cases use numbers from 0 with prefix "${wh}-" unless $wh==RT
    global NAMELENGTH MAXROUTES ACCEPTALLCHARS RECNAMECHARS

    set pre ${wh}- ; set k 0
    switch $wh {
	WP {
	    set oldname [lindex $args 0]
	    if { $oldname != "" && \
		    ($ACCEPTALLCHARS || [CheckName Ignore $oldname]) } {
		set pre [string range $oldname 0 [expr $NAMELENGTH-3]]
	    }
	    if { [set d [expr $NAMELENGTH-[string length $pre]]] > 9 } {
		set d 9
	    }
	    set max [expr int(pow(10,$d))-1]
	    while 1 {
		# will loop forever if more than 100000 are generated...
		set n "${pre}[format %0${d}d $k]"
		if { [IndexNamed WP $n] == -1 } { return $n }
		if { $k == $max } {
		    incr d ; set k 0
		    if { [set pre [string range $pre 0 end-1]] == "" } {
			return [NewName WP ZY-]
		    }
		} else { incr k }
	    }		
	}
	RT {
	    while 1 {
		if { [IndexNamed RT [incr k]] == -1 } { return $k }
	    }
	}
	default {
	    while 1 {
		set name ${pre}[format %06d [incr k]]
		if { [IndexNamed $wh $name] == -1 } { return $name }
	    }
	}
    }
    # not used
    return    
}

proc SetWPRoute {rt wps} {
    # insert (in order) RT name $rt in list of RTs of each known WP
    #  whose name belongs to $wps
    global WPRoute

    foreach wp $wps {
	if { [set ix [IndexNamed WP $wp]] != -1 } {
	    if { [lsearch -exact $WPRoute($ix) $rt] == -1 } {
		lappend WPRoute($ix) $rt
		set WPRoute($ix) [lsort $WPRoute($ix)]
	    }
	}
    }
    return
}

proc UnsetWPRoute {rt wps} {
    # delete RT name $rt in list of RTs of each given WP
    #  that is defined
    global WPRoute

    foreach wp $wps {
	set ix [IndexNamed WP $wp]
	if { $ix != -1 } {
	    set wi [lsearch -exact $WPRoute($ix) $rt]
	    if { $wi != -1 } {
		set WPRoute($ix) [lreplace $WPRoute($ix) $wi $wi]
	    }
	}
    }
    return
}

proc RenameWPRoute {oldname newname wps} {
    # change RT name in list of RTs of each given WP
    #  that is defined or add new name if old not found
    global WPRoute

    foreach wp $wps {
	set ix [IndexNamed WP $wp]
	if { $ix != -1 } {
	    set wi [lsearch -exact $WPRoute($ix) $oldname]
	    if { $wi != -1 } {
		set WPRoute($ix) [lreplace $WPRoute($ix) $wi $wi \
				      $newname]
	    } else { lappend $WPRoute($ix) $newname }
	    set WPRoute($ix) [lsort $WPRoute($ix)]
	}
    }
    return
}

proc DateCommt {date} {
    # create comment from date
    global COMMENTLENGTH NOLOWERCASE

    regsub -all {:|\.} $date "" date
    if { [string length $date] > $COMMENTLENGTH } {
	set date [string range "$date" 0 [expr $COMMENTLENGTH-1]]
    }
    if { $NOLOWERCASE } {
	return [string toupper "$date"]
    }
    return $date
}

## operations on groups

proc GRsElements {ixs rec wh} {
    # find elements of type $wh (in $TYPES or LAP) in groups with
    #  given indices; if $wh==GR the initial GRs are included in the result;
    #  undefined elements are not included
    #  $rec is 1 if search is recursive
    # return list of indices
    global GMember

    catch { unset GMember }
    if { $wh == "GR" } {
	foreach ix $ixs { set GMember($ix) 1 }
    }
    GRsElsCollect $ixs $rec $wh
    set l [array names GMember]
    catch { unset GMember }
    return $l
}

proc GRsElsCollect {ixs rec wh} {
    # mark defined elements of type $wh (in $TYPES or LAP) in groups with
    #  given indices
    #  $rec is 1 if search is recursive
    # marked elements with index $i will have GMember($i) set
    global GRConts GMember

    foreach ix $ixs {
	foreach p $GRConts($ix) {
	    if { [lindex $p 0] == $wh } {
		foreach e [lindex $p 1] {
		    if { [set eix [IndexNamed $wh $e]] != -1 } {
			set GMember($eix) 1
		    }
		}
		if { ! $rec } { break }
	    }
	    if { $rec && [lindex $p 0] == "GR" } {
		set rixs [Apply [lindex $p 1] IndexNamed GR]
		while { [set i [lsearch -exact $rixs -1]] != -1 } {
		    set rixs [lreplace $rixs $i $i]
		}
		GRsElsCollect $rixs 1 $wh
	    }
	}
    }
    return
}

proc GRWPNames {conts} {
    # find names of WPs in given GR contents
    # return pair with index of WP-pair entry in $conts, followed by list
    #  of names, on failure the index is meaningless and the list is empty

    set names {} ; set ics 0
    foreach p $conts {
	if { [lindex $p 0] == "WP" } {
	    set names [lindex $p 1]
	    break
	}
	incr ics
    }
    return [list $ics $names]    
}

## renaming items

proc InitWPRenaming {} {
    # this proc must be called before any input operation!
    # initialize variables before an input operation (get, load, import)
    #  for use with procs AskForName and ReplaceWPName
    # returns 0 if another renaming operation is under way
    global ReplNames MESS

    if { $ReplNames(busy) } {
	GMMessage $MESS(busytrylater)
	return 0
    }
    array set ReplNames {busy 1 old {} new {} wps {} grs {} how ask}
    return 1
}

proc EndWPRenaming {} {
    # this proc must be called after any input operation that stored data!
    # build a group with renamed WPs as well as GRs in which they occur
    #  after an input operation (get, load, import)
    global ReplNames

    if { $ReplNames(old) != {} || $ReplNames(wps) != {} } {
	set nwps $ReplNames(new)
	foreach m $ReplNames(wps) { lappend nwps [lindex $m 1] }
	set lp [list [list WP [lsort -dictionary $nwps]]]
	if { $ReplNames(grs) != {} } {
	    set ns {}
	    foreach n $ReplNames(grs) {
		if { [lsearch -exact $ns $n] == -1 && \
			 [IndexNamed GR $n] != -1 } {
		    lappend ns $n
		}
	    }
	    lappend lp [list GR [lsort -dictionary $ns]]
	}
	CreateGRFor renres "" $lp
    }
    set ReplNames(busy) 0
    return
}

proc GetReplNameInGR {name id} {
    # get replacement for a WP name appearing in a GR
    #  $id is the GR name
    # return $name if there is no replacement, otherwise the one that
    #  was done last
    global ReplNames

    set chg 0
    if { [set ix [lsearch -exact $ReplNames(old) $name]] != -1 } {
	set name [lindex $ReplNames(new) $ix] ; incr chg
    }
    foreach t $ReplNames(wps) {
	if { [lindex $t 0] == $name } {
	    set name [lindex $t 1] ; incr chg
	    break
	}
    }
    if { $chg && [lindex $ReplNames(grs) 0] != $id } {
	set ReplNames(grs) [linsert $ReplNames(grs) 0 $id]
    }
    return $name
}

proc AskForName {name} {
    # obtain a replacement for a WP $name which is not valid by one of
    #  - checking if it was already replaced
    #  - letting the user write the new name
    #  - applying a renaming method selected by the user
    #  - generating an automatic replacement
    # proc InitWPRenaming must be called before the first call to this proc
    #  and proc EndWPRenaming must be called after the renaming operation
    #  is finished
    # create modal dialog for displaying message
    #  buttons: OK, Cancel
    #  binding: return to accept
    # return empty string on cancel
    global MYGPS RECNAMECHARS NAMELENGTH GMResAsk COLOUR EPOSX EPOSY MESS TXT \
	    ReplNames CMDLINE

    if { [set ix [lsearch -exact $ReplNames(old) $name]] != -1 } {
	return [lindex $ReplNames(new) $ix]
    }
    if { $ReplNames(how) == "methall" } {
	set nn [RenameMethApplyTo $name $ReplNames(method) Ignore]
	lappend ReplNames(old) $name
	lappend ReplNames(new) $nn
	return $nn
    }
    if { $ReplNames(how) == "genall" || $CMDLINE } {
	set nn [NewName WP $name]
	lappend ReplNames(old) $name
	lappend ReplNames(new) $nn
	return $nn
    }
    GMToplevel .askname change +$EPOSX+$EPOSY . \
        {WM_DELETE_WINDOW {set GMResAsk cnc}} \
        [list <Key-Return> {set GMResAsk ok}]

    frame .askname.fr -relief flat -borderwidth 5 -bg $COLOUR(confbg)
    label .askname.fr.title -text "!!!" -relief sunken
    message .askname.fr.text -aspect 1000 \
	    -text [format $MESS(replname) $name $NAMELENGTH \
	             $RECNAMECHARS($MYGPS,mess)]
    entry .askname.fr.name -width $NAMELENGTH
    TextBindings .askname.fr.name

    set fbs .askname.fr.bs
    frame $fbs -relief flat -borderwidth 0
    button $fbs.ok -text $TXT(ok) -command { set GMResAsk ok }
    foreach x {gen meth} t {generate renamethod} { 
	menubutton $fbs.$x -text $TXT($t) -relief raised -menu $fbs.$x.m
	menu $fbs.$x.m
    }
    $fbs.gen.m add command -label $TXT(forthisWP) \
	-command { set ReplNames(how) ask ; set GMResAsk gen }
    $fbs.gen.m add command -label $TXT(forall) \
	-command { set ReplNames(how) genall ; set GMResAsk gen }
    # 2 menus are needed as not all platforms support cascade commands
    foreach x {ask methall} t {forthisWP forall} {
	set mx $fbs.meth.m.$x
	$fbs.meth.m add cascade -label $TXT($t) -menu $mx
	menu $mx
	menu $mx.m -postcommand \
	    [list FillDefsMenu renamethod $mx.m [list AskForNameMethod $x]]
	$mx add cascade -label $TXT(use) -menu $mx.m
	$mx add command -label $TXT(define) \
	    -command [list AskForNameMethod define-$x {}]
    }
    button $fbs.cancel -text $TXT(cancel) \
	    -command { set GMResAsk cnc }
    pack $fbs.ok $fbs.gen $fbs.meth $fbs.cancel -side left -pady 5
    pack .askname.fr.title .askname.fr.text .askname.fr.name $fbs \
	    -side top -pady 5
    pack .askname.fr -side top
    update idletasks
    set gs [grab current]
    grab .askname
    RaiseWindow .askname
    while 1 {
	tkwait variable GMResAsk
	switch $GMResAsk {
	    ""  { }
	    ok {
		set res [string trim [.askname.fr.name get]]
		if { [CheckName Ignore $res] } {
		    if { [lsearch -exact $ReplNames(new) $res] != -1 || \
			     [IndexNamed WP $res] != -1 } {
			GMMessage $MESS(idinuse) ; continue
		    }
		    break
		}
		bell
	    }
	    gen {
		set res [NewName WP $name]
		break
	    }
	    meth {
		if { $ReplNames(method) == "" } {
		    set ReplNames(how) ask
		    continue
		}
		set res [RenameMethApplyTo $name $ReplNames(method) Ignore]
		break		    
	    }
	    cnc {
		set res "" ; break
	    }
	}
    }
    if { $res != "" } {
	lappend ReplNames(old) $name
	lappend ReplNames(new) $res
    }
    DestroyRGrabs .askname $gs
    update idletasks
    return $res
}

proc AskForNameMethod {how method args} {
    # a WP renaming method is to be applied
    #  $how in {ask, methall, define-ask, define-methall} indicates
    #   whether the choice is for this or all WPs and if the method is to
    #   be defined
    #  $method is the name of renaming method to use or empty meaning,
    #   unless the method is to be defined, that operation is to be cancelled
    #  $args not in use but is needed because of proc FillMenu
    # this proc only changes the global variables that force
    #  proc AskForName to do the intended actions
    global ReplNames GMResAsk

    if { [regsub {^define-} $how "" how] } {
	set method [Define renamethod]
    }
    set ReplNames(how) $how ; set ReplNames(method) $method
    # must be the last one
    set GMResAsk meth
    return
}

proc SamePosnDat {posndat1 posndat2} {
    # check whether two positions are the same
    #  $posndat_ is a list with lat, long (in DDD) and datum

    foreach "lat1 long1 dat1" $posndat1 { break }
    foreach "lat2 long2 dat2" $posndat2 { break }
    if { $posndat1 != $posndat2 } {
	foreach "lat2 long2" [ToDatum $lat2 $long2 $dat2 $dat1] { break }
    }
    return [expr $lat1 == $lat2 && $long1 == $long2]
}

proc ReplaceWPName {name posndat} {
    # return a replacement name for a WP being read in
    #  $name is the name to be replaced
    #  $posndat is list with lat, long and datum defining the WP
    #    position (possibly not in the WP datum)
    # use record of previous replacements
    #  $ReplNames(wps), a list of triples with old name, new name and
    #    position+datum (as $posndat)
    # this list must be initialized when starting a reading operation (get,
    #  load, import) (see proc InitWPRenaming), and is kept as a stack
    #  with the last replacement done as its head
    global ReplNames

    foreach t $ReplNames(wps) {
	if { [lindex $t 0] == $name && [SamePosnDat $posndat [lindex $t 2]] } {
	    return [lindex $t 1]
	}
    }
    set n [NewName WP $name]
    set ReplNames(wps) [linsert $ReplNames(wps) 0 [list $name $n $posndat]]
    return $n
}

proc SamePosn {ix data} {
    # check whether the WP with given index has the same position as the
    #  WP with given data even if the datums used are different
    # return either 1, or list with lat, long (in DDD), and datum
    #  for the position of 2nd WP (but in the datum of 1st)
    global WPPosn WPDatum DataIndex

    set ip $DataIndex(WPPosn)
    set id $DataIndex(WPDatum)
    set p [lindex $data $ip] ; set d [lindex $data $id]
    if { $WPDatum($ix) != $d } {
	set p [ToDatum [lindex $p 0] [lindex $p 1] $d $WPDatum($ix)]
    }
    if { [ComputeDist $p $WPPosn($ix) $WPDatum($ix)] < 0.003 } { return 1 }
    return [list [lindex $p 0] [lindex $p 1] $WPDatum($ix)]
}

proc AddToNB {nb txt} {
    # add $txt to remark $nb

    if { $nb != "" } {
	return "$nb\n$txt"
    }
    return $txt
}

proc AddOldNameToObs {wh data name} {
    # add old name to remark field of item of type $wh
    global TXT DataIndex

    set in $DataIndex(${wh}Obs)
    set nb [lindex $data $in]
    return [lreplace $data $in $in [AddToNB $nb "$TXT(oname): $name"]]
}

proc WPChangeNames {methname args} {
    # change names of items of type $wh (not LAP)
    #   $methname is the name of renaming method to use or empty for cancel
    #   $args not in use but is needed because of proc FillMenu
    # select the items then rename them
    global MESS TXT NAMELENGTH RENMETHS

    if { $methname == "" || \
	     [set ixs [ChooseItems WP many]] == "" } { return }
    RenameInternalWPs $methname [Apply $ixs NameOf WP]
    return
}

proc GMGRRenameWPs {w methname args} {
    # change names of selected WPs in group window $w
    #   $methname is the name of renaming method to use or empty for cancel
    #   $args not in use but is needed because of proc FillMenu
    # by construction WP names in a GR are all different
    global TXT NAMELENGTH

    if { $methname == "" } { return }
    if { [set names [GMGRCollectWPNames $w]] != {} && \
	     [set names [GMChooseFrom many "$TXT(select) $TXT(nameWP)" \
			     $NAMELENGTH $names $names]] != {} } {
	RenameInternalWPs $methname $names
    }
    return
}

proc RenameInternalWPs {methname names} {
    # apply a renaming method to WPs in the data-base or referred to
    #  in a GR
    #  $names is a list of the WP names
    #  $methname is method name
    # start/end a renaming operation by calling procs InitWPRenaming
    #  and EndWPRenaming, ensuring no other renaming takes place
    # use proc CheckName to verify the result of the method and
    #  if the result is not acceptable use proc NewName to get a
    #  suitable one
    # update data-base, map, and edit/show windows
    global WPName WPRoute WPObs WPDispl RTWPoints GRConts IndexOf \
	ReplNames TXT

    set descmethod [lindex [GetDefFields renamethod $methname method] 0]
    if { $names == {} || \
	     [set method [lindex $descmethod 1]]  == {} || \
	     [InitWPRenaming] == 0 } {
	return
    }
    set replold {} ; set replnew {}
    foreach name $names {
	if { [set nname [RenameMethApply $method $name \
			     $replnew Ignore]]  == "" || \
		 ! [CheckName Ignore $nname] } {
	    set nname [NewName WP $name]
	}
	lappend replold $name
	lappend replnew $nname
	if { [set ix [IndexNamed WP $name]] != -1 } {
	    # update the data-base
	    unset IndexOf(WP,$name)
	    set IndexOf(WP,$nname) $ix
	    set WPName($ix) $nname
	    set WPObs($ix) [AddToNB $WPObs($ix) "$TXT(oname): $name"]

	    # update the items list and edit/show window
	    ListDelete WP $ix ; ListAdd WP $ix
	    UpdateItemWindows WP $ix

	    # update map
	    if { $WPDispl($ix) } {
		MoveOnMap WP $ix $name 1 $nname
	    }
 	}
    }
    # update RTs containing renamed WPs
    set rtixs {}
    foreach ixrt [array names RTWPoints] {
	foreach "chg RTWPoints($ixrt)" \
		    [ListReplace $RTWPoints($ixrt) $replold $replnew] {}
	if { $chg } { lappend rtixs $ixrt }
    }	
    UpdateWPsInWindows RT $rtixs $replold $replnew

    # update GRs containing renamed WPs
    set grixs {}
    foreach grix [array names GRConts] {
	foreach {ics gwps} [GRWPNames $GRConts($grix)] {}
	if { $gwps != {} } {
	    foreach {chg gwps} [ListReplace $gwps $replold $replnew] {}
	    if { $chg } {
		set gwps [lsort -dictionary $gwps]
		set GRConts($grix) [lreplace $GRConts($grix) $ics $ics \
					[list WP $gwps]]
		lappend grixs $grix
	    }
	}
    }
    UpdateWPsInWindows GR $grixs $replold $replnew

    set ReplNames(old) $replold
    set ReplNames(new) $replnew
    EndWPRenaming
    return
}

proc RenameMethApplyTo {name methname errproc} {
    # apply a renaming method to WP with given $name
    #  $methname is method name
    # to be used in the context of a renaming operation started by
    #  calling proc InitWPRenaming and ended by calling proc EndWPRenaming
    # use proc CheckName to verify the result of the method and
    #  if the result is not acceptable use proc NewName to get a
    #  suitable one
    # return the new name
    global ReplNames

    set descmethod [lindex [GetDefFields renamethod $methname method] 0]
    if { [set method [lindex $descmethod 1]]  == {} || \
	     [set nname [RenameMethApply $method $name \
			     $ReplNames(new) $errproc]] == "" || \
	     ! [CheckName $errproc $nname] } {
	return [NewName WP $name]
    }
    return $nname
}

## storing data items just read in

proc StoreWP {ix name data todispl} {
    # store WP data just read in
    #  $todispl is true if the WP should be mapped
    # can only be called after a call to InitWPRenaming (use of ReplaceWPName)
    # return name of stored WP
    global WPRoute WPDispl EQNAMEDATA

    if { $ix != -1 } {
	if { $EQNAMEDATA == "ovwrt" || [set pd [SamePosn $ix $data]] == 1 } {
	    set olddispl $WPDispl($ix)
	    SetItem WP $ix $data
	    if { $todispl || $olddispl } {
		set WPDispl($ix) 1
		MoveOnMap WP $ix $name 0 $name
	    }
	    UpdateItemWindows WP $ix
	    return $name
	}
	# replace name
	set data [AddOldNameToObs WP $data $name]
	set name [ReplaceWPName $name $pd]
	set data [lreplace $data 0 0 $name]
    }
    set ix [CreateItem WP $data]
    if { $todispl } { PutMap WP $ix }
    return $name
}

proc StoreRT {ix id data wps todispl} {
    # store RT data just read in
    #  $todispl is true if the RT should be mapped
    global RTWPoints RTDispl

    if { $ix != -1 } {
	if { $RTDispl($ix) } {
	    UnMapRT $ix
	    set todispl 1
	}
	UnsetWPRoute $id $RTWPoints($ix)
	SetItem RT $ix $data
	set RTDispl($ix) $todispl
	UpdateItemWindows RT $ix
    } else {
	set ix [CreateItem RT $data]
    }
    if { $todispl } { PutMap RT $ix }
    SetWPRoute $id $wps
    return
}

proc StoreTR {ix id data todispl} {
    # store TR data just read in
    #  $todispl is true if the TR should be mapped
    global TRDispl

    if { $ix != -1 } {
	if { $TRDispl($ix) } {
	    UnMapTR $ix
	    set todispl 1
	}
	SetItem TR $ix $data
	set TRDispl($ix) $todispl
	UpdateItemWindows TR $ix
    } else {
	set ix [CreateItem TR $data]
    }
    if { $todispl } { PutMap TR $ix }
    return
}

proc StoreLN {ix id data todispl} {
    # store TR data just read in
    #  $todispl is true if the LN should be mapped
    global LNDispl

    if { $ix != -1 } {
	if { $LNDispl($ix) } {
	    UnMapLN $ix
	    set todispl 1
	}
	SetItem LN $ix $data
	set LNDispl($ix) $todispl
	UpdateItemWindows LN $ix
    } else {
	set ix [CreateItem LN $data]
    }
    if { $todispl } { PutMap LN $ix }
    return
}

proc StoreLAP {ix name data} {
    # store LAP data just read in assumed to be a new lap

    if { $ix != -1 } {
	SetItem LAP $ix $data
	UpdateItemWindows LAP $ix
    } else { set ix [CreateItem LAP $data] }
    return $ix
}

proc StoreGR {ix id data todispl} {
    # store GR data just read in
    #  $todispl is true if the GR should be mapped
    global GRDispl

    if { $ix != -1 } {
	if { $GRDispl($ix) } {
	    UnMapGR $ix
	    set todispl 1
	}
	SetItem GR $ix $data
	set GRDispl($ix) $todispl
	UpdateItemWindows GR $ix
    } else {
	set ix [CreateItem GR $data]
    }
    if { $todispl } { PutMap GR $ix }
    return
}
