# copyright (C) 1997-2001 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

set rcsId {$Id: dialog.tcl,v 2.8 2000/12/31 23:14:19 jfontain Exp $}

class dialogBox {}

proc dialogBox::dialogBox {this parentPath args} composite {[new toplevel $parentPath] $args} {
    set path $widget::($this,path)
    wm group $path .                                                 ;# for proper window manager (windowmaker for example) behavior
    wm withdraw $path        ;# hide the window till all contained widgets are created so we will be able to know its requested size
    composite::manage $this [new frame $path -relief sunken -borderwidth 1 -height 2] separator [new frame $path] buttons
    set buttons $composite::($this,buttons,path)
    composite::manage $this [new button $buttons -text OK -command "dialogBox::done $this"] ok\
        [new button $buttons -text Cancel -command "delete $this"] cancel\
        [new button $buttons -text Help -command "dialogBox::help $this"] help

    grid $composite::($this,separator,path) -column 0 -row 1 -sticky ew
    grid $buttons -column 0 -row 2 -sticky nsew
    grid rowconfigure $path 0 -weight 1
    grid columnconfigure $path 0 -weight 1

    wm protocol $path WM_DELETE_WINDOW "delete $this"
    bind $path <KeyRelease-Escape> "delete $this"                          ;# add some standard key bindings on release so that user
    bind $path <KeyRelease-Return> "dialogBox::done $this"                ;# can see the buttons move, thanks to the key links below
    bind $path <KeyRelease-KP_Enter> "dialogBox::done $this"
    bind $path <F1> "dialogBox::help $this"
    composite::complete $this
}

proc dialogBox::~dialogBox {this} {
    if {[string length $composite::($this,-deletecommand)]>0} {
        uplevel #0 $composite::($this,-deletecommand)                                       ;# always invoke command at global level
    }
}

proc dialogBox::options {this} {                                        ;# force initialization of grab, title and transient options
    return [list\
        [list -buttons o]\
        [list -command {} {}]\
        [list -default {} {}]\
        [list -deletecommand {} {}]\
        [list -die 1 1]\
        [list -grab local]\
        [list -helpcommand {} {}]\
        [list -title {Dialog box}]\
        [list -transient 1]\
        [list -x 0]\
        [list -y 0]
    ]
}

proc dialogBox::set-buttons {this value} {
    if {$composite::($this,complete)} {
        error {option -buttons cannot be set dynamically}
    }
    if {![regexp {^(o|c|oc|ho|hc|hoc)$} $value]} {
        error "bad buttons value \"$value\": must be o, c, oc, ho, hc or hoc"
    }
    pack forget $composite::($this,ok,path) $composite::($this,cancel,path)
    set help [expr {[string first h $value]>=0}]
    set ok [expr {[string first o $value]>=0}]
    set cancel [expr {[string first c $value]>=0}]
    if {$help} {
        pack $composite::($this,help,path) -side left -expand 1 -pady 3 -padx 3
        new buttonKeysLink $composite::($this,help,path) F1 $widget::($this,path)
    }
    if {$ok} {
        pack $composite::($this,ok,path) -side left -expand 1 -pady 3
        new buttonKeysLink $composite::($this,ok,path) {Return KP_Enter} $widget::($this,path)
    }
    if {$cancel} {
        pack $composite::($this,cancel,path) -side left -expand 1 -pady 3
        new buttonKeysLink $composite::($this,cancel,path) Escape $widget::($this,path)
    }
}

proc dialogBox::set-default {this value} {                                                 ;# value is stored at the composite level
    if {$composite::($this,complete)} {
        error {option -default cannot be set dynamically}
    }
    switch $composite::($this,-default) {
        o {
            $composite::($this,ok,path) configure -default active
        }
        c {
            $composite::($this,cancel,path) configure -default active
        }
        default {
            error "bad default value \"$value\": must be o or c"
        }
    }
}

proc dialogBox::set-command {this value} {}                                  ;# do nothing, values are stored at the composite level
proc dialogBox::set-deletecommand {this value} {}
proc dialogBox::set-die {this value} {}
proc dialogBox::set-helpcommand {this value} {}

proc dialogBox::set-grab {this value} {
    switch $value {
        global {
            grab -global $widget::($this,path)
        }
        local {
            grab $widget::($this,path)
        }
        release {
            grab release $widget::($this,path)
        }
        default {
            error "bad grab value \"$value\": must be global, local or release"
        }
    }
}

proc dialogBox::set-title {this value} {
    wm title $widget::($this,path) $value
}

foreach option {-x -y} {
    proc dialogBox::set$option {this value} {
        if {[winfo ismapped $widget::($this,path)]} {
            place $this                            ;# if window if not visible, it will be positioned at the time it becomes visible
        }
    }
}

proc dialogBox::set-transient {this value} {
    if {$value} {
        wm transient $widget::($this,path) [winfo toplevel $widget::($this,path)]
    } else {
        wm transient $widget::($this,path) {}
    }
}

proc dialogBox::display {this path} {                                                ;# must be invoked for dialog box to be visible
    if {[string length $path]==0} {                                                           ;# undisplay, remove related resources
        if {[info exists ($this,displayed)]} {
            grid forget $($this,displayed)
            unset ($this,displayed)
        }
        return
    }
    if {[info exists ($this,displayed)]} {
        error "a widget ($($this,displayed)) is already displayed"
    }
    set ($this,displayed) $path
    grid $path -in $widget::($this,path) -column 0 -row 0 -sticky nsew -pady 3
    place $this
}

proc dialogBox::done {this} {
    if {[string length $composite::($this,-command)]>0} {                            ;# invoke eventually command for the dialog box
        uplevel #0 $composite::($this,-command)                            ;# always invoke command at global level as tk buttons do
    }
    if {[info exists composite::($this,-die)]&&$composite::($this,-die)} {  ;# dialog box may already have been destroyed in command
        delete $this
    }
}

proc dialogBox::place {this} {                                                          ;# make sure no part of widget is off screen
    update idletasks                                                                                 ;# make sure sizes are accurate
    set path $widget::($this,path)
    set x [minimum $composite::($this,-x) [expr {[winfo screenwidth $path]-[winfo reqwidth $path]}]]
    set y [minimum $composite::($this,-y) [expr {[winfo screenheight $path]-[winfo reqheight $path]}]]
    wm geometry $path +$x+$y
    wm deiconify $path                                                                                        ;# now show the window
}

proc dialogBox::help {this} {
    if {[string length $composite::($this,-helpcommand)]>0} {                                      ;# eventually invoke help command
        uplevel #0 $composite::($this,-helpcommand)                        ;# always invoke command at global level as tk buttons do
    }
}
