glimmer-dsl-swt 4.18.3.5 → 4.18.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/LICENSE.txt +20 -20
  4. data/README.md +782 -716
  5. data/RUBY_VERSION +1 -1
  6. data/VERSION +1 -1
  7. data/bin/girb +31 -31
  8. data/bin/girb_runner.rb +34 -34
  9. data/bin/glimmer +26 -26
  10. data/glimmer-dsl-swt.gemspec +10 -7
  11. data/lib/ext/glimmer.rb +41 -41
  12. data/lib/ext/glimmer/config.rb +167 -167
  13. data/lib/ext/rouge/themes/glimmer.rb +29 -29
  14. data/lib/glimmer-dsl-swt.rb +44 -44
  15. data/lib/glimmer/Rakefile +26 -26
  16. data/lib/glimmer/data_binding/list_selection_binding.rb +72 -72
  17. data/lib/glimmer/data_binding/observable_widget.rb +38 -38
  18. data/lib/glimmer/data_binding/shine.rb +44 -44
  19. data/lib/glimmer/data_binding/table_items_binding.rb +89 -89
  20. data/lib/glimmer/data_binding/tree_items_binding.rb +108 -108
  21. data/lib/glimmer/data_binding/widget_binding.rb +73 -73
  22. data/lib/glimmer/dsl/swt/animation_expression.rb +43 -43
  23. data/lib/glimmer/dsl/swt/async_exec_expression.rb +35 -35
  24. data/lib/glimmer/dsl/swt/bind_expression.rb +58 -58
  25. data/lib/glimmer/dsl/swt/block_property_expression.rb +41 -41
  26. data/lib/glimmer/dsl/swt/checkbox_group_selection_data_binding_expression.rb +61 -61
  27. data/lib/glimmer/dsl/swt/color_expression.rb +40 -40
  28. data/lib/glimmer/dsl/swt/column_properties_expression.rb +45 -45
  29. data/lib/glimmer/dsl/swt/combo_selection_data_binding_expression.rb +67 -67
  30. data/lib/glimmer/dsl/swt/cursor_expression.rb +44 -44
  31. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +63 -63
  32. data/lib/glimmer/dsl/swt/data_binding_expression.rb +55 -55
  33. data/lib/glimmer/dsl/swt/dialog_expression.rb +48 -48
  34. data/lib/glimmer/dsl/swt/directory_dialog_expression.rb +48 -48
  35. data/lib/glimmer/dsl/swt/display_expression.rb +40 -40
  36. data/lib/glimmer/dsl/swt/dnd_expression.rb +46 -46
  37. data/lib/glimmer/dsl/swt/dsl.rb +63 -63
  38. data/lib/glimmer/dsl/swt/exec_expression.rb +55 -55
  39. data/lib/glimmer/dsl/swt/expand_item_expression.rb +60 -60
  40. data/lib/glimmer/dsl/swt/file_dialog_expression.rb +48 -48
  41. data/lib/glimmer/dsl/swt/font_expression.rb +49 -49
  42. data/lib/glimmer/dsl/swt/image_expression.rb +50 -50
  43. data/lib/glimmer/dsl/swt/layout_data_expression.rb +46 -46
  44. data/lib/glimmer/dsl/swt/layout_expression.rb +50 -50
  45. data/lib/glimmer/dsl/swt/list_selection_data_binding_expression.rb +65 -65
  46. data/lib/glimmer/dsl/swt/menu_bar_expression.rb +54 -54
  47. data/lib/glimmer/dsl/swt/menu_expression.rb +53 -53
  48. data/lib/glimmer/dsl/swt/message_box_expression.rb +54 -54
  49. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -53
  50. data/lib/glimmer/dsl/swt/observe_expression.rb +53 -53
  51. data/lib/glimmer/dsl/swt/property_expression.rb +46 -46
  52. data/lib/glimmer/dsl/swt/radio_group_selection_data_binding_expression.rb +61 -61
  53. data/lib/glimmer/dsl/swt/rgb_expression.rb +33 -33
  54. data/lib/glimmer/dsl/swt/rgba_expression.rb +33 -33
  55. data/lib/glimmer/dsl/swt/shape_expression.rb +54 -54
  56. data/lib/glimmer/dsl/swt/shell_expression.rb +46 -46
  57. data/lib/glimmer/dsl/swt/swt_expression.rb +46 -46
  58. data/lib/glimmer/dsl/swt/sync_exec_expression.rb +36 -36
  59. data/lib/glimmer/dsl/swt/tab_item_expression.rb +54 -54
  60. data/lib/glimmer/dsl/swt/table_items_data_binding_expression.rb +52 -52
  61. data/lib/glimmer/dsl/swt/timer_exec_expression.rb +35 -0
  62. data/lib/glimmer/dsl/swt/transform_expression.rb +55 -55
  63. data/lib/glimmer/dsl/swt/tree_items_data_binding_expression.rb +52 -52
  64. data/lib/glimmer/dsl/swt/tree_properties_expression.rb +47 -47
  65. data/lib/glimmer/dsl/swt/widget_expression.rb +66 -66
  66. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +53 -53
  67. data/lib/glimmer/rake_task.rb +220 -220
  68. data/lib/glimmer/rake_task/list.rb +97 -97
  69. data/lib/glimmer/rake_task/package.rb +139 -139
  70. data/lib/glimmer/rake_task/scaffold.rb +765 -765
  71. data/lib/glimmer/swt/color_proxy.rb +107 -107
  72. data/lib/glimmer/swt/cursor_proxy.rb +66 -66
  73. data/lib/glimmer/swt/custom/animation.rb +243 -243
  74. data/lib/glimmer/swt/custom/checkbox_group.rb +181 -181
  75. data/lib/glimmer/swt/custom/code_text.rb +150 -93
  76. data/lib/glimmer/swt/custom/drawable.rb +49 -52
  77. data/lib/glimmer/swt/custom/radio_group.rb +176 -176
  78. data/lib/glimmer/swt/custom/shape.rb +297 -253
  79. data/lib/glimmer/swt/date_time_proxy.rb +85 -85
  80. data/lib/glimmer/swt/directory_dialog_proxy.rb +65 -65
  81. data/lib/glimmer/swt/display_proxy.rb +166 -155
  82. data/lib/glimmer/swt/dnd_proxy.rb +51 -51
  83. data/lib/glimmer/swt/expand_item_proxy.rb +97 -97
  84. data/lib/glimmer/swt/file_dialog_proxy.rb +66 -66
  85. data/lib/glimmer/swt/font_proxy.rb +94 -94
  86. data/lib/glimmer/swt/image_proxy.rb +184 -173
  87. data/lib/glimmer/swt/layout_data_proxy.rb +105 -105
  88. data/lib/glimmer/swt/layout_proxy.rb +112 -109
  89. data/lib/glimmer/swt/menu_proxy.rb +126 -126
  90. data/lib/glimmer/swt/message_box_proxy.rb +89 -89
  91. data/lib/glimmer/swt/packages.rb +37 -37
  92. data/lib/glimmer/swt/properties.rb +49 -49
  93. data/lib/glimmer/swt/sash_form_proxy.rb +53 -53
  94. data/lib/glimmer/swt/scrolled_composite_proxy.rb +37 -37
  95. data/lib/glimmer/swt/shell_proxy.rb +4 -1
  96. data/lib/glimmer/swt/style_constantizable.rb +157 -157
  97. data/lib/glimmer/swt/styled_text_proxy.rb +38 -38
  98. data/lib/glimmer/swt/swt_proxy.rb +59 -59
  99. data/lib/glimmer/swt/tab_item_proxy.rb +91 -91
  100. data/lib/glimmer/swt/table_column_proxy.rb +57 -57
  101. data/lib/glimmer/swt/table_proxy.rb +2 -2
  102. data/lib/glimmer/swt/transform_proxy.rb +109 -109
  103. data/lib/glimmer/swt/tree_proxy.rb +145 -145
  104. data/lib/glimmer/swt/widget_listener_proxy.rb +64 -64
  105. data/lib/glimmer/swt/widget_proxy.rb +957 -949
  106. data/lib/glimmer/ui/custom_shell.rb +82 -82
  107. data/lib/glimmer/ui/custom_widget.rb +315 -315
  108. data/lib/glimmer/util/proc_tracker.rb +39 -39
  109. data/samples/elaborate/contact_manager.rb +142 -142
  110. data/samples/elaborate/contact_manager/contact.rb +32 -32
  111. data/samples/elaborate/contact_manager/contact_manager_presenter.rb +47 -47
  112. data/samples/elaborate/contact_manager/contact_repository.rb +169 -169
  113. data/samples/elaborate/login.rb +123 -123
  114. data/samples/elaborate/meta_sample.rb +14 -3
  115. data/samples/elaborate/tetris.rb +16 -6
  116. data/samples/elaborate/tetris/model/block.rb +48 -48
  117. data/samples/elaborate/tetris/model/game.rb +2 -3
  118. data/samples/elaborate/tetris/model/past_game.rb +39 -39
  119. data/samples/elaborate/tetris/view/block.rb +1 -1
  120. data/samples/elaborate/tetris/view/high_score_dialog.rb +0 -7
  121. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  122. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +13 -11
  123. data/samples/elaborate/tic_tac_toe.rb +76 -76
  124. data/samples/elaborate/tic_tac_toe/board.rb +145 -145
  125. data/samples/elaborate/tic_tac_toe/cell.rb +48 -48
  126. data/samples/elaborate/user_profile.rb +76 -76
  127. data/samples/hello/hello_browser.rb +31 -31
  128. data/samples/hello/hello_button.rb +46 -46
  129. data/samples/hello/hello_canvas.rb +64 -63
  130. data/samples/hello/hello_canvas_animation.rb +3 -3
  131. data/samples/hello/hello_canvas_transform.rb +1 -1
  132. data/samples/hello/hello_checkbox.rb +85 -85
  133. data/samples/hello/hello_checkbox_group.rb +71 -71
  134. data/samples/hello/hello_code_text.rb +104 -84
  135. data/samples/hello/hello_combo.rb +63 -63
  136. data/samples/hello/hello_computed.rb +96 -96
  137. data/samples/hello/hello_computed/contact.rb +42 -42
  138. data/samples/hello/hello_custom_shell.rb +155 -155
  139. data/samples/hello/hello_custom_widget.rb +86 -86
  140. data/samples/hello/hello_date_time.rb +63 -63
  141. data/samples/hello/hello_dialog.rb +78 -78
  142. data/samples/hello/hello_directory_dialog.rb +60 -60
  143. data/samples/hello/hello_drag_and_drop.rb +50 -50
  144. data/samples/hello/hello_expand_bar.rb +110 -110
  145. data/samples/hello/hello_file_dialog.rb +60 -60
  146. data/samples/hello/hello_group.rb +104 -104
  147. data/samples/hello/hello_link.rb +80 -80
  148. data/samples/hello/hello_list_multi_selection.rb +74 -74
  149. data/samples/hello/hello_list_single_selection.rb +59 -59
  150. data/samples/hello/hello_menu_bar.rb +241 -241
  151. data/samples/hello/hello_message_box.rb +37 -37
  152. data/samples/hello/hello_pop_up_context_menu.rb +84 -84
  153. data/samples/hello/hello_radio.rb +108 -108
  154. data/samples/hello/hello_radio_group.rb +87 -87
  155. data/samples/hello/hello_sash_form.rb +137 -137
  156. data/samples/hello/hello_spinner.rb +69 -69
  157. data/samples/hello/hello_styled_text.rb +138 -138
  158. data/samples/hello/hello_tab.rb +50 -50
  159. data/samples/hello/hello_table.rb +7 -4
  160. data/samples/hello/hello_table/baseball_park.png +0 -0
  161. data/samples/hello/hello_world.rb +29 -29
  162. metadata +7 -4
@@ -1,145 +1,145 @@
1
- # Copyright (c) 2007-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/swt/widget_proxy'
23
-
24
- module Glimmer
25
- module SWT
26
- class TreeProxy < Glimmer::SWT::WidgetProxy
27
- include Glimmer
28
-
29
- attr_reader :tree_editor, :tree_editor_text_proxy
30
- attr_accessor :tree_properties
31
-
32
- def initialize(underscored_widget_name, parent, args)
33
- super
34
- @tree_editor = TreeEditor.new(swt_widget)
35
- @tree_editor.horizontalAlignment = SWTProxy[:left]
36
- @tree_editor.grabHorizontal = true
37
- @tree_editor.minimumHeight = 20
38
- end
39
-
40
- # Performs depth first search for tree items matching block condition
41
- # If no condition block is passed, returns all tree items
42
- # Returns a Java TreeItem array to easily set as selection on org.eclipse.swt.Tree if needed
43
- def depth_first_search(&condition)
44
- found = []
45
- recursive_depth_first_search(swt_widget.getItems.first, found, &condition)
46
- found.to_java(TreeItem)
47
- end
48
-
49
- # Returns all tree items including descendants
50
- def all_tree_items
51
- depth_first_search
52
- end
53
-
54
- def widget_property_listener_installers
55
- super.merge({
56
- Java::OrgEclipseSwtWidgets::Tree => {
57
- selection: lambda do |observer|
58
- on_widget_selected { |selection_event|
59
- observer.call(@swt_widget.getSelection)
60
- }
61
- end
62
- },
63
- })
64
- end
65
-
66
- def edit_in_progress?
67
- !!@edit_in_progress
68
- end
69
-
70
- def edit_selected_tree_item(before_write: nil, after_write: nil, after_cancel: nil)
71
- edit_tree_item(swt_widget.getSelection.first, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
72
- end
73
-
74
- def edit_tree_item(tree_item, before_write: nil, after_write: nil, after_cancel: nil)
75
- return if tree_item.nil?
76
- content {
77
- @tree_editor_text_proxy = text {
78
- focus true
79
- text tree_item.getText
80
- action_taken = false
81
- cancel = lambda {
82
- @tree_editor_text_proxy.swt_widget.dispose
83
- @tree_editor_text_proxy = nil
84
- after_cancel&.call
85
- @edit_in_progress = false
86
- }
87
- action = lambda { |event|
88
- begin
89
- if !action_taken && !@edit_in_progress
90
- action_taken = true
91
- @edit_in_progress = true
92
- new_text = @tree_editor_text_proxy.swt_widget.getText
93
- if new_text == tree_item.getText
94
- cancel.call
95
- else
96
- before_write&.call
97
- tree_item.setText(new_text)
98
- model = tree_item.getData
99
- model.send("#{tree_properties[:text]}=", new_text) # makes tree update itself, so must search for selected tree item again
100
- edited_tree_item = depth_first_search { |ti| ti.getData == model }.first
101
- swt_widget.showItem(edited_tree_item)
102
- @tree_editor_text_proxy.swt_widget.dispose
103
- @tree_editor_text_proxy = nil
104
- after_write&.call(edited_tree_item)
105
- @edit_in_progress = false
106
- end
107
- end
108
- rescue => e
109
- Glimmer::Config.logger.error {"Error encountered while editing tree item!\n#{e.full_message}"}
110
- end
111
- }
112
- on_focus_lost(&action)
113
- on_key_pressed { |key_event|
114
- if key_event.keyCode == swt(:cr)
115
- action.call(key_event)
116
- elsif key_event.keyCode == swt(:esc)
117
- cancel.call
118
- end
119
- }
120
- }
121
- @tree_editor_text_proxy.swt_widget.selectAll
122
- }
123
- @tree_editor.setEditor(@tree_editor_text_proxy.swt_widget, tree_item)
124
- end
125
-
126
- private
127
-
128
- def recursive_depth_first_search(tree_item, found, &condition)
129
- return if tree_item.nil?
130
- found << tree_item if condition.nil? || condition.call(tree_item)
131
- tree_item.getItems.each do |child_tree_item|
132
- recursive_depth_first_search(child_tree_item, found, &condition)
133
- end
134
- end
135
-
136
- def property_type_converters
137
- super.merge({
138
- selection: lambda do |value|
139
- depth_first_search {|ti| ti.getData == value}
140
- end,
141
- })
142
- end
143
- end
144
- end
145
- end
1
+ # Copyright (c) 2007-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/swt/widget_proxy'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ class TreeProxy < Glimmer::SWT::WidgetProxy
27
+ include Glimmer
28
+
29
+ attr_reader :tree_editor, :tree_editor_text_proxy
30
+ attr_accessor :tree_properties
31
+
32
+ def initialize(underscored_widget_name, parent, args)
33
+ super
34
+ @tree_editor = TreeEditor.new(swt_widget)
35
+ @tree_editor.horizontalAlignment = SWTProxy[:left]
36
+ @tree_editor.grabHorizontal = true
37
+ @tree_editor.minimumHeight = 20
38
+ end
39
+
40
+ # Performs depth first search for tree items matching block condition
41
+ # If no condition block is passed, returns all tree items
42
+ # Returns a Java TreeItem array to easily set as selection on org.eclipse.swt.Tree if needed
43
+ def depth_first_search(&condition)
44
+ found = []
45
+ recursive_depth_first_search(swt_widget.getItems.first, found, &condition)
46
+ found.to_java(TreeItem)
47
+ end
48
+
49
+ # Returns all tree items including descendants
50
+ def all_tree_items
51
+ depth_first_search
52
+ end
53
+
54
+ def widget_property_listener_installers
55
+ super.merge({
56
+ Java::OrgEclipseSwtWidgets::Tree => {
57
+ selection: lambda do |observer|
58
+ on_widget_selected { |selection_event|
59
+ observer.call(@swt_widget.getSelection)
60
+ }
61
+ end
62
+ },
63
+ })
64
+ end
65
+
66
+ def edit_in_progress?
67
+ !!@edit_in_progress
68
+ end
69
+
70
+ def edit_selected_tree_item(before_write: nil, after_write: nil, after_cancel: nil)
71
+ edit_tree_item(swt_widget.getSelection.first, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
72
+ end
73
+
74
+ def edit_tree_item(tree_item, before_write: nil, after_write: nil, after_cancel: nil)
75
+ return if tree_item.nil?
76
+ content {
77
+ @tree_editor_text_proxy = text {
78
+ focus true
79
+ text tree_item.getText
80
+ action_taken = false
81
+ cancel = lambda {
82
+ @tree_editor_text_proxy.swt_widget.dispose
83
+ @tree_editor_text_proxy = nil
84
+ after_cancel&.call
85
+ @edit_in_progress = false
86
+ }
87
+ action = lambda { |event|
88
+ begin
89
+ if !action_taken && !@edit_in_progress
90
+ action_taken = true
91
+ @edit_in_progress = true
92
+ new_text = @tree_editor_text_proxy.swt_widget.getText
93
+ if new_text == tree_item.getText
94
+ cancel.call
95
+ else
96
+ before_write&.call
97
+ tree_item.setText(new_text)
98
+ model = tree_item.getData
99
+ model.send("#{tree_properties[:text]}=", new_text) # makes tree update itself, so must search for selected tree item again
100
+ edited_tree_item = depth_first_search { |ti| ti.getData == model }.first
101
+ swt_widget.showItem(edited_tree_item)
102
+ @tree_editor_text_proxy.swt_widget.dispose
103
+ @tree_editor_text_proxy = nil
104
+ after_write&.call(edited_tree_item)
105
+ @edit_in_progress = false
106
+ end
107
+ end
108
+ rescue => e
109
+ Glimmer::Config.logger.error {"Error encountered while editing tree item!\n#{e.full_message}"}
110
+ end
111
+ }
112
+ on_focus_lost(&action)
113
+ on_key_pressed { |key_event|
114
+ if key_event.keyCode == swt(:cr)
115
+ action.call(key_event)
116
+ elsif key_event.keyCode == swt(:esc)
117
+ cancel.call
118
+ end
119
+ }
120
+ }
121
+ @tree_editor_text_proxy.swt_widget.selectAll
122
+ }
123
+ @tree_editor.setEditor(@tree_editor_text_proxy.swt_widget, tree_item)
124
+ end
125
+
126
+ private
127
+
128
+ def recursive_depth_first_search(tree_item, found, &condition)
129
+ return if tree_item.nil?
130
+ found << tree_item if condition.nil? || condition.call(tree_item)
131
+ tree_item.getItems.each do |child_tree_item|
132
+ recursive_depth_first_search(child_tree_item, found, &condition)
133
+ end
134
+ end
135
+
136
+ def property_type_converters
137
+ super.merge({
138
+ selection: lambda do |value|
139
+ depth_first_search {|ti| ti.getData == value}
140
+ end,
141
+ })
142
+ end
143
+ end
144
+ end
145
+ end
@@ -1,64 +1,64 @@
1
- # Copyright (c) 2007-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
- module Glimmer
23
- module SWT
24
- # Proxy for widget listeners
25
- #
26
- # Follows the Proxy Design Pattern
27
- class WidgetListenerProxy
28
-
29
- attr_reader :swt_widget, :swt_display, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
30
-
31
- def initialize(swt_widget:nil, swt_display:nil, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil, filter: false)
32
- @swt_widget = swt_widget
33
- @swt_display = swt_display
34
- @filter = filter
35
- @swt_listener = swt_listener
36
- @widget_add_listener_method = widget_add_listener_method
37
- @swt_listener_class = swt_listener_class
38
- @swt_listener_method = swt_listener_method
39
- @event_type = event_type
40
- @swt_constant = swt_constant
41
- end
42
-
43
- def widget_remove_listener_method
44
- @widget_add_listener_method.sub('add', 'remove')
45
- end
46
-
47
- def deregister
48
- return if @swt_widget&.is_disposed || @swt_display&.is_disposed
49
- if @swt_display
50
- if @filter
51
- @swt_display.removeFilter(@event_type, @swt_listener)
52
- else
53
- @swt_display.removeListener(@event_type, @swt_listener)
54
- end
55
- elsif @event_type
56
- @swt_widget.removeListener(@event_type, @swt_listener)
57
- else
58
- @swt_widget.send(widget_remove_listener_method, @swt_listener)
59
- end
60
- end
61
- alias unregister deregister # TODO consider dropping unregister (and in Observer too)
62
- end
63
- end
64
- end
1
+ # Copyright (c) 2007-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
+ module Glimmer
23
+ module SWT
24
+ # Proxy for widget listeners
25
+ #
26
+ # Follows the Proxy Design Pattern
27
+ class WidgetListenerProxy
28
+
29
+ attr_reader :swt_widget, :swt_display, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
30
+
31
+ def initialize(swt_widget:nil, swt_display:nil, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil, filter: false)
32
+ @swt_widget = swt_widget
33
+ @swt_display = swt_display
34
+ @filter = filter
35
+ @swt_listener = swt_listener
36
+ @widget_add_listener_method = widget_add_listener_method
37
+ @swt_listener_class = swt_listener_class
38
+ @swt_listener_method = swt_listener_method
39
+ @event_type = event_type
40
+ @swt_constant = swt_constant
41
+ end
42
+
43
+ def widget_remove_listener_method
44
+ @widget_add_listener_method.sub('add', 'remove')
45
+ end
46
+
47
+ def deregister
48
+ return if @swt_widget&.is_disposed || @swt_display&.is_disposed
49
+ if @swt_display
50
+ if @filter
51
+ @swt_display.removeFilter(@event_type, @swt_listener)
52
+ else
53
+ @swt_display.removeListener(@event_type, @swt_listener)
54
+ end
55
+ elsif @event_type
56
+ @swt_widget.removeListener(@event_type, @swt_listener)
57
+ else
58
+ @swt_widget.send(widget_remove_listener_method, @swt_listener)
59
+ end
60
+ end
61
+ alias unregister deregister # TODO consider dropping unregister (and in Observer too)
62
+ end
63
+ end
64
+ end
@@ -1,949 +1,957 @@
1
- # Copyright (c) 2007-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/swt/widget_listener_proxy'
23
- require 'glimmer/swt/color_proxy'
24
- require 'glimmer/swt/font_proxy'
25
- require 'glimmer/swt/swt_proxy'
26
- require 'glimmer/swt/display_proxy'
27
- require 'glimmer/swt/dnd_proxy'
28
- require 'glimmer/swt/image_proxy'
29
- require 'glimmer/swt/properties'
30
- require 'glimmer/swt/custom/drawable'
31
-
32
- # TODO refactor to make file smaller and extract sub-widget-proxies out of this
33
-
34
- module Glimmer
35
- module SWT
36
- # Proxy for SWT Widget objects
37
- #
38
- # Sets default SWT styles to widgets upon inititalizing as
39
- # per DEFAULT_STYLES
40
- #
41
- # Also, auto-initializes widgets as per initializer blocks
42
- # in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
43
- #
44
- # Follows the Proxy Design Pattern
45
- class WidgetProxy
46
- include Packages
47
- include Properties
48
- include Custom::Drawable
49
-
50
- DEFAULT_STYLES = {
51
- 'arrow' => [:arrow],
52
- 'button' => [:push],
53
- 'checkbox' => [:check],
54
- 'check' => [:check],
55
- 'drag_source' => [:drop_copy],
56
- 'drop_target' => [:drop_copy],
57
- 'expand_bar' => [:v_scroll],
58
- 'list' => [:border, :v_scroll],
59
- 'menu_item' => [:push],
60
- 'radio' => [:radio],
61
- 'scrolled_composite' => [:border, :h_scroll, :v_scroll],
62
- 'spinner' => [:border],
63
- 'styled_text' => [:border, :multi, :v_scroll, :h_scroll],
64
- 'table' => [:virtual, :border, :full_selection],
65
- 'text' => [:border],
66
- 'toggle' => [:toggle],
67
- 'tool_bar' => [:push],
68
- 'tool_item' => [:push],
69
- 'tree' => [:virtual, :border, :h_scroll, :v_scroll],
70
- 'date_drop_down' => [:date, :drop_down],
71
- 'time' => [:time],
72
- 'calendar' => [:calendar],
73
- }
74
-
75
- DEFAULT_INITIALIZERS = {
76
- composite: lambda do |composite|
77
- composite.layout = GridLayout.new if composite.get_layout.nil?
78
- end,
79
- canvas: lambda do |canvas|
80
- canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
81
- end,
82
- scrolled_composite: lambda do |scrolled_composite|
83
- scrolled_composite.expand_horizontal = true
84
- scrolled_composite.expand_vertical = true
85
- end,
86
- table: lambda do |table|
87
- table.setHeaderVisible(true)
88
- table.setLinesVisible(true)
89
- end,
90
- table_column: lambda do |table_column|
91
- table_column.setWidth(80)
92
- end,
93
- group: lambda do |group|
94
- group.layout = GridLayout.new if group.get_layout.nil?
95
- end,
96
- }
97
-
98
- KEYWORD_ALIASES = {
99
- 'arrow' => 'button',
100
- 'checkbox' => 'button',
101
- 'check' => 'button',
102
- 'radio' => 'button',
103
- 'toggle' => 'button',
104
- 'date' => 'date_time',
105
- 'date_drop_down' => 'date_time',
106
- 'time' => 'date_time',
107
- 'calendar' => 'date_time',
108
- }
109
-
110
- class << self
111
- # Instantiates the right WidgetProxy subclass for passed in keyword
112
- # Args are: keyword, parent, swt_widget_args (including styles)
113
- def create(*init_args, swt_widget: nil)
114
- return swt_widget.get_data('proxy') if swt_widget&.get_data('proxy')
115
- keyword, parent, args = init_args
116
- selected_widget_proxy_class = widget_proxy_class(keyword || underscored_widget_name(swt_widget))
117
- if init_args.empty?
118
- selected_widget_proxy_class.new(swt_widget: swt_widget)
119
- else
120
- selected_widget_proxy_class.new(*init_args)
121
- end
122
- end
123
-
124
- def widget_proxy_class(keyword)
125
- begin
126
- keyword = KEYWORD_ALIASES[keyword] if KEYWORD_ALIASES[keyword]
127
- class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
128
- Glimmer::SWT.const_get(class_name)
129
- rescue
130
- Glimmer::SWT::WidgetProxy
131
- end
132
- end
133
-
134
- def underscored_widget_name(swt_widget)
135
- swt_widget.class.name.split(/::|\./).last.underscore
136
- end
137
- end
138
-
139
- attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer
140
-
141
- # Initializes a new SWT Widget
142
- #
143
- # It is preferred to use `::create` method instead since it instantiates the
144
- # right subclass per widget keyword
145
- #
146
- # keyword, parent, swt_widget_args (including styles)
147
- #
148
- # Styles is a comma separate list of symbols representing SWT styles in lower case
149
- def initialize(*init_args, swt_widget: nil)
150
- if swt_widget.nil?
151
- underscored_widget_name, parent, args = init_args
152
- @parent_proxy = parent
153
- styles, extra_options = extract_args(underscored_widget_name, args)
154
- swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
155
- @swt_widget = swt_widget_class.new(@parent_proxy.swt_widget, style(underscored_widget_name, styles), *extra_options)
156
- else
157
- @swt_widget = swt_widget
158
- underscored_widget_name = self.class.underscored_widget_name(@swt_widget)
159
- parent_proxy_class = self.class.widget_proxy_class(self.class.underscored_widget_name(@swt_widget.parent))
160
- parent = swt_widget.parent
161
- @parent_proxy = parent&.get_data('proxy') || parent_proxy_class.new(swt_widget: parent)
162
- end
163
- if @swt_widget&.get_data('proxy').nil?
164
- @swt_widget.set_data('proxy', self)
165
- DEFAULT_INITIALIZERS[underscored_widget_name.to_s.to_sym]&.call(@swt_widget)
166
- @parent_proxy.post_initialize_child(self)
167
- end
168
- end
169
-
170
- # Subclasses may override to perform post initialization work on an added child
171
- def post_initialize_child(child)
172
- # No Op by default
173
- end
174
-
175
- # Subclasses may override to perform post add_content work
176
- def post_add_content
177
- # No Op by default
178
- end
179
-
180
- def extract_args(underscored_widget_name, args)
181
- @arg_extractor_mapping ||= {
182
- 'menu_item' => lambda do |args|
183
- index = args.delete(args.last) if args.last.is_a?(Numeric)
184
- extra_options = [index].compact
185
- styles = args
186
- [styles, extra_options]
187
- end,
188
- }
189
- if @arg_extractor_mapping[underscored_widget_name]
190
- @arg_extractor_mapping[underscored_widget_name].call(args)
191
- else
192
- extra_options = []
193
- style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
194
- if style_args.any?
195
- style_arg_start_index = args.index(style_args.first)
196
- style_arg_last_index = args.index(style_args.last)
197
- extra_options = args[style_arg_last_index+1..-1]
198
- args = args[style_arg_start_index..style_arg_last_index]
199
- elsif args.first.is_a?(Integer)
200
- extra_options = args[1..-1]
201
- args = args[0..0]
202
- end
203
- [args, extra_options]
204
- end
205
- end
206
-
207
- def has_attribute_getter?(attribute_getter_name, *args)
208
- attribute_getter_name = attribute_getter_name.to_s.underscore
209
- return false unless !attribute_getter_name.end_with?('=') && !attribute_getter_name.start_with?('set_')
210
- args.empty? && swt_widget.respond_to?(attribute_getter_name)
211
- end
212
-
213
- def has_attribute_setter?(attribute_setter_name, *args)
214
- attribute_setter_name = attribute_setter_name.to_s
215
- underscored_attribute_setter_name = attribute_setter_name.underscore
216
- return false unless attribute_setter_name.end_with?('=') || (attribute_setter_name.start_with?('set_') && !args.empty?)
217
- attribute_name = underscored_attribute_setter_name.sub(/^set_/, '').sub(/=$/, '')
218
- has_attribute?(attribute_name, *args)
219
- end
220
-
221
- def has_attribute?(attribute_name, *args)
222
- # TODO test that attribute getter responds too
223
- widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
224
- if widget_custom_attribute
225
- @swt_widget.respond_to?(widget_custom_attribute[:setter][:name])
226
- else
227
- @swt_widget.respond_to?(attribute_setter(attribute_name), args) || respond_to?(ruby_attribute_setter(attribute_name), args)
228
- end
229
- end
230
-
231
- def set_attribute(attribute_name, *args)
232
- # TODO Think about widget subclasses overriding set_attribute to add more attributes vs adding as Ruby attributes directly
233
- widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
234
- apply_property_type_converters(normalized_attribute(attribute_name), args)
235
- if widget_custom_attribute
236
- widget_custom_attribute[:setter][:invoker].call(@swt_widget, args)
237
- elsif @swt_widget.respond_to?(attribute_setter(attribute_name))
238
- @swt_widget.send(attribute_setter(attribute_name), *args) unless @swt_widget.send(attribute_getter(attribute_name)) == args.first
239
- elsif @swt_widget.respond_to?(ruby_attribute_setter(attribute_name))
240
- @swt_widget.send(ruby_attribute_setter(attribute_name), args)
241
- else
242
- send(ruby_attribute_setter(attribute_name), args)
243
- end
244
- end
245
-
246
- def get_attribute(attribute_name)
247
- widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
248
- if widget_custom_attribute
249
- if widget_custom_attribute[:getter][:invoker]
250
- widget_custom_attribute[:getter][:invoker].call(@swt_widget, [])
251
- else
252
- @swt_widget.send(widget_custom_attribute[:getter][:name])
253
- end
254
- elsif @swt_widget.respond_to?(attribute_getter(attribute_name))
255
- @swt_widget.send(attribute_getter(attribute_name))
256
- elsif @swt_widget.respond_to?(ruby_attribute_getter(attribute_name))
257
- @swt_widget.send(ruby_attribute_getter(attribute_name))
258
- elsif @swt_widget.respond_to?(attribute_name)
259
- @swt_widget.send(attribute_name)
260
- elsif respond_to?(ruby_attribute_getter(attribute_name))
261
- send(ruby_attribute_getter(attribute_name))
262
- else
263
- send(attribute_name)
264
- end
265
- end
266
-
267
- def pack_same_size
268
- bounds = @swt_widget.getBounds
269
- listener = on_control_resized {
270
- @swt_widget.setSize(bounds.width, bounds.height)
271
- @swt_widget.setLocation(bounds.x, bounds.y)
272
- }
273
- if @swt_widget.is_a?(Composite)
274
- @swt_widget.layout(true, true)
275
- else
276
- @swt_widget.pack(true)
277
- end
278
- @swt_widget.removeControlListener(listener.swt_listener)
279
- end
280
-
281
- def widget_property_listener_installers
282
- @swt_widget_property_listener_installers ||= {
283
- Java::OrgEclipseSwtWidgets::Control => {
284
- :focus => lambda do |observer|
285
- on_focus_gained { |focus_event|
286
- observer.call(true)
287
- }
288
- on_focus_lost { |focus_event|
289
- observer.call(false)
290
- }
291
- end,
292
- :selection => lambda do |observer|
293
- on_widget_selected { |selection_event|
294
- observer.call(@swt_widget.getSelection)
295
- } if can_handle_observation_request?(:on_widget_selected)
296
- end,
297
- :text => lambda do |observer|
298
- on_modify_text { |modify_event|
299
- observer.call(@swt_widget.getText)
300
- } if can_handle_observation_request?(:on_modify_text)
301
- end,
302
- },
303
- Java::OrgEclipseSwtWidgets::Combo => {
304
- :text => lambda do |observer|
305
- on_modify_text { |modify_event|
306
- observer.call(@swt_widget.getText)
307
- }
308
- end,
309
- },
310
- Java::OrgEclipseSwtWidgets::Table => {
311
- :selection => lambda do |observer|
312
- on_widget_selected { |selection_event|
313
- if has_style?(:multi)
314
- observer.call(@swt_widget.getSelection.map(&:get_data))
315
- else
316
- observer.call(@swt_widget.getSelection.first&.get_data)
317
- end
318
- }
319
- end,
320
- },
321
- Java::OrgEclipseSwtWidgets::Text => {
322
- :text => lambda do |observer|
323
- on_modify_text { |modify_event|
324
- observer.call(@swt_widget.getText)
325
- }
326
- end,
327
- :caret_position => lambda do |observer|
328
- on_swt_keydown { |event|
329
- observer.call(@swt_widget.getCaretPosition)
330
- }
331
- on_swt_keyup { |event|
332
- observer.call(@swt_widget.getCaretPosition)
333
- }
334
- on_swt_mousedown { |event|
335
- observer.call(@swt_widget.getCaretPosition)
336
- }
337
- on_swt_mouseup { |event|
338
- observer.call(@swt_widget.getCaretPosition)
339
- }
340
- end,
341
- :selection => lambda do |observer|
342
- on_swt_keydown { |event|
343
- observer.call(@swt_widget.getSelection)
344
- }
345
- on_swt_keyup { |event|
346
- observer.call(@swt_widget.getSelection)
347
- }
348
- on_swt_mousedown { |event|
349
- observer.call(@swt_widget.getSelection)
350
- }
351
- on_swt_mouseup { |event|
352
- observer.call(@swt_widget.getSelection)
353
- }
354
- end,
355
- :selection_count => lambda do |observer|
356
- on_swt_keydown { |event|
357
- observer.call(@swt_widget.getSelectionCount)
358
- }
359
- on_swt_keyup { |event|
360
- observer.call(@swt_widget.getSelectionCount)
361
- }
362
- on_swt_mousedown { |event|
363
- observer.call(@swt_widget.getSelectionCount)
364
- }
365
- on_swt_mouseup { |event|
366
- observer.call(@swt_widget.getSelectionCount)
367
- }
368
- end,
369
- :top_index => lambda do |observer|
370
- @last_top_index = @swt_widget.getTopIndex
371
- on_paint_control { |event|
372
- if @swt_widget.getTopIndex != @last_top_index
373
- @last_top_index = @swt_widget.getTopIndex
374
- observer.call(@last_top_index)
375
- end
376
- }
377
- end,
378
- },
379
- Java::OrgEclipseSwtCustom::StyledText => {
380
- :text => lambda do |observer|
381
- on_modify_text { |modify_event|
382
- observer.call(@swt_widget.getText)
383
- }
384
- end,
385
- :caret_position => lambda do |observer|
386
- on_caret_moved { |event|
387
- observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
388
- }
389
- on_swt_keyup { |event|
390
- observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
391
- }
392
- on_swt_mouseup { |event|
393
- observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
394
- }
395
- end,
396
- :caret_offset => lambda do |observer|
397
- on_caret_moved { |event|
398
- observer.call(@swt_widget.getCaretOffset) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
399
- }
400
- end,
401
- :selection => lambda do |observer|
402
- on_widget_selected { |event|
403
- observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
404
- }
405
- on_swt_keyup { |event|
406
- observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
407
- }
408
- on_swt_mouseup { |event|
409
- observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
410
- }
411
- end,
412
- :selection_count => lambda do |observer|
413
- on_widget_selected { |event|
414
- observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
415
- }
416
- on_swt_keyup { |event|
417
- observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
418
- }
419
- on_swt_mouseup { |event|
420
- observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
421
- }
422
- end,
423
- :selection_range => lambda do |observer|
424
- on_widget_selected { |event|
425
- observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
426
- }
427
- on_swt_keyup { |event|
428
- observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
429
- }
430
- on_swt_mouseup { |event|
431
- observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
432
- }
433
- end,
434
- :top_index => lambda do |observer|
435
- @last_top_index = @swt_widget.getTopIndex
436
- on_paint_control { |event|
437
- if @swt_widget.getTopIndex != @last_top_index
438
- @last_top_index = @swt_widget.getTopIndex
439
- observer.call(@last_top_index)
440
- end
441
- }
442
- end,
443
- :top_pixel => lambda do |observer|
444
- @last_top_pixel = @swt_widget.getTopPixel
445
- on_paint_control { |event|
446
- if @swt_widget.getTopPixel != @last_top_pixel
447
- @last_top_pixel = @swt_widget.getTopPixel
448
- observer.call(@last_top_pixel)
449
- end
450
- }
451
- end,
452
- },
453
- Java::OrgEclipseSwtWidgets::Button => {
454
- :selection => lambda do |observer|
455
- on_widget_selected { |selection_event|
456
- observer.call(@swt_widget.getSelection)
457
- }
458
- end
459
- },
460
- Java::OrgEclipseSwtWidgets::MenuItem => {
461
- :selection => lambda do |observer|
462
- on_widget_selected { |selection_event|
463
- observer.call(@swt_widget.getSelection)
464
- }
465
- end
466
- },
467
- Java::OrgEclipseSwtWidgets::Spinner => {
468
- :selection => lambda do |observer|
469
- on_widget_selected { |selection_event|
470
- observer.call(@swt_widget.getSelection)
471
- }
472
- end
473
- },
474
- Java::OrgEclipseSwtWidgets::DateTime =>
475
- [:year, :month, :day, :hours, :minutes, :seconds, :date_time, :date, :time].reduce({}) do |hash, attribute|
476
- hash.merge(
477
- attribute => lambda do |observer|
478
- on_widget_selected { |selection_event|
479
- observer.call(get_attribute(attribute))
480
- }
481
- end
482
- )
483
- end,
484
- }
485
- end
486
-
487
- def self.widget_exists?(underscored_widget_name)
488
- !!swt_widget_class_for(underscored_widget_name)
489
- end
490
-
491
- # Manual entries of SWT widget classes that conflict with Ruby classes
492
- def self.swt_widget_class_manual_entries
493
- {
494
- 'date_time' => Java::OrgEclipseSwtWidgets::DateTime
495
- }
496
- end
497
-
498
- # This supports widgets in and out of basic SWT
499
- def self.swt_widget_class_for(underscored_widget_name)
500
- # TODO clear memoization for a keyword if a custom widget was defined with that keyword
501
- unless flyweight_swt_widget_classes.keys.include?(underscored_widget_name)
502
- begin
503
- underscored_widget_name = KEYWORD_ALIASES[underscored_widget_name] if KEYWORD_ALIASES[underscored_widget_name]
504
- swt_widget_name = underscored_widget_name.camelcase(:upper)
505
- swt_widget_class = eval(swt_widget_name)
506
- # TODO fix issue with not detecting DateTime because it's conflicting with the Ruby DateTime
507
- unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
508
- swt_widget_class = swt_widget_class_manual_entries[underscored_widget_name]
509
- if swt_widget_class.nil?
510
- Glimmer::Config.logger.debug {"Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget"}
511
- return nil
512
- end
513
- end
514
- flyweight_swt_widget_classes[underscored_widget_name] = swt_widget_class
515
- rescue SyntaxError, NameError => e
516
- Glimmer::Config.logger.debug {e.full_message}
517
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
518
- nil
519
- rescue => e
520
- Glimmer::Config.logger.debug {e.full_message}
521
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
522
- nil
523
- end
524
- end
525
- flyweight_swt_widget_classes[underscored_widget_name]
526
- end
527
-
528
- # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
529
- def self.flyweight_swt_widget_classes
530
- @flyweight_swt_widget_classes ||= {}
531
- end
532
-
533
- def async_exec(&block)
534
- DisplayProxy.instance.async_exec(&block)
535
- end
536
-
537
- def sync_exec(&block)
538
- DisplayProxy.instance.sync_exec(&block)
539
- end
540
-
541
- def has_style?(style)
542
- comparison = interpret_style(style)
543
- (@swt_widget.style & comparison) == comparison
544
- end
545
-
546
- def dispose
547
- @swt_widget.dispose
548
- end
549
-
550
- def disposed?
551
- @swt_widget.isDisposed
552
- end
553
-
554
- # TODO Consider renaming these methods as they are mainly used for data-binding
555
-
556
- def can_add_observer?(property_name)
557
- @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact.map(&:keys).flatten.map(&:to_s).include?(property_name.to_s)
558
- end
559
-
560
- # Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
561
- def add_observer(observer, property_name)
562
- property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
563
- widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
564
- widget_listener_installers.to_a.first&.call(observer)
565
- end
566
-
567
- def remove_observer(observer, property_name)
568
- # TODO consider implementing if remove_observer is needed (consumers can remove listener via SWT API)
569
- end
570
-
571
- def ensure_drag_source_proxy(style=[])
572
- @drag_source_proxy ||= self.class.new('drag_source', self, style).tap do |proxy|
573
- proxy.set_attribute(:transfer, :text)
574
- end
575
- end
576
-
577
- def ensure_drop_target_proxy(style=[])
578
- @drop_target_proxy ||= self.class.new('drop_target', self, style).tap do |proxy|
579
- proxy.set_attribute(:transfer, :text)
580
- proxy.on_drag_enter { |event|
581
- event.detail = DNDProxy[:drop_copy]
582
- }
583
- end
584
- end
585
-
586
- # TODO eliminate duplication in the following methods perhaps by relying on exceptions
587
-
588
- def can_handle_observation_request?(observation_request)
589
- observation_request = observation_request.to_s
590
- if observation_request.start_with?('on_swt_')
591
- constant_name = observation_request.sub(/^on_swt_/, '')
592
- SWTProxy.has_constant?(constant_name)
593
- elsif observation_request.start_with?('on_')
594
- event = observation_request.sub(/^on_/, '')
595
- can_add_listener?(event) || can_handle_drag_observation_request?(observation_request) || can_handle_drop_observation_request?(observation_request)
596
- end
597
- end
598
-
599
- def can_handle_drag_observation_request?(observation_request)
600
- return false unless swt_widget.is_a?(Control)
601
- potential_drag_source = @drag_source_proxy.nil?
602
- ensure_drag_source_proxy
603
- @drag_source_proxy.can_handle_observation_request?(observation_request).tap do |result|
604
- if potential_drag_source && !result
605
- @drag_source_proxy.swt_widget.dispose
606
- @drag_source_proxy = nil
607
- end
608
- end
609
- rescue => e
610
- Glimmer::Config.logger.debug {e.full_message}
611
- false
612
- end
613
-
614
- def can_handle_drop_observation_request?(observation_request)
615
- return false unless swt_widget.is_a?(Control)
616
- potential_drop_target = @drop_target_proxy.nil?
617
- ensure_drop_target_proxy
618
- @drop_target_proxy.can_handle_observation_request?(observation_request).tap do |result|
619
- if potential_drop_target && !result
620
- @drop_target_proxy.swt_widget.dispose
621
- @drop_target_proxy = nil
622
- end
623
- end
624
- end
625
-
626
- def handle_observation_request(observation_request, &block)
627
- observation_request = observation_request.to_s
628
- if observation_request.start_with?('on_swt_')
629
- constant_name = observation_request.sub(/^on_swt_/, '')
630
- add_swt_event_listener(constant_name, &block)
631
- elsif observation_request.start_with?('on_')
632
- event = observation_request.sub(/^on_/, '')
633
- if can_add_listener?(event)
634
- event = observation_request.sub(/^on_/, '')
635
- add_listener(event, &block)
636
- elsif can_handle_drag_observation_request?(observation_request)
637
- @drag_source_proxy&.handle_observation_request(observation_request, &block)
638
- elsif can_handle_drop_observation_request?(observation_request)
639
- @drop_target_proxy&.handle_observation_request(observation_request, &block)
640
- end
641
- end
642
- end
643
-
644
- def content(&block)
645
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
646
- end
647
-
648
- def method_missing(method, *args, &block)
649
- if can_handle_observation_request?(method)
650
- handle_observation_request(method, &block)
651
- elsif has_attribute_setter?(method, *args)
652
- set_attribute(method, *args)
653
- elsif has_attribute_getter?(method, *args)
654
- get_attribute(method, *args)
655
- else
656
- swt_widget.send(method, *args, &block)
657
- end
658
- rescue => e
659
- Glimmer::Config.logger.debug { "Neither WidgetProxy nor #{swt_widget.class.name} can handle the method ##{method}" }
660
- Glimmer::Config.logger.debug { e.full_message }
661
- super
662
- # TODO consider get_attribute too
663
- end
664
-
665
- def respond_to?(method, *args, &block)
666
- super ||
667
- can_handle_observation_request?(method) ||
668
- swt_widget.respond_to?(method, *args, &block)
669
- end
670
-
671
- private
672
-
673
- def style(underscored_widget_name, styles)
674
- styles = [styles].flatten.compact
675
- styles = default_style(underscored_widget_name) if styles.empty?
676
- interpret_style(*styles)
677
- end
678
-
679
- def interpret_style(*styles)
680
- SWTProxy[*styles] rescue DNDProxy[*styles]
681
- end
682
-
683
- def default_style(underscored_widget_name)
684
- DEFAULT_STYLES[underscored_widget_name] || [:none]
685
- end
686
-
687
- # TODO refactor following methods to eliminate duplication
688
- # perhaps consider relying on raising an exception to avoid checking first
689
- # unless that gives obscure SWT errors
690
- # Otherwise, consider caching results from can_add_lsitener and using them in
691
- # add_listener knowing it will be called for sure afterwards
692
-
693
- def can_add_listener?(underscored_listener_name)
694
- !self.class.find_listener(@swt_widget.getClass, underscored_listener_name).empty?
695
- end
696
-
697
- def add_listener(underscored_listener_name, &block)
698
- widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
699
- widget_listener_proxy = nil
700
- safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
701
- listener = listener_class.new(listener_method => safe_block)
702
- @swt_widget.send(widget_add_listener_method, listener)
703
- 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)
704
- end
705
-
706
- # Looks through SWT class add***Listener methods till it finds one for which
707
- # the argument is a listener class that has an event method matching
708
- # underscored_listener_name
709
- def self.find_listener(swt_widget_class, underscored_listener_name)
710
- @listeners ||= {}
711
- listener_key = [swt_widget_class.name, underscored_listener_name]
712
- unless @listeners.has_key?(listener_key)
713
- listener_method_name = underscored_listener_name.camelcase(:lower)
714
- swt_widget_class.getMethods.each do |widget_add_listener_method|
715
- if widget_add_listener_method.getName.match(/add.*Listener/)
716
- widget_add_listener_method.getParameterTypes.each do |listener_type|
717
- listener_type.getMethods.each do |listener_method|
718
- if (listener_method.getName == listener_method_name)
719
- @listeners[listener_key] = [widget_add_listener_method.getName, listener_class(listener_type), listener_method.getName]
720
- return @listeners[listener_key]
721
- end
722
- end
723
- end
724
- end
725
- end
726
- @listeners[listener_key] = []
727
- end
728
- @listeners[listener_key]
729
- end
730
-
731
- # Returns a Ruby class that implements listener type Java interface with ability to easily
732
- # install a block that gets called upon calling a listener event method
733
- def self.listener_class(listener_type)
734
- @listener_classes ||= {}
735
- listener_class_key = listener_type.name
736
- unless @listener_classes.has_key?(listener_class_key)
737
- @listener_classes[listener_class_key] = Class.new(Object).tap do |listener_class|
738
- listener_class.send :include, (eval listener_type.name.sub("interface", ""))
739
- listener_class.define_method('initialize') do |event_method_block_mapping|
740
- @event_method_block_mapping = event_method_block_mapping
741
- end
742
- listener_type.getMethods.each do |event_method|
743
- listener_class.define_method(event_method.getName) do |*args|
744
- @event_method_block_mapping[event_method.getName]&.call(*args)
745
- end
746
- end
747
- end
748
- end
749
- @listener_classes[listener_class_key]
750
- end
751
-
752
- def add_swt_event_listener(swt_constant, &block)
753
- event_type = SWTProxy[swt_constant]
754
- widget_listener_proxy = nil
755
- safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
756
- @swt_widget.addListener(event_type, &safe_block)
757
- 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)
758
- end
759
-
760
- def widget_custom_attribute_mapping
761
- # TODO scope per widget class type just like other mappings
762
- @swt_widget_custom_attribute_mapping ||= {
763
- 'focus' => {
764
- getter: {name: 'isFocusControl'},
765
- setter: {name: 'setFocus', invoker: lambda { |widget, args| @swt_widget.setFocus if args.first }},
766
- },
767
- 'caret_position' => {
768
- getter: {name: 'getCaretPosition', invoker: lambda { |widget, args| @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x}},
769
- setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(args.first, args.first + @swt_widget.getSelectionCount) if args.first }},
770
- },
771
- 'selection_count' => {
772
- getter: {name: 'getSelectionCount'},
773
- setter: {name: 'setSelection', invoker: lambda { |widget, args|
774
- if args.first
775
- caret_position = @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x
776
- # TODO handle negative length
777
- @swt_widget.setSelection(caret_position, caret_position + args.first)
778
- end
779
- }},
780
- },
781
- }
782
- end
783
-
784
- def drag_source_style=(style)
785
- ensure_drag_source_proxy(style)
786
- end
787
-
788
- def drop_target_style=(style)
789
- ensure_drop_target_proxy(style)
790
- end
791
-
792
- def drag_source_transfer=(args)
793
- args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
794
- ensure_drag_source_proxy
795
- @drag_source_proxy.set_attribute(:transfer, args)
796
- end
797
-
798
- def drop_target_transfer=(args)
799
- args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
800
- ensure_drop_target_proxy
801
- @drop_target_proxy.set_attribute(:transfer, args)
802
- end
803
-
804
- def drag_source_effect=(args)
805
- args = args.first if args.is_a?(Array)
806
- ensure_drag_source_proxy
807
- @drag_source_proxy.set_attribute(:drag_source_effect, args)
808
- end
809
-
810
- def drop_target_effect=(args)
811
- args = args.first if args.is_a?(Array)
812
- ensure_drop_target_proxy
813
- @drop_target_proxy.set_attribute(:drop_target_effect, args)
814
- end
815
-
816
- def apply_property_type_converters(attribute_name, args)
817
- value = args
818
- converter = property_type_converters[attribute_name.to_sym]
819
- args[0..-1] = [converter.call(*value)] if converter
820
- if args.count == 1 && args.first.is_a?(ColorProxy)
821
- g_color = args.first
822
- args[0] = g_color.swt_color
823
- end
824
- end
825
-
826
- def property_type_converters
827
- color_converter = lambda do |value|
828
- if value.is_a?(Symbol) || value.is_a?(String)
829
- ColorProxy.new(value).swt_color
830
- else
831
- value
832
- end
833
- end
834
- # TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
835
- @property_type_converters ||= {
836
- accelerator: lambda { |*value|
837
- SWTProxy[*value]
838
- },
839
- alignment: lambda { |*value|
840
- SWTProxy[*value]
841
- },
842
- background: color_converter,
843
- background_image: lambda do |*value|
844
- image_proxy = ImageProxy.create(*value)
845
-
846
- if image_proxy&.file_path&.end_with?('.gif')
847
- image = image_proxy.swt_image
848
- width = image.get_bounds.width.to_i
849
- height = image.get_bounds.height.to_i
850
- image_number = 0
851
- loader = ImageLoader.new
852
- loader.load(image_proxy.input_stream)
853
- image.dispose
854
- image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display,loader.data[0].scaledTo(width, height))
855
- gc = org.eclipse.swt.graphics.GC.new(image)
856
- on_paint_control { |event|
857
- image_number = (image_number == loader.data.length - 1) ? 0 : image_number + 1
858
- next_frame_data = loader.data[image_number]
859
- image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display, next_frame_data.scaledTo(width, height))
860
- event.gc.drawImage(image, 0, 0, width, height, 0, 0, width, height)
861
- image.dispose
862
- }
863
- Thread.new {
864
- last_image_number = -1
865
- while last_image_number != image_number
866
- last_image_number = image_number
867
- sync_exec {
868
- redraw
869
- }
870
- delayTime = loader.data[image_number].delayTime.to_f / 100.0
871
- sleep(delayTime)
872
- end
873
- };
874
- image_proxy = nil
875
- else
876
- on_swt_Resize do |resize_event|
877
- image_proxy.scale_to(@swt_widget.getSize.x, @swt_widget.getSize.y)
878
- @swt_widget.setBackgroundImage(image_proxy.swt_image)
879
- end
880
- end
881
-
882
- image_proxy&.swt_image
883
- end,
884
- cursor: lambda do |value|
885
- cursor_proxy = nil
886
- if value.is_a?(CursorProxy)
887
- cursor_proxy = value
888
- elsif value.is_a?(Symbol) || value.is_a?(String) || value.is_a?(Integer)
889
- cursor_proxy = CursorProxy.new(value)
890
- end
891
- cursor_proxy ? cursor_proxy.swt_cursor : value
892
- end,
893
- :enabled => lambda do |value|
894
- !!value
895
- end,
896
- foreground: color_converter,
897
- link_foreground: color_converter,
898
- font: lambda do |value|
899
- if value.is_a?(Hash)
900
- font_properties = value
901
- FontProxy.new(self, font_properties).swt_font
902
- else
903
- value
904
- end
905
- end,
906
- image: lambda do |*value|
907
- ImageProxy.create(*value).swt_image
908
- end,
909
- images: lambda do |array|
910
- array.to_a.map do |value|
911
- ImageProxy.create(value).swt_image
912
- end.to_java(Image)
913
- end,
914
- items: lambda do |value|
915
- value.to_java :string
916
- end,
917
- text: lambda do |value|
918
- value.to_s
919
- end,
920
- transfer: lambda do |value|
921
- value = value.first if value.is_a?(Array) && value.size == 1 && value.first.is_a?(Array)
922
- transfer_object_extrapolator = lambda do |transfer_name|
923
- transfer_type = "#{transfer_name.to_s.camelcase(:upper)}Transfer".to_sym
924
- transfer_type_alternative = "#{transfer_name.to_s.upcase}Transfer".to_sym
925
- transfer_class = org.eclipse.swt.dnd.const_get(transfer_type) rescue org.eclipse.swt.dnd.const_get(transfer_type_alternative)
926
- transfer_class.getInstance
927
- end
928
- result = value
929
- if value.is_a?(Symbol) || value.is_a?(String)
930
- result = [transfer_object_extrapolator.call(value)]
931
- elsif value.is_a?(Array)
932
- result = value.map do |transfer_name|
933
- transfer_object_extrapolator.call(transfer_name)
934
- end
935
- end
936
- result = result.to_java(Transfer) unless result.is_a?(ArrayJavaProxy)
937
- result
938
- end,
939
- visible: lambda do |value|
940
- !!value
941
- end,
942
- weights: lambda do |value|
943
- value.to_java(:int)
944
- end,
945
- }
946
- end
947
- end
948
- end
949
- end
1
+ # Copyright (c) 2007-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/swt/widget_listener_proxy'
23
+ require 'glimmer/swt/color_proxy'
24
+ require 'glimmer/swt/font_proxy'
25
+ require 'glimmer/swt/swt_proxy'
26
+ require 'glimmer/swt/display_proxy'
27
+ require 'glimmer/swt/dnd_proxy'
28
+ require 'glimmer/swt/image_proxy'
29
+ require 'glimmer/swt/properties'
30
+ require 'glimmer/swt/custom/drawable'
31
+
32
+ # TODO refactor to make file smaller and extract sub-widget-proxies out of this
33
+
34
+ module Glimmer
35
+ module SWT
36
+ # Proxy for SWT Widget objects
37
+ #
38
+ # Sets default SWT styles to widgets upon inititalizing as
39
+ # per DEFAULT_STYLES
40
+ #
41
+ # Also, auto-initializes widgets as per initializer blocks
42
+ # in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
43
+ #
44
+ # Follows the Proxy Design Pattern
45
+ class WidgetProxy
46
+ include Packages
47
+ include Properties
48
+ include Custom::Drawable
49
+
50
+ DEFAULT_STYLES = {
51
+ 'arrow' => [:arrow],
52
+ 'button' => [:push],
53
+ 'checkbox' => [:check],
54
+ 'check' => [:check],
55
+ 'drag_source' => [:drop_copy],
56
+ 'drop_target' => [:drop_copy],
57
+ 'expand_bar' => [:v_scroll],
58
+ 'list' => [:border, :v_scroll],
59
+ 'menu_item' => [:push],
60
+ 'radio' => [:radio],
61
+ 'scrolled_composite' => [:border, :h_scroll, :v_scroll],
62
+ 'spinner' => [:border],
63
+ 'styled_text' => [:border, :multi, :v_scroll, :h_scroll],
64
+ 'table' => [:virtual, :border, :full_selection],
65
+ 'text' => [:border],
66
+ 'toggle' => [:toggle],
67
+ 'tool_bar' => [:push],
68
+ 'tool_item' => [:push],
69
+ 'tree' => [:virtual, :border, :h_scroll, :v_scroll],
70
+ 'date_drop_down' => [:date, :drop_down],
71
+ 'time' => [:time],
72
+ 'calendar' => [:calendar],
73
+ }
74
+
75
+ DEFAULT_INITIALIZERS = {
76
+ composite: lambda do |composite|
77
+ composite.layout = GridLayout.new if composite.get_layout.nil?
78
+ end,
79
+ canvas: lambda do |canvas|
80
+ canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
81
+ end,
82
+ scrolled_composite: lambda do |scrolled_composite|
83
+ scrolled_composite.expand_horizontal = true
84
+ scrolled_composite.expand_vertical = true
85
+ end,
86
+ table: lambda do |table|
87
+ table.setHeaderVisible(true)
88
+ table.setLinesVisible(true)
89
+ end,
90
+ table_column: lambda do |table_column|
91
+ table_column.setWidth(80)
92
+ end,
93
+ group: lambda do |group|
94
+ group.layout = GridLayout.new if group.get_layout.nil?
95
+ end,
96
+ }
97
+
98
+ KEYWORD_ALIASES = {
99
+ 'arrow' => 'button',
100
+ 'checkbox' => 'button',
101
+ 'check' => 'button',
102
+ 'radio' => 'button',
103
+ 'toggle' => 'button',
104
+ 'date' => 'date_time',
105
+ 'date_drop_down' => 'date_time',
106
+ 'time' => 'date_time',
107
+ 'calendar' => 'date_time',
108
+ }
109
+
110
+ class << self
111
+ # Instantiates the right WidgetProxy subclass for passed in keyword
112
+ # Args are: keyword, parent, swt_widget_args (including styles)
113
+ def create(*init_args, swt_widget: nil)
114
+ return swt_widget.get_data('proxy') if swt_widget&.get_data('proxy')
115
+ keyword, parent, args = init_args
116
+ selected_widget_proxy_class = widget_proxy_class(keyword || underscored_widget_name(swt_widget))
117
+ if init_args.empty?
118
+ selected_widget_proxy_class.new(swt_widget: swt_widget)
119
+ else
120
+ selected_widget_proxy_class.new(*init_args)
121
+ end
122
+ end
123
+
124
+ def widget_proxy_class(keyword)
125
+ begin
126
+ keyword = KEYWORD_ALIASES[keyword] if KEYWORD_ALIASES[keyword]
127
+ class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
128
+ Glimmer::SWT.const_get(class_name)
129
+ rescue
130
+ Glimmer::SWT::WidgetProxy
131
+ end
132
+ end
133
+
134
+ def underscored_widget_name(swt_widget)
135
+ swt_widget.class.name.split(/::|\./).last.underscore
136
+ end
137
+ end
138
+
139
+ attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer
140
+
141
+ # Initializes a new SWT Widget
142
+ #
143
+ # It is preferred to use `::create` method instead since it instantiates the
144
+ # right subclass per widget keyword
145
+ #
146
+ # keyword, parent, swt_widget_args (including styles)
147
+ #
148
+ # Styles is a comma separate list of symbols representing SWT styles in lower case
149
+ def initialize(*init_args, swt_widget: nil)
150
+ if swt_widget.nil?
151
+ underscored_widget_name, parent, args = init_args
152
+ @parent_proxy = parent
153
+ styles, extra_options = extract_args(underscored_widget_name, args)
154
+ swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
155
+ @swt_widget = swt_widget_class.new(@parent_proxy.swt_widget, style(underscored_widget_name, styles), *extra_options)
156
+ else
157
+ @swt_widget = swt_widget
158
+ underscored_widget_name = self.class.underscored_widget_name(@swt_widget)
159
+ parent_proxy_class = self.class.widget_proxy_class(self.class.underscored_widget_name(@swt_widget.parent))
160
+ parent = swt_widget.parent
161
+ @parent_proxy = parent&.get_data('proxy') || parent_proxy_class.new(swt_widget: parent)
162
+ end
163
+ if @swt_widget&.get_data('proxy').nil?
164
+ @swt_widget.set_data('proxy', self)
165
+ DEFAULT_INITIALIZERS[underscored_widget_name.to_s.to_sym]&.call(@swt_widget)
166
+ @parent_proxy.post_initialize_child(self)
167
+ end
168
+ @keyword = underscored_widget_name.to_s
169
+ if respond_to?(:on_widget_disposed)
170
+ on_widget_disposed {
171
+ clear_shapes
172
+ }
173
+ end
174
+ end
175
+
176
+ # Subclasses may override to perform post initialization work on an added child
177
+ def post_initialize_child(child)
178
+ # No Op by default
179
+ end
180
+
181
+ # Subclasses may override to perform post add_content work
182
+ def post_add_content
183
+ # No Op by default
184
+ end
185
+
186
+ def extract_args(underscored_widget_name, args)
187
+ @arg_extractor_mapping ||= {
188
+ 'menu_item' => lambda do |args|
189
+ index = args.delete(args.last) if args.last.is_a?(Numeric)
190
+ extra_options = [index].compact
191
+ styles = args
192
+ [styles, extra_options]
193
+ end,
194
+ }
195
+ if @arg_extractor_mapping[underscored_widget_name]
196
+ @arg_extractor_mapping[underscored_widget_name].call(args)
197
+ else
198
+ extra_options = []
199
+ style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
200
+ if style_args.any?
201
+ style_arg_start_index = args.index(style_args.first)
202
+ style_arg_last_index = args.index(style_args.last)
203
+ extra_options = args[style_arg_last_index+1..-1]
204
+ args = args[style_arg_start_index..style_arg_last_index]
205
+ elsif args.first.is_a?(Integer)
206
+ extra_options = args[1..-1]
207
+ args = args[0..0]
208
+ end
209
+ [args, extra_options]
210
+ end
211
+ end
212
+
213
+ def has_attribute_getter?(attribute_getter_name, *args)
214
+ attribute_getter_name = attribute_getter_name.to_s.underscore
215
+ return false unless !attribute_getter_name.end_with?('=') && !attribute_getter_name.start_with?('set_')
216
+ args.empty? && swt_widget.respond_to?(attribute_getter_name)
217
+ end
218
+
219
+ def has_attribute_setter?(attribute_setter_name, *args)
220
+ attribute_setter_name = attribute_setter_name.to_s
221
+ underscored_attribute_setter_name = attribute_setter_name.underscore
222
+ return false unless attribute_setter_name.end_with?('=') || (attribute_setter_name.start_with?('set_') && !args.empty?)
223
+ attribute_name = underscored_attribute_setter_name.sub(/^set_/, '').sub(/=$/, '')
224
+ has_attribute?(attribute_name, *args)
225
+ end
226
+
227
+ def has_attribute?(attribute_name, *args)
228
+ # TODO test that attribute getter responds too
229
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
230
+ if widget_custom_attribute
231
+ @swt_widget.respond_to?(widget_custom_attribute[:setter][:name])
232
+ else
233
+ @swt_widget.respond_to?(attribute_setter(attribute_name), args) || respond_to?(ruby_attribute_setter(attribute_name), args)
234
+ end
235
+ end
236
+
237
+ def set_attribute(attribute_name, *args)
238
+ # TODO Think about widget subclasses overriding set_attribute to add more attributes vs adding as Ruby attributes directly
239
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
240
+ apply_property_type_converters(normalized_attribute(attribute_name), args)
241
+ if widget_custom_attribute
242
+ widget_custom_attribute[:setter][:invoker].call(@swt_widget, args)
243
+ elsif @swt_widget.respond_to?(attribute_setter(attribute_name))
244
+ @swt_widget.send(attribute_setter(attribute_name), *args) unless @swt_widget.send(attribute_getter(attribute_name)) == args.first
245
+ elsif @swt_widget.respond_to?(ruby_attribute_setter(attribute_name))
246
+ @swt_widget.send(ruby_attribute_setter(attribute_name), args)
247
+ else
248
+ send(ruby_attribute_setter(attribute_name), args)
249
+ end
250
+ end
251
+
252
+ def get_attribute(attribute_name)
253
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
254
+ if widget_custom_attribute
255
+ if widget_custom_attribute[:getter][:invoker]
256
+ widget_custom_attribute[:getter][:invoker].call(@swt_widget, [])
257
+ else
258
+ @swt_widget.send(widget_custom_attribute[:getter][:name])
259
+ end
260
+ elsif @swt_widget.respond_to?(attribute_getter(attribute_name))
261
+ @swt_widget.send(attribute_getter(attribute_name))
262
+ elsif @swt_widget.respond_to?(ruby_attribute_getter(attribute_name))
263
+ @swt_widget.send(ruby_attribute_getter(attribute_name))
264
+ elsif @swt_widget.respond_to?(attribute_name)
265
+ @swt_widget.send(attribute_name)
266
+ elsif respond_to?(ruby_attribute_getter(attribute_name))
267
+ send(ruby_attribute_getter(attribute_name))
268
+ else
269
+ send(attribute_name)
270
+ end
271
+ end
272
+
273
+ def pack_same_size
274
+ bounds = @swt_widget.getBounds
275
+ listener = on_control_resized {
276
+ @swt_widget.setSize(bounds.width, bounds.height)
277
+ @swt_widget.setLocation(bounds.x, bounds.y)
278
+ }
279
+ if @swt_widget.is_a?(Composite)
280
+ @swt_widget.layout(true, true)
281
+ else
282
+ @swt_widget.pack(true)
283
+ end
284
+ @swt_widget.removeControlListener(listener.swt_listener)
285
+ end
286
+
287
+ def widget_property_listener_installers
288
+ @swt_widget_property_listener_installers ||= {
289
+ Java::OrgEclipseSwtWidgets::Control => {
290
+ :focus => lambda do |observer|
291
+ on_focus_gained { |focus_event|
292
+ observer.call(true)
293
+ }
294
+ on_focus_lost { |focus_event|
295
+ observer.call(false)
296
+ }
297
+ end,
298
+ :selection => lambda do |observer|
299
+ on_widget_selected { |selection_event|
300
+ observer.call(@swt_widget.getSelection)
301
+ } if can_handle_observation_request?(:on_widget_selected)
302
+ end,
303
+ :text => lambda do |observer|
304
+ on_modify_text { |modify_event|
305
+ observer.call(@swt_widget.getText)
306
+ } if can_handle_observation_request?(:on_modify_text)
307
+ end,
308
+ },
309
+ Java::OrgEclipseSwtWidgets::Combo => {
310
+ :text => lambda do |observer|
311
+ on_modify_text { |modify_event|
312
+ observer.call(@swt_widget.getText)
313
+ }
314
+ end,
315
+ },
316
+ Java::OrgEclipseSwtWidgets::Table => {
317
+ :selection => lambda do |observer|
318
+ on_widget_selected { |selection_event|
319
+ if has_style?(:multi)
320
+ observer.call(@swt_widget.getSelection.map(&:get_data))
321
+ else
322
+ observer.call(@swt_widget.getSelection.first&.get_data)
323
+ end
324
+ }
325
+ end,
326
+ },
327
+ Java::OrgEclipseSwtWidgets::Text => {
328
+ :text => lambda do |observer|
329
+ on_modify_text { |modify_event|
330
+ observer.call(@swt_widget.getText)
331
+ }
332
+ end,
333
+ :caret_position => lambda do |observer|
334
+ on_swt_keydown { |event|
335
+ observer.call(@swt_widget.getCaretPosition)
336
+ }
337
+ on_swt_keyup { |event|
338
+ observer.call(@swt_widget.getCaretPosition)
339
+ }
340
+ on_swt_mousedown { |event|
341
+ observer.call(@swt_widget.getCaretPosition)
342
+ }
343
+ on_swt_mouseup { |event|
344
+ observer.call(@swt_widget.getCaretPosition)
345
+ }
346
+ end,
347
+ :selection => lambda do |observer|
348
+ on_swt_keydown { |event|
349
+ observer.call(@swt_widget.getSelection)
350
+ }
351
+ on_swt_keyup { |event|
352
+ observer.call(@swt_widget.getSelection)
353
+ }
354
+ on_swt_mousedown { |event|
355
+ observer.call(@swt_widget.getSelection)
356
+ }
357
+ on_swt_mouseup { |event|
358
+ observer.call(@swt_widget.getSelection)
359
+ }
360
+ end,
361
+ :selection_count => lambda do |observer|
362
+ on_swt_keydown { |event|
363
+ observer.call(@swt_widget.getSelectionCount)
364
+ }
365
+ on_swt_keyup { |event|
366
+ observer.call(@swt_widget.getSelectionCount)
367
+ }
368
+ on_swt_mousedown { |event|
369
+ observer.call(@swt_widget.getSelectionCount)
370
+ }
371
+ on_swt_mouseup { |event|
372
+ observer.call(@swt_widget.getSelectionCount)
373
+ }
374
+ end,
375
+ :top_index => lambda do |observer|
376
+ @last_top_index = @swt_widget.getTopIndex
377
+ on_paint_control { |event|
378
+ if @swt_widget.getTopIndex != @last_top_index
379
+ @last_top_index = @swt_widget.getTopIndex
380
+ observer.call(@last_top_index)
381
+ end
382
+ }
383
+ end,
384
+ },
385
+ Java::OrgEclipseSwtCustom::StyledText => {
386
+ :text => lambda do |observer|
387
+ on_modify_text { |modify_event|
388
+ observer.call(@swt_widget.getText)
389
+ }
390
+ end,
391
+ :caret_position => lambda do |observer|
392
+ on_caret_moved { |event|
393
+ observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
394
+ }
395
+ on_swt_keyup { |event|
396
+ observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
397
+ }
398
+ on_swt_mouseup { |event|
399
+ observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
400
+ }
401
+ end,
402
+ :caret_offset => lambda do |observer|
403
+ on_caret_moved { |event|
404
+ observer.call(@swt_widget.getCaretOffset) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
405
+ }
406
+ end,
407
+ :selection => lambda do |observer|
408
+ on_widget_selected { |event|
409
+ observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
410
+ }
411
+ on_swt_keyup { |event|
412
+ observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
413
+ }
414
+ on_swt_mouseup { |event|
415
+ observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
416
+ }
417
+ end,
418
+ :selection_count => lambda do |observer|
419
+ on_widget_selected { |event|
420
+ observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
421
+ }
422
+ on_swt_keyup { |event|
423
+ observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
424
+ }
425
+ on_swt_mouseup { |event|
426
+ observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
427
+ }
428
+ end,
429
+ :selection_range => lambda do |observer|
430
+ on_widget_selected { |event|
431
+ observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
432
+ }
433
+ on_swt_keyup { |event|
434
+ observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
435
+ }
436
+ on_swt_mouseup { |event|
437
+ observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
438
+ }
439
+ end,
440
+ :top_index => lambda do |observer|
441
+ @last_top_index = @swt_widget.getTopIndex
442
+ on_paint_control { |event|
443
+ if @swt_widget.getTopIndex != @last_top_index
444
+ @last_top_index = @swt_widget.getTopIndex
445
+ observer.call(@last_top_index)
446
+ end
447
+ }
448
+ end,
449
+ :top_pixel => lambda do |observer|
450
+ @last_top_pixel = @swt_widget.getTopPixel
451
+ on_paint_control { |event|
452
+ if @swt_widget.getTopPixel != @last_top_pixel
453
+ @last_top_pixel = @swt_widget.getTopPixel
454
+ observer.call(@last_top_pixel)
455
+ end
456
+ }
457
+ end,
458
+ },
459
+ Java::OrgEclipseSwtWidgets::Button => {
460
+ :selection => lambda do |observer|
461
+ on_widget_selected { |selection_event|
462
+ observer.call(@swt_widget.getSelection)
463
+ }
464
+ end
465
+ },
466
+ Java::OrgEclipseSwtWidgets::MenuItem => {
467
+ :selection => lambda do |observer|
468
+ on_widget_selected { |selection_event|
469
+ observer.call(@swt_widget.getSelection)
470
+ }
471
+ end
472
+ },
473
+ Java::OrgEclipseSwtWidgets::Spinner => {
474
+ :selection => lambda do |observer|
475
+ on_widget_selected { |selection_event|
476
+ observer.call(@swt_widget.getSelection)
477
+ }
478
+ end
479
+ },
480
+ Java::OrgEclipseSwtWidgets::DateTime =>
481
+ [:year, :month, :day, :hours, :minutes, :seconds, :date_time, :date, :time].reduce({}) do |hash, attribute|
482
+ hash.merge(
483
+ attribute => lambda do |observer|
484
+ on_widget_selected { |selection_event|
485
+ observer.call(get_attribute(attribute))
486
+ }
487
+ end
488
+ )
489
+ end,
490
+ }
491
+ end
492
+
493
+ def self.widget_exists?(underscored_widget_name)
494
+ !!swt_widget_class_for(underscored_widget_name)
495
+ end
496
+
497
+ # Manual entries of SWT widget classes that conflict with Ruby classes
498
+ def self.swt_widget_class_manual_entries
499
+ {
500
+ 'date_time' => Java::OrgEclipseSwtWidgets::DateTime
501
+ }
502
+ end
503
+
504
+ # This supports widgets in and out of basic SWT
505
+ def self.swt_widget_class_for(underscored_widget_name)
506
+ # TODO clear memoization for a keyword if a custom widget was defined with that keyword
507
+ unless flyweight_swt_widget_classes.keys.include?(underscored_widget_name)
508
+ begin
509
+ underscored_widget_name = KEYWORD_ALIASES[underscored_widget_name] if KEYWORD_ALIASES[underscored_widget_name]
510
+ swt_widget_name = underscored_widget_name.camelcase(:upper)
511
+ swt_widget_class = eval(swt_widget_name)
512
+ # TODO fix issue with not detecting DateTime because it's conflicting with the Ruby DateTime
513
+ unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
514
+ swt_widget_class = swt_widget_class_manual_entries[underscored_widget_name]
515
+ if swt_widget_class.nil?
516
+ Glimmer::Config.logger.debug {"Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget"}
517
+ return nil
518
+ end
519
+ end
520
+ flyweight_swt_widget_classes[underscored_widget_name] = swt_widget_class
521
+ rescue SyntaxError, NameError => e
522
+ Glimmer::Config.logger.debug {e.full_message}
523
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
524
+ nil
525
+ rescue => e
526
+ Glimmer::Config.logger.debug {e.full_message}
527
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
528
+ nil
529
+ end
530
+ end
531
+ flyweight_swt_widget_classes[underscored_widget_name]
532
+ end
533
+
534
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
535
+ def self.flyweight_swt_widget_classes
536
+ @flyweight_swt_widget_classes ||= {}
537
+ end
538
+
539
+ def async_exec(&block)
540
+ DisplayProxy.instance.async_exec(&block)
541
+ end
542
+
543
+ def sync_exec(&block)
544
+ DisplayProxy.instance.sync_exec(&block)
545
+ end
546
+
547
+ def has_style?(style)
548
+ comparison = interpret_style(style)
549
+ (@swt_widget.style & comparison) == comparison
550
+ end
551
+
552
+ def dispose
553
+ @swt_widget.dispose
554
+ end
555
+
556
+ def disposed?
557
+ @swt_widget.isDisposed
558
+ end
559
+
560
+ # TODO Consider renaming these methods as they are mainly used for data-binding
561
+
562
+ def can_add_observer?(property_name)
563
+ @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact.map(&:keys).flatten.map(&:to_s).include?(property_name.to_s)
564
+ end
565
+
566
+ # Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
567
+ def add_observer(observer, property_name)
568
+ if !observer.respond_to?(:binding_options) || !observer.binding_options[:read_only]
569
+ property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
570
+ widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
571
+ widget_listener_installers.to_a.first&.call(observer)
572
+ end
573
+ end
574
+
575
+ def remove_observer(observer, property_name)
576
+ # TODO consider implementing if remove_observer is needed (consumers can remove listener via SWT API)
577
+ end
578
+
579
+ def ensure_drag_source_proxy(style=[])
580
+ @drag_source_proxy ||= self.class.new('drag_source', self, style).tap do |proxy|
581
+ proxy.set_attribute(:transfer, :text)
582
+ end
583
+ end
584
+
585
+ def ensure_drop_target_proxy(style=[])
586
+ @drop_target_proxy ||= self.class.new('drop_target', self, style).tap do |proxy|
587
+ proxy.set_attribute(:transfer, :text)
588
+ proxy.on_drag_enter { |event|
589
+ event.detail = DNDProxy[:drop_copy]
590
+ }
591
+ end
592
+ end
593
+
594
+ # TODO eliminate duplication in the following methods perhaps by relying on exceptions
595
+
596
+ def can_handle_observation_request?(observation_request)
597
+ observation_request = observation_request.to_s
598
+ if observation_request.start_with?('on_swt_')
599
+ constant_name = observation_request.sub(/^on_swt_/, '')
600
+ SWTProxy.has_constant?(constant_name)
601
+ elsif observation_request.start_with?('on_')
602
+ event = observation_request.sub(/^on_/, '')
603
+ can_add_listener?(event) || can_handle_drag_observation_request?(observation_request) || can_handle_drop_observation_request?(observation_request)
604
+ end
605
+ end
606
+
607
+ def can_handle_drag_observation_request?(observation_request)
608
+ return false unless swt_widget.is_a?(Control)
609
+ potential_drag_source = @drag_source_proxy.nil?
610
+ ensure_drag_source_proxy
611
+ @drag_source_proxy.can_handle_observation_request?(observation_request).tap do |result|
612
+ if potential_drag_source && !result
613
+ @drag_source_proxy.swt_widget.dispose
614
+ @drag_source_proxy = nil
615
+ end
616
+ end
617
+ rescue => e
618
+ Glimmer::Config.logger.debug {e.full_message}
619
+ false
620
+ end
621
+
622
+ def can_handle_drop_observation_request?(observation_request)
623
+ return false unless swt_widget.is_a?(Control)
624
+ potential_drop_target = @drop_target_proxy.nil?
625
+ ensure_drop_target_proxy
626
+ @drop_target_proxy.can_handle_observation_request?(observation_request).tap do |result|
627
+ if potential_drop_target && !result
628
+ @drop_target_proxy.swt_widget.dispose
629
+ @drop_target_proxy = nil
630
+ end
631
+ end
632
+ end
633
+
634
+ def handle_observation_request(observation_request, &block)
635
+ observation_request = observation_request.to_s
636
+ if observation_request.start_with?('on_swt_')
637
+ constant_name = observation_request.sub(/^on_swt_/, '')
638
+ add_swt_event_listener(constant_name, &block)
639
+ elsif observation_request.start_with?('on_')
640
+ event = observation_request.sub(/^on_/, '')
641
+ if can_add_listener?(event)
642
+ event = observation_request.sub(/^on_/, '')
643
+ add_listener(event, &block)
644
+ elsif can_handle_drag_observation_request?(observation_request)
645
+ @drag_source_proxy&.handle_observation_request(observation_request, &block)
646
+ elsif can_handle_drop_observation_request?(observation_request)
647
+ @drop_target_proxy&.handle_observation_request(observation_request, &block)
648
+ end
649
+ end
650
+ end
651
+
652
+ def content(&block)
653
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
654
+ end
655
+
656
+ def method_missing(method, *args, &block)
657
+ if can_handle_observation_request?(method)
658
+ handle_observation_request(method, &block)
659
+ elsif has_attribute_setter?(method, *args)
660
+ set_attribute(method, *args)
661
+ elsif has_attribute_getter?(method, *args)
662
+ get_attribute(method, *args)
663
+ else
664
+ swt_widget.send(method, *args, &block)
665
+ end
666
+ rescue => e
667
+ Glimmer::Config.logger.debug { "Neither WidgetProxy nor #{swt_widget.class.name} can handle the method ##{method}" }
668
+ Glimmer::Config.logger.debug { e.full_message }
669
+ super
670
+ # TODO consider get_attribute too
671
+ end
672
+
673
+ def respond_to?(method, *args, &block)
674
+ super ||
675
+ can_handle_observation_request?(method) ||
676
+ swt_widget.respond_to?(method, *args, &block)
677
+ end
678
+
679
+ private
680
+
681
+ def style(underscored_widget_name, styles)
682
+ styles = [styles].flatten.compact
683
+ styles = default_style(underscored_widget_name) if styles.empty?
684
+ interpret_style(*styles)
685
+ end
686
+
687
+ def interpret_style(*styles)
688
+ SWTProxy[*styles] rescue DNDProxy[*styles]
689
+ end
690
+
691
+ def default_style(underscored_widget_name)
692
+ DEFAULT_STYLES[underscored_widget_name] || [:none]
693
+ end
694
+
695
+ # TODO refactor following methods to eliminate duplication
696
+ # perhaps consider relying on raising an exception to avoid checking first
697
+ # unless that gives obscure SWT errors
698
+ # Otherwise, consider caching results from can_add_lsitener and using them in
699
+ # add_listener knowing it will be called for sure afterwards
700
+
701
+ def can_add_listener?(underscored_listener_name)
702
+ !self.class.find_listener(@swt_widget.getClass, underscored_listener_name).empty?
703
+ end
704
+
705
+ def add_listener(underscored_listener_name, &block)
706
+ widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
707
+ widget_listener_proxy = nil
708
+ safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
709
+ listener = listener_class.new(listener_method => safe_block)
710
+ @swt_widget.send(widget_add_listener_method, listener)
711
+ 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)
712
+ end
713
+
714
+ # Looks through SWT class add***Listener methods till it finds one for which
715
+ # the argument is a listener class that has an event method matching
716
+ # underscored_listener_name
717
+ def self.find_listener(swt_widget_class, underscored_listener_name)
718
+ @listeners ||= {}
719
+ listener_key = [swt_widget_class.name, underscored_listener_name]
720
+ unless @listeners.has_key?(listener_key)
721
+ listener_method_name = underscored_listener_name.camelcase(:lower)
722
+ swt_widget_class.getMethods.each do |widget_add_listener_method|
723
+ if widget_add_listener_method.getName.match(/add.*Listener/)
724
+ widget_add_listener_method.getParameterTypes.each do |listener_type|
725
+ listener_type.getMethods.each do |listener_method|
726
+ if (listener_method.getName == listener_method_name)
727
+ @listeners[listener_key] = [widget_add_listener_method.getName, listener_class(listener_type), listener_method.getName]
728
+ return @listeners[listener_key]
729
+ end
730
+ end
731
+ end
732
+ end
733
+ end
734
+ @listeners[listener_key] = []
735
+ end
736
+ @listeners[listener_key]
737
+ end
738
+
739
+ # Returns a Ruby class that implements listener type Java interface with ability to easily
740
+ # install a block that gets called upon calling a listener event method
741
+ def self.listener_class(listener_type)
742
+ @listener_classes ||= {}
743
+ listener_class_key = listener_type.name
744
+ unless @listener_classes.has_key?(listener_class_key)
745
+ @listener_classes[listener_class_key] = Class.new(Object).tap do |listener_class|
746
+ listener_class.send :include, (eval listener_type.name.sub("interface", ""))
747
+ listener_class.define_method('initialize') do |event_method_block_mapping|
748
+ @event_method_block_mapping = event_method_block_mapping
749
+ end
750
+ listener_type.getMethods.each do |event_method|
751
+ listener_class.define_method(event_method.getName) do |*args|
752
+ @event_method_block_mapping[event_method.getName]&.call(*args)
753
+ end
754
+ end
755
+ end
756
+ end
757
+ @listener_classes[listener_class_key]
758
+ end
759
+
760
+ def add_swt_event_listener(swt_constant, &block)
761
+ event_type = SWTProxy[swt_constant]
762
+ widget_listener_proxy = nil
763
+ safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
764
+ @swt_widget.addListener(event_type, &safe_block)
765
+ 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)
766
+ end
767
+
768
+ def widget_custom_attribute_mapping
769
+ # TODO scope per widget class type just like other mappings
770
+ @swt_widget_custom_attribute_mapping ||= {
771
+ 'focus' => {
772
+ getter: {name: 'isFocusControl'},
773
+ setter: {name: 'setFocus', invoker: lambda { |widget, args| @swt_widget.setFocus if args.first }},
774
+ },
775
+ 'caret_position' => {
776
+ getter: {name: 'getCaretPosition', invoker: lambda { |widget, args| @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x}},
777
+ setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(args.first, args.first + @swt_widget.getSelectionCount) if args.first }},
778
+ },
779
+ 'selection_count' => {
780
+ getter: {name: 'getSelectionCount'},
781
+ setter: {name: 'setSelection', invoker: lambda { |widget, args|
782
+ if args.first
783
+ caret_position = @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x
784
+ # TODO handle negative length
785
+ @swt_widget.setSelection(caret_position, caret_position + args.first)
786
+ end
787
+ }},
788
+ },
789
+ }
790
+ end
791
+
792
+ def drag_source_style=(style)
793
+ ensure_drag_source_proxy(style)
794
+ end
795
+
796
+ def drop_target_style=(style)
797
+ ensure_drop_target_proxy(style)
798
+ end
799
+
800
+ def drag_source_transfer=(args)
801
+ args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
802
+ ensure_drag_source_proxy
803
+ @drag_source_proxy.set_attribute(:transfer, args)
804
+ end
805
+
806
+ def drop_target_transfer=(args)
807
+ args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
808
+ ensure_drop_target_proxy
809
+ @drop_target_proxy.set_attribute(:transfer, args)
810
+ end
811
+
812
+ def drag_source_effect=(args)
813
+ args = args.first if args.is_a?(Array)
814
+ ensure_drag_source_proxy
815
+ @drag_source_proxy.set_attribute(:drag_source_effect, args)
816
+ end
817
+
818
+ def drop_target_effect=(args)
819
+ args = args.first if args.is_a?(Array)
820
+ ensure_drop_target_proxy
821
+ @drop_target_proxy.set_attribute(:drop_target_effect, args)
822
+ end
823
+
824
+ def apply_property_type_converters(attribute_name, args)
825
+ value = args
826
+ converter = property_type_converters[attribute_name.to_sym]
827
+ args[0..-1] = [converter.call(*value)] if converter
828
+ if args.count == 1 && args.first.is_a?(ColorProxy)
829
+ g_color = args.first
830
+ args[0] = g_color.swt_color
831
+ end
832
+ end
833
+
834
+ def property_type_converters
835
+ color_converter = lambda do |value|
836
+ if value.is_a?(Symbol) || value.is_a?(String)
837
+ ColorProxy.new(value).swt_color
838
+ else
839
+ value
840
+ end
841
+ end
842
+ # TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
843
+ @property_type_converters ||= {
844
+ accelerator: lambda { |*value|
845
+ SWTProxy[*value]
846
+ },
847
+ alignment: lambda { |*value|
848
+ SWTProxy[*value]
849
+ },
850
+ background: color_converter,
851
+ background_image: lambda do |*value|
852
+ image_proxy = ImageProxy.create(*value)
853
+
854
+ if image_proxy&.file_path&.end_with?('.gif')
855
+ image = image_proxy.swt_image
856
+ width = image.get_bounds.width.to_i
857
+ height = image.get_bounds.height.to_i
858
+ image_number = 0
859
+ loader = ImageLoader.new
860
+ loader.load(image_proxy.input_stream)
861
+ image.dispose
862
+ image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display,loader.data[0].scaledTo(width, height))
863
+ gc = org.eclipse.swt.graphics.GC.new(image)
864
+ on_paint_control { |event|
865
+ image_number = (image_number == loader.data.length - 1) ? 0 : image_number + 1
866
+ next_frame_data = loader.data[image_number]
867
+ image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display, next_frame_data.scaledTo(width, height))
868
+ event.gc.drawImage(image, 0, 0, width, height, 0, 0, width, height)
869
+ image.dispose
870
+ }
871
+ Thread.new {
872
+ last_image_number = -1
873
+ while last_image_number != image_number
874
+ last_image_number = image_number
875
+ sync_exec {
876
+ redraw
877
+ }
878
+ delayTime = loader.data[image_number].delayTime.to_f / 100.0
879
+ sleep(delayTime)
880
+ end
881
+ };
882
+ image_proxy = nil
883
+ else
884
+ on_swt_Resize do |resize_event|
885
+ image_proxy.scale_to(@swt_widget.getSize.x, @swt_widget.getSize.y)
886
+ @swt_widget.setBackgroundImage(image_proxy.swt_image)
887
+ end
888
+ end
889
+
890
+ image_proxy&.swt_image
891
+ end,
892
+ cursor: lambda do |value|
893
+ cursor_proxy = nil
894
+ if value.is_a?(CursorProxy)
895
+ cursor_proxy = value
896
+ elsif value.is_a?(Symbol) || value.is_a?(String) || value.is_a?(Integer)
897
+ cursor_proxy = CursorProxy.new(value)
898
+ end
899
+ cursor_proxy ? cursor_proxy.swt_cursor : value
900
+ end,
901
+ :enabled => lambda do |value|
902
+ !!value
903
+ end,
904
+ foreground: color_converter,
905
+ link_foreground: color_converter,
906
+ font: lambda do |value|
907
+ if value.is_a?(Hash)
908
+ font_properties = value
909
+ FontProxy.new(self, font_properties).swt_font
910
+ else
911
+ value
912
+ end
913
+ end,
914
+ image: lambda do |*value|
915
+ ImageProxy.create(*value).swt_image
916
+ end,
917
+ images: lambda do |array|
918
+ array.to_a.map do |value|
919
+ ImageProxy.create(value).swt_image
920
+ end.to_java(Image)
921
+ end,
922
+ items: lambda do |value|
923
+ value.to_java :string
924
+ end,
925
+ text: lambda do |value|
926
+ value.to_s
927
+ end,
928
+ transfer: lambda do |value|
929
+ value = value.first if value.is_a?(Array) && value.size == 1 && value.first.is_a?(Array)
930
+ transfer_object_extrapolator = lambda do |transfer_name|
931
+ transfer_type = "#{transfer_name.to_s.camelcase(:upper)}Transfer".to_sym
932
+ transfer_type_alternative = "#{transfer_name.to_s.upcase}Transfer".to_sym
933
+ transfer_class = org.eclipse.swt.dnd.const_get(transfer_type) rescue org.eclipse.swt.dnd.const_get(transfer_type_alternative)
934
+ transfer_class.getInstance
935
+ end
936
+ result = value
937
+ if value.is_a?(Symbol) || value.is_a?(String)
938
+ result = [transfer_object_extrapolator.call(value)]
939
+ elsif value.is_a?(Array)
940
+ result = value.map do |transfer_name|
941
+ transfer_object_extrapolator.call(transfer_name)
942
+ end
943
+ end
944
+ result = result.to_java(Transfer) unless result.is_a?(ArrayJavaProxy)
945
+ result
946
+ end,
947
+ visible: lambda do |value|
948
+ !!value
949
+ end,
950
+ weights: lambda do |value|
951
+ value.to_java(:int)
952
+ end,
953
+ }
954
+ end
955
+ end
956
+ end
957
+ end