glimmer-dsl-swt 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +47 -0
- data/RUBY_VERSION +1 -0
- data/VERSION +1 -0
- data/bin/girb +10 -0
- data/bin/girb_runner.rb +13 -0
- data/bin/glimmer +5 -0
- data/icons/scaffold_app.icns +0 -0
- data/lib/ext/glimmer.rb +13 -0
- data/lib/ext/glimmer/config.rb +18 -0
- data/lib/glimmer-dsl-swt.rb +12 -0
- data/lib/glimmer/data_binding/list_selection_binding.rb +52 -0
- data/lib/glimmer/data_binding/model_binding.rb +248 -0
- data/lib/glimmer/data_binding/observable.rb +21 -0
- data/lib/glimmer/data_binding/observable_array.rb +107 -0
- data/lib/glimmer/data_binding/observable_model.rb +108 -0
- data/lib/glimmer/data_binding/observable_widget.rb +17 -0
- data/lib/glimmer/data_binding/observer.rb +124 -0
- data/lib/glimmer/data_binding/shine.rb +23 -0
- data/lib/glimmer/data_binding/table_items_binding.rb +56 -0
- data/lib/glimmer/data_binding/tree_items_binding.rb +71 -0
- data/lib/glimmer/data_binding/widget_binding.rb +33 -0
- data/lib/glimmer/dsl/swt/async_exec_expression.rb +14 -0
- data/lib/glimmer/dsl/swt/bind_expression.rb +37 -0
- data/lib/glimmer/dsl/swt/color_expression.rb +19 -0
- data/lib/glimmer/dsl/swt/column_properties_expression.rb +24 -0
- data/lib/glimmer/dsl/swt/combo_selection_data_binding_expression.rb +42 -0
- data/lib/glimmer/dsl/swt/custom_widget_expression.rb +39 -0
- data/lib/glimmer/dsl/swt/data_binding_expression.rb +34 -0
- data/lib/glimmer/dsl/swt/dialog_expression.rb +26 -0
- data/lib/glimmer/dsl/swt/display_expression.rb +19 -0
- data/lib/glimmer/dsl/swt/dsl.rb +34 -0
- data/lib/glimmer/dsl/swt/exec_expression.rb +28 -0
- data/lib/glimmer/dsl/swt/layout_data_expression.rb +25 -0
- data/lib/glimmer/dsl/swt/layout_expression.rb +27 -0
- data/lib/glimmer/dsl/swt/list_selection_data_binding_expression.rb +44 -0
- data/lib/glimmer/dsl/swt/menu_bar_expression.rb +33 -0
- data/lib/glimmer/dsl/swt/menu_expression.rb +32 -0
- data/lib/glimmer/dsl/swt/message_box_expression.rb +29 -0
- data/lib/glimmer/dsl/swt/observe_expression.rb +32 -0
- data/lib/glimmer/dsl/swt/property_expression.rb +22 -0
- data/lib/glimmer/dsl/swt/rgb_expression.rb +12 -0
- data/lib/glimmer/dsl/swt/rgba_expression.rb +12 -0
- data/lib/glimmer/dsl/swt/shell_expression.rb +25 -0
- data/lib/glimmer/dsl/swt/swt_expression.rb +25 -0
- data/lib/glimmer/dsl/swt/sync_exec_expression.rb +15 -0
- data/lib/glimmer/dsl/swt/tab_item_expression.rb +33 -0
- data/lib/glimmer/dsl/swt/table_items_data_binding_expression.rb +31 -0
- data/lib/glimmer/dsl/swt/tree_items_data_binding_expression.rb +31 -0
- data/lib/glimmer/dsl/swt/tree_properties_expression.rb +26 -0
- data/lib/glimmer/dsl/swt/widget_expression.rb +35 -0
- data/lib/glimmer/dsl/swt/widget_listener_expression.rb +32 -0
- data/lib/glimmer/launcher.rb +196 -0
- data/lib/glimmer/package.rb +57 -0
- data/lib/glimmer/rake_task.rb +62 -0
- data/lib/glimmer/scaffold.rb +582 -0
- data/lib/glimmer/swt/color_proxy.rb +53 -0
- data/lib/glimmer/swt/display_proxy.rb +88 -0
- data/lib/glimmer/swt/font_proxy.rb +72 -0
- data/lib/glimmer/swt/layout_data_proxy.rb +84 -0
- data/lib/glimmer/swt/layout_proxy.rb +82 -0
- data/lib/glimmer/swt/menu_proxy.rb +101 -0
- data/lib/glimmer/swt/message_box_proxy.rb +48 -0
- data/lib/glimmer/swt/packages.rb +13 -0
- data/lib/glimmer/swt/shell_proxy.rb +152 -0
- data/lib/glimmer/swt/swt_proxy.rb +106 -0
- data/lib/glimmer/swt/tab_item_proxy.rb +65 -0
- data/lib/glimmer/swt/table_proxy.rb +150 -0
- data/lib/glimmer/swt/tree_proxy.rb +120 -0
- data/lib/glimmer/swt/widget_listener_proxy.rb +34 -0
- data/lib/glimmer/swt/widget_proxy.rb +489 -0
- data/lib/glimmer/ui/custom_shell.rb +45 -0
- data/lib/glimmer/ui/custom_widget.rb +244 -0
- data/lib/glimmer/util/proc_tracker.rb +16 -0
- data/vendor/swt/linux/swt.jar +0 -0
- data/vendor/swt/mac/swt.jar +0 -0
- data/vendor/swt/windows/swt.jar +0 -0
- metadata +307 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'glimmer/swt/widget_proxy'
|
2
|
+
|
3
|
+
module Glimmer
|
4
|
+
module SWT
|
5
|
+
class TreeProxy < Glimmer::SWT::WidgetProxy
|
6
|
+
include Glimmer
|
7
|
+
|
8
|
+
attr_reader :tree_editor, :tree_editor_text_proxy
|
9
|
+
attr_accessor :tree_properties
|
10
|
+
|
11
|
+
def initialize(underscored_widget_name, parent, args)
|
12
|
+
super
|
13
|
+
@tree_editor = TreeEditor.new(swt_widget)
|
14
|
+
@tree_editor.horizontalAlignment = SWTProxy[:left]
|
15
|
+
@tree_editor.grabHorizontal = true
|
16
|
+
@tree_editor.minimumHeight = 20
|
17
|
+
end
|
18
|
+
|
19
|
+
# Performs depth first search for tree items matching block condition
|
20
|
+
# If no condition block is passed, returns all tree items
|
21
|
+
# Returns a Java TreeItem array to easily set as selection on org.eclipse.swt.Tree if needed
|
22
|
+
def depth_first_search(&condition)
|
23
|
+
found = []
|
24
|
+
recursive_depth_first_search(swt_widget.getItems.first, found, &condition)
|
25
|
+
found.to_java(TreeItem)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns all tree items including descendants
|
29
|
+
def all_tree_items
|
30
|
+
depth_first_search
|
31
|
+
end
|
32
|
+
|
33
|
+
def widget_property_listener_installers
|
34
|
+
super.merge({
|
35
|
+
Java::OrgEclipseSwtWidgets::Tree => {
|
36
|
+
selection: lambda do |observer|
|
37
|
+
on_widget_selected { |selection_event|
|
38
|
+
observer.call(@swt_widget.getSelection)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
},
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
def edit_in_progress?
|
46
|
+
!!@edit_in_progress
|
47
|
+
end
|
48
|
+
|
49
|
+
def edit_selected_tree_item(before_write: nil, after_write: nil, after_cancel: nil)
|
50
|
+
edit_tree_item(swt_widget.getSelection.first, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
|
51
|
+
end
|
52
|
+
|
53
|
+
def edit_tree_item(tree_item, before_write: nil, after_write: nil, after_cancel: nil)
|
54
|
+
return if tree_item.nil?
|
55
|
+
content {
|
56
|
+
@tree_editor_text_proxy = text {
|
57
|
+
focus true
|
58
|
+
text tree_item.getText
|
59
|
+
action_taken = false
|
60
|
+
cancel = lambda {
|
61
|
+
@tree_editor_text_proxy.swt_widget.dispose
|
62
|
+
@tree_editor_text_proxy = nil
|
63
|
+
after_cancel&.call
|
64
|
+
@edit_in_progress = false
|
65
|
+
}
|
66
|
+
action = lambda { |event|
|
67
|
+
if !action_taken && !@edit_in_progress
|
68
|
+
action_taken = true
|
69
|
+
@edit_in_progress = true
|
70
|
+
new_text = @tree_editor_text_proxy.swt_widget.getText
|
71
|
+
if new_text == tree_item.getText
|
72
|
+
cancel.call
|
73
|
+
else
|
74
|
+
before_write&.call
|
75
|
+
tree_item.setText(new_text)
|
76
|
+
model = tree_item.getData
|
77
|
+
model.send("#{tree_properties[:text]}=", new_text) # makes tree update itself, so must search for selected tree item again
|
78
|
+
edited_tree_item = depth_first_search { |ti| ti.getData == model }.first
|
79
|
+
swt_widget.showItem(edited_tree_item)
|
80
|
+
@tree_editor_text_proxy.swt_widget.dispose
|
81
|
+
@tree_editor_text_proxy = nil
|
82
|
+
after_write&.call(edited_tree_item)
|
83
|
+
@edit_in_progress = false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
}
|
87
|
+
on_focus_lost(&action)
|
88
|
+
on_key_pressed { |key_event|
|
89
|
+
if key_event.keyCode == swt(:cr)
|
90
|
+
action.call(key_event)
|
91
|
+
elsif key_event.keyCode == swt(:esc)
|
92
|
+
cancel.call
|
93
|
+
end
|
94
|
+
}
|
95
|
+
}
|
96
|
+
@tree_editor_text_proxy.swt_widget.selectAll
|
97
|
+
}
|
98
|
+
@tree_editor.setEditor(@tree_editor_text_proxy.swt_widget, tree_item)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def recursive_depth_first_search(tree_item, found, &condition)
|
104
|
+
return if tree_item.nil?
|
105
|
+
found << tree_item if condition.nil? || condition.call(tree_item)
|
106
|
+
tree_item.getItems.each do |child_tree_item|
|
107
|
+
recursive_depth_first_search(child_tree_item, found, &condition)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def property_type_converters
|
112
|
+
super.merge({
|
113
|
+
selection: lambda do |value|
|
114
|
+
depth_first_search {|ti| ti.getData == value}
|
115
|
+
end,
|
116
|
+
})
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Glimmer
|
2
|
+
module SWT
|
3
|
+
# Proxy for widget listeners
|
4
|
+
#
|
5
|
+
# Follows the Proxy Design Pattern
|
6
|
+
class WidgetListenerProxy
|
7
|
+
|
8
|
+
attr_reader :swt_widget, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
|
9
|
+
|
10
|
+
def initialize(swt_widget:, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil)
|
11
|
+
@swt_widget = swt_widget
|
12
|
+
@swt_listener = swt_listener
|
13
|
+
@widget_add_listener_method = widget_add_listener_method
|
14
|
+
@swt_listener_class = swt_listener_class
|
15
|
+
@swt_listener_method = swt_listener_method
|
16
|
+
@event_type = event_type
|
17
|
+
@swt_constant = swt_constant
|
18
|
+
end
|
19
|
+
|
20
|
+
def widget_remove_listener_method
|
21
|
+
@widget_add_listener_method.sub('add', 'remove')
|
22
|
+
end
|
23
|
+
|
24
|
+
def unregister
|
25
|
+
# TODO consider renaming to deregister (and in Observer too)
|
26
|
+
if @event_type
|
27
|
+
@swt_widget.removeListener(@event_type, @swt_listener)
|
28
|
+
else
|
29
|
+
@swt_widget.send(widget_remove_listener_method, @swt_listener)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,489 @@
|
|
1
|
+
require 'glimmer'
|
2
|
+
require 'glimmer/swt/widget_listener_proxy'
|
3
|
+
require 'glimmer/swt/color_proxy'
|
4
|
+
require 'glimmer/swt/font_proxy'
|
5
|
+
require 'glimmer/swt/swt_proxy'
|
6
|
+
require 'glimmer/data_binding/observable_widget'
|
7
|
+
|
8
|
+
# TODO refactor to make file smaller and extract sub-widget-proxies out of this
|
9
|
+
|
10
|
+
module Glimmer
|
11
|
+
module SWT
|
12
|
+
# Proxy for SWT Widget objects
|
13
|
+
#
|
14
|
+
# Sets default SWT styles to widgets upon inititalizing as
|
15
|
+
# per DEFAULT_STYLES
|
16
|
+
#
|
17
|
+
# Also, auto-initializes widgets as per initializer blocks
|
18
|
+
# in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
|
19
|
+
#
|
20
|
+
# Follows the Proxy Design Pattern
|
21
|
+
class WidgetProxy
|
22
|
+
include Packages
|
23
|
+
include DataBinding::ObservableWidget
|
24
|
+
|
25
|
+
DEFAULT_STYLES = {
|
26
|
+
"text" => [:border],
|
27
|
+
"table" => [:border],
|
28
|
+
"tree" => [:virtual, :border, :h_scroll, :v_scroll],
|
29
|
+
"spinner" => [:border],
|
30
|
+
"styled_text" => [:border],
|
31
|
+
"list" => [:border, :v_scroll],
|
32
|
+
"button" => [:push],
|
33
|
+
"menu_item" => [:push],
|
34
|
+
}
|
35
|
+
|
36
|
+
DEFAULT_INITIALIZERS = {
|
37
|
+
"composite" => lambda do |composite|
|
38
|
+
composite.setLayout(GridLayout.new)
|
39
|
+
end,
|
40
|
+
"table" => lambda do |table|
|
41
|
+
table.setHeaderVisible(true)
|
42
|
+
table.setLinesVisible(true)
|
43
|
+
end,
|
44
|
+
"table_column" => lambda do |table_column|
|
45
|
+
table_column.setWidth(80)
|
46
|
+
end,
|
47
|
+
"group" => lambda do |group|
|
48
|
+
group.setLayout(GridLayout.new)
|
49
|
+
end,
|
50
|
+
}
|
51
|
+
|
52
|
+
attr_reader :swt_widget
|
53
|
+
|
54
|
+
# Initializes a new SWT Widget
|
55
|
+
#
|
56
|
+
# Styles is a comma separate list of symbols representing SWT styles in lower case
|
57
|
+
def initialize(underscored_widget_name, parent, args)
|
58
|
+
styles, extra_options = extract_args(underscored_widget_name, args)
|
59
|
+
swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
|
60
|
+
@swt_widget = swt_widget_class.new(parent.swt_widget, style(underscored_widget_name, styles), *extra_options)
|
61
|
+
DEFAULT_INITIALIZERS[underscored_widget_name]&.call(@swt_widget)
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_args(underscored_widget_name, args)
|
65
|
+
@arg_extractor_mapping ||= {
|
66
|
+
'menu_item' => lambda do |args|
|
67
|
+
index = args.delete(args.last) if args.last.is_a?(Numeric)
|
68
|
+
extra_options = [index].compact
|
69
|
+
styles = args
|
70
|
+
[styles, extra_options]
|
71
|
+
end,
|
72
|
+
}
|
73
|
+
if @arg_extractor_mapping[underscored_widget_name]
|
74
|
+
@arg_extractor_mapping[underscored_widget_name].call(args)
|
75
|
+
else
|
76
|
+
[args, []]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_attribute?(attribute_name, *args)
|
81
|
+
widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
|
82
|
+
if widget_custom_attribute
|
83
|
+
@swt_widget.respond_to?(widget_custom_attribute[:setter][:name])
|
84
|
+
else
|
85
|
+
@swt_widget.respond_to?(attribute_setter(attribute_name), args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_attribute(attribute_name, *args)
|
90
|
+
widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
|
91
|
+
if widget_custom_attribute
|
92
|
+
widget_custom_attribute[:setter][:invoker].call(@swt_widget, args)
|
93
|
+
else
|
94
|
+
apply_property_type_converters(attribute_name, args)
|
95
|
+
@swt_widget.send(attribute_setter(attribute_name), *args) unless @swt_widget.send(attribute_getter(attribute_name)) == args.first
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_attribute(attribute_name)
|
100
|
+
widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
|
101
|
+
if widget_custom_attribute
|
102
|
+
@swt_widget.send(widget_custom_attribute[:getter][:name])
|
103
|
+
else
|
104
|
+
@swt_widget.send(attribute_getter(attribute_name))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def pack_same_size
|
109
|
+
bounds = @swt_widget.getBounds
|
110
|
+
listener = on_control_resized {
|
111
|
+
@swt_widget.setSize(bounds.width, bounds.height)
|
112
|
+
@swt_widget.setLocation(bounds.x, bounds.y)
|
113
|
+
}
|
114
|
+
if @swt_widget.is_a?(Composite)
|
115
|
+
@swt_widget.layout(true, true)
|
116
|
+
else
|
117
|
+
@swt_widget.pack(true)
|
118
|
+
end
|
119
|
+
@swt_widget.removeControlListener(listener.swt_listener)
|
120
|
+
end
|
121
|
+
|
122
|
+
def widget_property_listener_installers
|
123
|
+
@swt_widget_property_listener_installers ||= {
|
124
|
+
Java::OrgEclipseSwtWidgets::Control => {
|
125
|
+
:focus => lambda do |observer|
|
126
|
+
on_focus_gained { |focus_event|
|
127
|
+
observer.call(true)
|
128
|
+
}
|
129
|
+
on_focus_lost { |focus_event|
|
130
|
+
observer.call(false)
|
131
|
+
}
|
132
|
+
end,
|
133
|
+
},
|
134
|
+
Java::OrgEclipseSwtWidgets::Text => {
|
135
|
+
:text => lambda do |observer|
|
136
|
+
on_modify_text { |modify_event|
|
137
|
+
observer.call(@swt_widget.getText)
|
138
|
+
}
|
139
|
+
end,
|
140
|
+
:caret_position => lambda do |observer|
|
141
|
+
on_event_keydown { |event|
|
142
|
+
observer.call(@swt_widget.getCaretPosition)
|
143
|
+
}
|
144
|
+
on_event_keyup { |event|
|
145
|
+
observer.call(@swt_widget.getCaretPosition)
|
146
|
+
}
|
147
|
+
on_event_mousedown { |event|
|
148
|
+
observer.call(@swt_widget.getCaretPosition)
|
149
|
+
}
|
150
|
+
on_event_mouseup { |event|
|
151
|
+
observer.call(@swt_widget.getCaretPosition)
|
152
|
+
}
|
153
|
+
end,
|
154
|
+
:selection => lambda do |observer|
|
155
|
+
on_event_keydown { |event|
|
156
|
+
observer.call(@swt_widget.getSelection)
|
157
|
+
}
|
158
|
+
on_event_keyup { |event|
|
159
|
+
observer.call(@swt_widget.getSelection)
|
160
|
+
}
|
161
|
+
on_event_mousedown { |event|
|
162
|
+
observer.call(@swt_widget.getSelection)
|
163
|
+
}
|
164
|
+
on_event_mouseup { |event|
|
165
|
+
observer.call(@swt_widget.getSelection)
|
166
|
+
}
|
167
|
+
end,
|
168
|
+
:selection_count => lambda do |observer|
|
169
|
+
on_event_keydown { |event|
|
170
|
+
observer.call(@swt_widget.getSelectionCount)
|
171
|
+
}
|
172
|
+
on_event_keyup { |event|
|
173
|
+
observer.call(@swt_widget.getSelectionCount)
|
174
|
+
}
|
175
|
+
on_event_mousedown { |event|
|
176
|
+
observer.call(@swt_widget.getSelectionCount)
|
177
|
+
}
|
178
|
+
on_event_mouseup { |event|
|
179
|
+
observer.call(@swt_widget.getSelectionCount)
|
180
|
+
}
|
181
|
+
end,
|
182
|
+
:top_index => lambda do |observer|
|
183
|
+
@last_top_index = @swt_widget.getTopIndex
|
184
|
+
on_paint_control { |event|
|
185
|
+
if @swt_widget.getTopIndex != @last_top_index
|
186
|
+
@last_top_index = @swt_widget.getTopIndex
|
187
|
+
observer.call(@last_top_index)
|
188
|
+
end
|
189
|
+
}
|
190
|
+
end,
|
191
|
+
},
|
192
|
+
Java::OrgEclipseSwtCustom::StyledText => {
|
193
|
+
:text => lambda do |observer|
|
194
|
+
on_modify_text { |modify_event|
|
195
|
+
observer.call(@swt_widget.getText)
|
196
|
+
}
|
197
|
+
end,
|
198
|
+
},
|
199
|
+
Java::OrgEclipseSwtWidgets::Button => {
|
200
|
+
:selection => lambda do |observer|
|
201
|
+
on_widget_selected { |selection_event|
|
202
|
+
observer.call(@swt_widget.getSelection)
|
203
|
+
}
|
204
|
+
end
|
205
|
+
},
|
206
|
+
Java::OrgEclipseSwtWidgets::MenuItem => {
|
207
|
+
:selection => lambda do |observer|
|
208
|
+
on_widget_selected { |selection_event|
|
209
|
+
observer.call(@swt_widget.getSelection)
|
210
|
+
}
|
211
|
+
end
|
212
|
+
},
|
213
|
+
Java::OrgEclipseSwtWidgets::Spinner => {
|
214
|
+
:selection => lambda do |observer|
|
215
|
+
on_widget_selected { |selection_event|
|
216
|
+
observer.call(@swt_widget.getSelection)
|
217
|
+
}
|
218
|
+
end
|
219
|
+
},
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.widget_exists?(underscored_widget_name)
|
224
|
+
!!swt_widget_class_for(underscored_widget_name)
|
225
|
+
end
|
226
|
+
|
227
|
+
# This supports widgets in and out of basic SWT
|
228
|
+
def self.swt_widget_class_for(underscored_widget_name)
|
229
|
+
swt_widget_name = underscored_widget_name.camelcase(:upper)
|
230
|
+
swt_widget_class = eval(swt_widget_name)
|
231
|
+
unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
|
232
|
+
Glimmer::Config.logger&.debug("Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget")
|
233
|
+
return nil
|
234
|
+
end
|
235
|
+
swt_widget_class
|
236
|
+
rescue NameError => e
|
237
|
+
Glimmer::Config.logger&.debug e.message
|
238
|
+
# Glimmer::Config.logger&.debug("#{e.message}\n#{e.backtrace.join("\n")}")
|
239
|
+
nil
|
240
|
+
rescue => e
|
241
|
+
Glimmer::Config.logger&.debug e.message
|
242
|
+
# Glimmer::Config.logger&.debug("#{e.message}\n#{e.backtrace.join("\n")}")
|
243
|
+
nil
|
244
|
+
end
|
245
|
+
|
246
|
+
def async_exec(&block)
|
247
|
+
DisplayProxy.instance.async_exec(&block)
|
248
|
+
end
|
249
|
+
|
250
|
+
def sync_exec(&block)
|
251
|
+
DisplayProxy.instance.sync_exec(&block)
|
252
|
+
end
|
253
|
+
|
254
|
+
def has_style?(style)
|
255
|
+
(@swt_widget.style & SWTProxy[style]) == SWTProxy[style]
|
256
|
+
end
|
257
|
+
|
258
|
+
def dispose
|
259
|
+
@swt_widget.dispose
|
260
|
+
end
|
261
|
+
|
262
|
+
# TODO Consider renaming these methods as they are mainly used for data-binding
|
263
|
+
|
264
|
+
def can_add_observer?(property_name)
|
265
|
+
@swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact.map(&:keys).flatten.map(&:to_s).include?(property_name.to_s)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
|
269
|
+
def add_observer(observer, property_name)
|
270
|
+
property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
|
271
|
+
widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
|
272
|
+
widget_listener_installers.to_a.each do |widget_listener_installer|
|
273
|
+
widget_listener_installer.call(observer)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def remove_observer(observer, property_name)
|
278
|
+
# TODO consider implementing if remove_observer is needed (consumers can remove listener via SWT API)
|
279
|
+
end
|
280
|
+
|
281
|
+
# TODO eliminate duplication in the following methods perhaps by relying on exceptions
|
282
|
+
|
283
|
+
def can_handle_observation_request?(observation_request)
|
284
|
+
observation_request = observation_request.to_s
|
285
|
+
if observation_request.start_with?('on_event_')
|
286
|
+
constant_name = observation_request.sub(/^on_event_/, '')
|
287
|
+
SWTProxy.has_constant?(constant_name)
|
288
|
+
elsif observation_request.start_with?('on_')
|
289
|
+
event = observation_request.sub(/^on_/, '')
|
290
|
+
can_add_listener?(event)
|
291
|
+
else
|
292
|
+
false
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def handle_observation_request(observation_request, &block)
|
297
|
+
if observation_request.start_with?('on_event_')
|
298
|
+
constant_name = observation_request.sub(/^on_event_/, '')
|
299
|
+
add_swt_event_listener(constant_name, &block)
|
300
|
+
elsif observation_request.start_with?('on_')
|
301
|
+
event = observation_request.sub(/^on_/, '')
|
302
|
+
add_listener(event, &block)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def content(&block)
|
307
|
+
Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
|
308
|
+
end
|
309
|
+
|
310
|
+
private
|
311
|
+
|
312
|
+
def style(underscored_widget_name, styles)
|
313
|
+
styles = [styles].flatten.compact
|
314
|
+
styles.empty? ? default_style(underscored_widget_name) : SWTProxy[*styles]
|
315
|
+
end
|
316
|
+
|
317
|
+
def default_style(underscored_widget_name)
|
318
|
+
styles = DEFAULT_STYLES[underscored_widget_name] || [:none]
|
319
|
+
SWTProxy[styles]
|
320
|
+
end
|
321
|
+
|
322
|
+
def attribute_setter(attribute_name)
|
323
|
+
"set#{attribute_name.to_s.camelcase(:upper)}"
|
324
|
+
end
|
325
|
+
|
326
|
+
def attribute_getter(attribute_name)
|
327
|
+
"get#{attribute_name.to_s.camelcase(:upper)}"
|
328
|
+
end
|
329
|
+
|
330
|
+
# TODO refactor following methods to eliminate duplication
|
331
|
+
# perhaps consider relying on raising an exception to avoid checking first
|
332
|
+
# unless that gives obscure SWT errors
|
333
|
+
# Otherwise, consider caching results from can_add_lsitener and using them in
|
334
|
+
# add_listener knowing it will be called for sure afterwards
|
335
|
+
|
336
|
+
def can_add_listener?(underscored_listener_name)
|
337
|
+
!self.class.find_listener(@swt_widget.getClass, underscored_listener_name).empty?
|
338
|
+
end
|
339
|
+
|
340
|
+
def add_listener(underscored_listener_name, &block)
|
341
|
+
widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
|
342
|
+
widget_listener_proxy = nil
|
343
|
+
safe_block = lambda { |event| block.call(event) unless @swt_widget.isDisposed }
|
344
|
+
listener = listener_class.new(listener_method => safe_block)
|
345
|
+
@swt_widget.send(widget_add_listener_method, listener)
|
346
|
+
widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Looks through SWT class add***Listener methods till it finds one for which
|
350
|
+
# the argument is a listener class that has an event method matching
|
351
|
+
# underscored_listener_name
|
352
|
+
def self.find_listener(swt_widget_class, underscored_listener_name)
|
353
|
+
@listeners ||= {}
|
354
|
+
listener_key = [swt_widget_class.name, underscored_listener_name]
|
355
|
+
unless @listeners.has_key?(listener_key)
|
356
|
+
listener_method_name = underscored_listener_name.camelcase(:lower)
|
357
|
+
swt_widget_class.getMethods.each do |widget_add_listener_method|
|
358
|
+
if widget_add_listener_method.getName.match(/add.*Listener/)
|
359
|
+
widget_add_listener_method.getParameterTypes.each do |listener_type|
|
360
|
+
listener_type.getMethods.each do |listener_method|
|
361
|
+
if (listener_method.getName == listener_method_name)
|
362
|
+
@listeners[listener_key] = [widget_add_listener_method.getName, listener_class(listener_type), listener_method.getName]
|
363
|
+
return @listeners[listener_key]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
@listeners[listener_key] = []
|
370
|
+
end
|
371
|
+
@listeners[listener_key]
|
372
|
+
end
|
373
|
+
|
374
|
+
# Returns a Ruby class that implements listener type Java interface with ability to easily
|
375
|
+
# install a block that gets called upon calling a listener event method
|
376
|
+
def self.listener_class(listener_type)
|
377
|
+
@listener_classes ||= {}
|
378
|
+
listener_class_key = listener_type.name
|
379
|
+
unless @listener_classes.has_key?(listener_class_key)
|
380
|
+
@listener_classes[listener_class_key] = Class.new(Object).tap do |listener_class|
|
381
|
+
listener_class.send :include, (eval listener_type.name.sub("interface", ""))
|
382
|
+
listener_class.define_method('initialize') do |event_method_block_mapping|
|
383
|
+
@event_method_block_mapping = event_method_block_mapping
|
384
|
+
end
|
385
|
+
listener_type.getMethods.each do |event_method|
|
386
|
+
listener_class.define_method(event_method.getName) do |event|
|
387
|
+
@event_method_block_mapping[event_method.getName]&.call(event)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
@listener_classes[listener_class_key]
|
393
|
+
end
|
394
|
+
|
395
|
+
def add_swt_event_listener(swt_constant, &block)
|
396
|
+
event_type = SWTProxy[swt_constant]
|
397
|
+
widget_listener_proxy = nil
|
398
|
+
safe_block = lambda { |event| block.call(event) unless @swt_widget.isDisposed }
|
399
|
+
@swt_widget.addListener(event_type, &safe_block)
|
400
|
+
widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: @swt_widget.getListeners(event_type).last, event_type: event_type, swt_constant: swt_constant)
|
401
|
+
end
|
402
|
+
|
403
|
+
def widget_custom_attribute_mapping
|
404
|
+
@swt_widget_custom_attribute_mapping ||= {
|
405
|
+
'focus' => {
|
406
|
+
getter: {name: 'isFocusControl'},
|
407
|
+
setter: {name: 'setFocus', invoker: lambda { |widget, args| @swt_widget.setFocus if args.first }},
|
408
|
+
},
|
409
|
+
'caret_position' => {
|
410
|
+
getter: {name: 'getCaretPosition'},
|
411
|
+
setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(args.first) if args.first }},
|
412
|
+
},
|
413
|
+
'selection_count' => {
|
414
|
+
getter: {name: 'getSelectionCount'},
|
415
|
+
setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(@swt_widget.getCaretPosition, @swt_widget.getCaretPosition + args.first) if args.first }},
|
416
|
+
},
|
417
|
+
}
|
418
|
+
end
|
419
|
+
|
420
|
+
def apply_property_type_converters(attribute_name, args)
|
421
|
+
if args.count == 1
|
422
|
+
value = args.first
|
423
|
+
converter = property_type_converters[attribute_name.to_sym]
|
424
|
+
args[0] = converter.call(value) if converter
|
425
|
+
end
|
426
|
+
if args.count == 1 && args.first.is_a?(ColorProxy)
|
427
|
+
g_color = args.first
|
428
|
+
args[0] = g_color.swt_color
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def property_type_converters
|
433
|
+
color_converter = lambda do |value|
|
434
|
+
if value.is_a?(Symbol) || value.is_a?(String)
|
435
|
+
ColorProxy.new(value).swt_color
|
436
|
+
else
|
437
|
+
value
|
438
|
+
end
|
439
|
+
end
|
440
|
+
# TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
|
441
|
+
@property_type_converters ||= {
|
442
|
+
:background => color_converter,
|
443
|
+
:background_image => lambda do |value|
|
444
|
+
if value.is_a?(String)
|
445
|
+
if value.start_with?('uri:classloader')
|
446
|
+
value = value.sub(/^uri\:classloader\:\//, '')
|
447
|
+
object = java.lang.Object.new
|
448
|
+
value = object.java_class.resource_as_stream(value)
|
449
|
+
value = java.io.BufferedInputStream.new(value)
|
450
|
+
end
|
451
|
+
image_data = ImageData.new(value)
|
452
|
+
# TODO in the future, look into unregistering this listener when no longer needed
|
453
|
+
on_event_Resize do |resize_event|
|
454
|
+
new_image_data = image_data.scaledTo(@swt_widget.getSize.x, @swt_widget.getSize.y)
|
455
|
+
@swt_widget.getBackgroundImage&.dispose
|
456
|
+
@swt_widget.setBackgroundImage(Image.new(@swt_widget.getDisplay, new_image_data))
|
457
|
+
end
|
458
|
+
Image.new(@swt_widget.getDisplay, image_data)
|
459
|
+
else
|
460
|
+
value
|
461
|
+
end
|
462
|
+
end,
|
463
|
+
:foreground => color_converter,
|
464
|
+
:font => lambda do |value|
|
465
|
+
if value.is_a?(Hash)
|
466
|
+
font_properties = value
|
467
|
+
FontProxy.new(self, font_properties).swt_font
|
468
|
+
else
|
469
|
+
value
|
470
|
+
end
|
471
|
+
end,
|
472
|
+
:items => lambda do |value|
|
473
|
+
value.to_java :string
|
474
|
+
end,
|
475
|
+
:text => lambda do |value|
|
476
|
+
if swt_widget.is_a?(Browser)
|
477
|
+
value.to_s
|
478
|
+
else
|
479
|
+
value.to_s
|
480
|
+
end
|
481
|
+
end,
|
482
|
+
:visible => lambda do |value|
|
483
|
+
!!value
|
484
|
+
end,
|
485
|
+
}
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|