megawidget.tcl 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. # megawidget.tcl
  2. #
  3. # Basic megawidget support classes. Experimental for any use other than
  4. # the ::tk::IconList megawdget, which is itself only designed for use in
  5. # the Unix file dialogs.
  6. #
  7. # Copyright (c) 2009-2010 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. package require Tk 8.6
  13. ::oo::class create ::tk::Megawidget {
  14. superclass ::oo::class
  15. method unknown {w args} {
  16. if {[string match .* $w]} {
  17. [self] create $w {*}$args
  18. return $w
  19. }
  20. next $w {*}$args
  21. }
  22. unexport new unknown
  23. self method create {name superclasses body} {
  24. next $name [list \
  25. superclass ::tk::MegawidgetClass {*}$superclasses]\;$body
  26. }
  27. }
  28. ::oo::class create ::tk::MegawidgetClass {
  29. variable w hull options IdleCallbacks
  30. constructor args {
  31. # Extract the "widget name" from the object name
  32. set w [namespace tail [self]]
  33. # Configure things
  34. tclParseConfigSpec [my varname options] [my GetSpecs] "" $args
  35. # Move the object out of the way of the hull widget
  36. rename [self] _tmp
  37. # Make the hull widget(s)
  38. my CreateHull
  39. bind $hull <Destroy> [list [namespace which my] destroy]
  40. # Rename things into their final places
  41. rename ::$w theWidget
  42. rename [self] ::$w
  43. # Make the contents
  44. my Create
  45. }
  46. destructor {
  47. foreach {name cb} [array get IdleCallbacks] {
  48. after cancel $cb
  49. unset IdleCallbacks($name)
  50. }
  51. if {[winfo exists $w]} {
  52. bind $hull <Destroy> {}
  53. destroy $w
  54. }
  55. }
  56. ####################################################################
  57. #
  58. # MegawidgetClass::configure --
  59. #
  60. # Implementation of 'configure' for megawidgets. Emulates the operation
  61. # of the standard Tk configure method fairly closely, which makes things
  62. # substantially more complex than they otherwise would be.
  63. #
  64. # This method assumes that the 'GetSpecs' method returns a description
  65. # of all the specifications of the options (i.e., as Tk returns except
  66. # with the actual values removed). It also assumes that the 'options'
  67. # array in the class holds all options; it is up to subclasses to set
  68. # traces on that array if they want to respond to configuration changes.
  69. #
  70. # TODO: allow unambiguous abbreviations.
  71. #
  72. method configure args {
  73. # Configure behaves differently depending on the number of arguments
  74. set argc [llength $args]
  75. if {$argc == 0} {
  76. return [lmap spec [my GetSpecs] {
  77. lappend spec $options([lindex $spec 0])
  78. }]
  79. } elseif {$argc == 1} {
  80. set opt [lindex $args 0]
  81. if {[info exists options($opt)]} {
  82. set spec [lsearch -inline -index 0 -exact [my GetSpecs] $opt]
  83. return [linsert $spec end $options($opt)]
  84. }
  85. } elseif {$argc == 2} {
  86. # Special case for where we're setting a single option. This
  87. # avoids some of the costly operations. We still do the [array
  88. # get] as this gives a sufficiently-consistent trace.
  89. set opt [lindex $args 0]
  90. if {[dict exists [array get options] $opt]} {
  91. # Actually set the new value of the option. Use a catch to
  92. # allow a megawidget user to throw an error from a write trace
  93. # on the options array to reject invalid values.
  94. try {
  95. array set options $args
  96. } on error {ret info} {
  97. # Rethrow the error to get a clean stack trace
  98. return -code error -errorcode [dict get $info -errorcode] $ret
  99. }
  100. return
  101. }
  102. } elseif {$argc % 2 == 0} {
  103. # Check that all specified options exist. Any unknown option will
  104. # cause the merged dictionary to be bigger than the options array
  105. set merge [dict merge [array get options] $args]
  106. if {[dict size $merge] == [array size options]} {
  107. # Actually set the new values of the options. Use a catch to
  108. # allow a megawidget user to throw an error from a write trace
  109. # on the options array to reject invalid values
  110. try {
  111. array set options $args
  112. } on error {ret info} {
  113. # Rethrow the error to get a clean stack trace
  114. return -code error -errorcode [dict get $info -errorcode] $ret
  115. }
  116. return
  117. }
  118. # Due to the order of the merge, the unknown options will be at
  119. # the end of the dict. This makes the first unknown option easy to
  120. # find.
  121. set opt [lindex [dict keys $merge] [array size options]]
  122. } else {
  123. set opt [lindex $args end]
  124. return -code error -errorcode [list TK VALUE_MISSING] \
  125. "value for \"$opt\" missing"
  126. }
  127. return -code error -errorcode [list TK LOOKUP OPTION $opt] \
  128. "bad option \"$opt\": must be [tclListValidFlags options]"
  129. }
  130. ####################################################################
  131. #
  132. # MegawidgetClass::cget --
  133. #
  134. # Implementation of 'cget' for megawidgets. Emulates the operation of
  135. # the standard Tk cget method fairly closely.
  136. #
  137. # This method assumes that the 'options' array in the class holds all
  138. # options; it is up to subclasses to set traces on that array if they
  139. # want to respond to configuration reads.
  140. #
  141. # TODO: allow unambiguous abbreviations.
  142. #
  143. method cget option {
  144. return $options($option)
  145. }
  146. ####################################################################
  147. #
  148. # MegawidgetClass::TraceOption --
  149. #
  150. # Sets up the tracing of an element of the options variable.
  151. #
  152. method TraceOption {option method args} {
  153. set callback [list my $method {*}$args]
  154. trace add variable options($option) write [namespace code $callback]
  155. }
  156. ####################################################################
  157. #
  158. # MegawidgetClass::GetSpecs --
  159. #
  160. # Return a list of descriptions of options supported by this
  161. # megawidget. Each option is described by the 4-tuple list, consisting
  162. # of the name of the option, the "option database" name, the "option
  163. # database" class-name, and the default value of the option. These are
  164. # the same values returned by calling the configure method of a widget,
  165. # except without the current values of the options.
  166. #
  167. method GetSpecs {} {
  168. return {
  169. {-takefocus takeFocus TakeFocus {}}
  170. }
  171. }
  172. ####################################################################
  173. #
  174. # MegawidgetClass::CreateHull --
  175. #
  176. # Creates the real main widget of the megawidget. This is often a frame
  177. # or toplevel widget, but isn't always (lightweight megawidgets might
  178. # use a content widget directly).
  179. #
  180. # The name of the hull widget is given by the 'w' instance variable. The
  181. # name should be written into the 'hull' instance variable. The command
  182. # created by this method will be renamed.
  183. #
  184. method CreateHull {} {
  185. return -code error -errorcode {TCL OO ABSTRACT_METHOD} \
  186. "method must be overridden"
  187. }
  188. ####################################################################
  189. #
  190. # MegawidgetClass::Create --
  191. #
  192. # Creates the content of the megawidget. The name of the widget to
  193. # create the content in will be in the 'hull' instance variable.
  194. #
  195. method Create {} {
  196. return -code error -errorcode {TCL OO ABSTRACT_METHOD} \
  197. "method must be overridden"
  198. }
  199. ####################################################################
  200. #
  201. # MegawidgetClass::WhenIdle --
  202. #
  203. # Arrange for a method to be called on the current instance when Tk is
  204. # idle. Only one such method call per method will be queued; subsequent
  205. # queuing actions before the callback fires will be silently ignored.
  206. # The additional args will be passed to the callback, and the callbacks
  207. # will be properly cancelled if the widget is destroyed.
  208. #
  209. method WhenIdle {method args} {
  210. if {![info exists IdleCallbacks($method)]} {
  211. set IdleCallbacks($method) [after idle [list \
  212. [namespace which my] DoWhenIdle $method $args]]
  213. }
  214. }
  215. method DoWhenIdle {method arguments} {
  216. unset IdleCallbacks($method)
  217. tailcall my $method {*}$arguments
  218. }
  219. }
  220. ####################################################################
  221. #
  222. # tk::SimpleWidget --
  223. #
  224. # Simple megawidget class that makes it easy create widgets that behave
  225. # like a ttk widget. It creates the hull as a ttk::frame and maps the
  226. # state manipulation methods of the overall megawidget to the equivalent
  227. # operations on the ttk::frame.
  228. #
  229. ::tk::Megawidget create ::tk::SimpleWidget {} {
  230. variable w hull options
  231. method GetSpecs {} {
  232. return {
  233. {-cursor cursor Cursor {}}
  234. {-takefocus takeFocus TakeFocus {}}
  235. }
  236. }
  237. method CreateHull {} {
  238. set hull [::ttk::frame $w -cursor $options(-cursor)]
  239. my TraceOption -cursor UpdateCursorOption
  240. }
  241. method UpdateCursorOption args {
  242. $hull configure -cursor $options(-cursor)
  243. }
  244. # Not fixed names, so can't forward
  245. method state args {
  246. tailcall $hull state {*}$args
  247. }
  248. method instate args {
  249. tailcall $hull instate {*}$args
  250. }
  251. }
  252. ####################################################################
  253. #
  254. # tk::FocusableWidget --
  255. #
  256. # Simple megawidget class that makes a ttk-like widget that has a focus
  257. # ring.
  258. #
  259. ::tk::Megawidget create ::tk::FocusableWidget ::tk::SimpleWidget {
  260. variable w hull options
  261. method GetSpecs {} {
  262. return {
  263. {-cursor cursor Cursor {}}
  264. {-takefocus takeFocus TakeFocus ::ttk::takefocus}
  265. }
  266. }
  267. method CreateHull {} {
  268. ttk::frame $w
  269. set hull [ttk::entry $w.cHull -takefocus 0 -cursor $options(-cursor)]
  270. pack $hull -expand yes -fill both -ipadx 2 -ipady 2
  271. my TraceOption -cursor UpdateCursorOption
  272. }
  273. }
  274. return
  275. # Local Variables:
  276. # mode: tcl
  277. # fill-column: 78
  278. # End: