iconlist.tcl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. # iconlist.tcl
  2. #
  3. # Implements the icon-list megawidget used in the "Tk" standard file
  4. # selection dialog boxes.
  5. #
  6. # Copyright (c) 1994-1998 Sun Microsystems, Inc.
  7. # Copyright (c) 2009 Donal K. Fellows
  8. #
  9. # See the file "license.terms" for information on usage and redistribution of
  10. # this file, and for a DISCLAIMER OF ALL WARRANTIES.
  11. #
  12. # API Summary:
  13. # tk::IconList <path> ?<option> <value>? ...
  14. # <path> add <imageName> <itemList>
  15. # <path> cget <option>
  16. # <path> configure ?<option>? ?<value>? ...
  17. # <path> deleteall
  18. # <path> destroy
  19. # <path> get <itemIndex>
  20. # <path> index <index>
  21. # <path> invoke
  22. # <path> see <index>
  23. # <path> selection anchor ?<int>?
  24. # <path> selection clear <first> ?<last>?
  25. # <path> selection get
  26. # <path> selection includes <item>
  27. # <path> selection set <first> ?<last>?
  28. package require Tk 8.6
  29. ::tk::Megawidget create ::tk::IconList ::tk::FocusableWidget {
  30. variable w canvas sbar accel accelCB fill font index \
  31. itemList itemsPerColumn list maxIH maxIW maxTH maxTW noScroll \
  32. numItems oldX oldY options rect selected selection textList
  33. constructor args {
  34. next {*}$args
  35. set accelCB {}
  36. }
  37. destructor {
  38. my Reset
  39. next
  40. }
  41. method GetSpecs {} {
  42. concat [next] {
  43. {-command "" "" ""}
  44. {-font "" "" "TkIconFont"}
  45. {-multiple "" "" "0"}
  46. }
  47. }
  48. # ----------------------------------------------------------------------
  49. method index i {
  50. if {![info exist list]} {
  51. set list {}
  52. }
  53. switch -regexp -- $i {
  54. "^-?[0-9]+$" {
  55. if {$i < 0} {
  56. set i 0
  57. }
  58. if {$i >= [llength $list]} {
  59. set i [expr {[llength $list] - 1}]
  60. }
  61. return $i
  62. }
  63. "^anchor$" {
  64. return $index(anchor)
  65. }
  66. "^end$" {
  67. return [llength $list]
  68. }
  69. "@-?[0-9]+,-?[0-9]+" {
  70. scan $i "@%d,%d" x y
  71. set item [$canvas find closest \
  72. [$canvas canvasx $x] [$canvas canvasy $y]]
  73. return [lindex [$canvas itemcget $item -tags] 1]
  74. }
  75. }
  76. }
  77. method selection {op args} {
  78. switch -exact -- $op {
  79. anchor {
  80. if {[llength $args] == 1} {
  81. set index(anchor) [$w index [lindex $args 0]]
  82. } else {
  83. return $index(anchor)
  84. }
  85. }
  86. clear {
  87. switch [llength $args] {
  88. 2 {
  89. lassign $args first last
  90. }
  91. 1 {
  92. set first [set last [lindex $args 0]]
  93. }
  94. default {
  95. return -code error -errorcode {TCL WRONGARGS} \
  96. "wrong # args: should be\
  97. \"[lrange [info level 0] 0 1] first ?last?\""
  98. }
  99. }
  100. set first [$w index $first]
  101. set last [$w index $last]
  102. if {$first > $last} {
  103. set tmp $first
  104. set first $last
  105. set last $tmp
  106. }
  107. set ind 0
  108. foreach item $selection {
  109. if {$item >= $first} {
  110. set first $ind
  111. break
  112. }
  113. incr ind
  114. }
  115. set ind [expr {[llength $selection] - 1}]
  116. for {} {$ind >= 0} {incr ind -1} {
  117. set item [lindex $selection $ind]
  118. if {$item <= $last} {
  119. set last $ind
  120. break
  121. }
  122. }
  123. if {$first > $last} {
  124. return
  125. }
  126. set selection [lreplace $selection $first $last]
  127. event generate $w <<ListboxSelect>>
  128. my DrawSelection
  129. }
  130. get {
  131. return $selection
  132. }
  133. includes {
  134. return [expr {[lindex $args 0] in $selection}]
  135. }
  136. set {
  137. switch [llength $args] {
  138. 2 {
  139. lassign $args first last
  140. }
  141. 1 {
  142. set first [set last [lindex $args 0]]
  143. }
  144. default {
  145. return -code error -errorcode {TCL WRONGARGS} \
  146. "wrong # args: should be\
  147. \"[lrange [info level 0] 0 1] first ?last?\""
  148. }
  149. }
  150. set first [$w index $first]
  151. set last [$w index $last]
  152. if {$first > $last} {
  153. set tmp $first
  154. set first $last
  155. set last $tmp
  156. }
  157. for {set i $first} {$i <= $last} {incr i} {
  158. lappend selection $i
  159. }
  160. set selection [lsort -integer -unique $selection]
  161. event generate $w <<ListboxSelect>>
  162. my DrawSelection
  163. }
  164. }
  165. }
  166. method get item {
  167. set rTag [lindex $list $item 2]
  168. lassign $itemList($rTag) iTag tTag text serial
  169. return $text
  170. }
  171. # Deletes all the items inside the canvas subwidget and reset the
  172. # iconList's state.
  173. #
  174. method deleteall {} {
  175. $canvas delete all
  176. unset -nocomplain selected rect list itemList
  177. set maxIW 1
  178. set maxIH 1
  179. set maxTW 1
  180. set maxTH 1
  181. set numItems 0
  182. set noScroll 1
  183. set selection {}
  184. set index(anchor) ""
  185. $sbar set 0.0 1.0
  186. $canvas xview moveto 0
  187. }
  188. # Adds an icon into the IconList with the designated image and text
  189. #
  190. method add {image items} {
  191. foreach text $items {
  192. set iID item$numItems
  193. set iTag [$canvas create image 0 0 -image $image -anchor nw \
  194. -tags [list icon $numItems $iID]]
  195. set tTag [$canvas create text 0 0 -text $text -anchor nw \
  196. -font $options(-font) -fill $fill \
  197. -tags [list text $numItems $iID]]
  198. set rTag [$canvas create rect 0 0 0 0 -fill "" -outline "" \
  199. -tags [list rect $numItems $iID]]
  200. lassign [$canvas bbox $iTag] x1 y1 x2 y2
  201. set iW [expr {$x2 - $x1}]
  202. set iH [expr {$y2 - $y1}]
  203. if {$maxIW < $iW} {
  204. set maxIW $iW
  205. }
  206. if {$maxIH < $iH} {
  207. set maxIH $iH
  208. }
  209. lassign [$canvas bbox $tTag] x1 y1 x2 y2
  210. set tW [expr {$x2 - $x1}]
  211. set tH [expr {$y2 - $y1}]
  212. if {$maxTW < $tW} {
  213. set maxTW $tW
  214. }
  215. if {$maxTH < $tH} {
  216. set maxTH $tH
  217. }
  218. lappend list [list $iTag $tTag $rTag $iW $iH $tW $tH $numItems]
  219. set itemList($rTag) [list $iTag $tTag $text $numItems]
  220. set textList($numItems) [string tolower $text]
  221. incr numItems
  222. }
  223. my WhenIdle Arrange
  224. return
  225. }
  226. # Gets called when the user invokes the IconList (usually by
  227. # double-clicking or pressing the Return key).
  228. #
  229. method invoke {} {
  230. if {$options(-command) ne "" && [llength $selection]} {
  231. uplevel #0 $options(-command)
  232. }
  233. }
  234. # If the item is not (completely) visible, scroll the canvas so that it
  235. # becomes visible.
  236. #
  237. method see rTag {
  238. if {$noScroll} {
  239. return
  240. }
  241. set sRegion [$canvas cget -scrollregion]
  242. if {$sRegion eq ""} {
  243. return
  244. }
  245. if {$rTag < 0 || $rTag >= [llength $list]} {
  246. return
  247. }
  248. set bbox [$canvas bbox item$rTag]
  249. set pad [expr {[$canvas cget -highlightthickness]+[$canvas cget -bd]}]
  250. set x1 [lindex $bbox 0]
  251. set x2 [lindex $bbox 2]
  252. incr x1 [expr {$pad * -2}]
  253. incr x2 [expr {$pad * -1}]
  254. set cW [expr {[winfo width $canvas] - $pad*2}]
  255. set scrollW [expr {[lindex $sRegion 2]-[lindex $sRegion 0]+1}]
  256. set dispX [expr {int([lindex [$canvas xview] 0]*$scrollW)}]
  257. set oldDispX $dispX
  258. # check if out of the right edge
  259. #
  260. if {($x2 - $dispX) >= $cW} {
  261. set dispX [expr {$x2 - $cW}]
  262. }
  263. # check if out of the left edge
  264. #
  265. if {($x1 - $dispX) < 0} {
  266. set dispX $x1
  267. }
  268. if {$oldDispX ne $dispX} {
  269. set fraction [expr {double($dispX) / double($scrollW)}]
  270. $canvas xview moveto $fraction
  271. }
  272. }
  273. # ----------------------------------------------------------------------
  274. # Places the icons in a column-major arrangement.
  275. #
  276. method Arrange {} {
  277. if {![info exists list]} {
  278. if {[info exists canvas] && [winfo exists $canvas]} {
  279. set noScroll 1
  280. $sbar configure -command ""
  281. }
  282. return
  283. }
  284. set W [winfo width $canvas]
  285. set H [winfo height $canvas]
  286. set pad [expr {[$canvas cget -highlightthickness]+[$canvas cget -bd]}]
  287. if {$pad < 2} {
  288. set pad 2
  289. }
  290. incr W [expr {$pad*-2}]
  291. incr H [expr {$pad*-2}]
  292. set dx [expr {$maxIW + $maxTW + 8}]
  293. if {$maxTH > $maxIH} {
  294. set dy $maxTH
  295. } else {
  296. set dy $maxIH
  297. }
  298. incr dy 2
  299. set shift [expr {$maxIW + 4}]
  300. set x [expr {$pad * 2}]
  301. set y [expr {$pad * 1}] ; # Why * 1 ?
  302. set usedColumn 0
  303. foreach sublist $list {
  304. set usedColumn 1
  305. lassign $sublist iTag tTag rTag iW iH tW tH
  306. set i_dy [expr {($dy - $iH)/2}]
  307. set t_dy [expr {($dy - $tH)/2}]
  308. $canvas coords $iTag $x [expr {$y + $i_dy}]
  309. $canvas coords $tTag [expr {$x + $shift}] [expr {$y + $t_dy}]
  310. $canvas coords $rTag $x $y [expr {$x+$dx}] [expr {$y+$dy}]
  311. incr y $dy
  312. if {($y + $dy) > $H} {
  313. set y [expr {$pad * 1}] ; # *1 ?
  314. incr x $dx
  315. set usedColumn 0
  316. }
  317. }
  318. if {$usedColumn} {
  319. set sW [expr {$x + $dx}]
  320. } else {
  321. set sW $x
  322. }
  323. if {$sW < $W} {
  324. $canvas configure -scrollregion [list $pad $pad $sW $H]
  325. $sbar configure -command ""
  326. $canvas xview moveto 0
  327. set noScroll 1
  328. } else {
  329. $canvas configure -scrollregion [list $pad $pad $sW $H]
  330. $sbar configure -command [list $canvas xview]
  331. set noScroll 0
  332. }
  333. set itemsPerColumn [expr {($H-$pad) / $dy}]
  334. if {$itemsPerColumn < 1} {
  335. set itemsPerColumn 1
  336. }
  337. my DrawSelection
  338. }
  339. method DrawSelection {} {
  340. $canvas delete selection
  341. $canvas itemconfigure selectionText -fill black
  342. $canvas dtag selectionText
  343. set cbg [ttk::style lookup TEntry -selectbackground focus]
  344. set cfg [ttk::style lookup TEntry -selectforeground focus]
  345. foreach item $selection {
  346. set rTag [lindex $list $item 2]
  347. foreach {iTag tTag text serial} $itemList($rTag) {
  348. break
  349. }
  350. set bbox [$canvas bbox $tTag]
  351. $canvas create rect $bbox -fill $cbg -outline $cbg \
  352. -tags selection
  353. $canvas itemconfigure $tTag -fill $cfg -tags selectionText
  354. }
  355. $canvas lower selection
  356. return
  357. }
  358. # Creates an IconList widget by assembling a canvas widget and a
  359. # scrollbar widget. Sets all the bindings necessary for the IconList's
  360. # operations.
  361. #
  362. method Create {} {
  363. variable hull
  364. set sbar [ttk::scrollbar $hull.sbar -orient horizontal -takefocus 0]
  365. catch {$sbar configure -highlightthickness 0}
  366. set canvas [canvas $hull.canvas -highlightthick 0 -takefocus 1 \
  367. -width 400 -height 120 -background white]
  368. pack $sbar -side bottom -fill x -padx 2 -pady {0 2}
  369. pack $canvas -expand yes -fill both -padx 2 -pady {2 0}
  370. $sbar configure -command [list $canvas xview]
  371. $canvas configure -xscrollcommand [list $sbar set]
  372. # Initializes the max icon/text width and height and other variables
  373. #
  374. set maxIW 1
  375. set maxIH 1
  376. set maxTW 1
  377. set maxTH 1
  378. set numItems 0
  379. set noScroll 1
  380. set selection {}
  381. set index(anchor) ""
  382. set fg [option get $canvas foreground Foreground]
  383. if {$fg eq ""} {
  384. set fill black
  385. } else {
  386. set fill $fg
  387. }
  388. # Creates the event bindings.
  389. #
  390. bind $canvas <Configure> [namespace code {my WhenIdle Arrange}]
  391. bind $canvas <1> [namespace code {my Btn1 %x %y}]
  392. bind $canvas <B1-Motion> [namespace code {my Motion1 %x %y}]
  393. bind $canvas <B1-Leave> [namespace code {my Leave1 %x %y}]
  394. bind $canvas <Control-1> [namespace code {my CtrlBtn1 %x %y}]
  395. bind $canvas <Shift-1> [namespace code {my ShiftBtn1 %x %y}]
  396. bind $canvas <B1-Enter> [list tk::CancelRepeat]
  397. bind $canvas <ButtonRelease-1> [list tk::CancelRepeat]
  398. bind $canvas <Double-ButtonRelease-1> \
  399. [namespace code {my Double1 %x %y}]
  400. bind $canvas <Control-B1-Motion> {;}
  401. bind $canvas <Shift-B1-Motion> [namespace code {my ShiftMotion1 %x %y}]
  402. bind $canvas <<PrevLine>> [namespace code {my UpDown -1}]
  403. bind $canvas <<NextLine>> [namespace code {my UpDown 1}]
  404. bind $canvas <<PrevChar>> [namespace code {my LeftRight -1}]
  405. bind $canvas <<NextChar>> [namespace code {my LeftRight 1}]
  406. bind $canvas <Return> [namespace code {my ReturnKey}]
  407. bind $canvas <KeyPress> [namespace code {my KeyPress %A}]
  408. bind $canvas <Control-KeyPress> ";"
  409. bind $canvas <Alt-KeyPress> ";"
  410. bind $canvas <FocusIn> [namespace code {my FocusIn}]
  411. bind $canvas <FocusOut> [namespace code {my FocusOut}]
  412. return $w
  413. }
  414. # This procedure is invoked when the mouse leaves an entry window with
  415. # button 1 down. It scrolls the window up, down, left, or right,
  416. # depending on where the mouse left the window, and reschedules itself
  417. # as an "after" command so that the window continues to scroll until the
  418. # mouse moves back into the window or the mouse button is released.
  419. #
  420. method AutoScan {} {
  421. if {![winfo exists $w]} return
  422. set x $oldX
  423. set y $oldY
  424. if {$noScroll} {
  425. return
  426. }
  427. if {$x >= [winfo width $canvas]} {
  428. $canvas xview scroll 1 units
  429. } elseif {$x < 0} {
  430. $canvas xview scroll -1 units
  431. } elseif {$y >= [winfo height $canvas]} {
  432. # do nothing
  433. } elseif {$y < 0} {
  434. # do nothing
  435. } else {
  436. return
  437. }
  438. my Motion1 $x $y
  439. set ::tk::Priv(afterId) [after 50 [namespace code {my AutoScan}]]
  440. }
  441. # ----------------------------------------------------------------------
  442. # Event handlers
  443. method Btn1 {x y} {
  444. focus $canvas
  445. set i [$w index @$x,$y]
  446. if {$i eq ""} {
  447. return
  448. }
  449. $w selection clear 0 end
  450. $w selection set $i
  451. $w selection anchor $i
  452. }
  453. method CtrlBtn1 {x y} {
  454. if {$options(-multiple)} {
  455. focus $canvas
  456. set i [$w index @$x,$y]
  457. if {$i eq ""} {
  458. return
  459. }
  460. if {[$w selection includes $i]} {
  461. $w selection clear $i
  462. } else {
  463. $w selection set $i
  464. $w selection anchor $i
  465. }
  466. }
  467. }
  468. method ShiftBtn1 {x y} {
  469. if {$options(-multiple)} {
  470. focus $canvas
  471. set i [$w index @$x,$y]
  472. if {$i eq ""} {
  473. return
  474. }
  475. if {[$w index anchor] eq ""} {
  476. $w selection anchor $i
  477. }
  478. $w selection clear 0 end
  479. $w selection set anchor $i
  480. }
  481. }
  482. # Gets called on button-1 motions
  483. #
  484. method Motion1 {x y} {
  485. set oldX $x
  486. set oldY $y
  487. set i [$w index @$x,$y]
  488. if {$i eq ""} {
  489. return
  490. }
  491. $w selection clear 0 end
  492. $w selection set $i
  493. }
  494. method ShiftMotion1 {x y} {
  495. set oldX $x
  496. set oldY $y
  497. set i [$w index @$x,$y]
  498. if {$i eq ""} {
  499. return
  500. }
  501. $w selection clear 0 end
  502. $w selection set anchor $i
  503. }
  504. method Double1 {x y} {
  505. if {[llength $selection]} {
  506. $w invoke
  507. }
  508. }
  509. method ReturnKey {} {
  510. $w invoke
  511. }
  512. method Leave1 {x y} {
  513. set oldX $x
  514. set oldY $y
  515. my AutoScan
  516. }
  517. method FocusIn {} {
  518. $w state focus
  519. if {![info exists list]} {
  520. return
  521. }
  522. if {[llength $selection]} {
  523. my DrawSelection
  524. }
  525. }
  526. method FocusOut {} {
  527. $w state !focus
  528. $w selection clear 0 end
  529. }
  530. # Moves the active element up or down by one element
  531. #
  532. # Arguments:
  533. # amount - +1 to move down one item, -1 to move back one item.
  534. #
  535. method UpDown amount {
  536. if {![info exists list]} {
  537. return
  538. }
  539. set curr [$w selection get]
  540. if {[llength $curr] == 0} {
  541. set i 0
  542. } else {
  543. set i [$w index anchor]
  544. if {$i eq ""} {
  545. return
  546. }
  547. incr i $amount
  548. }
  549. $w selection clear 0 end
  550. $w selection set $i
  551. $w selection anchor $i
  552. $w see $i
  553. }
  554. # Moves the active element left or right by one column
  555. #
  556. # Arguments:
  557. # amount - +1 to move right one column, -1 to move left one
  558. # column
  559. #
  560. method LeftRight amount {
  561. if {![info exists list]} {
  562. return
  563. }
  564. set curr [$w selection get]
  565. if {[llength $curr] == 0} {
  566. set i 0
  567. } else {
  568. set i [$w index anchor]
  569. if {$i eq ""} {
  570. return
  571. }
  572. incr i [expr {$amount * $itemsPerColumn}]
  573. }
  574. $w selection clear 0 end
  575. $w selection set $i
  576. $w selection anchor $i
  577. $w see $i
  578. }
  579. # Gets called when user enters an arbitrary key in the listbox.
  580. #
  581. method KeyPress key {
  582. append accel $key
  583. my Goto $accel
  584. after cancel $accelCB
  585. set accelCB [after 500 [namespace code {my Reset}]]
  586. }
  587. method Goto text {
  588. if {![info exists list]} {
  589. return
  590. }
  591. if {$text eq "" || $numItems == 0} {
  592. return
  593. }
  594. if {[llength [$w selection get]]} {
  595. set start [$w index anchor]
  596. } else {
  597. set start 0
  598. }
  599. set theIndex -1
  600. set less 0
  601. set len [string length $text]
  602. set len0 [expr {$len - 1}]
  603. set i $start
  604. # Search forward until we find a filename whose prefix is a
  605. # case-insensitive match with $text
  606. while {1} {
  607. if {[string equal -nocase -length $len0 $textList($i) $text]} {
  608. set theIndex $i
  609. break
  610. }
  611. incr i
  612. if {$i == $numItems} {
  613. set i 0
  614. }
  615. if {$i == $start} {
  616. break
  617. }
  618. }
  619. if {$theIndex > -1} {
  620. $w selection clear 0 end
  621. $w selection set $theIndex
  622. $w selection anchor $theIndex
  623. $w see $theIndex
  624. }
  625. }
  626. method Reset {} {
  627. unset -nocomplain accel
  628. }
  629. }
  630. return
  631. # Local Variables:
  632. # mode: tcl
  633. # fill-column: 78
  634. # End: