summaryrefslogtreecommitdiffstats
path: root/library/ttk/dialog.tcl
blob: cb3db4789de84f9311030aeb188ccbbc435be185 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#
# $Id: dialog.tcl,v 1.1 2006/10/31 01:42:27 hobbs Exp $
#
# Copyright (c) 2005, Joe English.  Freely redistributable.
#
# Ttk widget set: dialog boxes.
#
# TODO: option to keep dialog onscreen ("persistent" / "transient")
# TODO: accelerator keys.
# TODO: use message catalogs for button labels
# TODO: routines to selectively enable/disable individual command buttons
# TODO: use megawidgetoid API [$dlg dismiss] vs. [ttk::dialog::dismiss $dlg]
# TODO: MAYBE: option for app-modal dialogs
# TODO: MAYBE: [wm withdraw] dialog on dismiss instead of self-destructing 
#

namespace eval ttk::dialog {

    variable Config
    #
    # Spacing parameters:
    # (taken from GNOME HIG 2.0, may need adjustment for other platforms)
    # (textwidth just a guess)
    #
    set Config(margin)		12	;# space between icon and text
    set Config(interspace)	6	;# horizontal space between buttons
    set Config(sepspace) 	24	;# vertical space above buttons
    set Config(textwidth) 	400	;# width of dialog box text (pixels)

    variable DialogTypes	;# map -type => list of dialog options
    variable ButtonOptions	;# map button name => list of button options

    # stockButton -- define new built-in button
    #
    proc stockButton {button args} {
	variable ButtonOptions
	set ButtonOptions($button) $args
    }

    # Built-in button types:
    #
    stockButton ok 	-text OK
    stockButton cancel	-text Cancel
    stockButton yes	-text Yes
    stockButton no	-text No
    stockButton retry 	-text Retry

    # stockDialog -- define new dialog type.
    #
    proc stockDialog {type args} {
	variable DialogTypes
	set DialogTypes($type) $args
    }

    # Built-in dialog types:
    #
    stockDialog ok \
	-icon info -buttons {ok} -default ok
    stockDialog okcancel \
	-icon info -buttons {ok cancel} -default ok -cancel cancel
    stockDialog retrycancel \
	-icon question -buttons {retry cancel} -cancel cancel
    stockDialog yesno \
	-icon question -buttons {yes no}
    stockDialog yesnocancel \
	-icon question -buttons {yes no cancel} -cancel cancel
}

## ttk::dialog::nop --
#	Do nothing (used as a default callback command). 
#
proc ttk::dialog::nop {args} { }

## ttk::dialog -- dialog box constructor.
#
interp alias {} ttk::dialog {} ttk::dialog::Constructor

proc ttk::dialog::Constructor {dlg args} {
    upvar #0 $dlg D
    variable Config
    variable ButtonOptions
    variable DialogTypes

    # 
    # Option processing:
    #
    array set defaults {
	-title 		""
    	-message	""
	-detail		""
	-command	ttk::dialog::nop
	-icon 		""
	-buttons 	{}
	-labels 	{}
	-default 	{}
	-cancel		{}
	-parent		#AUTO
    }

    array set options [array get defaults]

    foreach {option value} $args {
	if {$option eq "-type"} {
	    array set options $DialogTypes($value)
	} elseif {![info exists options($option)]} {
	    set validOptions [join [lsort [array names options]] ", "]
	    return -code error \
	    	"Illegal option $option: must be one of $validOptions"
	}
    }
    array set options $args

    # ...
    #
    array set buttonOptions [array get ::ttk::dialog::ButtonOptions]
    foreach {button label} $options(-labels) {
	lappend buttonOptions($button) -text $label
    }

    #
    # Initialize dialog private data:
    #
    foreach option {-command -message -detail} {
	set D($option) $options($option)
    }

    toplevel $dlg -class Dialog; wm withdraw $dlg

    #
    # Determine default transient parent.
    #
    # NB: menus (including menubars) are considered toplevels,
    # so skip over those. 
    #
    if {$options(-parent) eq "#AUTO"} {
	set parent [winfo toplevel [winfo parent $dlg]]
	while {[winfo class $parent] eq "Menu" && $parent ne "."} {
	    set parent [winfo toplevel [winfo parent $parent]]
	}
	set options(-parent) $parent
    }

    #
    # Build dialog:
    #
    if {$options(-parent) ne ""} {
    	wm transient $dlg $options(-parent)
    }
    wm title $dlg $options(-title)
    wm protocol $dlg WM_DELETE_WINDOW { }

    set f [ttk::frame $dlg.f] 

    ttk::label $f.icon 
    if {$options(-icon) ne ""} {
	$f.icon configure -image [ttk::stockIcon dialog/$options(-icon)]
    }
    ttk::label $f.message -textvariable ${dlg}(-message) \
    	-font TkCaptionFont -wraplength $Config(textwidth)\
	-anchor w -justify left
    ttk::label $f.detail -textvariable ${dlg}(-detail) \
    	-font TkTextFont -wraplength $Config(textwidth) \
	-anchor w -justify left

    #
    # Command buttons:
    #
    set cmd [ttk::frame $f.cmd]
    set column 0
    grid columnconfigure $f.cmd 0 -weight 1

    foreach button $options(-buttons) {
	incr column
	eval [linsert $buttonOptions($button) 0 ttk::button $cmd.$button]
        $cmd.$button configure -command [list ttk::dialog::Done $dlg $button]
    	grid $cmd.$button -row 0 -column $column \
	    -padx [list $Config(interspace) 0] -sticky ew
	grid columnconfigure $cmd $column -uniform buttons
    }

    if {$options(-default) ne ""} {
    	keynav::defaultButton $cmd.$options(-default)
	focus $cmd.$options(-default)
    }
    if {$options(-cancel) ne ""} {
	bind $dlg <KeyPress-Escape> \
	    [list event generate $cmd.$options(-cancel) <<Invoke>>]
	wm protocol $dlg WM_DELETE_WINDOW \
	    [list event generate $cmd.$options(-cancel) <<Invoke>>]
    }

    #
    # Assemble dialog.
    #
    pack $f.cmd -side bottom -expand false -fill x \
    	-pady [list $Config(sepspace) $Config(margin)] -padx $Config(margin)

    if {0} {
	# GNOME and Apple HIGs say not to use separators.
	# But in case we want them anyway:
	#
	pack [ttk::separator $f.sep -orient horizontal] \
	    -side bottom -expand false -fill x \
	    -pady [list $Config(sepspace) 0] \
	    -padx $Config(margin)
    }

    if {$options(-icon) ne ""} {
	pack $f.icon -side left -anchor n -expand false \
	    -pady $Config(margin) -padx $Config(margin)
    }

    pack $f.message -side top -expand false -fill x \
    	-padx $Config(margin) -pady $Config(margin)
    if {$options(-detail) != ""} {
	pack $f.detail -side top -expand false -fill x \
	    -padx $Config(margin)
    }

    # Client area goes here.

    pack $f -expand true -fill both
    keynav::enableMnemonics $dlg
    wm deiconify $dlg
}

## ttk::dialog::clientframe --
#	Returns the widget path of the dialog client frame,
#	creating and managing it if necessary.
#
proc ttk::dialog::clientframe {dlg} { 
    variable Config
    set client $dlg.f.client
    if {![winfo exists $client]} {
	pack [ttk::frame $client] -side top -expand true -fill both \
		-pady $Config(margin) -padx $Config(margin)
	lower $client	;# so it's first in keyboard traversal order
    }
    return $client 
}

## ttk::dialog::Done --
#	-command callback for dialog command buttons (internal)
#
proc ttk::dialog::Done {dlg button} {
    upvar #0 $dlg D
    set rc [catch [linsert $D(-command) end $button] result]
    if {$rc == 1} {
    	return -code $rc -errorinfo $::errorInfo -errorcode $::errorCode $result
    } elseif {$rc == 3 || $rc == 4} {
	# break or continue -- don't dismiss dialog
	return
    }
    dismiss $dlg
}

## ttk::dialog::activate $dlg $button --
#	Simulate a button press.
#
proc ttk::dialog::activate {dlg button} {
    event generate $dlg.f.cmd.$button <<Invoke>>
}

## dismiss --
#	Dismiss the dialog (without invoking any actions).
#
proc ttk::dialog::dismiss {dlg} {
    uplevel #0 [list unset $dlg]
    destroy $dlg
}

#*EOF*