Balloon.tcl 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. # -*- mode: TCL; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*-
  2. #
  3. # $Id: Balloon.tcl,v 1.7 2008/02/27 22:17:28 hobbs Exp $
  4. #
  5. # Balloon.tcl --
  6. #
  7. # The help widget. It provides both "balloon" type of help
  8. # message and "status bar" type of help message. You can use
  9. # this widget to indicate the function of the widgets inside
  10. # your application.
  11. #
  12. # Copyright (c) 1993-1999 Ioi Kim Lam.
  13. # Copyright (c) 2000-2001 Tix Project Group.
  14. # Copyright (c) 2004 ActiveState
  15. #
  16. # See the file "license.terms" for information on usage and redistribution
  17. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  18. #
  19. tixWidgetClass tixBalloon {
  20. -classname TixBalloon
  21. -superclass tixShell
  22. -method {
  23. bind post unbind
  24. }
  25. -flag {
  26. -installcolormap -initwait -state -statusbar -cursor
  27. }
  28. -configspec {
  29. {-installcolormap installColormap InstallColormap false}
  30. {-initwait initWait InitWait 1000}
  31. {-state state State both}
  32. {-statusbar statusBar StatusBar ""}
  33. {-cursor cursor Cursor {}}
  34. }
  35. -default {
  36. {*background #ffff60}
  37. {*foreground black}
  38. {*borderWidth 0}
  39. {.borderWidth 1}
  40. {.background black}
  41. {*Label.anchor w}
  42. {*Label.justify left}
  43. }
  44. }
  45. # static seem to be -installcolormap -initwait -statusbar -cursor
  46. # Class Record
  47. #
  48. global tixBalloon
  49. set tixBalloon(bals) ""
  50. proc tixBalloon:InitWidgetRec {w} {
  51. upvar #0 $w data
  52. global tixBalloon
  53. tixChainMethod $w InitWidgetRec
  54. set data(isActive) 0
  55. set data(client) ""
  56. lappend tixBalloon(bals) $w
  57. }
  58. proc tixBalloon:ConstructWidget {w} {
  59. upvar #0 $w data
  60. tixChainMethod $w ConstructWidget
  61. if {[tk windowingsystem] eq "aqua"} {
  62. ::tk::unsupported::MacWindowStyle style $w help none
  63. } else {
  64. wm overrideredirect $w 1
  65. }
  66. catch {wm attributes $w -topmost 1}
  67. wm positionfrom $w program
  68. wm withdraw $w
  69. # Frame 1 : arrow
  70. frame $w.f1 -bd 0
  71. set data(w:label) [label $w.f1.lab -bd 0 -relief flat \
  72. -bitmap [tix getbitmap balarrow]]
  73. pack $data(w:label) -side left -padx 1 -pady 1
  74. # Frame 2 : Message
  75. frame $w.f2 -bd 0
  76. set data(w:message) [label $w.f2.message -padx 0 -pady 0 -bd 0]
  77. pack $data(w:message) -side left -expand yes -fill both -padx 10 -pady 1
  78. # Pack all
  79. pack $w.f1 -fill both
  80. pack $w.f2 -fill both
  81. # This is an event tag used by the clients
  82. #
  83. bind TixBal$w <Destroy> [list tixBalloon:ClientDestroy $w %W]
  84. }
  85. proc tixBalloon:Destructor {w} {
  86. global tixBalloon
  87. set bals ""
  88. foreach b $tixBalloon(bals) {
  89. if {$w != $b} {
  90. lappend bals $b
  91. }
  92. }
  93. set tixBalloon(bals) $bals
  94. tixChainMethod $w Destructor
  95. }
  96. #----------------------------------------------------------------------
  97. # Config:
  98. #----------------------------------------------------------------------
  99. proc tixBalloon:config-state {w value} {
  100. upvar #0 $w data
  101. set re {^(none|balloon|status|both)$}
  102. if {![regexp -- $re $value]} {
  103. error "invalid value $value, must be none, balloon, status, or both"
  104. }
  105. }
  106. #----------------------------------------------------------------------
  107. # "RAW" event bindings:
  108. #----------------------------------------------------------------------
  109. bind all <B1-Motion> "+tixBalloon_XXMotion %X %Y 1"
  110. bind all <B2-Motion> "+tixBalloon_XXMotion %X %Y 2"
  111. bind all <B3-Motion> "+tixBalloon_XXMotion %X %Y 3"
  112. bind all <B4-Motion> "+tixBalloon_XXMotion %X %Y 4"
  113. bind all <B5-Motion> "+tixBalloon_XXMotion %X %Y 5"
  114. bind all <Any-Motion> "+tixBalloon_XXMotion %X %Y 0"
  115. # Should %b be 0? %b is illegal
  116. bind all <Leave> "+tixBalloon_XXMotion %X %Y 0"
  117. bind all <Button> "+tixBalloon_XXButton %X %Y %b"
  118. bind all <ButtonRelease> "+tixBalloon_XXButtonUp %X %Y %b"
  119. proc tixBalloon_XXMotion {rootX rootY b} {
  120. global tixBalloon
  121. foreach w $tixBalloon(bals) {
  122. tixBalloon:XXMotion $w $rootX $rootY $b
  123. }
  124. }
  125. proc tixBalloon_XXButton {rootX rootY b} {
  126. global tixBalloon
  127. foreach w $tixBalloon(bals) {
  128. tixBalloon:XXButton $w $rootX $rootY $b
  129. }
  130. }
  131. proc tixBalloon_XXButtonUp {rootX rootY b} {
  132. global tixBalloon
  133. foreach w $tixBalloon(bals) {
  134. tixBalloon:XXButtonUp $w $rootX $rootY $b
  135. }
  136. }
  137. # return true if d is a descendant of w
  138. #
  139. proc tixIsDescendant {w d} {
  140. return [expr {$w eq "." || [string match $w.* $d]}]
  141. }
  142. # All the button events are fine if the ballooned widget is
  143. # a descendant of the grabbing widget
  144. #
  145. proc tixBalloon:GrabBad {w cw} {
  146. global tixBalloon
  147. set g [grab current $w]
  148. if {$g == ""} {
  149. return 0
  150. }
  151. if {[info exists tixBalloon(g_ignore,$g)]} {
  152. return 1
  153. }
  154. if {[info exists tixBalloon(g_ignore,[winfo class $g])]} {
  155. return 1
  156. }
  157. if {$g == $cw || [tixIsDescendant $g $cw]} {
  158. return 0
  159. }
  160. return 1
  161. }
  162. proc tixBalloon:XXMotion {w rootX rootY b} {
  163. upvar #0 $w data
  164. if {![info exists data(-state)]} {
  165. # puts "tixBalloon:XXMotion called without a state\n$w"
  166. set data(state) none
  167. return
  168. }
  169. if {$data(-state) eq "none"} {
  170. return
  171. }
  172. if {$b == 0} {
  173. if {[info exists data(b:1)]} {unset data(b:1)}
  174. if {[info exists data(b:2)]} {unset data(b:2)}
  175. if {[info exists data(b:3)]} {unset data(b:3)}
  176. if {[info exists data(b:4)]} {unset data(b:4)}
  177. if {[info exists data(b:5)]} {unset data(b:5)}
  178. }
  179. if {[llength [array names data b:*]]} {
  180. # Some buttons are down. Do nothing
  181. #
  182. return
  183. }
  184. set cw [winfo containing -displayof $w $rootX $rootY]
  185. if {[tixBalloon:GrabBad $w $cw]} {
  186. return
  187. }
  188. # Find the a client window that matches
  189. #
  190. if {$w eq $cw || [string match $w.* $cw]} {
  191. # Cursor moved over the balloon -- Ignore
  192. return
  193. }
  194. while {$cw != ""} {
  195. if {[info exists data(m:$cw)]} {
  196. set client $cw
  197. break
  198. } else {
  199. set cw [winfo parent $cw]
  200. }
  201. }
  202. if {![info exists client]} {
  203. # The cursor is at a position covered by a non-client
  204. # Popdown the balloon if it is up
  205. if {$data(isActive)} {
  206. tixBalloon:Deactivate $w
  207. }
  208. set data(client) ""
  209. if {[info exists data(cancel)]} {
  210. unset data(cancel)
  211. }
  212. return
  213. }
  214. if {$data(client) ne $client} {
  215. if {$data(isActive)} {
  216. tixBalloon:Deactivate $w
  217. }
  218. set data(client) $client
  219. after $data(-initwait) tixBalloon:SwitchToClient $w $client
  220. }
  221. }
  222. proc tixBalloon:XXButton {w rootX rootY b} {
  223. upvar #0 $w data
  224. tixBalloon:XXMotion $w $rootX $rootY $b
  225. set data(b:$b) 1
  226. if {$data(isActive)} {
  227. tixBalloon:Deactivate $w
  228. } else {
  229. set data(cancel) 1
  230. }
  231. }
  232. proc tixBalloon:XXButtonUp {w rootX rootY b} {
  233. upvar #0 $w data
  234. tixBalloon:XXMotion $w $rootX $rootY $b
  235. if {[info exists data(b:$b)]} {
  236. unset data(b:$b)
  237. }
  238. }
  239. #----------------------------------------------------------------------
  240. # "COOKED" event bindings:
  241. #----------------------------------------------------------------------
  242. # switch the balloon to a new client
  243. #
  244. proc tixBalloon:SwitchToClient {w client} {
  245. upvar #0 $w data
  246. if {![winfo exists $w]} {
  247. return
  248. }
  249. if {![winfo exists $client]} {
  250. return
  251. }
  252. if {$client ne $data(client)} {
  253. return
  254. }
  255. if {[info exists data(cancel)]} {
  256. unset data(cancel)
  257. return
  258. }
  259. if {[tixBalloon:GrabBad $w $w]} {
  260. return
  261. }
  262. tixBalloon:Activate $w
  263. }
  264. proc tixBalloon:ClientDestroy {w client} {
  265. if {![winfo exists $w]} {
  266. return
  267. }
  268. upvar #0 $w data
  269. if {$data(client) eq $client} {
  270. tixBalloon:Deactivate $w
  271. set data(client) ""
  272. }
  273. # Maybe thses have already been unset by the Destroy method
  274. #
  275. if {[info exists data(m:$client)]} {unset data(m:$client)}
  276. if {[info exists data(s:$client)]} {unset data(s:$client)}
  277. }
  278. #----------------------------------------------------------------------
  279. # Popping up balloon:
  280. #----------------------------------------------------------------------
  281. proc tixBalloon:Activate {w} {
  282. upvar #0 $w data
  283. if {[tixBalloon:GrabBad $w $w]} {
  284. return
  285. }
  286. if {[winfo containing -displayof $w \
  287. [winfo pointerx $w] [winfo pointery $w]] == ""} {
  288. return
  289. }
  290. if {![info exists data(-state)]} {
  291. # puts "tixBalloon:Activate called without a state\n$w"
  292. set data(state) none
  293. return
  294. }
  295. if {$data(-state) eq "none"} {
  296. return
  297. }
  298. switch -exact -- $data(-state) {
  299. "both" {
  300. tixBalloon:PopUp $w
  301. tixBalloon:SetStatus $w
  302. }
  303. "balloon" {
  304. tixBalloon:PopUp $w
  305. }
  306. "status" {
  307. tixBalloon:SetStatus $w
  308. }
  309. }
  310. set data(isActive) 1
  311. after 200 tixBalloon:Verify $w
  312. }
  313. # %% Perhaps this is no more needed
  314. #
  315. proc tixBalloon:Verify {w} {
  316. upvar #0 $w data
  317. if {![winfo exists $w]} {
  318. return
  319. }
  320. if {!$data(isActive)} {
  321. return
  322. }
  323. if {[tixBalloon:GrabBad $w $w]} {
  324. tixBalloon:Deactivate $w
  325. return
  326. }
  327. if {[winfo containing -displayof $w \
  328. [winfo pointerx $w] [winfo pointery $w]] == ""} {
  329. tixBalloon:Deactivate $w
  330. return
  331. }
  332. after 200 tixBalloon:Verify $w
  333. }
  334. proc tixBalloon:Deactivate {w} {
  335. upvar #0 $w data
  336. tixBalloon:PopDown $w
  337. tixBalloon:ClearStatus $w
  338. set data(isActive) 0
  339. if {[info exists data(cancel)]} {
  340. unset data(cancel)
  341. }
  342. }
  343. proc tixBalloon:PopUp {w} {
  344. upvar #0 $w data
  345. if {[string is true -strict $data(-installcolormap)]} {
  346. wm colormapwindows [winfo toplevel $data(client)] $w
  347. }
  348. # trick: the following lines allow the balloon window to
  349. # acquire a stable width and height when it is finally
  350. # put on the visible screen
  351. #
  352. set client $data(client)
  353. if {$data(m:$client) == ""} {return ""}
  354. $data(w:message) config -text $data(m:$client)
  355. wm geometry $w +10000+10000
  356. wm deiconify $w
  357. raise $w
  358. update
  359. # The windows may become destroyed as a result of the "update" command
  360. #
  361. if {![winfo exists $w]} {
  362. return
  363. }
  364. if {![winfo exists $client]} {
  365. return
  366. }
  367. # Put it on the visible screen
  368. #
  369. set x [expr {[winfo rootx $client]+[winfo width $client]/2}]
  370. set y [expr {int([winfo rooty $client]+[winfo height $client]/1.3)}]
  371. set width [winfo reqwidth $w]
  372. set height [winfo reqheight $w]
  373. set scrwidth [winfo vrootwidth $w]
  374. set scrheight [winfo vrootheight $w]
  375. # If the balloon is too far right, pull it back to the left
  376. #
  377. if {($x + $width) > $scrwidth} {
  378. set x [expr {$scrwidth - $width}]
  379. }
  380. # If the balloon is too far left, pull it back to the right
  381. #
  382. if {$x < 0} {
  383. set x 0
  384. }
  385. # If the listbox is below bottom of screen, put it upwards
  386. #
  387. if {($y + $height) > $scrheight} {
  388. set y [expr {$scrheight-$height}]
  389. }
  390. if {$y < 0} {
  391. set y 0
  392. }
  393. wm geometry $w +$x+$y
  394. after idle raise $w
  395. }
  396. proc tixBalloon:PopDown {w} {
  397. upvar #0 $w data
  398. # Close the balloon
  399. #
  400. wm withdraw $w
  401. # We don't set the data(client) to be zero, so that the balloon
  402. # will re-appear only if you move out then in the client window
  403. # set data(client) ""
  404. }
  405. proc tixBalloon:SetStatus {w} {
  406. upvar #0 $w data
  407. if {![winfo exists $data(-statusbar)]
  408. || ![info exists data(s:$data(client))]} {
  409. return
  410. }
  411. set vv [$data(-statusbar) cget -textvariable]
  412. if {$vv == ""} {
  413. $data(-statusbar) config -text $data(s:$data(client))
  414. } else {
  415. uplevel #0 set $vv [list $data(s:$data(client))]
  416. }
  417. }
  418. proc tixBalloon:ClearStatus {w} {
  419. upvar #0 $w data
  420. if {![winfo exists $data(-statusbar)]} {
  421. return
  422. }
  423. # Clear the StatusBar widget
  424. #
  425. set vv [$data(-statusbar) cget -textvariable]
  426. if {$vv == ""} {
  427. $data(-statusbar) config -text ""
  428. } else {
  429. uplevel #0 set $vv [list ""]
  430. }
  431. }
  432. #----------------------------------------------------------------------
  433. # PublicMethods:
  434. #----------------------------------------------------------------------
  435. # %% if balloon is already popped-up for this client, change mesage
  436. #
  437. proc tixBalloon:bind {w client args} {
  438. upvar #0 $w data
  439. set alreadyBound [info exists data(m:$client)]
  440. set opt(-balloonmsg) ""
  441. set opt(-statusmsg) ""
  442. set opt(-msg) ""
  443. tixHandleOptions opt {-balloonmsg -msg -statusmsg} $args
  444. if {$opt(-balloonmsg) != ""} {
  445. set data(m:$client) $opt(-balloonmsg)
  446. } else {
  447. set data(m:$client) $opt(-msg)
  448. }
  449. if {$opt(-statusmsg) != ""} {
  450. set data(s:$client) $opt(-statusmsg)
  451. } else {
  452. set data(s:$client) $opt(-msg)
  453. }
  454. tixAppendBindTag $client TixBal$w
  455. }
  456. proc tixBalloon:post {w client} {
  457. upvar #0 $w data
  458. if {![info exists data(m:$client)] || $data(m:$client) == ""} {
  459. return
  460. }
  461. tixBalloon:Enter $w $client
  462. incr data(fakeEnter)
  463. }
  464. proc tixBalloon:unbind {w client} {
  465. upvar #0 $w data
  466. if {[info exists data(m:$client)]} {
  467. if {[info exists data(m:$client)]} {unset data(m:$client)}
  468. if {[info exists data(s:$client)]} {unset data(s:$client)}
  469. if {[winfo exists $client]} {
  470. catch {tixDeleteBindTag $client TixBal$w}
  471. }
  472. }
  473. }
  474. #----------------------------------------------------------------------
  475. #
  476. # Utility function
  477. #
  478. #----------------------------------------------------------------------
  479. #
  480. # $w can be a widget name or a classs name
  481. proc tixBalIgnoreWhenGrabbed {wc} {
  482. global tixBalloon
  483. set tixBalloon(g_ignore,$wc) ""
  484. }
  485. tixBalIgnoreWhenGrabbed TixComboBox
  486. tixBalIgnoreWhenGrabbed Menu
  487. tixBalIgnoreWhenGrabbed Menubutton