#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2008 Miguel Filgueiras mig@ncc.up.pt Universidade do Porto
#
#    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 2 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:  3 September 2008
#
# 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 (main.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 (main.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 (main.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 (main.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 (main.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 (main.tcl) for description of data arrays
    global Storage IndexOf

    foreach n [array names IndexOf $wh,*] {
	unset IndexOf($n)
    }
    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 } {
		set WPRoute($ix) [linsert $WPRoute($ix) 0 $rt]
		set WPRoute($ix) [lsort $WPRoute($ix)]
	    }
	}
    }
    return
}

proc UnsetWPRoute {rt wps} {
    # delete RT name $rt in list of RTs of each known WP
    #  whose name belongs to $wps
    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 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
}

## 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
    global ReplacedNames ReplacedWPs GenerateAll ReplRTs ReplGRs

    set ReplacedNames "" ; set ReplacedWPs ""
    set GenerateAll 0
    set ReplRTs "" ; set ReplGRs ""
    return
}

proc EndWPRenaming {} {
    # this proc must be called after any input operation that stored data!
    # build a group with renamed WPs and RTs or GRs that have ambiguous
    #  WPs, after an input operation (get, load, import)
    global ReplacedNames ReplacedWPs ReplRTs ReplGRs

    if { $ReplacedNames == "" && $ReplacedWPs == "" } { return }
    set nwps ""
    foreach m [concat $ReplacedNames $ReplacedWPs] {
	lappend nwps [lindex $m 1]
    }
    set lp [list [list WP [lsort -dictionary $nwps]]]
    foreach wh "RT GR" {
	if { [set lns [set Repl${wh}s]] != "" } {
	    set ns ""
	    foreach n $lns {
		if { [lsearch -exact $ns $n] == -1 && \
			[IndexNamed $wh $n] != -1 } {
		    lappend ns $n
		}
	    }
	    lappend lp [list $wh [lsort -dictionary $ns]]
	}
    }
    CreateGRFor renres "" $lp
    return
}

proc GetReplacedName {name wh id} {
    # get replacement for an isolated WP name belonging to item $id
    #  of type $wh (in {RT, GR})
    # return $name if there is no replacement, otherwise the one that
    #  was done last
    global ReplacedNames ReplacedWPs ReplRTs ReplGRs

    set chg 0
    foreach p $ReplacedNames {
	if { [lindex $p 0] == $name } {
	    set name [lindex $p 1] ; incr chg
	    break
	}
    }
    foreach t $ReplacedWPs {
	if { [lindex $t 0] == $name } {
	    set name [lindex $t 1] ; incr chg
	    break
	}
    }
    if { $chg && [lindex [set Repl${wh}s] 0] != $id } {
	set Repl${wh}s [linsert [set Repl${wh}s] 0 $id]
    }
    return $name
}    

proc AskForName {name} {
    # ask for replacement of WP $name which is not valid
    #  but check first that there is not a replacement in list $ReplacedNames
    # on success add pair $name and new name to ReplacedNames
    # callers of this proc should initialize ReplacedNames to empty and
    #  GenerateAll to 0 before first call (see proc InitWPRenaming)
    # names already in use are not accepted!
    # create modal dialog for displaying message
    #  buttons: OK, Cancel
    #  binding: return to accept
    global MYGPS RECNAMECHARS NAMELENGTH GMResAsk COLOUR EPOSX EPOSY MESS TXT \
	    ReplacedNames GenerateAll CMDLINE

    foreach p $ReplacedNames {
	if { $name == [lindex $p 0] } { return [lindex $p 1] }
    }
    if { $GenerateAll || $CMDLINE } {
	set nn [NewName WP $name]
	lappend ReplacedNames [list $name $nn]
	return $nn
    }
    toplevel .askname
    wm protocol .askname WM_DELETE_WINDOW { set GMResAsk cnc }
    wm title .askname "$TXT(change)/GPS Manager"
    wm transient .askname .
    wm geometry .askname +$EPOSX+$EPOSY
    bind .askname <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
    frame .askname.fr.bs -relief flat -borderwidth 0
    button .askname.fr.bs.ok -text Ok -command { set GMResAsk ok }
    button .askname.fr.bs.gen -text $TXT(generate) \
	    -command { set GMResAsk gen }
    button .askname.fr.bs.genall -text $TXT(generateall) \
	    -command { set GenerateAll 1 ; set GMResAsk gen }
    button .askname.fr.bs.cancel -text $TXT(cancel) \
	    -command { set GMResAsk cnc }
    pack .askname.fr.bs.ok .askname.fr.bs.gen .askname.fr.bs.genall \
	    .askname.fr.bs.cancel -side left -pady 5
    pack .askname.fr.title .askname.fr.text .askname.fr.name .askname.fr.bs \
	    -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 nn [string trim [.askname.fr.name get]]
		if { [CheckName Ignore $nn] } {
		    if { [IndexNamed WP $nn] != -1 } {
			GMMessage $MESS(idinuse) ; continue
		    }
		    lappend ReplacedNames [list $name $nn]
		    set res $nn
		    break
		}
		bell
	    }
	    gen {
		set nn [NewName WP $name]
		lappend ReplacedNames [list $name $nn]
		set res $nn
		break
	    }
	    cnc {
		set res "" ; break
	    }
	}
    }
    DestroyRGrabs .askname $gs
    update idletasks
    return $res

}

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
    #  $ReplacedWPs, 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 ReplacedWPs

    foreach t $ReplacedWPs {
	if { [lindex $t 0] == $name && [SamePosnDat $posndat [lindex $t 2]] } {
	    return [lindex $t 1]
	}
    }
    set n [NewName WP $name]
    set ReplacedWPs [linsert $ReplacedWPs 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 [lreplace $p 2 end $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"]]
}

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