glimmer-dsl-tk 0.0.28 → 0.0.32

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.28
1
+ 0.0.32
Binary file
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2020-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/data_binding/observer'
23
+
24
+ module Glimmer
25
+ module DataBinding
26
+ module Tk
27
+ # Decorator for Observer that ensures it is only called once
28
+ # (subsequent invocations of call method do nothing)
29
+ module OneTimeObserver
30
+ def call(value)
31
+ unless @called
32
+ super
33
+ @called = true
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2020-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer'
23
+ require 'glimmer/dsl/expression'
24
+
25
+ module Glimmer
26
+ module DSL
27
+ module Tk
28
+ class BuiltInDialogExpression < Expression
29
+ def can_interpret?(parent, keyword, *args, &block)
30
+ keyword = "get_#{keyword}" if keyword.start_with?('open')
31
+ (keyword.start_with?('get') or keyword.start_with?('choose')) and
32
+ (
33
+ (block.nil? and ::Tk.respond_to?(keyword.camelcase)) or
34
+ keyword == 'choose_font'
35
+ )
36
+ end
37
+
38
+ def interpret(parent, keyword, *args, &block)
39
+ if args.first.is_a?(Hash)
40
+ options = args.first.symbolize_keys
41
+ options[:filetypes] = options.delete(:file_types) if options.keys.include?(:file_types)
42
+ options[:filetypes] = options[:filetypes].map { |key, value| "{#{key}} {#{value}}" } if options[:filetypes].is_a?(Hash)
43
+ options[:parent] = options[:parent].tk if options[:parent].is_a?(Glimmer::Tk::RootProxy)
44
+ args[0] = options
45
+ end
46
+ keyword = "get_#{keyword}" if keyword.start_with?('open') || keyword.start_with?('save') || keyword.start_with?('multiple')
47
+ if keyword == 'choose_font'
48
+ TkFont::Fontchooser.configure(:font => args, :command => block)
49
+ TkFont::Fontchooser.show
50
+ else
51
+ ::Tk.send(keyword.camelcase, *args)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -38,12 +38,13 @@ module Glimmer
38
38
  end
39
39
 
40
40
  def interpret(parent, keyword, *args, &block)
41
- parent.class
42
41
  model_binding = args[0]
43
42
  widget_binding_parameters = [parent, keyword]
44
43
  widget_binding = DataBinding::Tk::WidgetBinding.new(*widget_binding_parameters)
45
44
  #TODO make this options observer dependent and all similar observers in widget specific data binding handlers
46
- widget_binding.observe(model_binding)
45
+ registration = widget_binding.observe(model_binding)
46
+ parent.on('destroy') { registration.deregister }
47
+
47
48
  # TODO simplify this logic and put it where it belongs
48
49
  parent.add_observer(model_binding, keyword) if parent.respond_to?(:add_observer, [model_binding, keyword])
49
50
  widget_binding.call(model_binding.evaluate_property)
@@ -42,6 +42,7 @@ module Glimmer
42
42
  attribute
43
43
  shine_data_binding
44
44
  widget
45
+ built_in_dialog
45
46
  ]
46
47
  )
47
48
  end
@@ -33,11 +33,15 @@ module Glimmer
33
33
 
34
34
  def can_interpret?(parent, keyword, *args, &block)
35
35
  !EXCLUDED_KEYWORDS.include?(keyword) and
36
- parent.respond_to?(:tk) and
37
36
  Glimmer::Tk::WidgetProxy.widget_exists?(keyword)
37
+ (parent.respond_to?(:tk) or args.first.respond_to?(:tk))
38
38
  end
39
39
 
40
40
  def interpret(parent, keyword, *args, &block)
41
+ if keyword == 'toplevel' && args.first.respond_to?(:tk)
42
+ parent = args.first
43
+ args[0] = args.first.tk
44
+ end
41
45
  Glimmer::Tk::WidgetProxy.create(keyword, parent, args, &block)
42
46
  end
43
47
 
@@ -19,18 +19,14 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- require 'glimmer/tk/widget_proxy'
22
+ require 'glimmer/tk/toplevel_proxy'
23
23
 
24
24
  module Glimmer
25
25
  module Tk
26
26
  # Proxy for TkRoot
27
27
  #
28
28
  # Follows the Proxy Design Pattern
29
- class RootProxy < WidgetProxy
30
- REGEX_GEOMETRY = /[x+-]/
31
- DEFAULT_WIDTH = 190
32
- DEFAULT_HEIGHT = 95
33
-
29
+ class RootProxy < ToplevelProxy
34
30
  def initialize(*args, &block)
35
31
  @tk = ::TkRoot.new
36
32
  @tk.minsize = DEFAULT_WIDTH, DEFAULT_HEIGHT
@@ -50,10 +46,6 @@ module Glimmer
50
46
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Tk::RootExpression.new, keyword, *args, &block)
51
47
  end
52
48
 
53
- def has_attribute?(attribute, *args)
54
- %w[width height x y].include?(attribute.to_s) || super
55
- end
56
-
57
49
  def set_attribute(attribute, *args)
58
50
  case attribute.to_s
59
51
  when 'iconphoto'
@@ -70,45 +62,8 @@ module Glimmer
70
62
  end
71
63
  end
72
64
 
73
- def width
74
- geometry.split(REGEX_GEOMETRY)[0].to_i
75
- end
76
-
77
- def height
78
- geometry.split(REGEX_GEOMETRY)[1].to_i
79
- end
80
-
81
- def x
82
- sign_number(x_sign, geometry.split(REGEX_GEOMETRY)[2].to_i)
83
- end
84
-
85
- def y
86
- sign_number(y_sign, geometry.split(REGEX_GEOMETRY)[3].to_i)
87
- end
88
-
89
- def width=(value)
90
- @width = value.to_i
91
- self.geometry = "#{value.to_i}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
92
- end
93
-
94
- def height=(value)
95
- @height = value.to_i
96
- self.geometry = "#{@width || DEFAULT_WIDTH}x#{value.to_i}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
97
- end
98
-
99
- def x=(value)
100
- self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}#{y_sign}#{abs_y}"
101
- end
102
-
103
- def y=(value)
104
- self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}"
105
- end
106
-
107
65
  def handle_listener(listener_name, &listener)
108
66
  case listener_name.to_s.upcase
109
- when 'WM_DELETE_WINDOW', 'DELETE_WINDOW'
110
- listener_name = 'WM_DELETE_WINDOW'
111
- @tk.protocol(listener_name, &listener)
112
67
  when 'WM_OPEN_WINDOW', 'OPEN_WINDOW'
113
68
  @on_open_window_procs ||= []
114
69
  @on_open_window_procs << listener
@@ -128,36 +83,6 @@ module Glimmer
128
83
  end
129
84
  ::Tk.mainloop
130
85
  end
131
-
132
- private
133
-
134
- def sign_number(sign, number)
135
- "#{sign}1".to_i * number
136
- end
137
-
138
- def abs_x
139
- geometry.split(REGEX_GEOMETRY)[2].to_i
140
- end
141
-
142
- def abs_y
143
- geometry.split(REGEX_GEOMETRY)[3].to_i
144
- end
145
-
146
- def x_sign
147
- geometry_signs[0]
148
- end
149
-
150
- def y_sign
151
- geometry_signs[1]
152
- end
153
-
154
- def geometry_signs
155
- geometry.chars.select {|char| char.match(/[+-]/)}
156
- end
157
-
158
- def initialize_defaults
159
- self.background = '#ececec' if OS.mac?
160
- end
161
86
  end
162
87
  end
163
88
  end
@@ -28,8 +28,7 @@ module Glimmer
28
28
  # Follows the Proxy Design Pattern
29
29
  class TextProxy < WidgetProxy
30
30
  def handle_listener(listener_name, &listener)
31
- listener_name = listener_name.to_s.downcase
32
- case listener_name
31
+ case listener_name.to_s.downcase
33
32
  when '<<modified>>', '<modified>', 'modified'
34
33
  modified_listener = Proc.new do |*args|
35
34
  listener.call(*args)
@@ -38,27 +37,23 @@ module Glimmer
38
37
  bind('<Modified>', modified_listener)
39
38
  when '<<selection>>', '<selection>', 'selection'
40
39
  bind('<Selection>', listener)
41
- else
40
+ when 'destroy'
42
41
  super
43
- end
44
- end
45
-
46
- def text=(value)
47
- if value != @text
48
- if @text && value.start_with?(@text)
49
- insert('end', value[@text.size..-1])
50
- else
51
- delete('1.0', 'end')
52
- insert('end', value)
42
+ else
43
+ @tk.tag_add('__all__', '1.0', 'end') unless @tk.tag_names.include?('__all__')
44
+ # TODO make listener pass an event that has a modifiers attribute for easy representation of :shift, :meta, :control, etc... while a letter button is pressed
45
+ begin
46
+ @tk.tag_bind('__all__', listener_name, &listener)
47
+ rescue => e
48
+ Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
49
+ Glimmer::Config.logger.debug {e.full_message}
50
+ listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
51
+ listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
52
+ @tk.tag_bind('__all__', listener_name, &listener)
53
53
  end
54
- @text = value
55
54
  end
56
55
  end
57
-
58
- def text
59
- @text = get("1.0", 'end')
60
- end
61
-
56
+
62
57
  def add_selection_format(option, value)
63
58
  process_selection_ranges { |range_start, range_end| add_format(range_start, range_end, option, value) }
64
59
  end
@@ -158,7 +153,7 @@ module Glimmer
158
153
  end
159
154
 
160
155
  def applied_font_format_tags_and_regions(region_start, region_end)
161
- lines = text.split("\n")
156
+ lines = value.split("\n")
162
157
  tags_and_regions = []
163
158
  all_tag_names = @tk.tag_names - ['sel']
164
159
  (region_start.to_i..region_end.to_i).each do |line_number|
@@ -332,8 +327,17 @@ module Glimmer
332
327
  region1_parts.last.to_i >= region2_parts.last.to_i
333
328
  end
334
329
 
335
- def clone_font(font)
336
- ::TkFont.new(Hash[font.actual])
330
+ def insert_image(text_index, *image_args)
331
+ TkTextImage.new(@tk, 'insert', :image => image_argument(image_args))
332
+ end
333
+
334
+ def get_open_file_to_insert_image(text_index = 'insert')
335
+ image_filename = Glimmer::DSL::Tk::BuiltInDialogExpression.new.interpret(nil, 'get_open_file', filetypes: {
336
+ 'PNG Images' => '.png',
337
+ 'Gif Images' => '.gif',
338
+ 'PPM Images' => '.ppm'
339
+ })
340
+ insert_image('insert', image_filename) unless image_filename.nil? || image_filename.to_s.empty?
337
341
  end
338
342
 
339
343
  private
@@ -345,6 +349,10 @@ module Glimmer
345
349
  self.padx = 5
346
350
  self.pady = 5
347
351
  end
352
+
353
+ def clone_font(font)
354
+ ::TkFont.new(Hash[font.actual])
355
+ end
348
356
  end
349
357
  end
350
358
  end
@@ -0,0 +1,119 @@
1
+ # Copyright (c) 2020-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/tk/widget_proxy'
23
+
24
+ module Glimmer
25
+ module Tk
26
+ # Proxy for TkToplevel
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class ToplevelProxy < WidgetProxy
30
+ REGEX_GEOMETRY = /[x+-]/
31
+ DEFAULT_WIDTH = 190
32
+ DEFAULT_HEIGHT = 95
33
+
34
+ attr_reader :tk
35
+
36
+ def has_attribute?(attribute, *args)
37
+ %w[width height x y].include?(attribute.to_s) || super
38
+ end
39
+
40
+ def width
41
+ geometry.split(REGEX_GEOMETRY)[0].to_i
42
+ end
43
+
44
+ def height
45
+ geometry.split(REGEX_GEOMETRY)[1].to_i
46
+ end
47
+
48
+ def x
49
+ sign_number(x_sign, geometry.split(REGEX_GEOMETRY)[2].to_i)
50
+ end
51
+
52
+ def y
53
+ sign_number(y_sign, geometry.split(REGEX_GEOMETRY)[3].to_i)
54
+ end
55
+
56
+ def width=(value)
57
+ @width = value.to_i
58
+ self.geometry = "#{value.to_i}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
59
+ end
60
+
61
+ def height=(value)
62
+ @height = value.to_i
63
+ self.geometry = "#{@width || DEFAULT_WIDTH}x#{value.to_i}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
64
+ end
65
+
66
+ def x=(value)
67
+ self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}#{y_sign}#{abs_y}"
68
+ end
69
+
70
+ def y=(value)
71
+ self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}"
72
+ end
73
+
74
+ def handle_listener(listener_name, &listener)
75
+ case listener_name.to_s.upcase
76
+ when 'WM_DELETE_WINDOW', 'DELETE_WINDOW'
77
+ listener_name = 'WM_DELETE_WINDOW'
78
+ @tk.protocol(listener_name, &listener)
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def sign_number(sign, number)
87
+ "#{sign}1".to_i * number
88
+ end
89
+
90
+ def abs_x
91
+ geometry.split(REGEX_GEOMETRY)[2].to_i
92
+ end
93
+
94
+ def abs_y
95
+ geometry.split(REGEX_GEOMETRY)[3].to_i
96
+ end
97
+
98
+ def x_sign
99
+ geometry_signs[0]
100
+ end
101
+
102
+ def y_sign
103
+ geometry_signs[1]
104
+ end
105
+
106
+ def geometry_signs
107
+ geometry.chars.select {|char| char.match(/[+-]/)}
108
+ end
109
+
110
+ def initialize_defaults
111
+ self.background = '#ececec' if OS.mac?
112
+ on('DELETE_WINDOW') do
113
+ grab_release
114
+ destroy
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -19,6 +19,8 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ require 'glimmer/data_binding/tk/one_time_observer'
23
+
22
24
  module Glimmer
23
25
  module Tk
24
26
  # Proxy for Tk Widget objects
@@ -83,6 +85,27 @@ module Glimmer
83
85
  post_add_content if @block.nil?
84
86
  end
85
87
 
88
+ def root_parent_proxy
89
+ current = self
90
+ current = current.parent_proxy while current.parent_proxy
91
+ current
92
+ end
93
+
94
+ def toplevel_parent_proxy
95
+ ancestor_proxies.find {|widget_proxy| widget_proxy.is_a?(ToplevelProxy)}
96
+ end
97
+
98
+ # returns list of ancestors ordered from direct parent to root parent
99
+ def ancestor_proxies
100
+ ancestors = []
101
+ current = self
102
+ while current.parent_proxy
103
+ ancestors << current.parent_proxy
104
+ current = current.parent_proxy
105
+ end
106
+ ancestors
107
+ end
108
+
86
109
  def children
87
110
  @children ||= []
88
111
  end
@@ -227,7 +250,9 @@ module Glimmer
227
250
 
228
251
  def grid(options = {})
229
252
  options = options.stringify_keys
230
- index_in_parent = @parent_proxy.children.index(self)
253
+ index_in_parent = @parent_proxy&.children&.index(self)
254
+ options['rowspan'] = options.delete('row_span') if options.keys.include?('row_span')
255
+ options['columnspan'] = options.delete('column_span') if options.keys.include?('column_span')
231
256
  options['rowweight'] = options.delete('row_weight') if options.keys.include?('row_weight')
232
257
  options['columnweight'] = options.delete('column_weight') if options.keys.include?('column_weight')
233
258
  options['columnweight'] = options['rowweight'] = options.delete('weight') if options.keys.include?('weight')
@@ -238,10 +263,12 @@ module Glimmer
238
263
  options['columnminsize'] = options.delete('minwidth') if options.keys.include?('minwidth')
239
264
  options['columnminsize'] = options.delete('min_width') if options.keys.include?('min_width')
240
265
  options['columnminsize'] = options['rowminsize'] = options.delete('minsize') if options.keys.include?('minsize')
241
- TkGrid.rowconfigure(@parent_proxy.tk, index_in_parent, 'weight'=> options.delete('rowweight')) if options.keys.include?('rowweight')
242
- TkGrid.rowconfigure(@parent_proxy.tk, index_in_parent, 'minsize'=> options.delete('rowminsize')) if options.keys.include?('rowminsize')
243
- TkGrid.columnconfigure(@parent_proxy.tk, index_in_parent, 'weight'=> options.delete('columnweight')) if options.keys.include?('columnweight')
244
- TkGrid.columnconfigure(@parent_proxy.tk, index_in_parent, 'minsize'=> options.delete('columnminsize')) if options.keys.include?('columnminsize')
266
+ if index_in_parent
267
+ TkGrid.rowconfigure(@parent_proxy.tk, index_in_parent, 'weight'=> options.delete('rowweight')) if options.keys.include?('rowweight')
268
+ TkGrid.rowconfigure(@parent_proxy.tk, index_in_parent, 'minsize'=> options.delete('rowminsize')) if options.keys.include?('rowminsize')
269
+ TkGrid.columnconfigure(@parent_proxy.tk, index_in_parent, 'weight'=> options.delete('columnweight')) if options.keys.include?('columnweight')
270
+ TkGrid.columnconfigure(@parent_proxy.tk, index_in_parent, 'minsize'=> options.delete('columnminsize')) if options.keys.include?('columnminsize')
271
+ end
245
272
  @tk.grid(options)
246
273
  end
247
274
 
@@ -257,6 +284,11 @@ module Glimmer
257
284
  apply_style({"font" => value})
258
285
  end
259
286
 
287
+ def destroy
288
+ @tk.destroy
289
+ @on_destroy_procs&.each {|p| p.call(@tk)}
290
+ end
291
+
260
292
  def apply_style(options)
261
293
  @@style_number = 0 unless defined?(@@style_number)
262
294
  style = "style#{@@style_number += 1}.#{@tk.class.name.split('::').last}"
@@ -379,9 +411,9 @@ module Glimmer
379
411
  end,
380
412
  },
381
413
  ::Tk::Text => {
382
- 'text' => lambda do |observer|
414
+ 'value' => lambda do |observer|
383
415
  handle_listener('modified') do
384
- observer.call(text)
416
+ observer.call(value)
385
417
  end
386
418
  end,
387
419
  },
@@ -419,16 +451,31 @@ module Glimmer
419
451
 
420
452
  def handle_listener(listener_name, &listener)
421
453
  listener_name = listener_name.to_s
422
- begin
423
- @tk.bind(listener_name, &listener)
424
- rescue => e
425
- Glimmer::Config.logger.debug {e.full_message}
426
- listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
427
- listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
428
- @tk.bind(listener_name, &listener)
454
+ if listener_name == 'destroy'
455
+ # 'destroy' is a more reliable alternative listener binding to '<Destroy>'
456
+ @on_destroy_procs ||= []
457
+ listener.singleton_class.include(Glimmer::DataBinding::Tk::OneTimeObserver) unless listener.is_a?(Glimmer::DataBinding::Tk::OneTimeObserver)
458
+ @on_destroy_procs << listener
459
+ @tk.bind('<Destroy>', listener)
460
+ parent_proxy.handle_listener(listener_name, &listener) if parent_proxy
461
+ # TODO return a listener registration object that has a deregister method
462
+ else
463
+ begin
464
+ @tk.bind(listener_name, &listener)
465
+ rescue => e
466
+ Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
467
+ Glimmer::Config.logger.debug {e.full_message}
468
+ listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
469
+ listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
470
+ @tk.bind(listener_name, &listener)
471
+ end
429
472
  end
430
473
  end
431
474
 
475
+ def on(listener_name, &listener)
476
+ handle_listener(listener_name, &listener)
477
+ end
478
+
432
479
  def content(&block)
433
480
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Tk::WidgetExpression.new, keyword, *args, &block)
434
481
  end
@@ -87,7 +87,7 @@ class MetaSample
87
87
 
88
88
  on('command') do
89
89
  @selected_sample_index = index
90
- @code_text.text = File.read(file_path_for(selected_sample))
90
+ @code_text.value = File.read(file_path_for(selected_sample))
91
91
  end
92
92
  }
93
93
  end
@@ -102,7 +102,7 @@ class MetaSample
102
102
  parent_dir = File.join(Dir.home, '.glimmer-dsl-tk', 'samples', 'hello')
103
103
  FileUtils.mkdir_p(parent_dir)
104
104
  sample_file = File.join(parent_dir, "#{selected_sample.underscore}.rb")
105
- File.write(sample_file, @code_text.text)
105
+ File.write(sample_file, @code_text.value)
106
106
  FileUtils.cp_r(File.expand_path('../../icons', __dir__), File.dirname(File.dirname(parent_dir)))
107
107
  FileUtils.cp_r(File.expand_path('../hello/images', __dir__), parent_dir)
108
108
  sample_namespace_directory = File.expand_path("../hello/#{selected_sample.underscore}", __dir__)
@@ -120,7 +120,7 @@ class MetaSample
120
120
  text 'Reset'
121
121
 
122
122
  on('command') do
123
- @code_text.text = File.read(file_path_for(selected_sample))
123
+ @code_text.value = File.read(file_path_for(selected_sample))
124
124
  end
125
125
  }
126
126
  }
@@ -128,7 +128,7 @@ class MetaSample
128
128
 
129
129
  @code_text = text {
130
130
  grid row: 0, column: 1, column_weight: 1
131
- text File.read(file_path_for(selected_sample))
131
+ value File.read(file_path_for(selected_sample))
132
132
  }
133
133
  }
134
134
  @root.open