| 1 | package require Metawidget |
|---|
| 2 | |
|---|
| 3 | # name: Iconbox |
|---|
| 4 | # args: args: option-value pairs |
|---|
| 5 | # tries to find a font as column separator, creates all widgets, |
|---|
| 6 | # preconfigures tags, sets all bindings for the selection and inits |
|---|
| 7 | # private variables. |
|---|
| 8 | metawidget create Iconbox { |
|---|
| 9 | if { ! [ourinfo exists bInit] } { |
|---|
| 10 | # bindings for selection, here for the icon widgets (-view large/small) |
|---|
| 11 | foreach { sBind sStep iDir sAction sMode } { |
|---|
| 12 | <Button-1> m 0 n s <Control-1> m 0 t s |
|---|
| 13 | <Shift-1> m 0 n r <Shift-Control-1> m 0 a r |
|---|
| 14 | } { |
|---|
| 15 | bind Iconboxicon $sBind "catch {\[winfo parent \[winfo parent %W\]\] _select2 $sStep $iDir $sAction $sMode %W}" |
|---|
| 16 | } |
|---|
| 17 | |
|---|
| 18 | # bindings for selection for the text window itself |
|---|
| 19 | foreach { sBind sStep iDir sAction sMode } { |
|---|
| 20 | <Button-1> m 0 n s <Control-1> m 0 t s |
|---|
| 21 | <Shift-1> m 0 n r <Shift-Control-1> m 0 a r |
|---|
| 22 | |
|---|
| 23 | <Left> i -1 n s <Right> i 1 n s |
|---|
| 24 | <Shift-Left> i -1 n r <Shift-Right> i 1 n r |
|---|
| 25 | <Control-Left> i -1 c c <Control-Right> i 1 c c |
|---|
| 26 | <Shift-Control-Left> i -1 a r <Shift-Control-Right> i 1 a r |
|---|
| 27 | |
|---|
| 28 | <Up> r -1 n s <Down> r 1 n s |
|---|
| 29 | <Shift-Up> r -1 n r <Shift-Down> r 1 n r |
|---|
| 30 | <Control-Up> r -1 c c <Control-Down> r 1 c c |
|---|
| 31 | <Shift-Control-Up> r -1 a r <Shift-Control-Down> r 1 a r |
|---|
| 32 | |
|---|
| 33 | <Prior> p -1 n s <Next> p 1 n s |
|---|
| 34 | <Shift-Prior> p -1 n r <Shift-Next> p 1 n r |
|---|
| 35 | <Control-Prior> p -1 c c <Control-Next> p 1 c c |
|---|
| 36 | <Shift-Control-Prior> p -1 a r <Shift-Control-Next> p 1 a r |
|---|
| 37 | |
|---|
| 38 | <Control-space> r 0 t s |
|---|
| 39 | } { |
|---|
| 40 | bind Iconboxtext $sBind "catch {\[winfo parent %W\] _select $sStep $iDir $sAction $sMode}" |
|---|
| 41 | } |
|---|
| 42 | |
|---|
| 43 | # jump to start or end, should be improved one day |
|---|
| 44 | bind Iconboxtext <Home> "%W see 1.0" |
|---|
| 45 | bind Iconboxtext <End> "%W see end" |
|---|
| 46 | |
|---|
| 47 | # take over some useful Text class bindings |
|---|
| 48 | foreach sBind {<MouseWheel> <B2-Motion> <Button-2>} { |
|---|
| 49 | bind Iconboxtext $sBind [bind Text $sBind] |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | # find a font with one pixel width for column separation |
|---|
| 53 | _findColFonts |
|---|
| 54 | |
|---|
| 55 | our bInit 1 |
|---|
| 56 | } |
|---|
| 57 | |
|---|
| 58 | # internal widgets, gridded later |
|---|
| 59 | scrollbar $this.scrh -command "$this _dragX" -orient horiz |
|---|
| 60 | scrollbar $this.scrv -command "$this.text yview" |
|---|
| 61 | |
|---|
| 62 | text $this.cols -wrap none -cursor arrow -height 1 -spacing1 1 -spacing3 1 \ |
|---|
| 63 | -border 0 -bg gray -padx 1 -pady 0 |
|---|
| 64 | |
|---|
| 65 | text $this.text -wrap none -cursor arrow -spacing1 0 -spacing3 0 \ |
|---|
| 66 | -border 0 -bg white -padx 1 -pady 1 -insertofftime 0 -insertwidth 0 \ |
|---|
| 67 | -xscroll "$this _moveX" -yscroll "$this.scrv set" |
|---|
| 68 | |
|---|
| 69 | grid $this.text -column 0 -row 1 -sticky news |
|---|
| 70 | grid $this.scrv -column 1 -row 0 -sticky ns -rowspan 3 |
|---|
| 71 | grid columnconf $this 0 -weight 1 |
|---|
| 72 | grid rowconf $this 1 -weight 1 |
|---|
| 73 | |
|---|
| 74 | # tags for column header and column separator |
|---|
| 75 | $this.cols tag config tCols -border 1 -relief raised |
|---|
| 76 | $this.cols tag config tCsep -back gray -font [our sColFont] |
|---|
| 77 | $this.text tag config cursor -border 1 -relief solid |
|---|
| 78 | |
|---|
| 79 | # take Text class bindings out, no insertion cursor etc. |
|---|
| 80 | bindtags $this.cols [mkw.lshrink [bindtags $this.cols] Text] |
|---|
| 81 | bindtags $this.text [mkw.lchange [bindtags $this.text] Text Iconboxtext] |
|---|
| 82 | |
|---|
| 83 | # bindings to resize a column |
|---|
| 84 | $this.cols tag bind tDrag <Button-1> "$this _grab" |
|---|
| 85 | $this.cols tag bind tDrag <B1-Motion> "$this _drag" |
|---|
| 86 | $this.cols tag bind tDrag <ButtonRelease-1> "$this _drop" |
|---|
| 87 | $this.cols tag bind tText <1> "$this _sort" |
|---|
| 88 | |
|---|
| 89 | my lSel {} ;# list of selected icons |
|---|
| 90 | my lCols {} ;# column names |
|---|
| 91 | my lIcons {} ;# list of all icons |
|---|
| 92 | |
|---|
| 93 | my iAnchor {} ;# selection anchor row (for -view list) |
|---|
| 94 | my iCursor {} ;# cursor row (for -view list) |
|---|
| 95 | my sAnchor {} ;# selection anchor icon |
|---|
| 96 | my sCursor {} ;# cursor icon |
|---|
| 97 | |
|---|
| 98 | my -view large |
|---|
| 99 | my -columns {} |
|---|
| 100 | my -onselect {} |
|---|
| 101 | my -update full |
|---|
| 102 | my -selectmode multiple |
|---|
| 103 | my -grid 70 |
|---|
| 104 | |
|---|
| 105 | -font [$this.text cget -font] |
|---|
| 106 | } {} -default text |
|---|
| 107 | |
|---|
| 108 | # name: _findColFonts |
|---|
| 109 | # args: - |
|---|
| 110 | # columns are separated by a 1 pixel line, which is in fact a space character |
|---|
| 111 | # with a special font. the loops search for a font on that system, where the |
|---|
| 112 | # space char is 1 pixel wide. |
|---|
| 113 | metawidget proc Iconbox _findColFonts {} { |
|---|
| 114 | for { set iBest 1 } { $iBest <= 4 } { incr iBest } { |
|---|
| 115 | for { set iSize 1 } { $iSize <= 4 } { incr iSize } { |
|---|
| 116 | foreach sFamily [font families] { |
|---|
| 117 | set sDescr [list $sFamily $iSize] |
|---|
| 118 | if { [font measure $sDescr " "] == $iBest } { |
|---|
| 119 | our sColFont $sDescr |
|---|
| 120 | return |
|---|
| 121 | } |
|---|
| 122 | } |
|---|
| 123 | } |
|---|
| 124 | } |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | # name: _sort |
|---|
| 128 | # args: - |
|---|
| 129 | # sorts the column that has been clicked. |
|---|
| 130 | metawidget proc Iconbox _sort {} { |
|---|
| 131 | set sColumn [lindex [$this.cols tag names current] 0] |
|---|
| 132 | set iDecr [lindex [my aCols($sColumn)] 3] |
|---|
| 133 | sort [mkw.decode $iDecr {0 -incr 1 -decr}] -dict -column $sColumn |
|---|
| 134 | my aCols($sColumn) [lreplace [my aCols($sColumn)] 3 3 [mkw.decode $iDecr {1 0 0 1}]] |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | # name: _dragX |
|---|
| 138 | # args: args: from "scrollbar -command" |
|---|
| 139 | # called on horizontal scrollbar dragging. sets the view for column and |
|---|
| 140 | # main text widget simultaneously. |
|---|
| 141 | metawidget proc Iconbox _dragX { args } { |
|---|
| 142 | eval $this.cols xview $args |
|---|
| 143 | eval $this.text xview $args |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | # name: _moveX |
|---|
| 147 | # args: args: from "text -scrollx" |
|---|
| 148 | # called when main text widgets scrolls horizontally. adjusts the column |
|---|
| 149 | # text widget and the scrollbar. |
|---|
| 150 | metawidget proc Iconbox _moveX { args } { |
|---|
| 151 | eval $this.scrh set $args |
|---|
| 152 | eval $this.cols xview moveto [lindex $args 0] |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | # name: _setTabs |
|---|
| 156 | # args: bAll: if false, only tabs in column widget are set |
|---|
| 157 | # called when a column is resized. calculates the tabs for column and main |
|---|
| 158 | # widget. each column is separated by a tab. the text position inside a column |
|---|
| 159 | # is also based on a tab. |
|---|
| 160 | metawidget proc Iconbox _setTabs { {bAll 1} } { |
|---|
| 161 | set lTabs {} |
|---|
| 162 | set iX 0 |
|---|
| 163 | |
|---|
| 164 | foreach sColumn [my lCols] { |
|---|
| 165 | mkw.lassign [my aCols($sColumn)] iWidth sAlign |
|---|
| 166 | |
|---|
| 167 | # create a tab based on column alignment. For right align, the tab must |
|---|
| 168 | # be 1 space left of the column separating tab (it's the text widget!) |
|---|
| 169 | switch $sAlign { |
|---|
| 170 | l { lappend lTabs [expr $iX] } |
|---|
| 171 | r { lappend lTabs [expr $iX+$iWidth] } |
|---|
| 172 | c { lappend lTabs [expr $iX+$iWidth/2-[my iOneSpace]] } |
|---|
| 173 | n { lappend lTabs [expr $iX+$iWidth/2] } |
|---|
| 174 | } |
|---|
| 175 | |
|---|
| 176 | # create tab for column separation |
|---|
| 177 | lappend lTabs $sAlign [expr $iX+$iWidth] c |
|---|
| 178 | incr iX $iWidth |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | $this.cols config -tabs $lTabs |
|---|
| 182 | |
|---|
| 183 | # set for main window, if indicated (depends on -update) |
|---|
| 184 | if { $bAll } { |
|---|
| 185 | $this.text config -tabs $lTabs |
|---|
| 186 | } |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | # name: _getValues |
|---|
| 190 | # args: sColumn: valid column name |
|---|
| 191 | # called before dragging a column. extracts the values of the given column |
|---|
| 192 | # out of the lData table and returns them as a list. speeds up text clipping! |
|---|
| 193 | metawidget proc Iconbox _getValues { sColumn } { |
|---|
| 194 | set iPos [_findColumn $sColumn] |
|---|
| 195 | set lValues {} |
|---|
| 196 | foreach sIcon [my lIcons] { |
|---|
| 197 | lappend lValues [lindex [lindex [my aIcons($sIcon)] 2] $iPos] |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | return $lValues |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | # name: _clipText |
|---|
| 204 | # args: sText: text to clip |
|---|
| 205 | # sColumn: column to make sText fit into |
|---|
| 206 | # returns a portion of sText that is not wider then the width of sColumn. |
|---|
| 207 | metawidget proc Iconbox _clipText { sText sColumn {iExtra 0} } { |
|---|
| 208 | # get column width and substract the width of two tab chars. |
|---|
| 209 | # strange, but the text widget allocates pixels for a tab. |
|---|
| 210 | set iColWidth [expr [lindex [my aCols($sColumn)] 0]-[my iTwoTabs]-$iExtra-1] |
|---|
| 211 | |
|---|
| 212 | # for each cycle, cut off one character at the end and see |
|---|
| 213 | # if required space is less then available one. if so, get out. |
|---|
| 214 | for { set i [expr [string length $sText]-2] } { $i >= -1 } { incr i -1 } { |
|---|
| 215 | if { [font measure [my -font] $sText] <= $iColWidth } break; |
|---|
| 216 | set sText [string range $sText 0 $i] |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | return $sText |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | # name: _clipHeader |
|---|
| 223 | # args: sColumn: valid column name |
|---|
| 224 | # called when a column is dragged. clips the column header by replacing it. |
|---|
| 225 | # therefore the tCsel tag needs to be added again. tCsel is the left portion |
|---|
| 226 | # of a column, its bindings cause the column to be selected. (the right side |
|---|
| 227 | # is tDrag, to drag a column). |
|---|
| 228 | metawidget proc Iconbox _clipHeader { sColumn } { |
|---|
| 229 | mkw.lassign [$this.cols tag ranges $sColumn] sI1 sI2 |
|---|
| 230 | $this.cols delete $sI1+1ch $sI2-2ch |
|---|
| 231 | $this.cols insert $sI1+1ch [_clipText $sColumn $sColumn] |
|---|
| 232 | |
|---|
| 233 | mkw.lassign [$this.cols tag ranges $sColumn] sI1 sI2 |
|---|
| 234 | $this.cols tag add tText $sI1 $sI2-2ch |
|---|
| 235 | } |
|---|
| 236 | |
|---|
| 237 | # name: _clipColumn |
|---|
| 238 | # args: sColumn: valid column name |
|---|
| 239 | # vValues: name of private variable that holds column values |
|---|
| 240 | # called when a column is dragged. clips the values in the main widget. |
|---|
| 241 | # vValues must contain the values, or they are retrieved. clips each |
|---|
| 242 | # cell by replacing the current value with the clipped value. |
|---|
| 243 | metawidget proc Iconbox _clipColumn { sColumn {vValues {}} } { |
|---|
| 244 | if { $vValues == {} } { |
|---|
| 245 | set lValues [_getValues $sColumn] |
|---|
| 246 | } else { |
|---|
| 247 | set lValues [my $vValues] |
|---|
| 248 | } |
|---|
| 249 | |
|---|
| 250 | set iRow 0 |
|---|
| 251 | foreach { sI1 sI2 } [$this.text tag ranges $sColumn] { |
|---|
| 252 | set sValue [lindex $lValues $iRow] |
|---|
| 253 | |
|---|
| 254 | if { [catch { |
|---|
| 255 | set iExtra [image width [$this.text image cget $sI1+1ch -image]] |
|---|
| 256 | set sI1 $sI1+2ch |
|---|
| 257 | }] } { |
|---|
| 258 | set iExtra 0 |
|---|
| 259 | set sI1 $sI1+1ch |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | $this.text delete $sI1 $sI2-1ch |
|---|
| 263 | $this.text insert $sI1 [_clipText $sValue $sColumn $iExtra] |
|---|
| 264 | incr iRow |
|---|
| 265 | } |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | # name: _clipIcon |
|---|
| 269 | # args: sIcon: icon name |
|---|
| 270 | # clips the icon's caption so that it will take up no more than 2 lines |
|---|
| 271 | # when displayed as a large icon. |
|---|
| 272 | metawidget proc Iconbox _clipIcon { sIcon {bDoit 1} } { |
|---|
| 273 | set sText [lindex [lindex [my aIcons($sIcon)] 2] 0] |
|---|
| 274 | |
|---|
| 275 | if { $bDoit } { |
|---|
| 276 | set iWidth [expr 2*[my -grid]-5] |
|---|
| 277 | |
|---|
| 278 | for { set i [expr [string length $sText]-2] } { $i >= -1 } { incr i -1 } { |
|---|
| 279 | if { [font measure [my -font] $sText] <= $iWidth } break; |
|---|
| 280 | set sText [string range $sText 0 $i] |
|---|
| 281 | } |
|---|
| 282 | } |
|---|
| 283 | |
|---|
| 284 | [lindex [my aIcons($sIcon)] 3].l configure -text $sText |
|---|
| 285 | } |
|---|
| 286 | |
|---|
| 287 | # name: _grab |
|---|
| 288 | # args: {} |
|---|
| 289 | # called before column resize. stores some values in private variables for |
|---|
| 290 | # quicker access during actual dragging: column name into sCol, update mode |
|---|
| 291 | # in numeric form in iUpd, mouse x coord in iX0, column values in lValues. |
|---|
| 292 | metawidget proc Iconbox _grab {} { |
|---|
| 293 | my sCol [lindex [$this.cols tag names current] 0] |
|---|
| 294 | my iMin [lindex [my aCols([my sCol])] 2] |
|---|
| 295 | my iUpd [mkw.decode [my -update] {none 0 partial 1 full 2}] |
|---|
| 296 | my iX0 [expr [lindex [my aCols([my sCol])] 0]-[winfo pointerx .]] |
|---|
| 297 | my lValues [_getValues [my sCol]] |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | # name: _drag |
|---|
| 301 | # args: - |
|---|
| 302 | # called during dragging. calculates new width and udpates column data |
|---|
| 303 | # with it. sets tabs and clips column and, if -update == full, text. |
|---|
| 304 | metawidget proc Iconbox _drag {} { |
|---|
| 305 | set iX [expr [my iX0]+[winfo pointerx .]] |
|---|
| 306 | if { $iX < [my iMin] } { |
|---|
| 307 | set iX [my iMin] |
|---|
| 308 | } |
|---|
| 309 | |
|---|
| 310 | my aCols([my sCol]) [lreplace [my aCols([my sCol])] 0 0 $iX] |
|---|
| 311 | |
|---|
| 312 | _setTabs [my iUpd] |
|---|
| 313 | _clipHeader [my sCol] |
|---|
| 314 | |
|---|
| 315 | if { [my iUpd] == 2 } { |
|---|
| 316 | _clipColumn [my sCol] lValues |
|---|
| 317 | } |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | # name: _drop |
|---|
| 321 | # args: - |
|---|
| 322 | # called after column resize. sets tabs and clips one last time. |
|---|
| 323 | metawidget proc Iconbox _drop {} { |
|---|
| 324 | _setTabs |
|---|
| 325 | _clipHeader [my sCol] |
|---|
| 326 | _clipColumn [my sCol] lValues |
|---|
| 327 | |
|---|
| 328 | if { [my -view] == "list" && [my sCol] == [lindex [my lCols] 0] } { |
|---|
| 329 | _setListmodeTags |
|---|
| 330 | } |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | # name: _findIcon |
|---|
| 334 | # args: sIcon: icon name |
|---|
| 335 | # checks if given icon exists. |
|---|
| 336 | metawidget proc Iconbox _findIcon { sIcon } { |
|---|
| 337 | set iPos [lsearch [my lIcons] $sIcon] |
|---|
| 338 | |
|---|
| 339 | if { $iPos == -1 } { |
|---|
| 340 | error "Iconbox $this: Icon $sIcon does not exist." |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | return $iPos |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | # name: _findColumn |
|---|
| 347 | # args: sColumn: column name |
|---|
| 348 | # checks if given column is valid and returns its position. |
|---|
| 349 | metawidget proc Iconbox _findColumn { sColumn } { |
|---|
| 350 | set iPos [lsearch [my lCols] $sColumn] |
|---|
| 351 | |
|---|
| 352 | if { $iPos == -1 } { |
|---|
| 353 | error "Iconbox $this: Unknown column '$sColumn'." |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | return $iPos |
|---|
| 357 | } |
|---|
| 358 | |
|---|
| 359 | # name: _selIcon |
|---|
| 360 | # args: sIcon: icon to set or remove selection for |
|---|
| 361 | # bSelected: 0: remove, 1: set selection |
|---|
| 362 | # sets or removes the selection for an item (or all items, if sIcon == *). |
|---|
| 363 | # changes the look of the item as well as the selection list lSel. |
|---|
| 364 | metawidget proc Iconbox _selIcon { sIcon {bSelected 0} } { |
|---|
| 365 | if { $sIcon == "*" } { |
|---|
| 366 | foreach sIcon [my lIcons] { |
|---|
| 367 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 368 | $wIcon.l config -bg [$this.text cget -back] -fg [$this.text cget -fore] |
|---|
| 369 | } |
|---|
| 370 | my lSel {} |
|---|
| 371 | } elseif { $bSelected } { |
|---|
| 372 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 373 | $wIcon.l config -bg [$this.text cget -selectback] -fg [$this.text cget -selectfore] |
|---|
| 374 | my lSel [mkw.lextend [my lSel] $sIcon] |
|---|
| 375 | } else { |
|---|
| 376 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 377 | $wIcon.l config -bg [$this.text cget -back] -fg [$this.text cget -fore] |
|---|
| 378 | my lSel [mkw.lshrink [my lSel] $sIcon] |
|---|
| 379 | } |
|---|
| 380 | } |
|---|
| 381 | |
|---|
| 382 | # name: _select |
|---|
| 383 | # args: sStep : m: mouse, i: icon, r: row, p: page |
|---|
| 384 | # iDir : 0 for mouse, 1 or -1 otherwise |
|---|
| 385 | # sAction: n: new selection, a: append, c: cursor only, t: toggle |
|---|
| 386 | # sMode : s: single cell, r: range of cells, c: cursor only |
|---|
| 387 | # calls one of the two members that really set the selection. |
|---|
| 388 | metawidget proc Iconbox _select { sStep iDir sAction sMode } { |
|---|
| 389 | if { [my -view] == "list" } { |
|---|
| 390 | _select1 $sStep $iDir $sAction $sMode |
|---|
| 391 | } else { |
|---|
| 392 | _select2 $sStep $iDir $sAction $sMode |
|---|
| 393 | } |
|---|
| 394 | } |
|---|
| 395 | |
|---|
| 396 | # name: _select1 |
|---|
| 397 | # args: see _select |
|---|
| 398 | # called when view mode is "list". uses the standard sel tag for selection. |
|---|
| 399 | metawidget proc Iconbox _select1 { sStep iDir sAction sMode } { |
|---|
| 400 | focus $this.text |
|---|
| 401 | |
|---|
| 402 | # step 1: determine the new cursor position |
|---|
| 403 | switch $sStep { |
|---|
| 404 | m { |
|---|
| 405 | set iY [expr [winfo pointery $this.text]-[winfo rooty $this.text]] |
|---|
| 406 | set iRow [lindex [split [$this.text index @0,$iY] .] 0] |
|---|
| 407 | if { $iRow > [llength [my lIcons]] } return |
|---|
| 408 | } |
|---|
| 409 | r { |
|---|
| 410 | set iRows [llength [my lIcons]] |
|---|
| 411 | set iRow [expr [my iCursor]+$iDir] |
|---|
| 412 | if { $iRow < 1 } { set iRow 1 } |
|---|
| 413 | if { $iRow > $iRows } { set iRow $iRows } |
|---|
| 414 | } |
|---|
| 415 | p { |
|---|
| 416 | set iRows [llength [my lIcons]] |
|---|
| 417 | set iRowH [expr [font metrics [my -font] -linespace]+1] |
|---|
| 418 | set iStep [expr [winfo height $this.text]/$iRowH] |
|---|
| 419 | set iRow [expr [my iCursor]+$iDir*$iStep] |
|---|
| 420 | if { $iRow < 1 } { set iRow 1 } |
|---|
| 421 | if { $iRow > $iRows } { set iRow $iRows } |
|---|
| 422 | } |
|---|
| 423 | i { |
|---|
| 424 | return |
|---|
| 425 | } |
|---|
| 426 | } |
|---|
| 427 | |
|---|
| 428 | # step 2: get indexes of new cursor cell and set cursor tag there |
|---|
| 429 | $this.text tag remove cursor 1.0 end |
|---|
| 430 | mkw.lassign [$this.text tag nextrange tIcon $iRow.0] sI1 sI2 |
|---|
| 431 | $this.text tag add cursor $sI1 $sI2 |
|---|
| 432 | my iCursor $iRow |
|---|
| 433 | |
|---|
| 434 | # step 3: determine if sel tag will be removed or added to cells |
|---|
| 435 | switch $sAction { |
|---|
| 436 | n - a { |
|---|
| 437 | set sMethod add |
|---|
| 438 | } |
|---|
| 439 | t { |
|---|
| 440 | if { [lsearch [$this.text tag names $iRow.2] sel] != -1 } { |
|---|
| 441 | set sMethod remove |
|---|
| 442 | } else { |
|---|
| 443 | set sMethod add |
|---|
| 444 | } |
|---|
| 445 | } |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | # step 4: remove existing sel tags, if a new selection is set |
|---|
| 449 | if { $sAction == "n" || [my -selectmode] == "single" } { |
|---|
| 450 | $this.text tag remove sel 1.0 end |
|---|
| 451 | } |
|---|
| 452 | |
|---|
| 453 | # step 5: actually set the sel tag |
|---|
| 454 | if { [info exists sMethod] } { |
|---|
| 455 | if { ! ( $sMode == "r" && [my -selectmode] == "multiple" ) } { |
|---|
| 456 | my iAnchor [my iCursor] |
|---|
| 457 | } |
|---|
| 458 | |
|---|
| 459 | set iR1 [expr ([my iCursor]<[my iAnchor])?[my iCursor]:[my iAnchor]] |
|---|
| 460 | set iR2 [expr ([my iCursor]>[my iAnchor])?[my iCursor]:[my iAnchor]] |
|---|
| 461 | |
|---|
| 462 | for { set i $iR1 } { $i <= $iR2 } { incr i } { |
|---|
| 463 | mkw.lassign [$this.text tag nextrange tIcon $i.0] sI1 sI2 |
|---|
| 464 | $this.text tag $sMethod sel $sI1 $sI2 |
|---|
| 465 | } |
|---|
| 466 | } |
|---|
| 467 | |
|---|
| 468 | $this.text tag raise sel |
|---|
| 469 | $this.text tag raise cursor |
|---|
| 470 | $this.text see $iRow.0 |
|---|
| 471 | |
|---|
| 472 | my lSel {} |
|---|
| 473 | foreach { sI1 sI2 } [$this.text tag ranges sel] { |
|---|
| 474 | my lSel [linsert [my lSel] end [lindex [$this.text tag names $sI1] 0]] |
|---|
| 475 | } |
|---|
| 476 | |
|---|
| 477 | eval [my -onselect] |
|---|
| 478 | } |
|---|
| 479 | |
|---|
| 480 | # name: _select2 |
|---|
| 481 | # args: see _select |
|---|
| 482 | # wWindow: icon that was clicked |
|---|
| 483 | # called when view mode is "large" or "list". can't use sel tag, because the |
|---|
| 484 | # icons are real widgets. rather modifies there background color etc. |
|---|
| 485 | metawidget proc Iconbox _select2 { sStep iDir sAction sMode {wWindow {}} } { |
|---|
| 486 | focus $this.text |
|---|
| 487 | |
|---|
| 488 | # try to unset cursor icon |
|---|
| 489 | catch { |
|---|
| 490 | set wIcon [lindex [my aIcons([my sCursor])] 3] |
|---|
| 491 | $wIcon.l config -relief flat |
|---|
| 492 | } |
|---|
| 493 | |
|---|
| 494 | # step 1: determine icon to set cursor for |
|---|
| 495 | if { $sStep == "m" } { |
|---|
| 496 | if { $wWindow == {} } return |
|---|
| 497 | |
|---|
| 498 | # search icon name. something to improve! |
|---|
| 499 | set wParent [winfo parent $wWindow] |
|---|
| 500 | foreach sIcon [my lIcons] { |
|---|
| 501 | if { ! [string compare [lindex [my aIcons($sIcon)] 3] $wParent] } break |
|---|
| 502 | } |
|---|
| 503 | |
|---|
| 504 | my sCursor $sIcon |
|---|
| 505 | } else { |
|---|
| 506 | set iPos [lsearch [my lIcons] [my sCursor]] |
|---|
| 507 | |
|---|
| 508 | if { $sStep == "i" } { |
|---|
| 509 | set iStep 1 |
|---|
| 510 | } else { |
|---|
| 511 | mkw.lassign [$this.text bbox 1.$iPos] iX iY iW iH |
|---|
| 512 | set iStep [expr [winfo width $this.text]/$iW] |
|---|
| 513 | |
|---|
| 514 | if { $sStep == "p" } { |
|---|
| 515 | set iStep [expr $iStep * [winfo height $this.text]/$iH] |
|---|
| 516 | } |
|---|
| 517 | } |
|---|
| 518 | |
|---|
| 519 | #set sIcon [myindex lIcons [expr $iPos+$iDir*$iStep]] |
|---|
| 520 | set sIcon [lindex [my lIcons] [expr $iPos+$iDir*$iStep]] |
|---|
| 521 | if { $sIcon != {} } { |
|---|
| 522 | my sCursor $sIcon |
|---|
| 523 | } |
|---|
| 524 | } |
|---|
| 525 | |
|---|
| 526 | # step 2: determine if selection is set or removed |
|---|
| 527 | switch $sAction { |
|---|
| 528 | n - a { |
|---|
| 529 | set iSetsel 1 |
|---|
| 530 | } |
|---|
| 531 | t { |
|---|
| 532 | if { [lsearch [my lSel] [my sCursor]] == -1 } { |
|---|
| 533 | set iSetsel 1 |
|---|
| 534 | } else { |
|---|
| 535 | set iSetsel 0 |
|---|
| 536 | } |
|---|
| 537 | } |
|---|
| 538 | } |
|---|
| 539 | |
|---|
| 540 | # step 3: remove existing selection, if new selection will be set |
|---|
| 541 | if { $sAction == "n" || [my -selectmode] == "single" } { |
|---|
| 542 | _selIcon * 0 |
|---|
| 543 | } |
|---|
| 544 | |
|---|
| 545 | # step 4: actually set selection |
|---|
| 546 | if { [info exists iSetsel] } { |
|---|
| 547 | if { $sMode != "r" || [my -selectmode] == "single" } { |
|---|
| 548 | my sAnchor [my sCursor] |
|---|
| 549 | } |
|---|
| 550 | |
|---|
| 551 | set iPos1 [lsearch [my lIcons] [my sAnchor]] |
|---|
| 552 | set iPos2 [lsearch [my lIcons] [my sCursor]] |
|---|
| 553 | set iP1 [expr ($iPos1<$iPos2)?$iPos1:$iPos2] |
|---|
| 554 | set iP2 [expr ($iPos1>$iPos2)?$iPos1:$iPos2] |
|---|
| 555 | set lIcons [lrange [my lIcons] $iP1 $iP2] |
|---|
| 556 | foreach sIcon $lIcons { |
|---|
| 557 | _selIcon $sIcon $iSetsel |
|---|
| 558 | } |
|---|
| 559 | } |
|---|
| 560 | |
|---|
| 561 | # set cursor icon |
|---|
| 562 | catch { |
|---|
| 563 | set wIcon [lindex [my aIcons([my sCursor])] 3] |
|---|
| 564 | $wIcon.l config -relief solid |
|---|
| 565 | } |
|---|
| 566 | |
|---|
| 567 | $this.text see 1.[lsearch [my lIcons] [my sCursor]] |
|---|
| 568 | } |
|---|
| 569 | |
|---|
| 570 | # name: _setListmodeTags |
|---|
| 571 | # args: - |
|---|
| 572 | # for -view == list, the frame widgets are removed and plain text is used, |
|---|
| 573 | # plus the small image on the left side. in order for selection and column |
|---|
| 574 | # dragging to work, each line needs some tags. |
|---|
| 575 | metawidget proc Iconbox _setListmodeTags {} { |
|---|
| 576 | # set special tags for the icon itself (left column). tIcon is needed to |
|---|
| 577 | # set the selection. each icon gets a tag with the icon's name for |
|---|
| 578 | # identification, e.g. to attach bindings or get the selection. |
|---|
| 579 | foreach { sI1 sI2 } [$this.text tag ranges [lindex [my lCols] 0]] sIcon [my lIcons] { |
|---|
| 580 | $this.text tag add tIcon $sI1+2ch $sI2-1ch |
|---|
| 581 | $this.text tag add $sIcon $sI1+1ch $sI2-1ch |
|---|
| 582 | $this.text tag lower $sIcon |
|---|
| 583 | } |
|---|
| 584 | |
|---|
| 585 | # take over selection from lSel list. |
|---|
| 586 | foreach sIcon [my lSel] { |
|---|
| 587 | mkw.lassign [$this.text tag ranges $sIcon] sI1 sI2 |
|---|
| 588 | $this.text tag add sel $sI1+1ch $sI2 |
|---|
| 589 | } |
|---|
| 590 | |
|---|
| 591 | # add all the individual bindings to the icons. |
|---|
| 592 | foreach lBinds [myinfo vars lBind.*] { |
|---|
| 593 | mkw.lassign [split $lBinds .] x sIcon sBind |
|---|
| 594 | foreach sScript [my $lBinds] { |
|---|
| 595 | $this.text tag bind $sIcon $sBind $sScript |
|---|
| 596 | } |
|---|
| 597 | } |
|---|
| 598 | |
|---|
| 599 | # set cursor tag |
|---|
| 600 | my iCursor [expr [lsearch [my lIcons] [my sCursor]]+1] |
|---|
| 601 | if { [my iCursor] } { |
|---|
| 602 | mkw.lassign [$this.text tag nextrange tIcon [my iCursor].0] sI1 sI2 |
|---|
| 603 | $this.text tag add cursor $sI1 $sI2 |
|---|
| 604 | } |
|---|
| 605 | } |
|---|
| 606 | |
|---|
| 607 | # name: _redraw |
|---|
| 608 | # args: - |
|---|
| 609 | # draws the metawidget, depending on -view. for -view == large or small, |
|---|
| 610 | # the icon frames are inserted into the text widget with "text window create". |
|---|
| 611 | # for -view == list, the frames are not used. instead, the small image is |
|---|
| 612 | # inserted at the beginning of each line, followed by the icon's -values. |
|---|
| 613 | metawidget proc Iconbox _redraw {} { |
|---|
| 614 | # get the widgets out the text widget and unpack them. |
|---|
| 615 | foreach wWindow [$this.text window names] { |
|---|
| 616 | pack $wWindow |
|---|
| 617 | pack forget $wWindow |
|---|
| 618 | } |
|---|
| 619 | |
|---|
| 620 | if { [my -view] == "list" } { |
|---|
| 621 | # clear contents and show column row and scrollbar |
|---|
| 622 | $this.text delete 1.0 end |
|---|
| 623 | grid $this.cols -column 0 -row 0 -sticky news |
|---|
| 624 | grid $this.scrh -column 0 -row 2 -sticky we |
|---|
| 625 | |
|---|
| 626 | $this.text config -wrap none |
|---|
| 627 | |
|---|
| 628 | # create rows |
|---|
| 629 | set iRow 0 |
|---|
| 630 | foreach sIcon [my lIcons] { |
|---|
| 631 | incr iRow |
|---|
| 632 | $this.text insert $iRow.end "\n" |
|---|
| 633 | |
|---|
| 634 | # first tab is the text anchor, second is the column separator tab. |
|---|
| 635 | # each field gets a tag with its column's name. |
|---|
| 636 | foreach sValue [lindex [my aIcons($sIcon)] 2] sColumn [my lCols] { |
|---|
| 637 | $this.text insert $iRow.end "\t\t" |
|---|
| 638 | $this.text tag add $sColumn $iRow.end-2ch $iRow.end |
|---|
| 639 | $this.text insert $iRow.end-1ch $sValue |
|---|
| 640 | } |
|---|
| 641 | |
|---|
| 642 | $this.text image create $iRow.1 -image [lindex [my aIcons($sIcon)] 1] -align center -padx 2 |
|---|
| 643 | } |
|---|
| 644 | |
|---|
| 645 | # clip all columns and create tags |
|---|
| 646 | foreach sCol [my lCols] { |
|---|
| 647 | _clipColumn $sCol |
|---|
| 648 | } |
|---|
| 649 | |
|---|
| 650 | _setListmodeTags |
|---|
| 651 | } else { |
|---|
| 652 | # if we just had -view list, then clean up first |
|---|
| 653 | if { [winfo ismapped $this.cols] } { |
|---|
| 654 | # discard column widget and scrollbar |
|---|
| 655 | grid forget $this.cols $this.scrh |
|---|
| 656 | |
|---|
| 657 | # remove selection from all icons... |
|---|
| 658 | _selIcon * 0 |
|---|
| 659 | |
|---|
| 660 | # ...and take over what has been selected in the list view. |
|---|
| 661 | foreach { sI1 sI2 } [$this.text tag ranges sel] { |
|---|
| 662 | set sIcon [lindex [$this.text tag names $sI1] 0] |
|---|
| 663 | _selIcon $sIcon 1 |
|---|
| 664 | } |
|---|
| 665 | |
|---|
| 666 | # set the cursor icon |
|---|
| 667 | catch { [lindex [my aIcons([my sCursor])] 3].l config -relief flat } |
|---|
| 668 | my sCursor [lindex [my lIcons] [expr [my iCursor]-1]] |
|---|
| 669 | catch { [lindex [my aIcons([my sCursor])] 3].l config -relief solid } |
|---|
| 670 | } |
|---|
| 671 | |
|---|
| 672 | $this.text delete 1.0 end |
|---|
| 673 | $this.text config -wrap word |
|---|
| 674 | |
|---|
| 675 | # set some variables depending on the view |
|---|
| 676 | set iIcon [mkw.decode [my -view] {small 1 large 0 }] |
|---|
| 677 | set iWrap [mkw.decode [my -view] "small 0 large [my -grid]"] |
|---|
| 678 | set iGapx [mkw.decode [my -view] {small 15 large 5}] |
|---|
| 679 | set iGapy [mkw.decode [my -view] {small 0 large 10}] |
|---|
| 680 | set iMaxw 0 |
|---|
| 681 | set iMaxh 0 |
|---|
| 682 | |
|---|
| 683 | foreach sIcon [my lIcons] { |
|---|
| 684 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 685 | $wIcon.i config -image [lindex [my aIcons($sIcon)] $iIcon] |
|---|
| 686 | $wIcon.l config -wrap $iWrap |
|---|
| 687 | set iIWidth [winfo reqwidth $wIcon.i] |
|---|
| 688 | set iIHeight [winfo reqheight $wIcon.i] |
|---|
| 689 | set iLWidth [winfo reqwidth $wIcon.l] |
|---|
| 690 | set iLHeight [winfo reqheight $wIcon.l] |
|---|
| 691 | |
|---|
| 692 | # depending on the view, we must track the largest icon |
|---|
| 693 | if { $iIcon == 0 } { ;# large icons |
|---|
| 694 | # width is constant, height is sum of icon and text |
|---|
| 695 | set iMaxw [my -grid] |
|---|
| 696 | set iReqh [expr $iIHeight+$iLHeight] |
|---|
| 697 | if { $iReqh > $iMaxh } { |
|---|
| 698 | set iMaxh $iReqh |
|---|
| 699 | } |
|---|
| 700 | |
|---|
| 701 | place $wIcon.i -relx .5 -rely 0 -anchor n |
|---|
| 702 | place $wIcon.l -x 0 -y $iIHeight -relx .5 -rely 0 -anchor n |
|---|
| 703 | } else { ;# small icons |
|---|
| 704 | # height is the greater of image and text, width is their sum |
|---|
| 705 | set iReqw [expr $iIWidth+$iLWidth] |
|---|
| 706 | if { $iReqw > $iMaxw } { |
|---|
| 707 | set iMaxw $iReqw |
|---|
| 708 | } |
|---|
| 709 | |
|---|
| 710 | set iReqh [expr ($iIHeight>$iLHeight)?$iIHeight:$iLHeight] |
|---|
| 711 | if { $iReqh > $iMaxh } { |
|---|
| 712 | set iMaxh $iReqh |
|---|
| 713 | } |
|---|
| 714 | |
|---|
| 715 | place $wIcon.i -relx 0 -rely .5 -anchor w |
|---|
| 716 | place $wIcon.l -x $iIWidth -y 0 -relx 0 -rely .5 -anchor w |
|---|
| 717 | } |
|---|
| 718 | |
|---|
| 719 | $this.text window create end -align bottom -window $wIcon |
|---|
| 720 | } |
|---|
| 721 | |
|---|
| 722 | # set all icons to the size of the largest icon, so they get oriented. |
|---|
| 723 | incr iMaxw $iGapx |
|---|
| 724 | incr iMaxh $iGapy |
|---|
| 725 | foreach sIcon [my lIcons] { |
|---|
| 726 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 727 | $wIcon config -width $iMaxw -height $iMaxh |
|---|
| 728 | } |
|---|
| 729 | } |
|---|
| 730 | |
|---|
| 731 | catch { see [my sCursor] } |
|---|
| 732 | } |
|---|
| 733 | |
|---|
| 734 | # name: -columns |
|---|
| 735 | # args: lColumns: list of column specifications |
|---|
| 736 | # sets new columns. a column spec must have the format {text alignment |
|---|
| 737 | # width minwidth}. text is mandatory and must be unique, the rest is optional. |
|---|
| 738 | metawidget proc Iconbox -columns { lColumns } { |
|---|
| 739 | my lCols {} |
|---|
| 740 | eval unmy aCols |
|---|
| 741 | $this.cols delete 1.0 end |
|---|
| 742 | |
|---|
| 743 | foreach lColumn $lColumns { |
|---|
| 744 | set sAlign l |
|---|
| 745 | set iWidth 100 |
|---|
| 746 | set iMinwidth 10 |
|---|
| 747 | mkw.lassign $lColumn sText sAlign iWidth iMinwidth |
|---|
| 748 | |
|---|
| 749 | my aCols($sText) [list $iWidth $sAlign $iMinwidth 0] |
|---|
| 750 | my lCols [linsert [my lCols] end $sText] |
|---|
| 751 | |
|---|
| 752 | # first tab is the text anchor, second is the column separator tab. tags: |
|---|
| 753 | # tCols for the 3d look, tCsep for the gap, tDrag for resizing. |
|---|
| 754 | $this.cols insert 1.end "\t\t " |
|---|
| 755 | $this.cols tag add tCols 1.end-3ch 1.end-1ch |
|---|
| 756 | $this.cols tag add tCsep 1.end-1ch |
|---|
| 757 | $this.cols tag add tDrag 1.end-2ch 1.end |
|---|
| 758 | $this.cols tag add $sText 1.end-3ch 1.end |
|---|
| 759 | |
|---|
| 760 | $this.cols tag lower $sText |
|---|
| 761 | _clipHeader $sText |
|---|
| 762 | } |
|---|
| 763 | |
|---|
| 764 | my -columns $lColumns |
|---|
| 765 | _setTabs |
|---|
| 766 | } |
|---|
| 767 | |
|---|
| 768 | # name: -view |
|---|
| 769 | # args: sView: new view to set |
|---|
| 770 | # sets a new view mode: small icons, large icons, or list. |
|---|
| 771 | metawidget proc Iconbox -view { sView } { |
|---|
| 772 | my -view [mkw.complete $sView {small large list}] |
|---|
| 773 | _redraw |
|---|
| 774 | } |
|---|
| 775 | |
|---|
| 776 | # name: -update |
|---|
| 777 | # args: sMode: update mode for column resizing |
|---|
| 778 | # none resizes the column only, not the text. partial resizes the text but |
|---|
| 779 | # does not do clipping. full does everything. |
|---|
| 780 | metawidget proc Iconbox -update { sMode } { |
|---|
| 781 | my -update [mkw.complete $sMode {full partial none}] |
|---|
| 782 | } |
|---|
| 783 | |
|---|
| 784 | # name: -selectmode |
|---|
| 785 | # args: sMode: single, multiple |
|---|
| 786 | # if single, only one cell can be selected. |
|---|
| 787 | metawidget proc Iconbox -selectmode { sMode } { |
|---|
| 788 | my -selectmode [mkw.complete $sMode {single multiple}] |
|---|
| 789 | } |
|---|
| 790 | |
|---|
| 791 | # name: -font |
|---|
| 792 | # args: sFont: font specification |
|---|
| 793 | # sets the fonts for all two text widgets. separate fonts would have made |
|---|
| 794 | # geometry management too complicated. |
|---|
| 795 | metawidget proc Iconbox -font { sFont } { |
|---|
| 796 | $this.text config -font $sFont |
|---|
| 797 | $this.cols config -font $sFont |
|---|
| 798 | |
|---|
| 799 | my -font $sFont |
|---|
| 800 | |
|---|
| 801 | # calculate new width of those strange placeholders |
|---|
| 802 | my iOneSpace [font measure $sFont " "] |
|---|
| 803 | my iTwoTabs [font measure $sFont "\t\t"] |
|---|
| 804 | |
|---|
| 805 | # clip everything, in case font is larger then old one |
|---|
| 806 | foreach sCol [my lCols] { |
|---|
| 807 | _clipHeader $sCol |
|---|
| 808 | _clipColumn $sCol |
|---|
| 809 | } |
|---|
| 810 | |
|---|
| 811 | # update icons |
|---|
| 812 | foreach sIcon [my lIcons] { |
|---|
| 813 | [lindex [my aIcons($sIcon)] 3].l config -font $sFont |
|---|
| 814 | _clipIcon $sIcon |
|---|
| 815 | } |
|---|
| 816 | } |
|---|
| 817 | |
|---|
| 818 | # name: insert |
|---|
| 819 | # args: iRow: position to insert icon |
|---|
| 820 | # sIcon: icon name |
|---|
| 821 | # args: option-value pairs |
|---|
| 822 | # inserts a new icon. an icon is a frame with two labels inside, one for |
|---|
| 823 | # the image, one for the text. packing order depends on -view and is done in |
|---|
| 824 | # _redraw. |
|---|
| 825 | metawidget proc Iconbox insert { iPos sIcon args } { |
|---|
| 826 | if { [myinfo exists aIcons($sIcon)] } { |
|---|
| 827 | error "Iconbox $this: Icon $sIcon already exists." |
|---|
| 828 | } |
|---|
| 829 | |
|---|
| 830 | # eliminate dots and blanks, then get a unique widget name. ("read-me" |
|---|
| 831 | # is ok, but "read.me" also maps to read-me. sequence will add "#1" etc.). |
|---|
| 832 | regsub -all {[. ]} $sIcon {-} sNodots |
|---|
| 833 | set wIcon $this._i$sNodots |
|---|
| 834 | while { [winfo exists $wIcon] } { |
|---|
| 835 | if { [catch { incr iSeq }] } { set iSeq 1 } |
|---|
| 836 | set wIcon $this._i$sNodots#$iSeq |
|---|
| 837 | } |
|---|
| 838 | |
|---|
| 839 | # create widgets. two labels for image and caption. |
|---|
| 840 | frame $wIcon -border 0 -bg white |
|---|
| 841 | label $wIcon.i -padx 0 -pady 0 -bg white -border 0 |
|---|
| 842 | label $wIcon.l -padx 0 -pady 0 -bg white -border 0 -font [my -font] |
|---|
| 843 | |
|---|
| 844 | # replace Label class bindings with selection bindings |
|---|
| 845 | bindtags $wIcon.i [mkw.lchange [bindtags $wIcon.i] Label Iconboxicon] |
|---|
| 846 | bindtags $wIcon.l [mkw.lchange [bindtags $wIcon.l] Label Iconboxicon] |
|---|
| 847 | |
|---|
| 848 | # make sure packing order is right (icon above text widget) |
|---|
| 849 | raise $wIcon $this.text |
|---|
| 850 | |
|---|
| 851 | # icon properties: large image, small image, values for -view == list, widget, userdata |
|---|
| 852 | my aIcons($sIcon) [list {} {} {} $wIcon {}] |
|---|
| 853 | my lIcons [linsert [my lIcons] $iPos $sIcon] |
|---|
| 854 | |
|---|
| 855 | eval iconconfigure {$sIcon} -text {$sIcon} $args |
|---|
| 856 | |
|---|
| 857 | # queue an event for _redraw when idle. delete any old events |
|---|
| 858 | # so that there will be exactly one redraw after several inserts. |
|---|
| 859 | catch { after cancel [my jRedraw] } |
|---|
| 860 | my jRedraw [after idle $this _redraw] |
|---|
| 861 | } |
|---|
| 862 | |
|---|
| 863 | # name: delete |
|---|
| 864 | # args: args: list of icon names, nothing for all icons |
|---|
| 865 | # deletes the icons specified by $args. destroys the frame widget and, if |
|---|
| 866 | # -view == list (where the frames are not packed), deletes the associated |
|---|
| 867 | # rows. finally updates internal data. |
|---|
| 868 | metawidget proc Iconbox delete { args } { |
|---|
| 869 | if { ! [llength $args] } { |
|---|
| 870 | foreach sIcon [my lIcons] { |
|---|
| 871 | destroy [lindex [my aIcons($sIcon)] 3] |
|---|
| 872 | } |
|---|
| 873 | |
|---|
| 874 | $this.text delete 1.0 end |
|---|
| 875 | |
|---|
| 876 | my lSel {} |
|---|
| 877 | my lIcons {} |
|---|
| 878 | eval unmy aIcons |
|---|
| 879 | } else { |
|---|
| 880 | foreach sIcon $args { |
|---|
| 881 | _findIcon $sIcon |
|---|
| 882 | destroy [lindex [my aIcons($sIcon)] 3] |
|---|
| 883 | |
|---|
| 884 | if { [my -view] == "list" } { |
|---|
| 885 | mkw.lassign [$this.text tag ranges $sIcon] sI1 sI2 |
|---|
| 886 | $this.text delete "$sI1 linestart" "$sI1 linestart+1line" |
|---|
| 887 | } |
|---|
| 888 | |
|---|
| 889 | my lSel [mkw.lshrink [my lSel] $sIcon] |
|---|
| 890 | my lIcons [mkw.lshrink [my lIcons] $sIcon] |
|---|
| 891 | unmy aIcons($sIcon) |
|---|
| 892 | } |
|---|
| 893 | } |
|---|
| 894 | } |
|---|
| 895 | |
|---|
| 896 | # name: iconconfigure |
|---|
| 897 | # args: sIcon: icon name |
|---|
| 898 | # args: option-value pairs |
|---|
| 899 | # configures an icon. the three special options are stored in the icon's |
|---|
| 900 | # record, all others (like -text for the icon's caption) are applied |
|---|
| 901 | # to the icon's text label. |
|---|
| 902 | metawidget proc Iconbox iconconfigure { sIcon args } { |
|---|
| 903 | set iRow [expr [_findIcon $sIcon] + 1] |
|---|
| 904 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 905 | set args [mkw.options $args * {-text *} {-image *} {-smallimage *} {-values *} {-user *}] |
|---|
| 906 | |
|---|
| 907 | if { $args != {} } { |
|---|
| 908 | eval $wIcon.l configure $args |
|---|
| 909 | } |
|---|
| 910 | |
|---|
| 911 | if { [info exists -text] } { |
|---|
| 912 | my aIcons($sIcon) [lreplace [my aIcons($sIcon)] 2 2 [lreplace [lindex [my aIcons($sIcon)] 2] 0 0 ${-text}]] |
|---|
| 913 | _clipIcon $sIcon |
|---|
| 914 | |
|---|
| 915 | if { ! [catch {$this.text index $sIcon.first}] } { |
|---|
| 916 | mkw.lassign [$this.text tag ranges $sIcon] sI1 sI2 |
|---|
| 917 | $this.text delete $sI1 $sI2 |
|---|
| 918 | $this.text insert $sI1 ${-text} |
|---|
| 919 | _setListmodeTags |
|---|
| 920 | } |
|---|
| 921 | } |
|---|
| 922 | |
|---|
| 923 | if { [info exists -image] } { |
|---|
| 924 | my aIcons($sIcon) [lreplace [my aIcons($sIcon)] 0 0 ${-image}] |
|---|
| 925 | if { [my -view] == "large" } { |
|---|
| 926 | $wIcon.i config -image ${-image} |
|---|
| 927 | } |
|---|
| 928 | } |
|---|
| 929 | |
|---|
| 930 | if { [info exists -smallimage] } { |
|---|
| 931 | my aIcons($sIcon) [lreplace [my aIcons($sIcon)] 1 1 ${-smallimage}] |
|---|
| 932 | if { [my -view] == "small" } { |
|---|
| 933 | $wIcon.i config -image ${-smallimage} |
|---|
| 934 | } |
|---|
| 935 | } |
|---|
| 936 | |
|---|
| 937 | if { [info exists -values] } { |
|---|
| 938 | if { [llength ${-values}] >= [llength [my lCols]] } { |
|---|
| 939 | error "Iconbox $this: Too many values for option -values." |
|---|
| 940 | } |
|---|
| 941 | |
|---|
| 942 | set lValues [lindex [my aIcons($sIcon)] 2] |
|---|
| 943 | set lValues [concat [list [lindex $lValues 0]] ${-values}] |
|---|
| 944 | my aIcons($sIcon) [lreplace [my aIcons($sIcon)] 2 2 $lValues] |
|---|
| 945 | |
|---|
| 946 | if { ! [catch {$this.text index $sIcon.first}] } { |
|---|
| 947 | foreach sValue ${-values} sColumn [lrange [my lCols] 1 end] { |
|---|
| 948 | mkw.lassign [$this.text tag nextrange $sColumn $iRow.0] sI1 sI2 |
|---|
| 949 | $this.text delete $sI1+1ch $sI2-1ch |
|---|
| 950 | $this.text insert $sI1+1ch $sValue |
|---|
| 951 | } |
|---|
| 952 | } |
|---|
| 953 | } |
|---|
| 954 | |
|---|
| 955 | if { [info exists -user] } { |
|---|
| 956 | my aIcons($sIcon) [lreplace [my aIcons($sIcon)] 4 4 ${-user}] |
|---|
| 957 | } |
|---|
| 958 | } |
|---|
| 959 | |
|---|
| 960 | # name: iconcget |
|---|
| 961 | # args: sIcon: icon name |
|---|
| 962 | # args: option-value pairs |
|---|
| 963 | # retrieves an icon's option. the values of the three special options are |
|---|
| 964 | # taken from the icon's record, all others from the icon's text label. |
|---|
| 965 | metawidget proc Iconbox iconcget { sIcon args } { |
|---|
| 966 | _findIcon $sIcon |
|---|
| 967 | |
|---|
| 968 | switch -- $sOption { |
|---|
| 969 | -icon { return [lindex [my aIcons($sItem)] 0] } |
|---|
| 970 | -smallicon { return [lindex [my aIcons($sItem)] 1] } |
|---|
| 971 | -values { return [lindex [my aIcons($sItem)] 2] } |
|---|
| 972 | -user { return [lindex [my aIcons($sItem)] 4] } |
|---|
| 973 | default { |
|---|
| 974 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 975 | eval $wIcon.l cget $args |
|---|
| 976 | } |
|---|
| 977 | } |
|---|
| 978 | } |
|---|
| 979 | |
|---|
| 980 | # name: iconbind |
|---|
| 981 | # args: sIcon: icon name |
|---|
| 982 | # args: as accepted by the bind command |
|---|
| 983 | # defines bindings for an icon. all bindings must be stored because for |
|---|
| 984 | # -view == list, the icons are displayed differently (plain text, not |
|---|
| 985 | # the frame and the labels). |
|---|
| 986 | metawidget proc Iconbox iconbind { sIcon args } { |
|---|
| 987 | _findIcon $sIcon |
|---|
| 988 | |
|---|
| 989 | # for each icon, maintain a variable containing sequence-script |
|---|
| 990 | # pairs. if $sScript is empy, remove it. if $sScript start with +, |
|---|
| 991 | # append it. otherwise reset list with given binding. |
|---|
| 992 | if { [llength $args] == 2 } { |
|---|
| 993 | mkw.lassign $args sBind sScript |
|---|
| 994 | if { $sScript == {} } { |
|---|
| 995 | unmy lBind.$sIcon.$sBind |
|---|
| 996 | } elseif { [string index $sScript 0] == "+" } { |
|---|
| 997 | my lBind.$sIcon.$sBind [linsert [my lBind.$sIcon.$sBind] end $sScript] |
|---|
| 998 | } else { |
|---|
| 999 | my lBind.$sIcon.$sBind [list $sScript] |
|---|
| 1000 | } |
|---|
| 1001 | } |
|---|
| 1002 | |
|---|
| 1003 | # apply binding to image and text label |
|---|
| 1004 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 1005 | eval bind $wIcon.i $args |
|---|
| 1006 | eval bind $wIcon.l $args |
|---|
| 1007 | } |
|---|
| 1008 | |
|---|
| 1009 | # name: selection |
|---|
| 1010 | # args: sAction: set, get or clear |
|---|
| 1011 | # args: depends on sAction |
|---|
| 1012 | # modifies the selection. because icons are made with widgets and inserted |
|---|
| 1013 | # into the text, the text's sel tag cannot be used. rather the background of |
|---|
| 1014 | # the text label is changed. |
|---|
| 1015 | metawidget proc Iconbox selection { sAction args } { |
|---|
| 1016 | switch [mkw.complete $sAction {clear get set}] { |
|---|
| 1017 | clear { |
|---|
| 1018 | $this.text tag remove sel 1.0 end |
|---|
| 1019 | _selIcon * 0 |
|---|
| 1020 | } |
|---|
| 1021 | |
|---|
| 1022 | get { |
|---|
| 1023 | return [my lSel] |
|---|
| 1024 | } |
|---|
| 1025 | |
|---|
| 1026 | set { |
|---|
| 1027 | $this.text tag remove sel 1.0 end |
|---|
| 1028 | _selIcon * 0 |
|---|
| 1029 | |
|---|
| 1030 | foreach sIcon [lindex $args 0] { |
|---|
| 1031 | _findIcon $sIcon |
|---|
| 1032 | _selIcon $sIcon 1 |
|---|
| 1033 | |
|---|
| 1034 | if { [my -view] == "list" } { |
|---|
| 1035 | mkw.lassign [$this.text tag ranges $sIcon] sI1 sI2 |
|---|
| 1036 | $this.text tag add sel $sI1 $sI2 |
|---|
| 1037 | } |
|---|
| 1038 | } |
|---|
| 1039 | } |
|---|
| 1040 | } |
|---|
| 1041 | } |
|---|
| 1042 | |
|---|
| 1043 | # name: sort |
|---|
| 1044 | # args: args: as accepted by lsort, plus -column |
|---|
| 1045 | # sorts the icons, either by their name or by one of the columns. |
|---|
| 1046 | metawidget proc Iconbox sort { args } { |
|---|
| 1047 | set args [mkw.options $args * {-column *}] |
|---|
| 1048 | |
|---|
| 1049 | # build table with two columns: one is icon name, second is sort criteria |
|---|
| 1050 | set lData {} |
|---|
| 1051 | if { ! [info exists -column] } { |
|---|
| 1052 | # if no column is specified, just take the icon's caption |
|---|
| 1053 | foreach sIcon [my lIcons] { |
|---|
| 1054 | set wIcon [lindex [my aIcons($sIcon)] 3] |
|---|
| 1055 | lappend lData [list $sIcon [$wIcon.l cget -text]] |
|---|
| 1056 | } |
|---|
| 1057 | } else { |
|---|
| 1058 | set iPos [lsearch [my lCols] ${-column}] |
|---|
| 1059 | if { $iPos == -1 } { |
|---|
| 1060 | error "Iconbox $this: Invalid column ${-column}" |
|---|
| 1061 | } |
|---|
| 1062 | |
|---|
| 1063 | # otherwise, extract the value specified by -column |
|---|
| 1064 | foreach sIcon [my lIcons] { |
|---|
| 1065 | lappend lData [list $sIcon [lindex [lindex [my aIcons($sIcon)] 2] $iPos]] |
|---|
| 1066 | } |
|---|
| 1067 | } |
|---|
| 1068 | |
|---|
| 1069 | # sort it. the first column now contains the icons in sorted order |
|---|
| 1070 | set lData [eval lsort $args -index 1 \$lData] |
|---|
| 1071 | |
|---|
| 1072 | # extract first column, rebuilt icon list, and redraw |
|---|
| 1073 | my lIcons {} |
|---|
| 1074 | foreach lRow $lData { |
|---|
| 1075 | my lIcons [linsert [my lIcons] end [lindex $lRow 0]] |
|---|
| 1076 | } |
|---|
| 1077 | |
|---|
| 1078 | _redraw |
|---|
| 1079 | } |
|---|
| 1080 | |
|---|
| 1081 | # name: see |
|---|
| 1082 | # args: sIcon: icon names |
|---|
| 1083 | # makes an icon visible. simply transforms the cell speci into a |
|---|
| 1084 | # real text index and calls the "see" command. |
|---|
| 1085 | metawidget proc Iconbox see { sIcon } { |
|---|
| 1086 | set iPos [_checkItem $sIcon] |
|---|
| 1087 | |
|---|
| 1088 | if { [my -view] == "list" } { |
|---|
| 1089 | $this.cols see 1.0 |
|---|
| 1090 | $this.text see [expr $iPos+1].0 |
|---|
| 1091 | } else { |
|---|
| 1092 | $this.text see 1.$iPos |
|---|
| 1093 | } |
|---|
| 1094 | } |
|---|
| 1095 | |
|---|
| 1096 | # name: bind |
|---|
| 1097 | # args: args: as accepted by the bind command. |
|---|
| 1098 | # applies bindings to the text widget itself. |
|---|
| 1099 | metawidget proc Iconbox bind_ { args } { |
|---|
| 1100 | eval bind $this.text $args |
|---|
| 1101 | } |
|---|
| 1102 | |
|---|
| 1103 | # name: size |
|---|
| 1104 | # args: - |
|---|
| 1105 | # returns the number of rows. |
|---|
| 1106 | metawidget proc Iconbox size {} { |
|---|
| 1107 | return [llength [my lIcons]] |
|---|
| 1108 | } |
|---|
| 1109 | |
|---|
| 1110 | # name: names |
|---|
| 1111 | # args: - |
|---|
| 1112 | # returns the icon names. |
|---|
| 1113 | metawidget proc Iconbox names {} { |
|---|
| 1114 | return [my lIcons] |
|---|
| 1115 | } |
|---|
| 1116 | |
|---|
| 1117 | metawidget command Iconbox _sort _sort |
|---|
| 1118 | metawidget command Iconbox _dragX _dragX |
|---|
| 1119 | metawidget command Iconbox _moveX _moveX |
|---|
| 1120 | metawidget command Iconbox _grab _grab |
|---|
| 1121 | metawidget command Iconbox _drag _drag |
|---|
| 1122 | metawidget command Iconbox _drop _drop |
|---|
| 1123 | metawidget command Iconbox _select _select |
|---|
| 1124 | metawidget command Iconbox _select2 _select2 |
|---|
| 1125 | metawidget command Iconbox _redraw _redraw |
|---|
| 1126 | |
|---|
| 1127 | metawidget command Iconbox insert insert |
|---|
| 1128 | metawidget command Iconbox delete delete |
|---|
| 1129 | metawidget command Iconbox iconconfigure iconconfigure |
|---|
| 1130 | metawidget command Iconbox iconcget iconcget |
|---|
| 1131 | metawidget command Iconbox iconbind iconbind |
|---|
| 1132 | metawidget command Iconbox selection selection |
|---|
| 1133 | metawidget command Iconbox sort sort |
|---|
| 1134 | metawidget command Iconbox see see |
|---|
| 1135 | metawidget command Iconbox bind bind_ |
|---|
| 1136 | metawidget command Iconbox size size |
|---|
| 1137 | metawidget command Iconbox names names |
|---|
| 1138 | |
|---|
| 1139 | metawidget option Iconbox -columns -columns |
|---|
| 1140 | metawidget option Iconbox -view -view |
|---|
| 1141 | metawidget option Iconbox -update -update |
|---|
| 1142 | metawidget option Iconbox -selectmode -selectmode |
|---|
| 1143 | metawidget option Iconbox -font -font |
|---|
| 1144 | metawidget option Iconbox -onselect |
|---|
| 1145 | |
|---|
| 1146 | proc testGetFiles {} { |
|---|
| 1147 | global sPath |
|---|
| 1148 | if { $sPath == "/" } { set sPath "" } |
|---|
| 1149 | |
|---|
| 1150 | .ibox delete |
|---|
| 1151 | |
|---|
| 1152 | foreach sFile [glob -nocomplain $sPath/*] { |
|---|
| 1153 | set sTail [file tail $sFile] |
|---|
| 1154 | set iSize [file size $sFile] |
|---|
| 1155 | set sMod [clock format [file mtime $sFile] -format {%D %H:%M}] |
|---|
| 1156 | |
|---|
| 1157 | set sIcon $sFile |
|---|
| 1158 | |
|---|
| 1159 | if { [file isfile $sFile] } { |
|---|
| 1160 | .ibox insert end $sIcon -text $sTail -image pFI -smallimage pFi -values [list $iSize $sMod] |
|---|
| 1161 | } else { |
|---|
| 1162 | .ibox insert end $sIcon -text $sTail -image pFO -smallimage pFo -values [list {} $sMod] |
|---|
| 1163 | .ibox iconbind $sIcon <Double-1> "set sPath {$sIcon}; testGetFiles" |
|---|
| 1164 | } |
|---|
| 1165 | } |
|---|
| 1166 | |
|---|
| 1167 | .ibox bind <Key-Return> { |
|---|
| 1168 | set sPath [lindex [.ibox selection get] 0] |
|---|
| 1169 | testGetFiles |
|---|
| 1170 | } |
|---|
| 1171 | } |
|---|
| 1172 | |
|---|
| 1173 | proc test {} { |
|---|
| 1174 | image create photo pFo -file ./demos/images/Folder.gif |
|---|
| 1175 | image create photo pFi -file ./demos/images/File.gif |
|---|
| 1176 | image create photo pFO -file ./demos/images/Folder32.gif |
|---|
| 1177 | image create photo pFI -file ./demos/images/File32.gif |
|---|
| 1178 | |
|---|
| 1179 | pack [label .head -text "Enter a path below and press Browse"] -side top -anchor w |
|---|
| 1180 | pack [frame .ctrl] -side top -fill x |
|---|
| 1181 | pack [button .ctrl.list -text "List" -command {.ibox config -view list}] -side right |
|---|
| 1182 | pack [button .ctrl.smll -text "Small" -command {.ibox config -view small}] -side right |
|---|
| 1183 | pack [button .ctrl.lrge -text "Large" -command {.ibox config -view large}] -side right |
|---|
| 1184 | pack [button .ctrl.back -text "Back" -command {set sPath [file dirname $sPath]; testGetFiles}] -side right |
|---|
| 1185 | pack [button .ctrl.brws -text "Browse" -command testGetFiles] -side right |
|---|
| 1186 | pack [entry .ctrl.path -textvariable sPath] -side left -fill x -expand 1 |
|---|
| 1187 | pack [iconbox .ibox -columns {{Name l 100} {Size r} {Modified}}] -side bottom -fill both -expand 1 |
|---|
| 1188 | } |
|---|
| 1189 | |
|---|
| 1190 | #test |
|---|