glimmer-dsl-tk 0.0.24 → 0.0.28

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,350 @@
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 Tk::Text
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class TextProxy < WidgetProxy
30
+ def handle_listener(listener_name, &listener)
31
+ listener_name = listener_name.to_s.downcase
32
+ case listener_name
33
+ when '<<modified>>', '<modified>', 'modified'
34
+ modified_listener = Proc.new do |*args|
35
+ listener.call(*args)
36
+ @tk.modified = false
37
+ end
38
+ bind('<Modified>', modified_listener)
39
+ when '<<selection>>', '<selection>', 'selection'
40
+ bind('<Selection>', listener)
41
+ else
42
+ 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)
53
+ end
54
+ @text = value
55
+ end
56
+ end
57
+
58
+ def text
59
+ @text = get("1.0", 'end')
60
+ end
61
+
62
+ def add_selection_format(option, value)
63
+ process_selection_ranges { |range_start, range_end| add_format(range_start, range_end, option, value) }
64
+ end
65
+
66
+ def remove_selection_format(option, value)
67
+ process_selection_ranges { |range_start, range_end| remove_format(range_start, range_end, option, value) }
68
+ end
69
+
70
+ def toggle_selection_format(option, value)
71
+ process_selection_ranges { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
72
+ end
73
+
74
+ def add_selection_font_format(option, value)
75
+ process_selection_ranges { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
76
+ end
77
+
78
+ def remove_selection_font_format(option, value)
79
+ process_selection_ranges { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
80
+ end
81
+
82
+ def toggle_selection_font_format(option, value)
83
+ process_selection_ranges { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
84
+ end
85
+
86
+ def process_selection_ranges(&processor)
87
+ @tk.tag_ranges('sel').each do |region|
88
+ range_start = region.first
89
+ range_end = region.last
90
+ processor.call(range_start, range_end)
91
+ end
92
+ end
93
+
94
+ def applied_format?(region_start, region_end, option, value)
95
+ !applied_format_tags(region_start, region_end, option, value).empty?
96
+ end
97
+
98
+ def applied_format_tags(region_start, region_end, option, value)
99
+ tag_names = @tk.tag_names - ['sel']
100
+
101
+ tag_names.select do |tag_name|
102
+ @tk.tag_ranges(tag_name).any? do |range|
103
+ 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)
104
+ @tk.tag_cget(tag_name, option) == value
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def add_format(region_start, region_end, option, value)
111
+ @@tag_number = 0 unless defined?(@@tag_number)
112
+ tag = "tag#{@@tag_number += 1}"
113
+ @tk.tag_configure(tag, {option => value})
114
+ @tk.tag_add(tag, region_start, region_end)
115
+ tag
116
+ end
117
+
118
+ def remove_format(region_start, region_end, option, value)
119
+ partial_intersection_option_applied_tags = tag_names.select do |tag_name|
120
+ @tk.tag_ranges(tag_name).any? do |range|
121
+ if range.first.to_f.between?(region_start.to_f, region_end.to_f) or
122
+ range.last.to_f.between?(region_start.to_f, region_end.to_f) or
123
+ (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))
124
+ @tk.tag_cget(tag_name, option) == value
125
+ end
126
+ end
127
+ end
128
+
129
+ partial_intersection_option_applied_tags.each do |tag_name|
130
+ @tk.tag_remove(tag_name, region_start, region_end)
131
+ end
132
+
133
+ nil
134
+ end
135
+
136
+ # toggles option/value tag (removes if already applied)
137
+ def toggle_format(region_start, region_end, option, value)
138
+ if applied_format?(region_start, region_end, option, value)
139
+ remove_format(region_start, region_end, option, value)
140
+ else
141
+ add_format(region_start, region_end, option, value)
142
+ end
143
+ end
144
+
145
+ # TODO Algorithm for font option formatting
146
+ # for a region, grab all the latest tags for each subregion as well as the widget font for subregions without a tag
147
+ # for each part of the region covered by a tag, augment its font with new font option (or remove if that is what is needed)
148
+ # Once add and remove are implemented, implement toggle
149
+ # Also, there is a need for a method that checks if a font option value applies to an entire region (to decide which way to toggle with toggle method)
150
+ def applied_font_format?(region_start, region_end, font_option, value)
151
+ applied_font_format_tags_and_regions(region_start, region_end).all? do |tag, region_start, region_end|
152
+ if tag.nil?
153
+ @tk.font.send(font_option) == value
154
+ else
155
+ @tk.tag_cget(tag, 'font').send(font_option) == value
156
+ end
157
+ end
158
+ end
159
+
160
+ def applied_font_format_tags_and_regions(region_start, region_end)
161
+ lines = text.split("\n")
162
+ tags_and_regions = []
163
+ all_tag_names = @tk.tag_names - ['sel']
164
+ (region_start.to_i..region_end.to_i).each do |line_number|
165
+ start_character_index = 0
166
+ start_character_index = region_start.to_s.split('.').last.to_i if line_number == region_start.to_i
167
+ end_character_index = lines[line_number - 1].size
168
+ end_character_index = region_end.to_s.split('.').last.to_i if line_number == region_end.to_i
169
+ (start_character_index...end_character_index).each do |character_index|
170
+ text_index = "#{line_number}.#{character_index}"
171
+ # TODO reimplement the following using @tk.tag_names without arg since passing an arg seems broken and returns inaccurate results
172
+ region_tag = all_tag_names.reverse.find do |tag|
173
+ @tk.tag_cget(tag, 'font') && @tk.tag_ranges(tag).any? do |range_start, range_end|
174
+ 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)
175
+ end
176
+ end
177
+ end_text_index = add_to_text_index(text_index, 1)
178
+ if tags_and_regions&.last && region_tag == tags_and_regions.last.first
179
+ tags_and_regions.last[2] = end_text_index
180
+ else
181
+ tags_and_regions << [region_tag, text_index, end_text_index]
182
+ end
183
+ end
184
+ end
185
+ tags_and_regions
186
+ end
187
+
188
+ def add_font_format(region_start, region_end, font_option, value)
189
+ applied_font_format_tags_and_regions(region_start, region_end).each do |tag, tag_region_start, tag_region_end|
190
+ if tag
191
+ bigger_region_tag = @tk.tag_ranges(tag).any? do |range_start, range_end|
192
+ text_index_less_than_other_text_index?(range_start, tag_region_start) || text_index_greater_than_other_text_index?(range_end, tag_region_end)
193
+ end
194
+ if bigger_region_tag
195
+ @tk.tag_ranges(tag).each do |range_start, range_end|
196
+ if text_index_less_than_other_text_index?(range_start, tag_region_start) && text_index_less_than_or_equal_to_other_text_index?(range_end, tag_region_end) && text_index_greater_than_or_equal_to_other_text_index?(range_end, tag_region_start)
197
+ font = @tk.tag_cget(tag, 'font')
198
+ remove_format(range_start, range_end, 'font', font)
199
+ add_format(range_start, tag_region_start, 'font', font)
200
+ font_clone = clone_font(font)
201
+ font_clone.send("#{font_option}=", value)
202
+ add_format(tag_region_start, tag_region_end, 'font', font_clone)
203
+ elsif text_index_greater_than_other_text_index?(range_end, tag_region_end) && text_index_greater_than_or_equal_to_other_text_index?(range_start, tag_region_start) && text_index_less_than_or_equal_to_other_text_index?(range_start, tag_region_end)
204
+ font = @tk.tag_cget(tag, 'font')
205
+ remove_format(range_start, range_end, 'font', font)
206
+ add_format(tag_region_end, range_end, 'font', font)
207
+ font_clone = clone_font(font)
208
+ font_clone.send("#{font_option}=", value)
209
+ add_format(tag_region_start, tag_region_end, 'font', font_clone)
210
+ elsif text_index_less_than_other_text_index?(range_start, tag_region_start) && text_index_greater_than_other_text_index?(range_end, tag_region_end)
211
+ font = @tk.tag_cget(tag, 'font')
212
+ remove_format(range_start, range_end, 'font', font)
213
+ add_format(range_start, tag_region_start, 'font', font)
214
+ remove_format(range_start, range_end, 'font', font)
215
+ add_format(tag_region_end, range_end, 'font', font)
216
+ font_clone = clone_font(font)
217
+ font_clone.send("#{font_option}=", value)
218
+ add_format(tag_region_start, tag_region_end, 'font', font_clone)
219
+ end
220
+ end
221
+ else
222
+ current_font = @tk.tag_cget(tag, 'font')
223
+ current_font.send("#{font_option}=", value)
224
+ end
225
+ else
226
+ add_format(tag_region_start, tag_region_end, 'font', default_font_attributes.merge(font_option => value))
227
+ end
228
+ end
229
+ end
230
+
231
+ def remove_font_format(region_start, region_end, font_option, value)
232
+ applied_font_format_tags_and_regions(region_start, region_end).each do |tag, tag_region_start, tag_region_end|
233
+ if tag
234
+ bigger_region_tag = @tk.tag_ranges(tag).any? do |range_start, range_end|
235
+ text_index_less_than_other_text_index?(range_start, tag_region_start) || text_index_greater_than_other_text_index?(range_end, tag_region_end)
236
+ end
237
+ if bigger_region_tag
238
+ @tk.tag_ranges(tag).each do |range_start, range_end|
239
+ if text_index_less_than_other_text_index?(range_start, tag_region_start) && text_index_less_than_or_equal_to_other_text_index?(range_end, tag_region_end) && text_index_greater_than_or_equal_to_other_text_index?(range_end, tag_region_start)
240
+ font = @tk.tag_cget(tag, 'font')
241
+ remove_format(range_start, range_end, 'font', font)
242
+ add_format(range_start, subtract_from_text_index(tag_region_start, 1), 'font', font)
243
+ font_clone = clone_font(font)
244
+ font_clone.send("#{font_option}=", default_for_font_option(font_option))
245
+ add_format(tag_region_start, tag_region_end, 'font', font_clone)
246
+ elsif text_index_greater_than_other_text_index?(range_end, tag_region_end) && text_index_greater_than_or_equal_to_other_text_index?(range_start, tag_region_start) && text_index_less_than_or_equal_to_other_text_index?(range_start, tag_region_end)
247
+ font = @tk.tag_cget(tag, 'font')
248
+ remove_format(range_start, range_end, 'font', font)
249
+ add_format(add_to_text_index(tag_region_end, 1), range_end, 'font', font)
250
+ font_clone = clone_font(font)
251
+ font_clone.send("#{font_option}=", default_for_font_option(font_option))
252
+ add_format(tag_region_start, tag_region_end, 'font', font_clone)
253
+ elsif text_index_less_than_other_text_index?(range_start, tag_region_start) && text_index_greater_than_other_text_index?(range_end, tag_region_end)
254
+ font = @tk.tag_cget(tag, 'font')
255
+ remove_format(range_start, range_end, 'font', font)
256
+ add_format(range_start, subtract_from_text_index(tag_region_start, 1), 'font', font)
257
+ remove_format(range_start, range_end, 'font', font)
258
+ add_format(add_to_text_index(tag_region_end, 1), range_end, 'font', font)
259
+ font_clone = clone_font(font)
260
+ font_clone.send("#{font_option}=", default_for_font_option(font_option))
261
+ add_format(tag_region_start, tag_region_end, 'font', font_clone)
262
+ end
263
+ end
264
+ else
265
+ current_font = @tk.tag_cget(tag, 'font')
266
+ current_font.send("#{font_option}=", default_for_font_option(font_option))
267
+ end
268
+ else
269
+ add_format(tag_region_start, tag_region_end, 'font', default_font_attributes.merge(font_option => default_for_font_option(font_option)))
270
+ end
271
+ end
272
+ end
273
+
274
+ # toggles option/value tag (removes if already applied)
275
+ def toggle_font_format(region_start, region_end, option, value)
276
+ if applied_font_format?(region_start, region_end, option, value)
277
+ remove_font_format(region_start, region_end, option, value)
278
+ else
279
+ add_font_format(region_start, region_end, option, value)
280
+ end
281
+ end
282
+
283
+ def default_for_font_option(font_option)
284
+ @tk.font.send(font_option)
285
+ end
286
+
287
+ def default_font_attributes
288
+ Hash[@tk.font.actual]
289
+ end
290
+
291
+ def add_to_text_index(text_index, addition)
292
+ text_index_parts = text_index.split('.')
293
+ line = text_index_parts.first
294
+ char_index = text_index_parts.last
295
+ char_index = char_index.to_i + addition
296
+ "#{line}.#{char_index}"
297
+ end
298
+
299
+ def subtract_from_text_index(text_index, subtraction)
300
+ add_to_text_index(text_index, -1 * subtraction)
301
+ end
302
+
303
+ def text_index_less_than_other_text_index?(region1, region2)
304
+ region1_parts = region1.to_s.split('.')
305
+ region2_parts = region2.to_s.split('.')
306
+ return true if region1_parts.first.to_i < region2_parts.first.to_i
307
+ return false if region1_parts.first.to_i > region2_parts.first.to_i
308
+ region1_parts.last.to_i < region2_parts.last.to_i
309
+ end
310
+
311
+ def text_index_less_than_or_equal_to_other_text_index?(region1, region2)
312
+ region1_parts = region1.to_s.split('.')
313
+ region2_parts = region2.to_s.split('.')
314
+ return true if region1_parts.first.to_i < region2_parts.first.to_i
315
+ return false if region1_parts.first.to_i > region2_parts.first.to_i
316
+ region1_parts.last.to_i <= region2_parts.last.to_i
317
+ end
318
+
319
+ def text_index_greater_than_other_text_index?(region1, region2)
320
+ region1_parts = region1.to_s.split('.')
321
+ region2_parts = region2.to_s.split('.')
322
+ return true if region1_parts.first.to_i > region2_parts.first.to_i
323
+ return false if region1_parts.first.to_i < region2_parts.first.to_i
324
+ region1_parts.last.to_i > region2_parts.last.to_i
325
+ end
326
+
327
+ def text_index_greater_than_or_equal_to_other_text_index?(region1, region2)
328
+ region1_parts = region1.to_s.split('.')
329
+ region2_parts = region2.to_s.split('.')
330
+ return true if region1_parts.first.to_i > region2_parts.first.to_i
331
+ return false if region1_parts.first.to_i < region2_parts.first.to_i
332
+ region1_parts.last.to_i >= region2_parts.last.to_i
333
+ end
334
+
335
+ def clone_font(font)
336
+ ::TkFont.new(Hash[font.actual])
337
+ end
338
+
339
+ private
340
+
341
+ def initialize_defaults
342
+ super
343
+ self.font = {family: 'Courier New'}
344
+ self.wrap = 'none'
345
+ self.padx = 5
346
+ self.pady = 5
347
+ end
348
+ end
349
+ end
350
+ end
@@ -34,7 +34,9 @@ module Glimmer
34
34
  begin
35
35
  class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
36
36
  Glimmer::Tk.const_get(class_name)
37
- rescue
37
+ rescue => e
38
+ Glimmer::Config.logger.debug {"Unable to instantiate custom class name for #{keyword} ... defaulting to Glimmer::Tk::WidgetProxy"}
39
+ Glimmer::Config.logger.debug {e.full_message}
38
40
  Glimmer::Tk::WidgetProxy
39
41
  end
40
42
  end
@@ -54,13 +56,15 @@ module Glimmer
54
56
  tk_widget_class = eval(tk_widget_name)
55
57
  break
56
58
  rescue RuntimeError, SyntaxError, NameError => e
57
- Glimmer::Config.logger.debug e.full_message
59
+ Glimmer::Config.logger.debug {e.full_message}
58
60
  end
59
61
  end
60
- tk_widget_class
62
+ tk_widget_class if tk_widget_class.respond_to?(:new)
61
63
  end
62
64
  end
63
65
 
66
+ FONTS_PREDEFINED = %w[default text fixed menu heading caption small_caption icon tooltip]
67
+
64
68
  attr_reader :parent_proxy, :tk, :args, :keyword, :children
65
69
 
66
70
  # Initializes a new Tk Widget
@@ -74,8 +78,8 @@ module Glimmer
74
78
  tk_widget_class = self.class.tk_widget_class_for(underscored_widget_name)
75
79
  @tk = tk_widget_class.new(@parent_proxy.tk, *args)
76
80
  # a common widget initializer
77
- initialize_defaults
78
81
  @parent_proxy.post_initialize_child(self)
82
+ initialize_defaults
79
83
  post_add_content if @block.nil?
80
84
  end
81
85
 
@@ -104,6 +108,8 @@ module Glimmer
104
108
  @tk.send(attribute_setter(attribute), @tk.send(attribute))
105
109
  result = true
106
110
  rescue => e
111
+ Glimmer::Config.logger.debug { "No tk attribute setter for #{attribute}" }
112
+ Glimmer::Config.logger.debug { e.full_message }
107
113
  result = false
108
114
  end
109
115
  result
@@ -114,7 +120,8 @@ module Glimmer
114
120
  # TK Widget currently doesn't support respond_to? properly, so I have to resort to this trick for now
115
121
  @tk.send(attribute)
116
122
  true
117
- rescue
123
+ rescue => e
124
+ Glimmer::Config.logger.debug { "No tk attribute getter setter for #{attribute}" }
118
125
  false
119
126
  end
120
127
  end
@@ -125,7 +132,8 @@ module Glimmer
125
132
  begin
126
133
  @tk.tile_instate(attribute)
127
134
  true
128
- rescue
135
+ rescue => e
136
+ Glimmer::Config.logger.debug { "No tk state for #{attribute}" }
129
137
  false
130
138
  end
131
139
  else
@@ -144,45 +152,55 @@ module Glimmer
144
152
  tk_widget_has_attribute_getter_setter?(attribute) or
145
153
  has_state?(attribute) or
146
154
  has_attributes_attribute?(attribute) or
147
- respond_to?(attribute_setter(attribute), args)
155
+ respond_to?(attribute_setter(attribute), args) or
156
+ respond_to?(attribute_setter(attribute), *args, super_only: true) or
157
+ respond_to?(attribute, *args, super_only: true)
148
158
  end
149
159
 
150
160
  def set_attribute(attribute, *args)
151
- widget_custom_attribute = widget_custom_attribute_mapping[tk.class] && widget_custom_attribute_mapping[tk.class][attribute.to_s]
152
- if respond_to?(attribute, super_only: true)
153
- send(attribute, *args)
154
- elsif respond_to?(attribute_setter(attribute), super_only: true)
155
- send(attribute_setter(attribute), *args)
156
- elsif widget_custom_attribute
157
- widget_custom_attribute[:setter][:invoker].call(@tk, args)
158
- elsif tk_widget_has_attribute_setter?(attribute)
159
- unless args.size == 1 && @tk.send(attribute) == args.first
160
- if args.size == 1
161
- @tk.send(attribute_setter(attribute), *args)
161
+ begin
162
+ widget_custom_attribute = widget_custom_attribute_mapping[tk.class] && widget_custom_attribute_mapping[tk.class][attribute.to_s]
163
+ if respond_to?(attribute_setter(attribute), super_only: true)
164
+ send(attribute_setter(attribute), *args)
165
+ elsif respond_to?(attribute, super_only: true) && self.class.instance_method(attribute).parameters.size > 0
166
+ send(attribute, *args)
167
+ elsif widget_custom_attribute
168
+ widget_custom_attribute[:setter][:invoker].call(@tk, args)
169
+ elsif tk_widget_has_attribute_setter?(attribute)
170
+ unless args.size == 1 && @tk.send(attribute) == args.first
171
+ if args.size == 1
172
+ @tk.send(attribute_setter(attribute), *args)
173
+ else
174
+ @tk.send(attribute_setter(attribute), args)
175
+ end
176
+ end
177
+ elsif tk_widget_has_attribute_getter_setter?(attribute)
178
+ @tk.send(attribute, *args)
179
+ elsif has_state?(attribute)
180
+ attribute = attribute.sub(/=$/, '')
181
+ if !!args.first
182
+ @tk.tile_state(attribute)
162
183
  else
163
- @tk.send(attribute_setter(attribute), args)
184
+ @tk.tile_state("!#{attribute}")
164
185
  end
165
- end
166
- elsif tk_widget_has_attribute_getter_setter?(attribute)
167
- @tk.send(attribute, *args)
168
- elsif has_state?(attribute)
169
- attribute = attribute.sub(/=$/, '')
170
- if !!args.first
171
- @tk.tile_state(attribute)
186
+ elsif has_attributes_attribute?(attribute)
187
+ attribute = attribute.sub(/=$/, '')
188
+ @tk.attributes(attribute, args.first)
172
189
  else
173
- @tk.tile_state("!#{attribute}")
190
+ raise "#{self} cannot handle attribute #{attribute} with args #{args.inspect}"
174
191
  end
175
- elsif has_attributes_attribute?(attribute)
176
- attribute = attribute.sub(/=$/, '')
177
- @tk.attributes(attribute, args.first)
178
- else
179
- send(attribute_setter(attribute), args)
192
+ rescue => e
193
+ Glimmer::Config.logger.debug {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
194
+ Glimmer::Config.logger.debug {e.full_message}
195
+ apply_style(attribute => args.first)
180
196
  end
181
197
  end
182
198
 
183
199
  def get_attribute(attribute)
184
200
  widget_custom_attribute = widget_custom_attribute_mapping[tk.class] && widget_custom_attribute_mapping[tk.class][attribute.to_s]
185
- if widget_custom_attribute
201
+ if respond_to?(attribute, super_only: true)
202
+ send(attribute)
203
+ elsif widget_custom_attribute
186
204
  widget_custom_attribute[:getter][:invoker].call(@tk, args)
187
205
  elsif tk_widget_has_attribute_getter_setter?(attribute)
188
206
  @tk.send(attribute)
@@ -201,6 +219,12 @@ module Glimmer
201
219
  "#{attribute}="
202
220
  end
203
221
 
222
+ def style=(styles)
223
+ styles.each do |attribute, value|
224
+ apply_style(attribute => value)
225
+ end
226
+ end
227
+
204
228
  def grid(options = {})
205
229
  options = options.stringify_keys
206
230
  index_in_parent = @parent_proxy.children.index(self)
@@ -221,6 +245,25 @@ module Glimmer
221
245
  @tk.grid(options)
222
246
  end
223
247
 
248
+ def font=(value)
249
+ if (value.is_a?(Symbol) || value.is_a?(String)) && FONTS_PREDEFINED.include?(value.to_s.downcase)
250
+ @tk.font = "tk_#{value}_font".camelcase(:upper)
251
+ else
252
+ @tk.font = value.is_a?(TkFont) ? value : TkFont.new(value)
253
+ end
254
+ rescue => e
255
+ Glimmer::Config.logger.debug {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
256
+ Glimmer::Config.logger.debug {e.full_message}
257
+ apply_style({"font" => value})
258
+ end
259
+
260
+ def apply_style(options)
261
+ @@style_number = 0 unless defined?(@@style_number)
262
+ style = "style#{@@style_number += 1}.#{@tk.class.name.split('::').last}"
263
+ ::Tk::Tile::Style.configure(style, options)
264
+ @tk.style = style
265
+ end
266
+
224
267
  def widget_custom_attribute_mapping
225
268
  # TODO consider extracting to modules/subclasses
226
269
  @widget_custom_attribute_mapping ||= {
@@ -276,6 +319,12 @@ module Glimmer
276
319
  setter: {name: 'text=', invoker: lambda { |widget, args| @tk.textvariable&.value = args.first }},
277
320
  },
278
321
  },
322
+ ::Tk::Tile::TSpinbox => {
323
+ 'text' => {
324
+ getter: {name: 'text', invoker: lambda { |widget, args| @tk.textvariable&.value }},
325
+ setter: {name: 'text=', invoker: lambda { |widget, args| @tk.textvariable&.value = args.first }},
326
+ },
327
+ },
279
328
  ::Tk::Root => {
280
329
  'text' => {
281
330
  getter: {name: 'text', invoker: lambda { |widget, args| @tk.title }},
@@ -309,11 +358,33 @@ module Glimmer
309
358
  },
310
359
  ::Tk::Tile::TEntry => {
311
360
  'text' => lambda do |observer|
312
- tk.textvariable.trace('write') {
361
+ @tk.textvariable.trace('write') {
313
362
  observer.call(@tk.textvariable.value)
314
363
  }
315
364
  end,
316
365
  },
366
+ ::Tk::Tile::TSpinbox => {
367
+ 'text' => lambda do |observer|
368
+ @tk.command {
369
+ observer.call(@tk.textvariable&.value)
370
+ }
371
+ @tk.validate('key')
372
+ @tk.validatecommand { |validate_args|
373
+ observer.call(validate_args.value)
374
+ new_icursor = validate_args.index
375
+ new_icursor += validate_args.string.size if validate_args.action == 1
376
+ @tk.icursor = new_icursor
377
+ true
378
+ }
379
+ end,
380
+ },
381
+ ::Tk::Text => {
382
+ 'text' => lambda do |observer|
383
+ handle_listener('modified') do
384
+ observer.call(text)
385
+ end
386
+ end,
387
+ },
317
388
  ::Tk::Tile::TRadiobutton => {
318
389
  'variable' => lambda do |observer|
319
390
  @tk.command {
@@ -384,7 +455,10 @@ module Glimmer
384
455
  private
385
456
 
386
457
  def initialize_defaults
387
- grid unless @tk.is_a?(::Tk::Toplevel)
458
+ options = {}
459
+ options[:sticky] = 'nsew'
460
+ options[:column_weight] = 1 if @parent_proxy.children.count == 1
461
+ grid(options) unless @tk.is_a?(::Tk::Toplevel)
388
462
  end
389
463
  end
390
464
  end