iconlist.tcl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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
  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. if {[tk windowingsystem] eq "aqua"} {
  403. bind $canvas <Shift-MouseWheel> [namespace code {my MouseWheel [expr {40 * (%D)}]}]
  404. bind $canvas <Option-Shift-MouseWheel> [namespace code {my MouseWheel [expr {400 * (%D)}]}]
  405. } else {
  406. bind $canvas <Shift-MouseWheel> [namespace code {my MouseWheel %D}]
  407. }
  408. if {[tk windowingsystem] eq "x11"} {
  409. bind $canvas <Shift-4> [namespace code {my MouseWheel 120}]
  410. bind $canvas <Shift-5> [namespace code {my MouseWheel -120}]
  411. }
  412. bind $canvas <<PrevLine>> [namespace code {my UpDown -1}]
  413. bind $canvas <<NextLine>> [namespace code {my UpDown 1}]
  414. bind $canvas <<PrevChar>> [namespace code {my LeftRight -1}]
  415. bind $canvas <<NextChar>> [namespace code {my LeftRight 1}]
  416. bind $canvas <Return> [namespace code {my ReturnKey}]
  417. bind $canvas <KeyPress> [namespace code {my KeyPress %A}]
  418. bind $canvas <Control-KeyPress> ";"
  419. bind $canvas <Alt-KeyPress> ";"
  420. bind $canvas <FocusIn> [namespace code {my FocusIn}]
  421. bind $canvas <FocusOut> [namespace code {my FocusOut}]
  422. return $w
  423. }
  424. # This procedure is invoked when the mouse leaves an entry window with
  425. # button 1 down. It scrolls the window up, down, left, or right,
  426. # depending on where the mouse left the window, and reschedules itself
  427. # as an "after" command so that the window continues to scroll until the
  428. # mouse moves back into the window or the mouse button is released.
  429. #
  430. method AutoScan {} {
  431. if {![winfo exists $w]} return
  432. set x $oldX
  433. set y $oldY
  434. if {$noScroll} {
  435. return
  436. }
  437. if {$x >= [winfo width $canvas]} {
  438. $canvas xview scroll 1 units
  439. } elseif {$x < 0} {
  440. $canvas xview scroll -1 units
  441. } elseif {$y >= [winfo height $canvas]} {
  442. # do nothing
  443. } elseif {$y < 0} {
  444. # do nothing
  445. } else {
  446. return
  447. }
  448. my Motion1 $x $y
  449. set ::tk::Priv(afterId) [after 50 [namespace code {my AutoScan}]]
  450. }
  451. # ----------------------------------------------------------------------
  452. # Event handlers
  453. method MouseWheel {amount} {
  454. if {$noScroll || $::tk_strictMotif} {
  455. return
  456. }
  457. if {$amount > 0} {
  458. $canvas xview scroll [expr {(-119-$amount) / 120}] units
  459. } else {
  460. $canvas xview scroll [expr {-($amount / 120)}] units
  461. }
  462. }
  463. method Btn1 {x y} {
  464. focus $canvas
  465. set i [$w index @$x,$y]
  466. if {$i eq ""} {
  467. return
  468. }
  469. $w selection clear 0 end
  470. $w selection set $i
  471. $w selection anchor $i
  472. }
  473. method CtrlBtn1 {x y} {
  474. if {$options(-multiple)} {
  475. focus $canvas
  476. set i [$w index @$x,$y]
  477. if {$i eq ""} {
  478. return
  479. }
  480. if {[$w selection includes $i]} {
  481. $w selection clear $i
  482. } else {
  483. $w selection set $i
  484. $w selection anchor $i
  485. }
  486. }
  487. }
  488. method ShiftBtn1 {x y} {
  489. if {$options(-multiple)} {
  490. focus $canvas
  491. set i [$w index @$x,$y]
  492. if {$i eq ""} {
  493. return
  494. }
  495. if {[$w index anchor] eq ""} {
  496. $w selection anchor $i
  497. }
  498. $w selection clear 0 end
  499. $w selection set anchor $i
  500. }
  501. }
  502. # Gets called on button-1 motions
  503. #
  504. method Motion1 {x y} {
  505. set oldX $x
  506. set oldY $y
  507. set i [$w index @$x,$y]
  508. if {$i eq ""} {
  509. return
  510. }
  511. $w selection clear 0 end
  512. $w selection set $i
  513. }
  514. method ShiftMotion1 {x y} {
  515. set oldX $x
  516. set oldY $y
  517. set i [$w index @$x,$y]
  518. if {$i eq ""} {
  519. return
  520. }
  521. $w selection clear 0 end
  522. $w selection set anchor $i
  523. }
  524. method Double1 {x y} {
  525. if {[llength $selection]} {
  526. $w invoke
  527. }
  528. }
  529. method ReturnKey {} {
  530. $w invoke
  531. }
  532. method Leave1 {x y} {
  533. set oldX $x
  534. set oldY $y
  535. my AutoScan
  536. }
  537. method FocusIn {} {
  538. $w state focus
  539. if {![info exists list]} {
  540. return
  541. }
  542. if {[llength $selection]} {
  543. my DrawSelection
  544. }
  545. }
  546. method FocusOut {} {
  547. $w state !focus
  548. $w selection clear 0 end
  549. }
  550. # Moves the active element up or down by one element
  551. #
  552. # Arguments:
  553. # amount - +1 to move down one item, -1 to move back one item.
  554. #
  555. method UpDown amount {
  556. if {![info exists list]} {
  557. return
  558. }
  559. set curr [$w selection get]
  560. if {[llength $curr] == 0} {
  561. set i 0
  562. } else {
  563. set i [$w index anchor]
  564. if {$i eq ""} {
  565. return
  566. }
  567. incr i $amount
  568. }
  569. $w selection clear 0 end
  570. $w selection set $i
  571. $w selection anchor $i
  572. $w see $i
  573. }
  574. # Moves the active element left or right by one column
  575. #
  576. # Arguments:
  577. # amount - +1 to move right one column, -1 to move left one
  578. # column
  579. #
  580. method LeftRight amount {
  581. if {![info exists list]} {
  582. return
  583. }
  584. set curr [$w selection get]
  585. if {[llength $curr] == 0} {
  586. set i 0
  587. } else {
  588. set i [$w index anchor]
  589. if {$i eq ""} {
  590. return
  591. }
  592. incr i [expr {$amount * $itemsPerColumn}]
  593. }
  594. $w selection clear 0 end
  595. $w selection set $i
  596. $w selection anchor $i
  597. $w see $i
  598. }
  599. # Gets called when user enters an arbitrary key in the listbox.
  600. #
  601. method KeyPress key {
  602. append accel $key
  603. my Goto $accel
  604. after cancel $accelCB
  605. set accelCB [after 500 [namespace code {my Reset}]]
  606. }
  607. method Goto text {
  608. if {![info exists list]} {
  609. return
  610. }
  611. if {$text eq "" || $numItems == 0} {
  612. return
  613. }
  614. if {[llength [$w selection get]]} {
  615. set start [$w index anchor]
  616. } else {
  617. set start 0
  618. }
  619. set theIndex -1
  620. set less 0
  621. set len [string length $text]
  622. set len0 [expr {$len - 1}]
  623. set i $start
  624. # Search forward until we find a filename whose prefix is a
  625. # case-insensitive match with $text
  626. while {1} {
  627. if {[string equal -nocase -length $len0 $textList($i) $text]} {
  628. set theIndex $i
  629. break
  630. }
  631. incr i
  632. if {$i == $numItems} {
  633. set i 0
  634. }
  635. if {$i == $start} {
  636. break
  637. }
  638. }
  639. if {$theIndex >= 0} {
  640. $w selection clear 0 end
  641. $w selection set $theIndex
  642. $w selection anchor $theIndex
  643. $w see $theIndex
  644. }
  645. }
  646. method Reset {} {
  647. unset -nocomplain accel
  648. }
  649. }
  650. return
  651. # Local Variables:
  652. # mode: tcl
  653. # fill-column: 78
  654. # End: