combobox.tcl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. #
  2. # Combobox bindings.
  3. #
  4. # <<NOTE-WM-TRANSIENT>>:
  5. #
  6. # Need to set [wm transient] just before mapping the popdown
  7. # instead of when it's created, in case a containing frame
  8. # has been reparented [#1818441].
  9. #
  10. # On Windows: setting [wm transient] prevents the parent
  11. # toplevel from becoming inactive when the popdown is posted
  12. # (Tk 8.4.8+)
  13. #
  14. # On X11: WM_TRANSIENT_FOR on override-redirect windows
  15. # may be used by compositing managers and by EWMH-aware
  16. # window managers (even though the older ICCCM spec says
  17. # it's meaningless).
  18. #
  19. # On OSX: [wm transient] does utterly the wrong thing.
  20. # Instead, we use [MacWindowStyle "help" "noActivates hideOnSuspend"].
  21. # The "noActivates" attribute prevents the parent toplevel
  22. # from deactivating when the popdown is posted, and is also
  23. # necessary for "help" windows to receive mouse events.
  24. # "hideOnSuspend" makes the popdown disappear (resp. reappear)
  25. # when the parent toplevel is deactivated (resp. reactivated).
  26. # (see [#1814778]). Also set [wm resizable 0 0], to prevent
  27. # TkAqua from shrinking the scrollbar to make room for a grow box
  28. # that isn't there.
  29. #
  30. # In order to work around other platform quirks in TkAqua,
  31. # [grab] and [focus] are set in <Map> bindings instead of
  32. # immediately after deiconifying the window.
  33. #
  34. namespace eval ttk::combobox {
  35. variable Values ;# Values($cb) is -listvariable of listbox widget
  36. variable State
  37. set State(entryPress) 0
  38. }
  39. ### Combobox bindings.
  40. #
  41. # Duplicate the Entry bindings, override if needed:
  42. #
  43. ttk::copyBindings TEntry TCombobox
  44. bind TCombobox <Down> { ttk::combobox::Post %W }
  45. bind TCombobox <Escape> { ttk::combobox::Unpost %W }
  46. bind TCombobox <Button-1> { ttk::combobox::Press "" %W %x %y }
  47. bind TCombobox <Shift-Button-1> { ttk::combobox::Press "s" %W %x %y }
  48. bind TCombobox <Double-Button-1> { ttk::combobox::Press "2" %W %x %y }
  49. bind TCombobox <Triple-Button-1> { ttk::combobox::Press "3" %W %x %y }
  50. bind TCombobox <B1-Motion> { ttk::combobox::Drag %W %x }
  51. bind TCombobox <Motion> { ttk::combobox::Motion %W %x %y }
  52. ttk::bindMouseWheel TCombobox [list ttk::combobox::Scroll %W]
  53. bind TCombobox <<TraverseIn>> { ttk::combobox::TraverseIn %W }
  54. ### Combobox listbox bindings.
  55. #
  56. bind ComboboxListbox <ButtonRelease-1> { ttk::combobox::LBSelected %W }
  57. bind ComboboxListbox <Return> { ttk::combobox::LBSelected %W }
  58. bind ComboboxListbox <Escape> { ttk::combobox::LBCancel %W }
  59. bind ComboboxListbox <Tab> { ttk::combobox::LBTab %W next }
  60. bind ComboboxListbox <<PrevWindow>> { ttk::combobox::LBTab %W prev }
  61. bind ComboboxListbox <Destroy> { ttk::combobox::LBCleanup %W }
  62. bind ComboboxListbox <Motion> { ttk::combobox::LBHover %W %x %y }
  63. bind ComboboxListbox <Map> { focus -force %W }
  64. switch -- [tk windowingsystem] {
  65. win32 {
  66. # Dismiss listbox when user switches to a different application.
  67. # NB: *only* do this on Windows (see #1814778)
  68. bind ComboboxListbox <FocusOut> { ttk::combobox::LBCancel %W }
  69. }
  70. }
  71. ### Combobox popdown window bindings.
  72. #
  73. bind ComboboxPopdown <Map> { ttk::combobox::MapPopdown %W }
  74. bind ComboboxPopdown <Unmap> { ttk::combobox::UnmapPopdown %W }
  75. bind ComboboxPopdown <Button> \
  76. { ttk::combobox::Unpost [winfo parent %W] }
  77. ### Option database settings.
  78. #
  79. option add *TCombobox*Listbox.font TkTextFont widgetDefault
  80. option add *TCombobox*Listbox.relief flat widgetDefault
  81. option add *TCombobox*Listbox.highlightThickness 0 widgetDefault
  82. ## Platform-specific settings.
  83. #
  84. switch -- [tk windowingsystem] {
  85. x11 {
  86. option add *TCombobox*Listbox.background white widgetDefault
  87. }
  88. aqua {
  89. option add *TCombobox*Listbox.borderWidth 0 widgetDefault
  90. }
  91. }
  92. ### Binding procedures.
  93. #
  94. ## Press $mode $x $y -- Button binding for comboboxes.
  95. # Either post/unpost the listbox, or perform Entry widget binding,
  96. # depending on widget state and location of button press.
  97. #
  98. proc ttk::combobox::Press {mode w x y} {
  99. variable State
  100. $w instate disabled { return }
  101. set State(entryPress) [expr {
  102. [$w instate !readonly]
  103. && [string match *textarea [$w identify element $x $y]]
  104. }]
  105. focus $w
  106. if {$State(entryPress)} {
  107. switch -- $mode {
  108. s { ttk::entry::Shift-Press $w $x ; # Shift }
  109. 2 { ttk::entry::Select $w $x word ; # Double click}
  110. 3 { ttk::entry::Select $w $x line ; # Triple click }
  111. "" -
  112. default { ttk::entry::Press $w $x }
  113. }
  114. } else {
  115. Post $w
  116. }
  117. }
  118. ## Drag -- B1-Motion binding for comboboxes.
  119. # If the initial Button event was handled by Entry binding,
  120. # perform Entry widget drag binding; otherwise nothing.
  121. #
  122. proc ttk::combobox::Drag {w x} {
  123. variable State
  124. if {$State(entryPress)} {
  125. ttk::entry::Drag $w $x
  126. }
  127. }
  128. ## Motion --
  129. # Set cursor.
  130. #
  131. proc ttk::combobox::Motion {w x y} {
  132. variable State
  133. ttk::saveCursor $w State(userConfCursor) [ttk::cursor text]
  134. if { [$w identify $x $y] eq "textarea"
  135. && [$w instate {!readonly !disabled}]
  136. } {
  137. ttk::setCursor $w text
  138. } else {
  139. ttk::setCursor $w $State(userConfCursor)
  140. }
  141. }
  142. ## TraverseIn -- receive focus due to keyboard navigation
  143. # For editable comboboxes, set the selection and insert cursor.
  144. #
  145. proc ttk::combobox::TraverseIn {w} {
  146. $w instate {!readonly !disabled} {
  147. $w selection range 0 end
  148. $w icursor end
  149. }
  150. }
  151. ## SelectEntry $cb $index --
  152. # Set the combobox selection in response to a user action.
  153. #
  154. proc ttk::combobox::SelectEntry {cb index} {
  155. $cb current $index
  156. $cb selection range 0 end
  157. $cb icursor end
  158. event generate $cb <<ComboboxSelected>> -when mark
  159. }
  160. ## Scroll -- Mousewheel binding
  161. #
  162. proc ttk::combobox::Scroll {cb dir} {
  163. $cb instate disabled { return }
  164. set max [llength [$cb cget -values]]
  165. set current [$cb current]
  166. incr current $dir
  167. if {$max != 0 && $current == $current % $max} {
  168. SelectEntry $cb $current
  169. }
  170. }
  171. ## LBSelected $lb -- Activation binding for listbox
  172. # Set the combobox value to the currently-selected listbox value
  173. # and unpost the listbox.
  174. #
  175. proc ttk::combobox::LBSelected {lb} {
  176. set cb [LBMaster $lb]
  177. LBSelect $lb
  178. Unpost $cb
  179. focus $cb
  180. }
  181. ## LBCancel --
  182. # Unpost the listbox.
  183. #
  184. proc ttk::combobox::LBCancel {lb} {
  185. Unpost [LBMaster $lb]
  186. }
  187. ## LBTab -- Tab key binding for combobox listbox.
  188. # Set the selection, and navigate to next/prev widget.
  189. #
  190. proc ttk::combobox::LBTab {lb dir} {
  191. set cb [LBMaster $lb]
  192. switch -- $dir {
  193. next { set newFocus [tk_focusNext $cb] }
  194. prev { set newFocus [tk_focusPrev $cb] }
  195. }
  196. if {$newFocus ne ""} {
  197. LBSelect $lb
  198. Unpost $cb
  199. # The [grab release] call in [Unpost] queues events that later
  200. # re-set the focus (@@@ NOTE: this might not be true anymore).
  201. # Set new focus later:
  202. after 0 [list ttk::traverseTo $newFocus]
  203. }
  204. }
  205. ## LBHover -- <Motion> binding for combobox listbox.
  206. # Follow selection on mouseover.
  207. #
  208. proc ttk::combobox::LBHover {w x y} {
  209. $w selection clear 0 end
  210. $w activate @$x,$y
  211. $w selection set @$x,$y
  212. }
  213. ## MapPopdown -- <Map> binding for ComboboxPopdown
  214. #
  215. proc ttk::combobox::MapPopdown {w} {
  216. [winfo parent $w] state pressed
  217. ttk::globalGrab $w
  218. }
  219. ## UnmapPopdown -- <Unmap> binding for ComboboxPopdown
  220. #
  221. proc ttk::combobox::UnmapPopdown {w} {
  222. [winfo parent $w] state !pressed
  223. ttk::releaseGrab $w
  224. }
  225. ## PopdownWindow --
  226. # Returns the popdown widget associated with a combobox,
  227. # creating it if necessary.
  228. #
  229. proc ttk::combobox::PopdownWindow {cb} {
  230. if {![winfo exists $cb.popdown]} {
  231. set poplevel [PopdownToplevel $cb.popdown]
  232. set popdown [ttk::frame $poplevel.f -style ComboboxPopdownFrame]
  233. ttk::scrollbar $popdown.sb \
  234. -orient vertical -command [list $popdown.l yview]
  235. listbox $popdown.l \
  236. -listvariable ttk::combobox::Values($cb) \
  237. -yscrollcommand [list $popdown.sb set] \
  238. -exportselection false \
  239. -selectmode browse \
  240. -activestyle none \
  241. ;
  242. bindtags $popdown.l \
  243. [list $popdown.l ComboboxListbox Listbox $popdown all]
  244. grid $popdown.l -row 0 -column 0 -padx {1 0} -pady 1 -sticky nsew
  245. grid $popdown.sb -row 0 -column 1 -padx {0 1} -pady 1 -sticky ns
  246. grid columnconfigure $popdown 0 -weight 1
  247. grid rowconfigure $popdown 0 -weight 1
  248. grid $popdown -sticky news -padx 0 -pady 0
  249. grid rowconfigure $poplevel 0 -weight 1
  250. grid columnconfigure $poplevel 0 -weight 1
  251. }
  252. return $cb.popdown
  253. }
  254. ## PopdownToplevel -- Create toplevel window for the combobox popdown
  255. #
  256. # See also <<NOTE-WM-TRANSIENT>>
  257. #
  258. proc ttk::combobox::PopdownToplevel {w} {
  259. toplevel $w -class ComboboxPopdown
  260. wm withdraw $w
  261. switch -- [tk windowingsystem] {
  262. default -
  263. x11 {
  264. $w configure -relief flat -borderwidth 0
  265. wm attributes $w -type combo
  266. wm overrideredirect $w true
  267. }
  268. win32 {
  269. $w configure -relief flat -borderwidth 0
  270. wm overrideredirect $w true
  271. wm attributes $w -topmost 1
  272. }
  273. aqua {
  274. $w configure -relief solid -borderwidth 0
  275. tk::unsupported::MacWindowStyle style $w \
  276. help {noActivates hideOnSuspend}
  277. wm resizable $w 0 0
  278. }
  279. }
  280. return $w
  281. }
  282. ## ConfigureListbox --
  283. # Set listbox values, selection, height, and scrollbar visibility
  284. # from current combobox values.
  285. #
  286. proc ttk::combobox::ConfigureListbox {cb} {
  287. variable Values
  288. set popdown [PopdownWindow $cb].f
  289. set values [$cb cget -values]
  290. set current [$cb current]
  291. if {$current < 0} {
  292. set current 0 ;# no current entry, highlight first one
  293. }
  294. set Values($cb) $values
  295. $popdown.l selection clear 0 end
  296. $popdown.l selection set $current
  297. $popdown.l activate $current
  298. $popdown.l see $current
  299. set height [llength $values]
  300. if {$height > [$cb cget -height]} {
  301. set height [$cb cget -height]
  302. grid $popdown.sb
  303. grid configure $popdown.l -padx {1 0}
  304. } else {
  305. grid remove $popdown.sb
  306. grid configure $popdown.l -padx 1
  307. }
  308. $popdown.l configure -height $height
  309. }
  310. ## PlacePopdown --
  311. # Set popdown window geometry.
  312. #
  313. # @@@TODO: factor with menubutton::PostPosition
  314. #
  315. proc ttk::combobox::PlacePopdown {cb popdown} {
  316. set x [winfo rootx $cb]
  317. set y [winfo rooty $cb]
  318. set w [winfo width $cb]
  319. set h [winfo height $cb]
  320. set style [$cb cget -style]
  321. if { $style eq {} } {
  322. set style TCombobox
  323. }
  324. set postoffset [ttk::style lookup $style -postoffset {} {0 0 0 0}]
  325. foreach var {x y w h} delta $postoffset {
  326. incr $var $delta
  327. }
  328. set H [winfo reqheight $popdown]
  329. if {$y + $h + $H > [winfo screenheight $popdown]} {
  330. set Y [expr {$y - $H}]
  331. } else {
  332. set Y [expr {$y + $h}]
  333. }
  334. wm geometry $popdown ${w}x${H}+${x}+${Y}
  335. }
  336. ## Post $cb --
  337. # Pop down the associated listbox.
  338. #
  339. proc ttk::combobox::Post {cb} {
  340. # Don't do anything if disabled:
  341. #
  342. $cb instate disabled { return }
  343. # ASSERT: ![$cb instate pressed]
  344. # Run -postcommand callback:
  345. #
  346. uplevel #0 [$cb cget -postcommand]
  347. set popdown [PopdownWindow $cb]
  348. ConfigureListbox $cb
  349. update idletasks ;# needed for geometry propagation.
  350. PlacePopdown $cb $popdown
  351. # See <<NOTE-WM-TRANSIENT>>
  352. switch -- [tk windowingsystem] {
  353. x11 - win32 { wm transient $popdown [winfo toplevel $cb] }
  354. }
  355. # Post the listbox:
  356. #
  357. wm attribute $popdown -topmost 1
  358. wm deiconify $popdown
  359. raise $popdown
  360. }
  361. ## Unpost $cb --
  362. # Unpost the listbox.
  363. #
  364. proc ttk::combobox::Unpost {cb} {
  365. if {[winfo exists $cb.popdown]} {
  366. wm withdraw $cb.popdown
  367. }
  368. grab release $cb.popdown ;# in case of stuck or unexpected grab [#1239190]
  369. }
  370. ## LBMaster $lb --
  371. # Return the combobox main widget that owns the listbox.
  372. #
  373. proc ttk::combobox::LBMaster {lb} {
  374. winfo parent [winfo parent [winfo parent $lb]]
  375. }
  376. ## LBSelect $lb --
  377. # Transfer listbox selection to combobox value.
  378. #
  379. proc ttk::combobox::LBSelect {lb} {
  380. set cb [LBMaster $lb]
  381. set selection [$lb curselection]
  382. if {[llength $selection] == 1} {
  383. SelectEntry $cb [lindex $selection 0]
  384. }
  385. }
  386. ## LBCleanup $lb --
  387. # <Destroy> binding for combobox listboxes.
  388. # Cleans up by unsetting the linked textvariable.
  389. #
  390. # Note: we can't just use { unset [%W cget -listvariable] }
  391. # because the widget command is already gone when this binding fires).
  392. # [winfo parent] still works, fortunately.
  393. #
  394. proc ttk::combobox::LBCleanup {lb} {
  395. variable Values
  396. unset Values([LBMaster $lb])
  397. }
  398. #*EOF*