| [32535cc] | 1 | package require Metawidget |
|---|
| 2 | |
|---|
| 3 | # name: Tabcontrol |
|---|
| 4 | # args: args: option-value pairs for metawidget configuration |
|---|
| 5 | # defines the internal widgets and private variables. initializes the -width |
|---|
| 6 | # option by calling its member function. |
|---|
| 7 | metawidget create Tabcontrol { |
|---|
| 8 | frame $this.tabs -border 2 -relief flat ;# contains all rows and tabs |
|---|
| 9 | label $this.actv -border 2 -relief raised ;# the active tab is also a frame |
|---|
| 10 | frame $this.work -border 2 -relief raised ;# the work area |
|---|
| 11 | frame $this.gbar -border 0 ;# a gray line to hide the lower |
|---|
| 12 | ;# ...border of the active tab |
|---|
| 13 | my iWorkY 0 ;# y-coordinate where the work area begins |
|---|
| 14 | my iAutoW 0 ;# minimum width of $this.tabs, if -width in auto mode |
|---|
| 15 | my lAllTabs {} ;# list of all defined tab names |
|---|
| 16 | my sActTab {} ;# name of the currently active tab |
|---|
| 17 | my wActWin {} ;# the active tab's associated user widget |
|---|
| 18 | |
|---|
| 19 | -width 100% |
|---|
| 20 | |
|---|
| 21 | bind $this <Configure> "$this _drawTabs" ;# to update tab geometry if resized |
|---|
| 22 | } {} -default tabs |
|---|
| 23 | |
|---|
| 24 | # name: _drawTabs |
|---|
| 25 | # args: - |
|---|
| 26 | # redraws the tabs. called when resized or tabs are configured. basically |
|---|
| 27 | # calculates the new geometry of the tabs container $this.tabs, of the |
|---|
| 28 | # the active tab $this.actv and the line that hides its lower border. |
|---|
| 29 | # the work area's top pixel coordinate is also adjusted. |
|---|
| 30 | metawidget proc Tabcontrol _drawTabs {} { |
|---|
| 31 | # position tab container depending on -width option. |
|---|
| 32 | switch [my sWmode] { |
|---|
| 33 | pct { place $this.tabs -x 0 -y 0 -relh 1 -relw [my iWidth] -w 0 } |
|---|
| 34 | abs { place $this.tabs -x 0 -y 0 -relh 1 -w [my iWidth] -relw 0 } |
|---|
| 35 | req { place $this.tabs -x 0 -y 0 -relh 1 -w [my iAutoW] -relw 0 } |
|---|
| 36 | } |
|---|
| 37 | |
|---|
| 38 | # if an active tab is set, places the $this.actv window exactly over it, |
|---|
| 39 | # but sizes it slightly bigger. places the $this.gbar frame so that |
|---|
| 40 | # $this.actv and $this.work look like one window. |
|---|
| 41 | if { [my sActTab] != {} } { |
|---|
| 42 | set iX [winfo x [my wTab]] |
|---|
| 43 | set iY [expr [winfo y [my wRow]]-2] |
|---|
| 44 | set iW [expr [winfo w [my wTab]]+4] |
|---|
| 45 | set iH [expr [winfo h [my wRow]]+2] |
|---|
| 46 | |
|---|
| 47 | place $this.actv -x $iX -y $iY -w $iW -h $iH |
|---|
| 48 | place $this.gbar -x [expr $iX+1] -y [expr $iY+$iH-2] -w [expr $iW-2] -h 2 |
|---|
| 49 | place $this.work -x 0 -y [my iWorkY] -relw 1 -h [expr [winfo height $this.tabs]-[my iWorkY]] |
|---|
| 50 | } |
|---|
| 51 | } |
|---|
| 52 | |
|---|
| 53 | # name: _findTab |
|---|
| 54 | # args: sTab: Tab name to retrieve data for |
|---|
| 55 | # raises an exception, if the tab doesn't exist. otherwise, returns |
|---|
| 56 | # the tab's properties: widget name, row, user window and command. |
|---|
| 57 | metawidget proc Tabcontrol _findTab { sTab } { |
|---|
| 58 | if { ! [myinfo exists aTabs($sTab)] } { |
|---|
| 59 | error "Tabcontrol $this: No such tab '$sTab'" |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | return [my aTabs($sTab)] |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | # name: _calcGeom |
|---|
| 66 | # args: - |
|---|
| 67 | # calculates the minimum required with of the container $this.tabs, so that |
|---|
| 68 | # the labels of all tabs are completely visible. retrieves the 'required with' |
|---|
| 69 | # of all tab rows and calculates the longest one. used when -width is 'auto'. |
|---|
| 70 | # also calculates the top border of the work area. |
|---|
| 71 | metawidget proc Tabcontrol _calcGeom {} { |
|---|
| 72 | update |
|---|
| 73 | |
|---|
| 74 | set iAutoW 0 |
|---|
| 75 | foreach wWindow [winfo children $this.tabs] { |
|---|
| 76 | set iWidth [winfo reqw $wWindow] |
|---|
| 77 | if { $iWidth < $iAutoW } continue |
|---|
| 78 | set iAutoW $iWidth |
|---|
| 79 | } |
|---|
| 80 | my iAutoW [expr $iAutoW+4] |
|---|
| 81 | |
|---|
| 82 | set iWorkY 2 |
|---|
| 83 | foreach wWindow [winfo children $this.tabs] { |
|---|
| 84 | incr iWorkY [expr [winfo reqh $wWindow]-2] |
|---|
| 85 | } |
|---|
| 86 | my iWorkY $iWorkY |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | # name: _updateActive |
|---|
| 90 | # args: - |
|---|
| 91 | # make the active tab look like the original tab (which stays in its row) |
|---|
| 92 | # remember: all tabs are simply packed into their row frame. to make a tab |
|---|
| 93 | # look raised, the $this.actv frame is configured to look like the |
|---|
| 94 | # to-be-raised tab and placed over it slighly bigger by the _drawTabs function. |
|---|
| 95 | metawidget proc Tabcontrol _updateActive {} { |
|---|
| 96 | if { [my sActTab] == {} } return |
|---|
| 97 | |
|---|
| 98 | $this.actv config -text [[my wTab] cget -text] -image [[my wTab] cget -image] \ |
|---|
| 99 | -backg [[my wTab] cget -backg] -foreg [[my wTab] cget -foreg] \ |
|---|
| 100 | -font [[my wTab] cget -font] -cursor [[my wTab] cget -cursor] \ |
|---|
| 101 | -under [[my wTab] cget -under] -anchor [[my wTab] cget -anchor] \ |
|---|
| 102 | -padx [[my wTab] cget -padx] -pady [[my wTab] cget -pady] |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | # name: -width |
|---|
| 106 | # args: sWidth: new width value |
|---|
| 107 | # option member. controls the width of the container $this.tabs and its |
|---|
| 108 | # behaviour when the toplevel is resized. sWidth can be 'auto', an integer, |
|---|
| 109 | # or an integer followed by a percent sign. sets the two private variables |
|---|
| 110 | # sWmode and iWidth accordingly. they are used in the _drawTabs member. |
|---|
| 111 | metawidget proc Tabcontrol -width { sWidth } { |
|---|
| 112 | if { [regexp {^([0-9]+)%$} $sWidth sDummy iWidth] } { |
|---|
| 113 | my sWmode pct |
|---|
| 114 | my iWidth [expr $iWidth/100.] |
|---|
| 115 | } elseif { [regexp {^[0-9]+$} $sWidth iWidth] } { |
|---|
| 116 | my sWmode abs |
|---|
| 117 | my iWidth $iWidth |
|---|
| 118 | } elseif { $sWidth == "auto" } { |
|---|
| 119 | my sWmode req |
|---|
| 120 | my iWidth [my iAutoW] |
|---|
| 121 | } else { |
|---|
| 122 | error "Tabcontrol $this: Bad width $sWidth. Must be an integer, integer% or 'auto'" |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | my -width $sWidth |
|---|
| 126 | _drawTabs |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | # name: insert |
|---|
| 130 | # args: sTab: name of the new tab |
|---|
| 131 | # iRow: row to place the tab in |
|---|
| 132 | # args: option-value pairs for tab configuration |
|---|
| 133 | # defines and displays a new tab. derives the row's widget name and eventually |
|---|
| 134 | # creates it if it doesn't exist. creates a label for the tab and packs it |
|---|
| 135 | # into its row. initializes tab properties. |
|---|
| 136 | metawidget proc Tabcontrol insert { sTab iRow args } { |
|---|
| 137 | if { [myinfo exists aTabs($sTab)] } { |
|---|
| 138 | error "Tabcontrol $this: Tab '$sTab' already exists" |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | set wRow $this.tabs.row$iRow ;# wRow is child of $this.tabs |
|---|
| 142 | set wTab $wRow.tab$sTab ;# wTab is child of wRow |
|---|
| 143 | |
|---|
| 144 | if { ! [myinfo exists lTabs$iRow] } { ;# if wRow doesn't exist |
|---|
| 145 | frame $wRow -border 0 ;# then create it and |
|---|
| 146 | my lTabs$iRow {} ;# initialize a list for all its tabs |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | # create tab widget, simply pack it on the left, create mouse click binding |
|---|
| 150 | label $wTab -border 2 -relief raised |
|---|
| 151 | pack $wTab -side left -ipadx 2 -fill both -expand 1 |
|---|
| 152 | bind $wTab <1> "$this invoke $sTab" |
|---|
| 153 | |
|---|
| 154 | my aTabs($sTab) [list $wTab $iRow {} {}] ;# initialize tab prop/s |
|---|
| 155 | eval tabconfigure {$sTab} -text {$sTab} $args ;# configure it a bit |
|---|
| 156 | |
|---|
| 157 | _calcGeom ;# update required width |
|---|
| 158 | invoke $sTab ;# and raise the new tab |
|---|
| 159 | |
|---|
| 160 | # add tab to the all-tabs list and to the row-specific list |
|---|
| 161 | my lAllTabs [mkw.lextend [my lAllTabs] $sTab] |
|---|
| 162 | my lTabs$iRow [mkw.lextend [my lTabs$iRow] $sTab] |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | # name: delete |
|---|
| 166 | # args: sTab: tab to delete |
|---|
| 167 | # deletes a tab. displays another tab, if there's one left. |
|---|
| 168 | metawidget proc Tabcontrol delete { sTab } { |
|---|
| 169 | mkw.lassign [_findTab $sTab] wTab iRow wWindow |
|---|
| 170 | |
|---|
| 171 | pack forget $wWindow |
|---|
| 172 | destroy $wTab |
|---|
| 173 | unmy aTabs($sTab) |
|---|
| 174 | |
|---|
| 175 | # update all-tabs list and row-specific list |
|---|
| 176 | my lAllTabs [mkw.lshrink [my lAllTabs] $sTab] |
|---|
| 177 | my lTabs$iRow [mkw.lshrink [my lTabs$iRow] $sTab] |
|---|
| 178 | |
|---|
| 179 | # if there is some other tab left and the deleted tab was active, then |
|---|
| 180 | # raise the other tab. otherwise hide the $this.actv frame. |
|---|
| 181 | if { [llength [my lAllTabs]] } { |
|---|
| 182 | if { $sTab == [my sActTab] } { |
|---|
| 183 | invoke [lindex [my lAllTabs] 0] |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | _calcGeom |
|---|
| 187 | _drawTabs |
|---|
| 188 | } else { |
|---|
| 189 | place forget $this.actv |
|---|
| 190 | place forget $this.gbar |
|---|
| 191 | my sActTab {} |
|---|
| 192 | } |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | # name: tabconfigure |
|---|
| 196 | # args: sTab: tab to be configured |
|---|
| 197 | # args: option-value pairs |
|---|
| 198 | # configures a tab. the two special options -window and -command are stored |
|---|
| 199 | # in the tab's property list (no immediate action required). all other options |
|---|
| 200 | # are passed to the tab's widget. |
|---|
| 201 | metawidget proc Tabcontrol tabconfigure { sTab args } { |
|---|
| 202 | mkw.lassign [_findTab $sTab] wTab |
|---|
| 203 | |
|---|
| 204 | set lArgs [mkw.options $args * {-window *} {-command *}] |
|---|
| 205 | |
|---|
| 206 | if { [info exists -window] } { |
|---|
| 207 | my aTabs($sTab) [lreplace [my aTabs($sTab)] 2 2 ${-window}] |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | if { [info exists -command] } { |
|---|
| 211 | my aTabs($sTab) [lreplace [my aTabs($sTab)] 3 3 ${-command}] |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | eval $wTab config $lArgs |
|---|
| 215 | |
|---|
| 216 | _calcGeom |
|---|
| 217 | _drawTabs |
|---|
| 218 | _updateActive |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | # name: tabcget |
|---|
| 222 | # args: sTab: tab to retrieve option value for |
|---|
| 223 | # sOption: name of an option |
|---|
| 224 | # retrieves an option value of a tab. either returns a tab property value for |
|---|
| 225 | # one of the special options -window and -command, or tries to get it directly |
|---|
| 226 | # from the tab's widget. |
|---|
| 227 | metawidget proc Tabcontrol tabcget { sTab sOption } { |
|---|
| 228 | mkw.lassign [_findTab $sTab] wTab iRow wWindow sCommand |
|---|
| 229 | |
|---|
| 230 | set wRow $this.tabs.row$iRow.tab$sTab |
|---|
| 231 | set wTab $wRow.tab$sTab |
|---|
| 232 | |
|---|
| 233 | switch -- $sOption { |
|---|
| 234 | -window { return $wWindow } |
|---|
| 235 | -command { return $sCommand } |
|---|
| 236 | default { return [$wTab cget $sOption] } |
|---|
| 237 | } |
|---|
| 238 | } |
|---|
| 239 | |
|---|
| 240 | # name: invoke |
|---|
| 241 | # args: sTab: tab to raise |
|---|
| 242 | # raises a tab, i.e. makes it look like in the foreground and displays its |
|---|
| 243 | # associated user window. Also, the row with the active tab is always the lowest |
|---|
| 244 | # one. therefore the rows are rearranged. |
|---|
| 245 | metawidget proc Tabcontrol invoke { sTab } { |
|---|
| 246 | mkw.lassign [_findTab $sTab] wTab iRow wWindow sCommand |
|---|
| 247 | |
|---|
| 248 | catch { pack forget [my wActWin ] } ;# hide user-widget from active tab |
|---|
| 249 | my wTab $wTab ;# update private variables... |
|---|
| 250 | my wRow [winfo parent $wTab] ;# ...needed later e.g. for _drawTabs |
|---|
| 251 | my sActTab $sTab |
|---|
| 252 | my wActWin $wWindow |
|---|
| 253 | |
|---|
| 254 | # rearrange rows so that the one with the new active tab is at the bottom. |
|---|
| 255 | set iY 0 |
|---|
| 256 | set lRows [mkw.lshrink [lsort [winfo children $this.tabs]] [my wRow]] |
|---|
| 257 | foreach wRow [linsert $lRows end [my wRow]] { |
|---|
| 258 | raise $wRow |
|---|
| 259 | place $wRow -x 0 -y $iY -relw 1 |
|---|
| 260 | incr iY [expr [winfo reqheight $wRow]-2] |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | # pack and raise the new tab's user-widget, if it exists |
|---|
| 264 | if { [winfo exist $wWindow] } { |
|---|
| 265 | pack $wWindow -in $this.work -padx 2 -pady 2 -fill both -expand 1 |
|---|
| 266 | raise $wWindow |
|---|
| 267 | } |
|---|
| 268 | |
|---|
| 269 | update |
|---|
| 270 | _drawTabs |
|---|
| 271 | _updateActive |
|---|
| 272 | |
|---|
| 273 | if { $sCommand != {} } { |
|---|
| 274 | eval $sCommand ;# eventually execute the tab's command, if there is one |
|---|
| 275 | } |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | # name: get |
|---|
| 279 | # args: sWhat: row number or 'active' or nothing |
|---|
| 280 | # returns all tabs, the active tab or the tabs of a given row. |
|---|
| 281 | metawidget proc Tabcontrol get { {sWhat {}} } { |
|---|
| 282 | if { $sWhat == {} } { |
|---|
| 283 | return [my lAllTabs] |
|---|
| 284 | } elseif { $sWhat == "active" } { |
|---|
| 285 | return [my sActTab] |
|---|
| 286 | } elseif { [regexp {^[0-9]+$} $sWhat] } { |
|---|
| 287 | return [my lTabs$sWhat] |
|---|
| 288 | } else { |
|---|
| 289 | error "Tabcontrol $this: Bad index $sWhat. Should be active, an integer or nothing." |
|---|
| 290 | } |
|---|
| 291 | } |
|---|
| 292 | |
|---|
| 293 | # name: bind |
|---|
| 294 | # args: sTab: tab to set binding for (or get from) |
|---|
| 295 | # args: arguments for the bind command |
|---|
| 296 | # creates or retrieves bindings for the given tab. |
|---|
| 297 | metawidget proc Tabcontrol bind_ { sTab args } { |
|---|
| 298 | mkw.lassign [_findTab $sTab] wRow wTab |
|---|
| 299 | eval bind $wTab $args |
|---|
| 300 | } |
|---|
| 301 | |
|---|
| 302 | metawidget command Tabcontrol _drawTabs _drawTabs |
|---|
| 303 | |
|---|
| 304 | metawidget command Tabcontrol insert insert |
|---|
| 305 | metawidget command Tabcontrol delete delete |
|---|
| 306 | metawidget command Tabcontrol tabconfigure tabconfigure |
|---|
| 307 | metawidget command Tabcontrol tabcget tabcget |
|---|
| 308 | metawidget command Tabcontrol invoke invoke |
|---|
| 309 | metawidget command Tabcontrol get get |
|---|
| 310 | metawidget command Tabcontrol bind bind_ |
|---|
| 311 | |
|---|
| 312 | metawidget option Tabcontrol -width -width |
|---|
| 313 | |
|---|
| 314 | proc test {} { |
|---|
| 315 | global iTabno |
|---|
| 316 | set iTabno 0 |
|---|
| 317 | |
|---|
| 318 | wm minsize . 200 200 |
|---|
| 319 | |
|---|
| 320 | [text .code] insert end [info body test] |
|---|
| 321 | |
|---|
| 322 | frame .fwid -border 2 -relief groove |
|---|
| 323 | pack [radiobutton .fwid.auto -text "Set it to Auto mode" -value 0 -command {.tabs config -width auto}] -anchor w |
|---|
| 324 | pack [radiobutton .fwid.half -text "Half of window width" -value 1 -command {.tabs config -width 50%}] -anchor w |
|---|
| 325 | pack [radiobutton .fwid.full -text "Full width of window" -value 2 -command {.tabs config -width 100%}] -anchor w |
|---|
| 326 | pack [radiobutton .fwid.exct -text "Exactly 300 pixels" -value 3 -command {.tabs config -width 300}] -anchor w |
|---|
| 327 | |
|---|
| 328 | frame .fcrt |
|---|
| 329 | pack [button .fcrt.tcre -text "Create a tab" -command { |
|---|
| 330 | incr iTabno |
|---|
| 331 | label .lbl$iTabno -text "This is tab $iTabno" -font {Helvetica 24} -bg white |
|---|
| 332 | .tabs insert Tab$iTabno 1 -window .lbl$iTabno |
|---|
| 333 | }] -fill x |
|---|
| 334 | pack [button .fcrt.tdel -text "Delete last tab" -command { |
|---|
| 335 | .tabs delete Tab$iTabno |
|---|
| 336 | destroy .lbl$iTabno |
|---|
| 337 | incr iTabno -1 |
|---|
| 338 | }] -fill x |
|---|
| 339 | |
|---|
| 340 | tabcontrol .tabs -width auto |
|---|
| 341 | pack .tabs -fill both -expand 1 -padx 10 -pady 10 |
|---|
| 342 | |
|---|
| 343 | .tabs insert width 0 -text "Change Width" -window .fwid -underline 7 |
|---|
| 344 | .tabs insert edit 0 -text "Create Tabs" -window .fcrt -underline 7 |
|---|
| 345 | .tabs insert source 0 -text "View Source" -window .code -underline 5 |
|---|
| 346 | .tabs invoke width |
|---|
| 347 | |
|---|
| 348 | bind . <Alt-w> {.tabs invoke width} |
|---|
| 349 | bind . <Alt-t> {.tabs invoke edit} |
|---|
| 350 | bind . <Alt-s> {.tabs invoke source} |
|---|
| 351 | } |
|---|
| 352 | |
|---|
| 353 | #test |
|---|