glimmer-dsl-swt 4.18.4.4 → 4.18.4.9

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