glimmer-dsl-tk 0.0.30 → 0.0.34

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -27,48 +27,118 @@ module Glimmer
27
27
  #
28
28
  # Follows the Proxy Design Pattern
29
29
  class TextProxy < WidgetProxy
30
+ ALL_TAG = '__all__'
31
+ FORMAT_DEFAULT_MAP = {
32
+ 'justify' => 'left',
33
+ }
34
+
30
35
  def handle_listener(listener_name, &listener)
31
- listener_name = listener_name.to_s.downcase
32
- case listener_name
36
+ case listener_name.to_s.downcase
33
37
  when '<<modified>>', '<modified>', 'modified'
34
38
  modified_listener = Proc.new do |*args|
39
+ @modified_count ||= 0
40
+ @modified_count += 1
35
41
  listener.call(*args)
42
+ apply_all_tag
43
+ @insert_mark_moved_proc&.call
36
44
  @tk.modified = false
37
45
  end
38
- bind('<Modified>', modified_listener)
46
+ @tk.bind('<Modified>', modified_listener)
39
47
  when '<<selection>>', '<selection>', 'selection'
40
- bind('<Selection>', listener)
41
- else
48
+ @tk.bind('<Selection>', listener)
49
+ when 'destroy'
42
50
  super
51
+ when 'insertmarkmove', 'insertmarkmoved', 'insert_mark_move', 'insert_mark_moved'
52
+ if @insert_mark_moved_proc.nil?
53
+ handle_listener('KeyPress') do |event|
54
+ @insert_mark_moved_proc&.call
55
+ end
56
+ handle_listener('KeyRelease') do |event|
57
+ @insert_mark_moved_proc&.call
58
+ end
59
+ handle_listener('ButtonPress') do |event|
60
+ @insert_mark_moved_proc&.call
61
+ end
62
+ handle_listener('ButtonRelease') do |event|
63
+ @insert_mark_moved_proc&.call
64
+ end
65
+ end
66
+ @insert_mark = @tk.index('insert')
67
+ @insert_mark_moved_proc = Proc.new do
68
+ new_insert_mark = @tk.index('insert')
69
+ if new_insert_mark != @insert_mark
70
+ @insert_mark = new_insert_mark
71
+ listener.call(new_insert_mark)
72
+ end
73
+ end
74
+ else
75
+ apply_all_tag
76
+ # 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
77
+ @listeners ||= {}
78
+ begin
79
+ @listeners[listener_name] ||= []
80
+ @tk.tag_bind(ALL_TAG, listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
81
+ @listeners[listener_name] << listener
82
+ rescue => e
83
+ @listeners.delete(listener_name)
84
+ Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
85
+ Glimmer::Config.logger.debug {e.full_message}
86
+ listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
87
+ listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
88
+ @listeners[listener_name] ||= []
89
+ @tk.tag_bind(ALL_TAG, listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
90
+ @listeners[listener_name] << listener
91
+ end
43
92
  end
44
93
  end
45
-
46
- def add_selection_format(option, value)
47
- process_selection_ranges { |range_start, range_end| add_format(range_start, range_end, option, value) }
94
+
95
+ def edit_undo
96
+ @tk.edit_undo if @modified_count.to_i > 2 # <Modified> fires twice the first time, which is equivalent to one change.
48
97
  end
49
98
 
50
- def remove_selection_format(option, value)
51
- process_selection_ranges { |range_start, range_end| remove_format(range_start, range_end, option, value) }
99
+ def edit_redo
100
+ begin
101
+ @tk.edit_redo
102
+ rescue => e
103
+ # No Op
104
+ end
52
105
  end
53
106
 
54
- def toggle_selection_format(option, value)
55
- process_selection_ranges { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
107
+ def add_selection_format(option, value, no_selection_default: :insert_word)
108
+ process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| add_format(range_start, range_end, option, value) }
56
109
  end
57
110
 
58
- def add_selection_font_format(option, value)
59
- process_selection_ranges { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
111
+ def remove_selection_format(option, value, no_selection_default: :insert_word)
112
+ process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| remove_format(range_start, range_end, option, value) }
60
113
  end
61
114
 
62
- def remove_selection_font_format(option, value)
63
- process_selection_ranges { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
115
+ def toggle_selection_format(option, value, no_selection_default: :insert_word)
116
+ process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
64
117
  end
65
118
 
66
- def toggle_selection_font_format(option, value)
67
- process_selection_ranges { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
119
+ def add_selection_font_format(option, value, no_selection_default: :insert_word)
120
+ process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
68
121
  end
69
122
 
70
- def process_selection_ranges(&processor)
71
- @tk.tag_ranges('sel').each do |region|
123
+ def remove_selection_font_format(option, value, no_selection_default: :insert_word)
124
+ process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
125
+ end
126
+
127
+ def toggle_selection_font_format(option, value, no_selection_default: :insert_word)
128
+ process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
129
+ end
130
+
131
+ def process_selection_ranges(no_selection_default: :insert_word, &processor)
132
+ regions = @tk.tag_ranges('sel')
133
+ if regions.empty?
134
+ case no_selection_default
135
+ when :insert_word
136
+ regions = [[@tk.index('insert wordstart'), @tk.index('insert wordend + 1 char')]]
137
+ when :insert_letter
138
+ regions = [[@tk.index('insert'), @tk.index('insert + 1 char')]]
139
+ end
140
+ end
141
+ regions.each do |region|
72
142
  range_start = region.first
73
143
  range_end = region.last
74
144
  processor.call(range_start, range_end)
@@ -80,7 +150,7 @@ module Glimmer
80
150
  end
81
151
 
82
152
  def applied_format_tags(region_start, region_end, option, value)
83
- tag_names = @tk.tag_names - ['sel']
153
+ tag_names = @tk.tag_names - ['sel', ALL_TAG]
84
154
 
85
155
  tag_names.select do |tag_name|
86
156
  @tk.tag_ranges(tag_name).any? do |range|
@@ -91,9 +161,26 @@ module Glimmer
91
161
  end
92
162
  end
93
163
 
164
+ def applied_format_value(text_index = nil, option)
165
+ text_index ||= @tk.index('insert')
166
+ region_start = text_index
167
+ region_end = text_index
168
+ tag_names = @tk.tag_names - ['sel', ALL_TAG]
169
+
170
+ values = tag_names.map do |tag_name|
171
+ @tk.tag_ranges(tag_name).map do |range|
172
+ if text_index_less_than_or_equal_to_other_text_index?(range.first, region_start) && text_index_greater_than_or_equal_to_other_text_index?(range.last, region_end)
173
+ @tk.tag_cget(tag_name, option)
174
+ end
175
+ end
176
+ end.flatten.reject {|value| value.to_s.empty?}
177
+
178
+ values.last || (@tk.send(option) rescue FORMAT_DEFAULT_MAP[option])
179
+ end
180
+
94
181
  def add_format(region_start, region_end, option, value)
95
182
  @@tag_number = 0 unless defined?(@@tag_number)
96
- tag = "tag#{@@tag_number += 1}"
183
+ tag = "tag_#{option}_#{@@tag_number += 1}"
97
184
  @tk.tag_configure(tag, {option => value})
98
185
  @tk.tag_add(tag, region_start, region_end)
99
186
  tag
@@ -144,15 +231,14 @@ module Glimmer
144
231
  def applied_font_format_tags_and_regions(region_start, region_end)
145
232
  lines = value.split("\n")
146
233
  tags_and_regions = []
147
- all_tag_names = @tk.tag_names - ['sel']
234
+ all_tag_names = (@tk.tag_names - ['sel', ALL_TAG]).select {|tag_name| tag_name.include?('_font_')}
148
235
  (region_start.to_i..region_end.to_i).each do |line_number|
149
236
  start_character_index = 0
150
237
  start_character_index = region_start.to_s.split('.').last.to_i if line_number == region_start.to_i
151
- end_character_index = lines[line_number - 1].size
238
+ end_character_index = lines[line_number - 1].to_s.size
152
239
  end_character_index = region_end.to_s.split('.').last.to_i if line_number == region_end.to_i
153
240
  (start_character_index...end_character_index).each do |character_index|
154
241
  text_index = "#{line_number}.#{character_index}"
155
- # TODO reimplement the following using @tk.tag_names without arg since passing an arg seems broken and returns inaccurate results
156
242
  region_tag = all_tag_names.reverse.find do |tag|
157
243
  @tk.tag_cget(tag, 'font') && @tk.tag_ranges(tag).any? do |range_start, range_end|
158
244
  text_index_less_than_or_equal_to_other_text_index?(range_start, text_index) && text_index_greater_than_or_equal_to_other_text_index?(range_end, text_index)
@@ -168,6 +254,27 @@ module Glimmer
168
254
  end
169
255
  tags_and_regions
170
256
  end
257
+
258
+ def applied_font_format_value(text_index = nil, font_option)
259
+ text_index ||= @tk.index('insert')
260
+ region_start = text_index
261
+ region_end = @tk.index("#{text_index} + 1 chars")
262
+ tag_names = applied_font_format_tags_and_regions(region_start, region_end).map(&:first)
263
+
264
+ values = tag_names.map do |tag_name|
265
+ @tk.tag_ranges(tag_name).map do |range|
266
+ if text_index_less_than_or_equal_to_other_text_index?(range.first, region_start) && text_index_greater_than_or_equal_to_other_text_index?(range.last, region_end)
267
+ @tk.tag_cget(tag_name, 'font')
268
+ end
269
+ end
270
+ end.flatten.reject {|value| value.to_s.empty?}
271
+
272
+ font = values.last
273
+
274
+ value = font && font.send(font_option)
275
+
276
+ value || Hash[@tk.font.actual][font_option]
277
+ end
171
278
 
172
279
  def add_font_format(region_start, region_end, font_option, value)
173
280
  applied_font_format_tags_and_regions(region_start, region_end).each do |tag, tag_region_start, tag_region_end|
@@ -337,11 +444,16 @@ module Glimmer
337
444
  self.wrap = 'none'
338
445
  self.padx = 5
339
446
  self.pady = 5
447
+ on('Modified') { apply_all_tag }
340
448
  end
341
449
 
342
450
  def clone_font(font)
343
451
  ::TkFont.new(Hash[font.actual])
344
452
  end
453
+
454
+ def apply_all_tag
455
+ @tk.tag_add(ALL_TAG, '1.0', 'end')
456
+ end
345
457
  end
346
458
  end
347
459
  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,7 @@ 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)
231
254
  options['rowspan'] = options.delete('row_span') if options.keys.include?('row_span')
232
255
  options['columnspan'] = options.delete('column_span') if options.keys.include?('column_span')
233
256
  options['rowweight'] = options.delete('row_weight') if options.keys.include?('row_weight')
@@ -240,10 +263,12 @@ module Glimmer
240
263
  options['columnminsize'] = options.delete('minwidth') if options.keys.include?('minwidth')
241
264
  options['columnminsize'] = options.delete('min_width') if options.keys.include?('min_width')
242
265
  options['columnminsize'] = options['rowminsize'] = options.delete('minsize') if options.keys.include?('minsize')
243
- TkGrid.rowconfigure(@parent_proxy.tk, index_in_parent, 'weight'=> options.delete('rowweight')) if options.keys.include?('rowweight')
244
- TkGrid.rowconfigure(@parent_proxy.tk, index_in_parent, 'minsize'=> options.delete('rowminsize')) if options.keys.include?('rowminsize')
245
- TkGrid.columnconfigure(@parent_proxy.tk, index_in_parent, 'weight'=> options.delete('columnweight')) if options.keys.include?('columnweight')
246
- 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
247
272
  @tk.grid(options)
248
273
  end
249
274
 
@@ -259,6 +284,11 @@ module Glimmer
259
284
  apply_style({"font" => value})
260
285
  end
261
286
 
287
+ def destroy
288
+ @tk.destroy
289
+ @on_destroy_procs&.each {|p| p.call(@tk)}
290
+ end
291
+
262
292
  def apply_style(options)
263
293
  @@style_number = 0 unless defined?(@@style_number)
264
294
  style = "style#{@@style_number += 1}.#{@tk.class.name.split('::').last}"
@@ -421,17 +451,31 @@ module Glimmer
421
451
 
422
452
  def handle_listener(listener_name, &listener)
423
453
  listener_name = listener_name.to_s
424
- begin
425
- @tk.bind(listener_name, &listener)
426
- rescue => e
427
- Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
428
- Glimmer::Config.logger.debug {e.full_message}
429
- listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
430
- listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
431
- @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
432
472
  end
433
473
  end
434
474
 
475
+ def on(listener_name, &listener)
476
+ handle_listener(listener_name, &listener)
477
+ end
478
+
435
479
  def content(&block)
436
480
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Tk::WidgetExpression.new, keyword, *args, &block)
437
481
  end