glimmer-dsl-tk 0.0.24 → 0.0.28

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.
@@ -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