#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
#  Copyright (c) 2004 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: gendials.tcl
#  Last change:  4 July 2004
#
# Includes contributions by
#  - Brian Baulch (baulchb@onthenet.com.au) marked "BSB contribution"
#  - Stefan Heinen (stefan.heinen@djh-freeweb.de) marked "SH contribution"
#

  # modal dialogs

proc GMMessage {mess args} {
    # create modal dialog for displaying message
    #  if $args=="wait"  return only when user acknowledges message
    #      except in command-line mode or if using the slow op window
    # single button: OK; binding: return
    global COLOUR EPOSX EPOSY TXT UNIX CMDLINE

    if { $CMDLINE} {
	puts stderr $mess
	flush stderr
	return
    }
    if { [winfo exists .slowop] } {
	SlowOpMessage $mess
	return
    }
    # this avoids bugs but may create havoc with grabs
    destroy .mess
    set gs [grab current]
    toplevel .mess
    if { ! $UNIX } {
	# SH contribution
	focus .mess
    }

    wm protocol .mess WM_DELETE_WINDOW [list DestroyRGrabs .mess $gs]
    wm title .mess "$TXT(message)/GPS Manager"
    wm transient .mess .
    wm geometry .mess +$EPOSX+$EPOSY
    bind .mess <Key-Return> [list DestroyRGrabs .mess $gs]

    frame .mess.fr -borderwidth 5 -bg $COLOUR(messbg)
    label .mess.fr.title -text "!!!" -relief sunken
    label .mess.fr.text -text $mess
    button .mess.fr.ok -text Ok -command [list DestroyRGrabs .mess $gs]
    pack .mess.fr -side top
    pack .mess.fr.title .mess.fr.text .mess.fr.ok -side top -pady 5
    RaiseWindow .mess
    update idletasks
    grab .mess
    if { $args == "wait" } {
	while 1 {
	    after 500
	    update
	    if { ! [winfo exists .mess] } { return }
	}
    }
    return
}

proc GMConfirm {mess} {
    # create modal dialog for asking for confirmation
    #  buttons: OK, Cancel; bindings: return, delete
    global GMResConf COLOUR EPOSX EPOSY TXT CMDLINE

    if { $CMDLINE } { return 1 }
    destroy .messres
    toplevel .messres
    wm protocol .messres WM_DELETE_WINDOW { set GMResConf 0 }
    wm title .messres "$TXT(message)/GPS Manager"
    wm transient .messres .
    wm geometry .messres +$EPOSX+$EPOSY
    bind .messres <Key-Return> { set GMResConf 1 }
    bind .messres <Key-Delete> { set GMResConf 0 }

    frame .messres.fr -borderwidth 5 -bg $COLOUR(confbg)
    label .messres.fr.title -text "???" -relief sunken
    label .messres.fr.text -text $mess
    frame .messres.fr.bs
    button .messres.fr.bs.ok -text Ok -command { set GMResConf 1 }
    button .messres.fr.bs.cancel -text $TXT(no) -command { set GMResConf 0 }
    pack .messres.fr.bs.ok .messres.fr.bs.cancel -side left -pady 5
    pack .messres.fr.title .messres.fr.text .messres.fr.bs -side top -pady 5
    pack .messres.fr -side top
    update idletasks
    set gs [grab current]
    grab .messres
    RaiseWindow .messres
    tkwait variable GMResConf
    DestroyRGrabs .messres $gs
    update idletasks
    return $GMResConf
}

proc GMSelect {mess blist vlist} {
    # create modal dialog for selecting one element from list $blist
    #  with associated return values in $vlist
    # buttons are created for each list element
    #  bindings: return for first, delete for last element
    # (see proc GMChooseFrom for selection using a listbox)
    global GMResSel COLOUR EPOSX EPOSY TXT

    # assumes first and last elements of vlist are return values for
    #  Return and Delete keys, respectively

    destroy .messres
    toplevel .messres
    wm protocol .messres WM_DELETE_WINDOW "set GMResSel [lindex $vlist 0]"
    wm title .messres "$TXT(selection)/GPS Manager"
    wm transient .messres .
    wm geometry .messres +$EPOSX+$EPOSY
    bind .messres <Key-Return> "set GMResSel [lindex $vlist 0]"
    bind .messres <Key-Delete> "set GMResSel [lindex $vlist end]"

    frame .messres.fr -borderwidth 5 -bg $COLOUR(selbg)
    label .messres.fr.title -text "???" -relief sunken
    label .messres.fr.text -text $mess
    frame .messres.fr.frsel
    foreach e $blist v $vlist {
	button .messres.fr.frsel.b$e -text $e -command "set GMResSel $v"
	pack .messres.fr.frsel.b$e -side left
    }
    pack .messres.fr -side top
    pack .messres.fr.title .messres.fr.text .messres.fr.frsel -side top -pady 5
    set gs [grab current]
    update idletasks
    grab .messres
    RaiseWindow .messres
    tkwait variable GMResSel
    DestroyRGrabs .messres $gs
    update idletasks
    return $GMResSel
}

proc GMChooseFrom {how mess wd blist vlist args} {
    # create modal dialog for selecting elements from list $blist
    #  with associated return values in $vlist
    #  $how in {single, many, many_0} defines number of elements that can
    #   be selected, many_0 meaning that 0 is an alternative
    # a listbox is used with width $wd
    #  $args if present is a pair with $vars $descs, suitable for use
    #   with proc GMSetupParams, so that parameters may be selected
    # buttons: OK, Cancel
    # bindings: return for commit, extended select mode on listbox,
    #  make visible by initial char on listbox
    # return list with selected values upon normal termination, and
    #  an empty list or -1 if $how==many_0
    # (see also proc GMSelect for selection of only one element with buttons)
    global GMResult DPOSX DPOSY COLOUR TXT UNIX

    set w .gmchoosefr
    if { [winfo exists $w] } { Raise $w ; bell ; return }

    toplevel $w
    wm protocol $w WM_DELETE_WINDOW { set GMResult cnc }
    wm title $w "$mess/GPS Manager"
    wm transient $w .
    wm geometry $w +$DPOSX+$DPOSY
    bind $w <Key-Return> { set GMResult ok }

    frame $w.fr -borderwidth 5 -bg $COLOUR(dialbg)
    label $w.fr.text -text $mess

    # adjust list height according to number of parameters
    set lh [expr 15-[llength [lindex $args 0]]]
    if { [set ll [llength $blist]] > $lh } {
	set ll $lh
    }
    frame $w.fr.frbx
    if { $how == "single" } {
	set mode single
    } else { set mode extended }
    listbox $w.fr.frbx.bx -height $ll -width $wd -relief flat \
	    -selectmode $mode -yscrollcommand "$w.fr.frbx.bscr set" \
	    -exportselection 0
    # SH contribution: no such bindings in non-unix systems
    if { $UNIX } {
	bind $w.fr.frbx.bx <Enter> { focus %W }
	bind $w.fr.frbx.bx <Leave> "focus $w.fr.frbx"
    }
    bind $w.fr.frbx.bx <Key> { ScrollListIndex %W %A }
    scrollbar $w.fr.frbx.bscr -command "$w.fr.frbx.bx yview"
    foreach i $blist { $w.fr.frbx.bx insert end $i }
    if { $ll == 1 } { $w.fr.frbx.bx selection set 0 }

    if { $args != "" } {
	set opts 1
	frame $w.fr.fopt
	foreach "menus es" \
	    [GMSetupParams $w.fr.fopt [lindex $args 0] [lindex $args 1]] {}
    } else { set opts 0 ; set menus 0 }

    frame $w.fr.frbt
    button $w.fr.frbt.ok -text Ok -command { set GMResult ok }
    button $w.fr.frbt.cnc -text $TXT(cancel) -command { set GMResult cnc }

    pack $w.fr.frbt.ok $w.fr.frbt.cnc -side left -pady 5
    pack $w.fr.frbx.bx $w.fr.frbx.bscr -side left -fill y
    if { $opts } {
	pack $w.fr.text $w.fr.frbx $w.fr.fopt $w.fr.frbt -side top -pady 5
    } else {
	pack $w.fr.text $w.fr.frbx $w.fr.frbt -side top -pady 5
    }
    pack $w.fr

    update idletasks
    set gs [grab current]
    grab $w
    if { $menus } {
	Raise .fdlg
    } else { RaiseWindow .fdlg }
    while 1 {
	tkwait variable GMResult

	switch $GMResult {
	    ""  { }
	    cnc { 
		if { $how == "many_0" } { set res -1 } else { set res "" }
		break
	    }
	    ok {
		set ss [$w.fr.frbx.bx curselection]
		if { $ss == "" && $how != "many_0" } {
		    bell
		    continue
		}
		set res ""
		foreach i $ss {
		    lappend res [lindex $vlist $i]
		}
		if { $opts } {
		    GMUseEntries $w.fr.fopt $es
		}
		break
	    }
	}
    }
    DestroyRGrabs $w $gs
    update idletasks
    return $res
}

proc GMChooseParams {mess vars descs} {
    # create modal dialog for choosing parameters
    #  $vars and $descs are as described in GMSetupParams
    #  buttons: OK, Cancel
    #  bindings: return for commit
    # return 0 if cancelled
    global GMResult DPOSX DPOSY COLOUR TXT

    set w .gmchooseprsr
    if { [winfo exists $w] } { Raise $w ; bell ; return }

    toplevel $w
    wm protocol $w WM_DELETE_WINDOW { set GMResult cnc }
    wm title $w "$mess/GPS Manager"
    wm transient $w .
    wm geometry $w +$DPOSX+$DPOSY
    bind $w <Key-Return> { set GMResult ok }

    frame $w.fr -borderwidth 5 -bg $COLOUR(dialbg)
    label $w.fr.text -text $mess

    frame $w.fr.fopt
    foreach "menus es" [GMSetupParams $w.fr.fopt $vars $descs] {}

    frame $w.fr.frbt
    button $w.fr.frbt.ok -text Ok -command { set GMResult ok }
    button $w.fr.frbt.cnc -text $TXT(cancel) -command { set GMResult cnc }

    pack $w.fr.frbt.ok $w.fr.frbt.cnc -side left -pady 5
    pack $w.fr.text $w.fr.fopt $w.fr.frbt -side top -pady 5
    pack $w.fr

    update idletasks
    set gs [grab current]
    grab $w
    if { $menus } {
	Raise .fdlg
    } else { RaiseWindow .fdlg }
    while 1 {
	tkwait variable GMResult

	switch $GMResult {
	    ""  { }
	    cnc { 
		set res 0 ; break
	    }
	    ok {
		GMUseEntries $w.fr.fopt $es
		set res 1 ; break
	    }
	}
    }
    DestroyRGrabs $w $gs
    update idletasks
    return $res
}

##### information window

proc DisplayInfo {mess} {
    # display information on a dialog
    # the dialog is created if it not exists, otherwise the message
    #  will be added to it
    global CMDLINE COLOUR EPOSX EPOSY TXT

    if { $CMDLINE } { return }

    set frt .gminfo.fr.frt
    if { ! [winfo exists .gminfo] } {
	toplevel .gminfo
	wm protocol .gminfo WM_DELETE_WINDOW { destroy .gminfo }
	wm title .gminfo "$TXT(info)/GPS Manager"
	wm transient .gminfo
	wm geometry .gminfo +$EPOSX+$EPOSY

	frame .gminfo.fr -borderwidth 5 -bg $COLOUR(messbg)
	label .gminfo.fr.title -text $TXT(info) -relief sunken

	frame $frt -relief flat -borderwidth 0
	text $frt.txt -width 70 -font fixed -wrap word -exportselection 1 \
	    -yscrollcommand "$frt.tscrl set"
	bind $frt.txt <space> "$frt.txt yview scroll 1 pages ; break"
	bind $frt.txt <Key-Delete> "$frt.txt yview scroll -1 pages ; break"
	bind $frt.txt <Any-Key> break
	bind $frt.txt <Button-2> break
	scrollbar $frt.tscrl -command "$frt.txt yview"

	set frb .gminfo.fr.frb
	frame $frb -relief flat -borderwidth 0
	button $frb.ok -text $TXT(ok) -command { destroy .gminfo }

	grid config $frt.txt -column 0 -row 1 -sticky nesw
	grid config $frt.tscrl -column 1 -row 1 -sticky nesw
	grid config $frb.ok -column 1 -row 0
	pack .gminfo.fr.title $frt $frb -side top -pady 5
	pack .gminfo.fr
    }
    $frt.txt insert end "$mess\n"
    $frt.txt see end
    update idletasks
    return
}

### dialog window for controlling slow operations

proc SlowOpWindow {mess} {
    # create dialog for controlling slow operation
    # to be called by application before entering the main loop of the slow
    #  operation
    # within the loop there should be calls to proc SlowOpAborted that
    #  returns 1 if the operation is to be aborted, or updates the interface
    #  and returns 0  otherwise
    # any call within the loop to GMMessage will be diverted to this dialog
    # after the main loop there should be a call to proc SlowOpFinish
    global SlowOp COLOUR MAPCOLOUR EPOSX EPOSY TXT CMDLINE USESLOWOPWINDOW

    if { $CMDLINE || ! $USESLOWOPWINDOW } { return }

    if { [winfo exists .slowop] } {
	.slowop.fr.title configure -text $mess
	return
    }

    array set SlowOp [list \
	    aborting 0  status "$TXT(working)..."  grabs [grab current]]
    toplevel .slowop
    wm protocol .slowop WM_DELETE_WINDOW { set SlowOp(aborting) 1 }
    wm title .slowop "$TXT(opinprogr)/GPS Manager"
    wm transient .slowop
    wm geometry .slowop +$EPOSX+$EPOSY

    frame .slowop.fr -borderwidth 5 -bg $COLOUR(messbg)
    label .slowop.fr.title -text $mess -relief sunken

    set frs .slowop.fr.frs
    frame $frs -relief flat -borderwidth 0
    label $frs.st -textvariable SlowOp(status) -fg $MAPCOLOUR(trvwrnimportant)
    checkbutton $frs.light -disabledforeground $COLOUR(check) -state disabled

    set frt .slowop.fr.frt
    frame $frt -relief flat -borderwidth 0
    text $frt.txt -width 50 -font fixed -wrap word \
	    -yscrollcommand "$frt.tscrl set"
    bind $frt.txt <space> "$frt.txt yview scroll 1 pages ; break"
    bind $frt.txt <Key-Delete> "$frt.txt yview scroll -1 pages ; break"
    bind $frt.txt <Any-Key> break
    scrollbar $frt.tscrl -command "$frt.txt yview"

    set frb .slowop.fr.frb
    frame $frb -relief flat -borderwidth 0
    button $frb.abort -text $TXT(abort) -command SlowOpAbort
    button $frb.ok -text $TXT(ok) -state disabled \
	    -command [list DestroyRGrabs .slowop $SlowOp(grabs)]

    pack $frs.st $frs.light -side left
    grid config $frt.txt -column 0 -row 1 -sticky nesw
    grid config $frt.tscrl -column 1 -row 1 -sticky nesw
    grid config $frb.abort -column 0 -row 0
    grid config $frb.ok -column 1 -row 0
    pack .slowop.fr.title $frs $frt $frb -side top -pady 5
    pack .slowop.fr
    update idletasks
    grab .slowop
    RaiseWindow .slowop
    return
}

proc SlowOpAbort {} {
    # the user aborted the operation
    # not to be called directly from the application
    global SlowOp TXT

    set SlowOp(aborting) 1
    set SlowOp(status) $TXT(aborted)
    return
}

proc SlowOpMessage {mess} {
    # show message in slow operation dialog window
    # not to be called directly from the application

    set txt .slowop.fr.frt.txt
    $txt insert end "$mess\n"
    $txt see end
    update idletasks
    return
}    

proc SlowOpFinish {mess} {
    # to be called by application when the operation ends (either normally
    #  or not)
    #  $mess will be displayed if not empty
    # the dialog window will be closed silently if there were no messages,
    #  otherwise the Ok button is activated and the user must acknowledge it
    global SlowOp TXT

    if { ! [winfo exists .slowop] } {
	if { $mess != "" } { GMMessage $mess }
	return
    }
    if { ! $SlowOp(aborting) } { set SlowOp(status) $TXT(errwarn) }
    set SlowOp(aborting) 0
    if { $mess != "" } { SlowOpMessage $mess }
    set txt .slowop.fr.frt.txt
    if { [$txt index end] == 2.0 } {
	DestroyRGrabs .slowop $SlowOp(grabs)
	return
    }
    set frb .slowop.fr.frb
    foreach b "abort ok" st "disabled normal" {
	$frb.$b configure -state $st
    }
    return
}

proc SlowOpAborted {} {
    # to be called by the application to test if the operation was aborted
    # if not a call to update is made to ensure that the window is usable
    # return 1 if yes
    global SlowOp TXT

    if { ! [winfo exists .slowop] } { return 0 }
    if { $SlowOp(aborting) } {
	set SlowOp(status) $TXT(aborted)
	return 1
    }
    set frs .slowop.fr.frs
    $frs.light toggle
    update
    return 0
}

### opening files

proc GMOpenFile {act wh mode} {
    # create modal dialog for selecting and opening a file
    #  $act is string describing the action to do on the file
    #  $wh in $FileTypes (see proc GMStart, main.tcl)
    #  $mode in {r, w}
    #  buttons: OK, Cancel
    #  binding: return and double-left for commit, left-click for select
    global GMResult COLOUR DPOSX DPOSY LISTHEIGHT File FileTypes TXT MESS \
	    UNIX

    toplevel .fdlg
    wm protocol .fdlg WM_DELETE_WINDOW { set GMResult cnc }
    wm title .fdlg "$TXT(file)/GPS Manager"
    wm transient .fdlg .
    wm geometry .fdlg +$DPOSX+$DPOSY
    bind .fdlg <Key-Return> { set GMResult ok }

    frame .fdlg.fr -borderwidth 5 -bg $COLOUR(selbg)
    label .fdlg.fr.title -text [format $MESS(fileact) $act $TXT(name$wh)] \
	    -relief sunken
    if { ! $UNIX } {
	menubutton .fdlg.fr.vols -text $TXT(volume) -menu .fdlg.fr.vols.m
	menu .fdlg.fr.vols.m
	bind .fdlg.fr.vols <Button-1> {
	    FillMenuExec .fdlg.fr.vols.m {ChangeVolume .fdlg} file volume
	}
    }
    entry .fdlg.fr.wdir -width 30
    ShowTEdit .fdlg.fr.wdir [pwd] 0

    frame .fdlg.fr.frbx
    listbox .fdlg.fr.frbx.box -height $LISTHEIGHT -width 30 \
	    -yscrollcommand ".fdlg.fr.frbx.bscr set" \
 	    -selectmode single -exportselection 1
    bind .fdlg.fr.frbx.box <Double-1> {
            global GMResult
            set GMResult [%W nearest %y]
    }
    bind .fdlg.fr.frbx.box <Button-1> {
	.fdlg.fr.fn delete 0 end
	.fdlg.fr.fn insert 0 [%W get [%W nearest %y]]
    }
    scrollbar .fdlg.fr.frbx.bscr -command ".fdlg.fr.frbx.box yview"
    FillDir .fdlg.fr.frbx.box

    entry .fdlg.fr.fn -width 30
    .fdlg.fr.fn insert 0 $File($wh)
    TextBindings .fdlg.fr.fn
    
    frame .fdlg.fr.bs
    button .fdlg.fr.bs.ok -text Ok -command { set GMResult ok }
    button .fdlg.fr.bs.cnc -text $TXT(cancel) \
	    -command { set GMResult cnc }

    pack .fdlg.fr.bs.ok .fdlg.fr.bs.cnc -side left -pady 5
    pack .fdlg.fr.frbx.box .fdlg.fr.frbx.bscr -side left -fill y
    if { $UNIX } {
	pack .fdlg.fr.title .fdlg.fr.wdir .fdlg.fr.frbx .fdlg.fr.fn \
		.fdlg.fr.bs -side top -pady 5
    } else {
	pack .fdlg.fr.title .fdlg.fr.vols .fdlg.fr.wdir .fdlg.fr.frbx \
		.fdlg.fr.fn .fdlg.fr.bs -side top -pady 5
    }
    pack .fdlg.fr -side top

    update idletasks
    set gs [grab current]
    grab .fdlg
    RaiseWindow .fdlg
    while 1 {
	tkwait variable GMResult

	switch $GMResult {
	    ""  { }
	    cnc { 
		set res ".."
		break
	    }
	    ok {
		set fn [.fdlg.fr.fn get]
		set f [GMCheckFile open $fn $mode]
		if { $f != ".." } {
		    set File($wh) $fn
		    set res $f
		    break
		}
	    }
	    0 {
		cd ..
		ShowTEdit .fdlg.fr.wdir [pwd] 0
		.fdlg.fr.frbx.box delete 0 end ; FillDir .fdlg.fr.frbx.box
		.fdlg.fr.fn delete 0 end
		foreach i $FileTypes { set File($i) "" }
	    }
	    default {
		set fn [.fdlg.fr.frbx.box get $GMResult]
		set f [GMCheckFile open $fn $mode]
		if { $f != ".." } {
		    set File($wh) $fn
		    set res $f
		    break
		}
	    }
	}
    }
    DestroyRGrabs .fdlg $gs
    update idletasks
    return $res
}

proc GMOpenFileParms {act wh mode vars vals} {
    # create modal dialog for selecting and opening a file and parameters
    # see arguments of proc GMGetFileName
    global File

    set fname [GMGetFileName $act $wh $mode $vars $vals]
    if { $fname == ".." } { return ".." }
    return [open $fname $mode]
}

proc GMGetFileName {act wh mode vars vals} {
    # create modal dialog for selecting a file name and parameters
    #  $act is string describing the action to do on the file
    #  $wh in $FileTypes (see proc GMStart, main.tcl)
    #  $mode in {r, w}
    #  $vars is list of (global) vars to set
    #  $vals is associated list of value descriptions (see proc GMSetupParams)
    #  buttons: OK, Cancel
    #  binding: return and double-left for commit, left-click for select
    global GMResult COLOUR DPOSX DPOSY LISTHEIGHT File FileTypes TXT MESS \
	    UNIX

    toplevel .fdlg
    wm protocol .fdlg WM_DELETE_WINDOW { set GMResult cnc }
    wm title .fdlg "$TXT(file)/GPS Manager"
    wm transient .fdlg .
    wm geometry .fdlg +$DPOSX+$DPOSY
    bind .fdlg <Key-Return> { set GMResult ok }

    frame .fdlg.fr -borderwidth 5 -bg $COLOUR(selbg)
    label .fdlg.fr.title -text [format $MESS(fileact) $act $TXT(name$wh)] \
	    -relief sunken
    if { ! $UNIX } {
	menubutton .fdlg.fr.vols -text $TXT(volume) -menu .fdlg.fr.vols.m
	menu .fdlg.fr.vols.m
	bind .fdlg.fr.vols <Button-1> {
	    FillMenuExec .fdlg.fr.vols.m {ChangeVolume .fdlg} file volume
	}
    }
    entry .fdlg.fr.wdir -width 30
    ShowTEdit .fdlg.fr.wdir [pwd] 0

    # adjust list height according to number of parameters
    set lh [expr $LISTHEIGHT-[llength $vars]]
    frame .fdlg.fr.frbx
    listbox .fdlg.fr.frbx.box -height $lh -width 30 \
	    -yscrollcommand ".fdlg.fr.frbx.bscr set" \
 	    -selectmode single -exportselection 1
    bind .fdlg.fr.frbx.box <Double-1> {
            global GMResult
            set GMResult [%W nearest %y]
    }
    bind .fdlg.fr.frbx.box <Button-1> {
	.fdlg.fr.fn delete 0 end
	.fdlg.fr.fn insert 0 [%W get [%W nearest %y]]
    }
    scrollbar .fdlg.fr.frbx.bscr -command ".fdlg.fr.frbx.box yview"
    FillDir .fdlg.fr.frbx.box
    # BSB contribution: wheelmouse scrolling
    Mscroll .fdlg.fr.frbx.box

    entry .fdlg.fr.fn -width 30
    .fdlg.fr.fn insert 0 $File($wh)
    TextBindings .fdlg.fr.fn

    frame .fdlg.fr.fopt
    foreach "menus es" [GMSetupParams .fdlg.fr.fopt $vars $vals] {}

    frame .fdlg.fr.bs
    button .fdlg.fr.bs.ok -text Ok -command { set GMResult ok }
    button .fdlg.fr.bs.cnc -text $TXT(cancel) \
	-command { set GMResult cnc }

    pack .fdlg.fr.bs.ok .fdlg.fr.bs.cnc -side left -pady 5
    pack .fdlg.fr.frbx.box .fdlg.fr.frbx.bscr -side left -fill y
    if { $UNIX } {
	pack .fdlg.fr.title .fdlg.fr.wdir .fdlg.fr.frbx .fdlg.fr.fn \
		.fdlg.fr.fopt .fdlg.fr.bs -side top -pady 5
    } else {
	pack .fdlg.fr.title .fdlg.fr.vols .fdlg.fr.wdir .fdlg.fr.frbx \
		.fdlg.fr.fn .fdlg.fr.fopt .fdlg.fr.bs -side top -pady 5
    }
    pack .fdlg.fr -side top

    update idletasks
    set gs [grab current]
    grab .fdlg
    if { $menus } {
	Raise .fdlg
    } else { RaiseWindow .fdlg }
    while 1 {
	tkwait variable GMResult

	switch $GMResult {
	    ""  { }
	    cnc { 
		set res ".." ; break
	    }
	    ok {
		set fn [.fdlg.fr.fn get]
		set f [GMCheckFile check $fn $mode]
		if { $f != ".." } {
		    set File($wh) $fn
		    GMUseEntries .fdlg.fr.fopt $es
		    set res $fn
		    break
		}
	    }
	    0 {
		cd ..
		ShowTEdit .fdlg.fr.wdir [pwd] 0
		.fdlg.fr.frbx.box delete 0 end ; FillDir .fdlg.fr.frbx.box
		.fdlg.fr.fn delete 0 end
		foreach i $FileTypes { set File($i) "" }
	        }
	    default {
		set fn [.fdlg.fr.frbx.box get $GMResult]
		set f [GMCheckFile check $fn $mode]
		if { $f != ".." } {
		    set File($wh) $fn
		    set res $fn
		    break
		}
	    }
	}
    }
    DestroyRGrabs .fdlg $gs
    update idletasks
    return $res
}

proc GMCheckFile {how f mode} {
    # check name of file $f and if ok either open it and return file descriptor
    #  or return file name; otherwise return ".."
    #  $how in {open check}
    #  $mode in {r, w}
    global PERMS File FileTypes TXT MESS

    if { $f == "" } { bell ; return ".." }
    if { [file isdirectory $f] } {
	if { [file executable $f] } {
	    cd $f
	    ShowTEdit .fdlg.fr.wdir [pwd] 0
	    .fdlg.fr.frbx.box delete 0 end
	    FillDir .fdlg.fr.frbx.box
	    .fdlg.fr.fn delete 0 end
	    foreach i $FileTypes { set File($i) "" }
	} else {
	    bell
	}
    } elseif { $mode == "r" } {
	if { [file readable $f] } {
	    switch $how {
		open { return [open $f r] }
		check { return $f }
	    }
	} else {
	    bell
	}
    } elseif { [file exists $f] } {
	if { [file writable $f] } {
	    set m [GMSelect $MESS(filexists) \
		    [list $TXT(ovwrt) $TXT(app) $TXT(cancel)] "w a 0"]
	    if { $m != 0 } {
		switch $how {
		    open { return [open $f $m $PERMS] }
		    check { return $f }
		}
	    }
	} else {
	    bell
	}
    } elseif { [file writable [pwd]] } {
	switch $how {
	    open { return [open $f $mode $PERMS] }
	    check { return $f }
	}
    } else {
	bell
    }
    return ".."
}

proc ChangeVolume {w vol} {
    # file volume has changed $vol in file-selection dialog $w
    global FileTypes

    if { ! [file isdirectory $vol] } { bell ; return }
    cd $vol
    ShowTEdit .fdlg.fr.wdir [pwd] 0
    .fdlg.fr.frbx.box delete 0 end ; FillDir .fdlg.fr.frbx.box
    .fdlg.fr.fn delete 0 end
    foreach i $FileTypes { set File($i) "" }
    return
}

### utilities for dealing with parameters in a dialog

proc GMSetupParams {w vars descs} {
    # set-up widgets for setting parameters in a dialog
    #  $w is window parent
    #  $vars is list of (global) vars to set; they must have a value
    #    except those associated to entries which will be initialized to ""
    #  $descs is associated list of value descriptions as:
    #      @TEXT   checkbutton with label TEXT, values 0 1
    #      =TEXT   entry with label TEXT
    #      !TEXT=MENUPROC/ARGS menubutton with label TEXT and menu filled by
    #         proc MENUPROC; the arguments to the call are:
    #                - the menu window
    #                - the command to be associated with final entries, whose
    #                arguments are the selected value and the menu window
    #                - the elements of the list ARGS
    #      +TEXT/LIST radiobutton with possible values in LIST and label TEXT
    #      /TEXT|LIST radiobutton with possible values in LIST and label TEXT
    #      LIST    radiobutton with possible values in LIST
    # LISTs above cannot have repeated elements
    # return pair with flag set if there are menubuttons, and list of entries,
    #  each as a pair: path from $w to entry and name of global variable to
    #  set; the list can be processed by proc GMUseEntries
    global COLOUR

    set i 0 ; set es "" ; set menus 0
    foreach v $vars os $descs {
	global $v
	frame $w.f$i
	switch -glob -- $os {
	    @* {
		set os [string range $os 1 end]
		checkbutton $w.f$i.c$v -text $os -variable $v \
		   -anchor w -onvalue 1 -offvalue 0 -selectcolor $COLOUR(check)
		pack $w.f$i.c$v
	    }
	    =* {
		set os [string range $os 1 end]
		label $w.f$i.l$v -text $os
		entry $w.f$i.e$v -width 30
		if { [catch {set $v}] } {
		    set $v ""
		} else { $w.f$i.e$v insert 0 [set $v] }
		TextBindings $w.f$i.e$v
		pack $w.f$i.l$v $w.f$i.e$v -side left
		lappend es "f$i.e$v $v"
	    }
	    !* {
		set menus 1
		regexp {^!(.+)=(.+)/(.*)$} $os m lab menuproc mpargs
		set mb $w.f$i.mb$v
		menubutton $mb -text $lab -relief raised \
		    -direction below -menu $mb.m
		menu $mb.m
		eval $menuproc $mb.m GMChangeParam $mpargs
		if { [catch {set $v}] } {
		    set $v ""
		}
		label $w.f$i.l$v -textvariable $v
		pack $mb $w.f$i.l$v -side left
	    }
	    +* -  /* {
		if { ! [regexp {^\+(.+)/(.+)$} $os m lab lst] && \
			 ! [regexp {^/(.+)\|(.+)$} $os m lab lst] } {
		    BUG Bad argument to GMSetuParams
		    continue
		}
		label $w.f$i.l$v -text $lab
		pack $w.f$i.l$v -side left
		foreach o $lst {
		    radiobutton $w.f$i.r$o -text $o -variable $v \
			    -value $o -anchor w -selectcolor $COLOUR(check)
		    pack $w.f$i.r$o -side left -padx 2
		}
		$w.f$i.r[lindex $lst 0] invoke
	    }
	    default {
		set d 1
		foreach o $os {
		    radiobutton $w.f$i.r$o -text $o -variable $v \
			    -value $o -anchor w -selectcolor $COLOUR(check)
		    if { $d } { $w.f$i.r$o invoke ; set d 0 }
		    pack $w.f$i.r$o -side left -padx 2
		}
	    }
	}
	pack $w.f$i -side top -fill x -expand 1
	incr i
    }
    return [list $menus $es]
}

proc GMChangeParam {val varmenu args} {
    # parameter value changed by a selection in a menu
    #  $varmenu is either the menu path assumed to have a single occurrence of
    #   .mbVARNAME. where VARNAME is the name of the global variable to set,
    #   or of the form =VARNAME
    #  $args may be TXT to force value to be $TXT($val)
    global TXT

    if { ! [regexp {^=(.+)$} $varmenu x v] } {
	regexp {\.mb(.+)\.} $varmenu m v
    }
    if { $args == "TXT" } {
	set val $TXT($val)
    }
    global $v
    set $v $val
    return
}

proc GMUseEntries {w es} {
    # set global variables according to entries set-up by proc GMSetupParams
    #  $w is window parent
    #  $es is list of pairs with path from $w to entry and
    #   name of global variable to set

    foreach e $es {
	set v [lindex $e 1]
	global $v
	set $v [$w.[lindex $e 0] get]
    }
    return
}


