glimmer-dsl-swt 4.17.4.0 → 4.17.7.0

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +191 -29
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +18 -5
  6. data/lib/glimmer/dsl/swt/checkbox_group_selection_data_binding_expression.rb +61 -0
  7. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +2 -0
  8. data/lib/glimmer/dsl/swt/dsl.rb +2 -0
  9. data/lib/glimmer/dsl/swt/expand_item_expression.rb +60 -0
  10. data/lib/glimmer/dsl/swt/radio_group_selection_data_binding_expression.rb +61 -0
  11. data/lib/glimmer/dsl/swt/widget_expression.rb +1 -0
  12. data/lib/glimmer/swt/custom/checkbox_group.rb +160 -0
  13. data/lib/glimmer/swt/custom/code_text.rb +20 -13
  14. data/lib/glimmer/swt/custom/radio_group.rb +155 -0
  15. data/lib/glimmer/swt/expand_item_proxy.rb +97 -0
  16. data/lib/glimmer/swt/image_proxy.rb +5 -0
  17. data/lib/glimmer/swt/menu_proxy.rb +1 -1
  18. data/lib/glimmer/swt/sash_form_proxy.rb +1 -1
  19. data/lib/glimmer/swt/styled_text_proxy.rb +43 -0
  20. data/lib/glimmer/swt/tab_item_proxy.rb +1 -1
  21. data/lib/glimmer/swt/widget_proxy.rb +94 -35
  22. data/lib/glimmer/ui/custom_widget.rb +3 -0
  23. data/samples/elaborate/meta_sample.rb +36 -31
  24. data/samples/hello/hello_checkbox.rb +85 -0
  25. data/samples/hello/hello_checkbox_group.rb +68 -0
  26. data/samples/hello/hello_combo.rb +12 -12
  27. data/samples/hello/hello_expand_bar.rb +110 -0
  28. data/samples/hello/hello_list_multi_selection.rb +23 -23
  29. data/samples/hello/hello_list_single_selection.rb +14 -13
  30. data/samples/hello/hello_radio.rb +108 -0
  31. data/samples/hello/hello_radio_group.rb +84 -0
  32. data/samples/hello/hello_styled_text.rb +138 -0
  33. metadata +17 -4
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2007-2020 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/dsl/expression'
23
+ require 'glimmer/data_binding/model_binding'
24
+ require 'glimmer/data_binding/widget_binding'
25
+
26
+ module Glimmer
27
+ module DSL
28
+ module SWT
29
+ class CheckboxGroupSelectionDataBindingExpression < Expression
30
+
31
+ def can_interpret?(parent, keyword, *args, &block)
32
+ keyword == 'selection' and
33
+ block.nil? and
34
+ (parent.is_a?(Glimmer::SWT::Custom::CheckboxGroup) or (parent.is_a?(Glimmer::UI::CustomWidget) and parent.body_root.is_a?(Glimmer::SWT::Custom::CheckboxGroup)) ) and
35
+ args.size == 1 and
36
+ args[0].is_a?(DataBinding::ModelBinding) and
37
+ args[0].evaluate_options_property.is_a?(Array)
38
+ end
39
+
40
+ def interpret(parent, keyword, *args, &block)
41
+ model_binding = args[0]
42
+
43
+ #TODO make this options observer dependent and all similar observers in widget specific data binding handlers
44
+ # TODO consider delegating some of this work
45
+ widget_binding = DataBinding::WidgetBinding.new(parent, 'items')
46
+ widget_binding.call(model_binding.evaluate_options_property)
47
+ model = model_binding.base_model
48
+ widget_binding.observe(model, model_binding.options_property_name)
49
+
50
+ widget_binding = DataBinding::WidgetBinding.new(parent, 'selection')
51
+ widget_binding.call(model_binding.evaluate_property)
52
+ widget_binding.observe(model, model_binding.property_name_expression)
53
+
54
+ parent.on_widget_selected do
55
+ model_binding.call(widget_binding.evaluate_property)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -26,6 +26,8 @@ require 'glimmer/dsl/top_level_expression'
26
26
  require 'glimmer/ui/custom_widget'
27
27
  require 'glimmer/ui/custom_shell'
28
28
  require 'glimmer/swt/custom/code_text'
29
+ require 'glimmer/swt/custom/radio_group'
30
+ require 'glimmer/swt/custom/checkbox_group'
29
31
 
30
32
  module Glimmer
31
33
  module DSL
@@ -41,6 +41,8 @@ module Glimmer
41
41
  layout
42
42
  widget_listener
43
43
  combo_selection_data_binding
44
+ checkbox_group_selection_data_binding
45
+ radio_group_selection_data_binding
44
46
  list_selection_data_binding
45
47
  tree_items_data_binding
46
48
  table_items_data_binding
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2007-2020 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer'
23
+ require 'glimmer/dsl/static_expression'
24
+ require 'glimmer/dsl/parent_expression'
25
+ require 'glimmer/swt/widget_proxy'
26
+ require 'glimmer/swt/expand_item_proxy'
27
+
28
+ module Glimmer
29
+ module DSL
30
+ module SWT
31
+ class ExpandItemExpression < StaticExpression
32
+ include ParentExpression
33
+
34
+ include_package 'org.eclipse.swt.widgets'
35
+
36
+ def can_interpret?(parent, keyword, *args, &block)
37
+ initial_condition = (keyword == 'expand_item') and parent.respond_to?(:swt_widget)
38
+ if initial_condition
39
+ if parent.swt_widget.is_a?(ExpandBar)
40
+ return true
41
+ else
42
+ Glimmer::Config.logger.error {"expand_item widget may only be used directly under a expand_bar widget!"}
43
+ end
44
+ end
45
+ false
46
+ end
47
+
48
+ def interpret(parent, keyword, *args, &block)
49
+ Glimmer::SWT::ExpandItemProxy.new(parent, args)
50
+ end
51
+
52
+ def add_content(parent, &block)
53
+ super
54
+ parent.post_add_content
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2007-2020 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/dsl/expression'
23
+ require 'glimmer/data_binding/model_binding'
24
+ require 'glimmer/data_binding/widget_binding'
25
+
26
+ module Glimmer
27
+ module DSL
28
+ module SWT
29
+ class RadioGroupSelectionDataBindingExpression < Expression
30
+
31
+ def can_interpret?(parent, keyword, *args, &block)
32
+ keyword == 'selection' and
33
+ block.nil? and
34
+ (parent.is_a?(Glimmer::SWT::Custom::RadioGroup) or (parent.is_a?(Glimmer::UI::CustomWidget) and parent.body_root.is_a?(Glimmer::SWT::Custom::RadioGroup)) ) and
35
+ args.size == 1 and
36
+ args[0].is_a?(DataBinding::ModelBinding) and
37
+ args[0].evaluate_options_property.is_a?(Array)
38
+ end
39
+
40
+ def interpret(parent, keyword, *args, &block)
41
+ model_binding = args[0]
42
+
43
+ #TODO make this options observer dependent and all similar observers in widget specific data binding handlers
44
+ # TODO consider delegating some of this work
45
+ widget_binding = DataBinding::WidgetBinding.new(parent, 'items')
46
+ widget_binding.call(model_binding.evaluate_options_property)
47
+ model = model_binding.base_model
48
+ widget_binding.observe(model, model_binding.options_property_name)
49
+
50
+ widget_binding = DataBinding::WidgetBinding.new(parent, 'selection')
51
+ widget_binding.call(model_binding.evaluate_property)
52
+ widget_binding.observe(model, model_binding.property_name_expression)
53
+
54
+ parent.on_widget_selected do
55
+ model_binding.call(widget_binding.evaluate_property)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -57,3 +57,4 @@ require 'glimmer/swt/tree_proxy'
57
57
  require 'glimmer/swt/table_proxy'
58
58
  require 'glimmer/swt/table_column_proxy'
59
59
  require 'glimmer/swt/sash_form_proxy'
60
+ require 'glimmer/swt/styled_text_proxy'
@@ -0,0 +1,160 @@
1
+ require 'glimmer/ui/custom_widget'
2
+
3
+ module Glimmer
4
+ module SWT
5
+ module Custom
6
+ # CodeText is a customization of StyledText with support for Ruby Syntax Highlighting
7
+ class CheckboxGroup
8
+ include Glimmer::UI::CustomWidget
9
+
10
+ body {
11
+ composite # just an empty composite to hold checkboxs upon data-binding `selection`
12
+ }
13
+
14
+ def items=(text_array)
15
+ selection_value = selection
16
+ @items = Array[*text_array]
17
+ build_checkboxes
18
+ end
19
+
20
+ def items
21
+ @items || []
22
+ end
23
+
24
+ def selection=(selection_texts)
25
+ items.count.times do |index|
26
+ checkbox = checkboxes[index]
27
+ item = items[index]
28
+ label_text = labels[index]&.text
29
+ checkbox.selection = selection_texts.to_a.include?(label_text)
30
+ end
31
+ selection_texts
32
+ end
33
+
34
+ def selection
35
+ selection_indices.map do |selection_index|
36
+ labels[selection_index]&.text
37
+ end
38
+ end
39
+
40
+ def selection_indices=(indices)
41
+ self.selection=(indices.to_a.map {|index| items[index]})
42
+ end
43
+ alias select selection_indices=
44
+
45
+ def selection_indices
46
+ checkboxes.each_with_index.map do |checkbox, index|
47
+ index if checkbox.selection
48
+ end.to_a.compact
49
+ end
50
+
51
+ def checkboxes
52
+ @checkboxes ||= []
53
+ end
54
+ alias checks checkboxes
55
+
56
+ def labels
57
+ @labels ||= []
58
+ end
59
+
60
+ def can_handle_observation_request?(observation_request)
61
+ checkboxes.first&.can_handle_observation_request?(observation_request) || super(observation_request)
62
+ end
63
+
64
+ def handle_observation_request(observation_request, &block)
65
+ observation_requests << [observation_request, block]
66
+ delegate_observation_request_to_checkboxes(observation_request, &block)
67
+ super
68
+ end
69
+
70
+ def delegate_observation_request_to_checkboxes(observation_request, &block)
71
+ if observation_request != 'on_widget_disposed'
72
+ checkboxes.count.times do |index|
73
+ checkbox = checkboxes[index]
74
+ label = labels[index]
75
+ listener_block = lambda do |event|
76
+ event.widget = self.swt_widget
77
+ block.call(event)
78
+ end
79
+ if observation_request == 'on_widget_selected'
80
+ checkbox.handle_observation_request(observation_request, &listener_block) if checkbox.can_handle_observation_request?(observation_request)
81
+ label.handle_observation_request('on_mouse_up', &listener_block)
82
+ else
83
+ checkbox.handle_observation_request(observation_request, &listener_block) if checkbox.can_handle_observation_request?(observation_request)
84
+ label.handle_observation_request(observation_request, &listener_block) if label.can_handle_observation_request?(observation_request)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def observation_requests
91
+ @observation_requests ||= Set.new
92
+ end
93
+
94
+ def has_attribute?(attribute_name, *args)
95
+ (@composites.to_a + @checkboxes.to_a + @labels.to_a).map do |widget_proxy|
96
+ return true if widget_proxy.has_attribute?(attribute_name, *args)
97
+ end
98
+ super
99
+ end
100
+
101
+ def set_attribute(attribute_name, *args)
102
+ excluded_attributes = ['selection']
103
+ unless excluded_attributes.include?(attribute_name.to_s)
104
+ (@composites.to_a + @checkboxes.to_a + @labels.to_a).each do |widget_proxy|
105
+ widget_proxy.set_attribute(attribute_name, *args) if widget_proxy.has_attribute?(attribute_name, *args)
106
+ end
107
+ end
108
+ super
109
+ end
110
+
111
+ private
112
+
113
+ def build_checkboxes
114
+ current_selection = selection
115
+ @composites.to_a.each(&:dispose)
116
+ @checkboxes = []
117
+ @labels = []
118
+ @composites = []
119
+ items.each do |item|
120
+ body_root.content {
121
+ @composites << composite {
122
+ grid_layout(2, false) {
123
+ margin_width 0
124
+ margin_height 0
125
+ horizontal_spacing 0
126
+ vertical_spacing 0
127
+ }
128
+ checkboxes << checkbox { |checkbox_proxy|
129
+ on_widget_selected {
130
+ self.selection_indices = checkboxes.each_with_index.map {|cb, i| i if cb.selection}.to_a.compact
131
+ }
132
+ }
133
+ labels << label { |label_proxy|
134
+ layout_data :fill, :center, true, false
135
+ text item
136
+ on_mouse_up { |event|
137
+ found_text = labels.each_with_index.detect {|l, i| event.widget == l.swt_widget}[0]&.text
138
+ selection_values = self.selection
139
+ if selection_values.include?(found_text)
140
+ selection_values.delete(found_text)
141
+ else
142
+ selection_values << found_text
143
+ end
144
+ self.selection = selection_values
145
+ }
146
+ }
147
+ }
148
+ }
149
+ end
150
+ observation_requests.to_a.each do |observation_request, block|
151
+ delegate_observation_request_to_checkboxes(observation_request, &block)
152
+ end
153
+ self.selection = current_selection
154
+ end
155
+ end
156
+
157
+ CheckGroup = CheckboxGroup
158
+ end
159
+ end
160
+ end
@@ -38,11 +38,13 @@ module Glimmer
38
38
  swt_widget&.text
39
39
  end
40
40
 
41
- def syntax_highlighting
41
+ def syntax_highlighting
42
42
  return [] if text.to_s.strip.empty?
43
43
  code = text
44
- lexer = Rouge::Lexer.find_fancy('ruby', code)
45
- lex = lexer.lex(code).to_a
44
+ return @syntax_highlighting if @last_code == code
45
+ @last_code = code
46
+ @lexer ||= Rouge::Lexer.find_fancy('ruby', code)
47
+ lex = @lexer.lex(code).to_a
46
48
  code_size = 0
47
49
  lex_hashes = lex.map do |pair|
48
50
  {token_type: pair.first, token_text: pair.last}
@@ -50,7 +52,15 @@ module Glimmer
50
52
  hash[:token_index] = code_size
51
53
  code_size += hash[:token_text].size
52
54
  end
53
- # TODO consider grouping by line to optimize performance for line get style listener
55
+ code_lines = code.split("\n")
56
+ line_index = 0
57
+ @syntax_highlighting = code_lines_map = code_lines.reduce({}) do |hash, line|
58
+ line_hashes = []
59
+ line_hashes << lex_hashes.shift while lex_hashes.any? && lex_hashes.first[:token_index].between?(line_index, line_index + line.size)
60
+ hash.merge(line_index => line_hashes).tap do
61
+ line_index += line.size + 1
62
+ end
63
+ end
54
64
  end
55
65
 
56
66
  before_body {
@@ -68,15 +78,12 @@ module Glimmer
68
78
 
69
79
  on_line_get_style { |line_style_event|
70
80
  styles = []
71
- line_style_event.lineOffset
72
- syntax_highlighting.each do |token_hash|
73
- if token_hash[:token_index] >= line_style_event.lineOffset && token_hash[:token_index] < (line_style_event.lineOffset + line_style_event.lineText.size)
74
- start_index = token_hash[:token_index]
75
- size = token_hash[:token_text].size
76
- token_color = SYNTAX_COLOR_MAP[token_hash[:token_type].name] || [:black]
77
- token_color = color(*token_color).swt_color
78
- styles << StyleRange.new(start_index, size, token_color, nil)
79
- end
81
+ syntax_highlighting[line_style_event.lineOffset].to_a.each do |token_hash|
82
+ start_index = token_hash[:token_index]
83
+ size = token_hash[:token_text].size
84
+ token_color = SYNTAX_COLOR_MAP[token_hash[:token_type].name] || [:black]
85
+ token_color = color(*token_color).swt_color
86
+ styles << StyleRange.new(start_index, size, token_color, nil)
80
87
  end
81
88
  line_style_event.styles = styles.to_java(StyleRange) unless styles.empty?
82
89
  }
@@ -0,0 +1,155 @@
1
+ require 'glimmer/ui/custom_widget'
2
+
3
+ module Glimmer
4
+ module SWT
5
+ module Custom
6
+ # CodeText is a customization of StyledText with support for Ruby Syntax Highlighting
7
+ class RadioGroup
8
+ include Glimmer::UI::CustomWidget
9
+
10
+ body {
11
+ composite # just an empty composite to hold radios upon data-binding `selection`
12
+ }
13
+
14
+ def items=(text_array)
15
+ selection_value = selection
16
+ @items = Array[*text_array]
17
+ build_radios
18
+ end
19
+
20
+ def items
21
+ @items || []
22
+ end
23
+
24
+ def selection=(text)
25
+ radios.count.times do |index|
26
+ radio = radios[index]
27
+ item = items[index]
28
+ radio.selection = item == text
29
+ end
30
+ end
31
+
32
+ def selection
33
+ selection_value = labels[selection_index]&.text unless selection_index == -1
34
+ selection_value.to_s
35
+ end
36
+
37
+ def selection_index=(index)
38
+ self.selection=(items[index])
39
+ end
40
+ alias select selection_index=
41
+
42
+ def selection_index
43
+ radios.index(radios.detect(&:selection)) || -1
44
+ end
45
+
46
+ def radios
47
+ @radios ||= []
48
+ end
49
+
50
+ def labels
51
+ @labels ||= []
52
+ end
53
+
54
+ def can_handle_observation_request?(observation_request)
55
+ radios.first&.can_handle_observation_request?(observation_request) || super(observation_request)
56
+ end
57
+
58
+ def handle_observation_request(observation_request, &block)
59
+ observation_requests << [observation_request, block]
60
+ delegate_observation_request_to_radios(observation_request, &block)
61
+ super
62
+ end
63
+
64
+ def delegate_observation_request_to_radios(observation_request, &block)
65
+ if observation_request != 'on_widget_disposed'
66
+ radios.count.times do |index|
67
+ radio = radios[index]
68
+ label = labels[index]
69
+ if observation_request == 'on_widget_selected'
70
+ radio_block = lambda do |event|
71
+ if event.widget.selection || selection_index == -1
72
+ event.widget = self.swt_widget
73
+ block.call(event)
74
+ end
75
+ end
76
+ label_block = lambda do |event|
77
+ self.selection_index = index
78
+ block.call(event)
79
+ end
80
+ radio.handle_observation_request(observation_request, &radio_block) if radio.can_handle_observation_request?(observation_request)
81
+ label.handle_observation_request('on_mouse_up', &label_block)
82
+ else
83
+ listener_block = lambda do |event|
84
+ event.widget = self.swt_widget
85
+ block.call(event)
86
+ end
87
+ radio.handle_observation_request(observation_request, &listener_block) if radio.can_handle_observation_request?(observation_request)
88
+ label.handle_observation_request(observation_request, &listener_block) if label.can_handle_observation_request?(observation_request)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def observation_requests
95
+ @observation_requests ||= Set.new
96
+ end
97
+
98
+ def has_attribute?(attribute_name, *args)
99
+ (@composites.to_a + @radios.to_a + @labels.to_a).map do |widget_proxy|
100
+ return true if widget_proxy.has_attribute?(attribute_name, *args)
101
+ end
102
+ super
103
+ end
104
+
105
+ def set_attribute(attribute_name, *args)
106
+ excluded_attributes = ['selection']
107
+ unless excluded_attributes.include?(attribute_name.to_s)
108
+ (@composites.to_a + @radios.to_a + @labels.to_a).each do |widget_proxy|
109
+ widget_proxy.set_attribute(attribute_name, *args) if widget_proxy.has_attribute?(attribute_name, *args)
110
+ end
111
+ end
112
+ super
113
+ end
114
+
115
+ private
116
+
117
+ def build_radios
118
+ current_selection = selection
119
+ @composites.to_a.each(&:dispose)
120
+ @radios = []
121
+ @labels = []
122
+ @composites = []
123
+ items.each do |item|
124
+ body_root.content {
125
+ @composites << composite {
126
+ grid_layout(2, false) {
127
+ margin_width 0
128
+ margin_height 0
129
+ horizontal_spacing 0
130
+ vertical_spacing 0
131
+ }
132
+ radios << radio { |radio_proxy|
133
+ on_widget_selected {
134
+ self.selection = items[radios.index(radio_proxy)]
135
+ }
136
+ }
137
+ labels << label { |label_proxy|
138
+ layout_data :fill, :center, true, false
139
+ text item
140
+ on_mouse_up {
141
+ self.selection = label_proxy.text
142
+ }
143
+ }
144
+ }
145
+ }
146
+ end
147
+ observation_requests.to_a.each do |observation_request, block|
148
+ delegate_observation_request_to_radios(observation_request, &block)
149
+ end
150
+ self.selection = current_selection
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end