glimmer-dsl-tk 0.0.30 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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