glimmer-dsl-swt 4.18.4.3 → 4.18.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/LICENSE.txt +20 -20
  4. data/README.md +76 -60
  5. data/RUBY_VERSION +1 -1
  6. data/VERSION +1 -1
  7. data/bin/girb +31 -31
  8. data/bin/girb_runner.rb +34 -34
  9. data/bin/glimmer +26 -26
  10. data/glimmer-dsl-swt.gemspec +9 -9
  11. data/lib/ext/glimmer.rb +41 -41
  12. data/lib/ext/glimmer/config.rb +167 -167
  13. data/lib/ext/rouge/themes/glimmer.rb +29 -29
  14. data/lib/glimmer-dsl-swt.rb +44 -44
  15. data/lib/glimmer/Rakefile +26 -26
  16. data/lib/glimmer/data_binding/list_selection_binding.rb +72 -72
  17. data/lib/glimmer/data_binding/observable_widget.rb +38 -38
  18. data/lib/glimmer/data_binding/shine.rb +44 -44
  19. data/lib/glimmer/data_binding/table_items_binding.rb +89 -89
  20. data/lib/glimmer/data_binding/tree_items_binding.rb +108 -108
  21. data/lib/glimmer/data_binding/widget_binding.rb +73 -73
  22. data/lib/glimmer/dsl/swt/animation_expression.rb +43 -43
  23. data/lib/glimmer/dsl/swt/async_exec_expression.rb +35 -35
  24. data/lib/glimmer/dsl/swt/bind_expression.rb +58 -58
  25. data/lib/glimmer/dsl/swt/block_property_expression.rb +41 -41
  26. data/lib/glimmer/dsl/swt/checkbox_group_selection_data_binding_expression.rb +61 -61
  27. data/lib/glimmer/dsl/swt/color_expression.rb +40 -40
  28. data/lib/glimmer/dsl/swt/column_properties_expression.rb +45 -45
  29. data/lib/glimmer/dsl/swt/combo_selection_data_binding_expression.rb +67 -67
  30. data/lib/glimmer/dsl/swt/cursor_expression.rb +44 -44
  31. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +63 -63
  32. data/lib/glimmer/dsl/swt/data_binding_expression.rb +55 -55
  33. data/lib/glimmer/dsl/swt/dialog_expression.rb +48 -48
  34. data/lib/glimmer/dsl/swt/directory_dialog_expression.rb +48 -48
  35. data/lib/glimmer/dsl/swt/display_expression.rb +40 -40
  36. data/lib/glimmer/dsl/swt/dnd_expression.rb +46 -46
  37. data/lib/glimmer/dsl/swt/dsl.rb +63 -63
  38. data/lib/glimmer/dsl/swt/exec_expression.rb +55 -55
  39. data/lib/glimmer/dsl/swt/expand_item_expression.rb +60 -60
  40. data/lib/glimmer/dsl/swt/file_dialog_expression.rb +48 -48
  41. data/lib/glimmer/dsl/swt/font_expression.rb +49 -49
  42. data/lib/glimmer/dsl/swt/image_expression.rb +50 -50
  43. data/lib/glimmer/dsl/swt/layout_data_expression.rb +46 -46
  44. data/lib/glimmer/dsl/swt/layout_expression.rb +50 -50
  45. data/lib/glimmer/dsl/swt/list_selection_data_binding_expression.rb +65 -65
  46. data/lib/glimmer/dsl/swt/menu_bar_expression.rb +54 -54
  47. data/lib/glimmer/dsl/swt/menu_expression.rb +53 -53
  48. data/lib/glimmer/dsl/swt/message_box_expression.rb +54 -54
  49. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -53
  50. data/lib/glimmer/dsl/swt/observe_expression.rb +53 -53
  51. data/lib/glimmer/dsl/swt/property_expression.rb +46 -46
  52. data/lib/glimmer/dsl/swt/radio_group_selection_data_binding_expression.rb +61 -61
  53. data/lib/glimmer/dsl/swt/rgb_expression.rb +33 -33
  54. data/lib/glimmer/dsl/swt/rgba_expression.rb +33 -33
  55. data/lib/glimmer/dsl/swt/shape_expression.rb +54 -54
  56. data/lib/glimmer/dsl/swt/shell_expression.rb +46 -46
  57. data/lib/glimmer/dsl/swt/swt_expression.rb +46 -46
  58. data/lib/glimmer/dsl/swt/sync_exec_expression.rb +36 -36
  59. data/lib/glimmer/dsl/swt/tab_item_expression.rb +54 -54
  60. data/lib/glimmer/dsl/swt/table_items_data_binding_expression.rb +52 -52
  61. data/lib/glimmer/dsl/swt/timer_exec_expression.rb +35 -0
  62. data/lib/glimmer/dsl/swt/transform_expression.rb +55 -55
  63. data/lib/glimmer/dsl/swt/tree_items_data_binding_expression.rb +52 -52
  64. data/lib/glimmer/dsl/swt/tree_properties_expression.rb +47 -47
  65. data/lib/glimmer/dsl/swt/widget_expression.rb +66 -66
  66. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +53 -53
  67. data/lib/glimmer/rake_task.rb +220 -220
  68. data/lib/glimmer/rake_task/list.rb +97 -97
  69. data/lib/glimmer/rake_task/package.rb +139 -139
  70. data/lib/glimmer/rake_task/scaffold.rb +765 -765
  71. data/lib/glimmer/swt/color_proxy.rb +107 -107
  72. data/lib/glimmer/swt/cursor_proxy.rb +66 -66
  73. data/lib/glimmer/swt/custom/animation.rb +243 -243
  74. data/lib/glimmer/swt/custom/checkbox_group.rb +181 -181
  75. data/lib/glimmer/swt/custom/code_text.rb +23 -20
  76. data/lib/glimmer/swt/custom/drawable.rb +49 -49
  77. data/lib/glimmer/swt/custom/radio_group.rb +176 -176
  78. data/lib/glimmer/swt/custom/shape.rb +297 -297
  79. data/lib/glimmer/swt/date_time_proxy.rb +85 -85
  80. data/lib/glimmer/swt/directory_dialog_proxy.rb +65 -65
  81. data/lib/glimmer/swt/display_proxy.rb +166 -166
  82. data/lib/glimmer/swt/dnd_proxy.rb +51 -51
  83. data/lib/glimmer/swt/expand_item_proxy.rb +97 -97
  84. data/lib/glimmer/swt/file_dialog_proxy.rb +66 -66
  85. data/lib/glimmer/swt/font_proxy.rb +94 -94
  86. data/lib/glimmer/swt/image_proxy.rb +184 -184
  87. data/lib/glimmer/swt/layout_data_proxy.rb +105 -105
  88. data/lib/glimmer/swt/layout_proxy.rb +112 -112
  89. data/lib/glimmer/swt/menu_proxy.rb +126 -126
  90. data/lib/glimmer/swt/message_box_proxy.rb +89 -89
  91. data/lib/glimmer/swt/packages.rb +37 -37
  92. data/lib/glimmer/swt/properties.rb +49 -49
  93. data/lib/glimmer/swt/sash_form_proxy.rb +53 -53
  94. data/lib/glimmer/swt/scrolled_composite_proxy.rb +37 -37
  95. data/lib/glimmer/swt/style_constantizable.rb +157 -157
  96. data/lib/glimmer/swt/styled_text_proxy.rb +38 -38
  97. data/lib/glimmer/swt/swt_proxy.rb +59 -59
  98. data/lib/glimmer/swt/tab_item_proxy.rb +91 -91
  99. data/lib/glimmer/swt/table_column_proxy.rb +57 -57
  100. data/lib/glimmer/swt/transform_proxy.rb +109 -109
  101. data/lib/glimmer/swt/tree_proxy.rb +145 -145
  102. data/lib/glimmer/swt/widget_listener_proxy.rb +64 -64
  103. data/lib/glimmer/swt/widget_proxy.rb +957 -957
  104. data/lib/glimmer/ui/custom_shell.rb +82 -82
  105. data/lib/glimmer/ui/custom_widget.rb +315 -315
  106. data/lib/glimmer/util/proc_tracker.rb +39 -39
  107. data/samples/elaborate/contact_manager.rb +142 -142
  108. data/samples/elaborate/contact_manager/contact.rb +32 -32
  109. data/samples/elaborate/contact_manager/contact_manager_presenter.rb +47 -47
  110. data/samples/elaborate/contact_manager/contact_repository.rb +169 -169
  111. data/samples/elaborate/login.rb +123 -123
  112. data/samples/elaborate/meta_sample.rb +1 -1
  113. data/samples/elaborate/tetris/model/block.rb +48 -48
  114. data/samples/elaborate/tetris/model/past_game.rb +39 -39
  115. data/samples/elaborate/tic_tac_toe.rb +76 -76
  116. data/samples/elaborate/tic_tac_toe/board.rb +145 -145
  117. data/samples/elaborate/tic_tac_toe/cell.rb +48 -48
  118. data/samples/elaborate/user_profile.rb +76 -76
  119. data/samples/hello/hello_browser.rb +31 -31
  120. data/samples/hello/hello_button.rb +46 -46
  121. data/samples/hello/hello_canvas.rb +64 -64
  122. data/samples/hello/hello_canvas_transform.rb +1 -1
  123. data/samples/hello/hello_checkbox.rb +85 -85
  124. data/samples/hello/hello_checkbox_group.rb +71 -71
  125. data/samples/hello/hello_code_text.rb +104 -104
  126. data/samples/hello/hello_combo.rb +63 -63
  127. data/samples/hello/hello_computed.rb +96 -96
  128. data/samples/hello/hello_computed/contact.rb +42 -42
  129. data/samples/hello/hello_custom_shell.rb +155 -155
  130. data/samples/hello/hello_custom_widget.rb +86 -86
  131. data/samples/hello/hello_date_time.rb +63 -63
  132. data/samples/hello/hello_dialog.rb +78 -78
  133. data/samples/hello/hello_directory_dialog.rb +60 -60
  134. data/samples/hello/hello_drag_and_drop.rb +50 -50
  135. data/samples/hello/hello_expand_bar.rb +110 -110
  136. data/samples/hello/hello_file_dialog.rb +60 -60
  137. data/samples/hello/hello_group.rb +104 -104
  138. data/samples/hello/hello_link.rb +80 -80
  139. data/samples/hello/hello_list_multi_selection.rb +74 -74
  140. data/samples/hello/hello_list_single_selection.rb +59 -59
  141. data/samples/hello/hello_menu_bar.rb +241 -241
  142. data/samples/hello/hello_message_box.rb +37 -37
  143. data/samples/hello/hello_pop_up_context_menu.rb +84 -84
  144. data/samples/hello/hello_radio.rb +108 -108
  145. data/samples/hello/hello_radio_group.rb +87 -87
  146. data/samples/hello/hello_sash_form.rb +137 -137
  147. data/samples/hello/hello_spinner.rb +69 -69
  148. data/samples/hello/hello_styled_text.rb +138 -138
  149. data/samples/hello/hello_tab.rb +50 -50
  150. data/samples/hello/hello_world.rb +29 -29
  151. metadata +6 -6
  152. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
  153. data/samples/hello/hello_canvas_transform/hello_canvas_transform_image.png +0 -0
@@ -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,957 @@
1
- # Copyright (c) 2007-2021 Andy Maleh
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining
4
- # a copy of this software and associated documentation files (the
5
- # "Software"), to deal in the Software without restriction, including
6
- # without limitation the rights to use, copy, modify, merge, publish,
7
- # distribute, sublicense, and/or sell copies of the Software, and to
8
- # permit persons to whom the Software is furnished to do so, subject to
9
- # the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be
12
- # included in all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
-
22
- require 'glimmer/swt/widget_listener_proxy'
23
- require 'glimmer/swt/color_proxy'
24
- require 'glimmer/swt/font_proxy'
25
- require 'glimmer/swt/swt_proxy'
26
- require 'glimmer/swt/display_proxy'
27
- require 'glimmer/swt/dnd_proxy'
28
- require 'glimmer/swt/image_proxy'
29
- require 'glimmer/swt/properties'
30
- require 'glimmer/swt/custom/drawable'
31
-
32
- # TODO refactor to make file smaller and extract sub-widget-proxies out of this
33
-
34
- module Glimmer
35
- module SWT
36
- # Proxy for SWT Widget objects
37
- #
38
- # Sets default SWT styles to widgets upon inititalizing as
39
- # per DEFAULT_STYLES
40
- #
41
- # Also, auto-initializes widgets as per initializer blocks
42
- # in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
43
- #
44
- # Follows the Proxy Design Pattern
45
- class WidgetProxy
46
- include Packages
47
- include Properties
48
- include Custom::Drawable
49
-
50
- DEFAULT_STYLES = {
51
- 'arrow' => [:arrow],
52
- 'button' => [:push],
53
- 'checkbox' => [:check],
54
- 'check' => [:check],
55
- 'drag_source' => [:drop_copy],
56
- 'drop_target' => [:drop_copy],
57
- 'expand_bar' => [:v_scroll],
58
- 'list' => [:border, :v_scroll],
59
- 'menu_item' => [:push],
60
- 'radio' => [:radio],
61
- 'scrolled_composite' => [:border, :h_scroll, :v_scroll],
62
- 'spinner' => [:border],
63
- 'styled_text' => [:border, :multi, :v_scroll, :h_scroll],
64
- 'table' => [:virtual, :border, :full_selection],
65
- 'text' => [:border],
66
- 'toggle' => [:toggle],
67
- 'tool_bar' => [:push],
68
- 'tool_item' => [:push],
69
- 'tree' => [:virtual, :border, :h_scroll, :v_scroll],
70
- 'date_drop_down' => [:date, :drop_down],
71
- 'time' => [:time],
72
- 'calendar' => [:calendar],
73
- }
74
-
75
- DEFAULT_INITIALIZERS = {
76
- composite: lambda do |composite|
77
- composite.layout = GridLayout.new if composite.get_layout.nil?
78
- end,
79
- canvas: lambda do |canvas|
80
- canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
81
- end,
82
- scrolled_composite: lambda do |scrolled_composite|
83
- scrolled_composite.expand_horizontal = true
84
- scrolled_composite.expand_vertical = true
85
- end,
86
- table: lambda do |table|
87
- table.setHeaderVisible(true)
88
- table.setLinesVisible(true)
89
- end,
90
- table_column: lambda do |table_column|
91
- table_column.setWidth(80)
92
- end,
93
- group: lambda do |group|
94
- group.layout = GridLayout.new if group.get_layout.nil?
95
- end,
96
- }
97
-
98
- KEYWORD_ALIASES = {
99
- 'arrow' => 'button',
100
- 'checkbox' => 'button',
101
- 'check' => 'button',
102
- 'radio' => 'button',
103
- 'toggle' => 'button',
104
- 'date' => 'date_time',
105
- 'date_drop_down' => 'date_time',
106
- 'time' => 'date_time',
107
- 'calendar' => 'date_time',
108
- }
109
-
110
- class << self
111
- # Instantiates the right WidgetProxy subclass for passed in keyword
112
- # Args are: keyword, parent, swt_widget_args (including styles)
113
- def create(*init_args, swt_widget: nil)
114
- return swt_widget.get_data('proxy') if swt_widget&.get_data('proxy')
115
- keyword, parent, args = init_args
116
- selected_widget_proxy_class = widget_proxy_class(keyword || underscored_widget_name(swt_widget))
117
- if init_args.empty?
118
- selected_widget_proxy_class.new(swt_widget: swt_widget)
119
- else
120
- selected_widget_proxy_class.new(*init_args)
121
- end
122
- end
123
-
124
- def widget_proxy_class(keyword)
125
- begin
126
- keyword = KEYWORD_ALIASES[keyword] if KEYWORD_ALIASES[keyword]
127
- class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
128
- Glimmer::SWT.const_get(class_name)
129
- rescue
130
- Glimmer::SWT::WidgetProxy
131
- end
132
- end
133
-
134
- def underscored_widget_name(swt_widget)
135
- swt_widget.class.name.split(/::|\./).last.underscore
136
- end
137
- end
138
-
139
- attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer
140
-
141
- # Initializes a new SWT Widget
142
- #
143
- # It is preferred to use `::create` method instead since it instantiates the
144
- # right subclass per widget keyword
145
- #
146
- # keyword, parent, swt_widget_args (including styles)
147
- #
148
- # Styles is a comma separate list of symbols representing SWT styles in lower case
149
- def initialize(*init_args, swt_widget: nil)
150
- if swt_widget.nil?
151
- underscored_widget_name, parent, args = init_args
152
- @parent_proxy = parent
153
- styles, extra_options = extract_args(underscored_widget_name, args)
154
- swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
155
- @swt_widget = swt_widget_class.new(@parent_proxy.swt_widget, style(underscored_widget_name, styles), *extra_options)
156
- else
157
- @swt_widget = swt_widget
158
- underscored_widget_name = self.class.underscored_widget_name(@swt_widget)
159
- parent_proxy_class = self.class.widget_proxy_class(self.class.underscored_widget_name(@swt_widget.parent))
160
- parent = swt_widget.parent
161
- @parent_proxy = parent&.get_data('proxy') || parent_proxy_class.new(swt_widget: parent)
162
- end
163
- if @swt_widget&.get_data('proxy').nil?
164
- @swt_widget.set_data('proxy', self)
165
- DEFAULT_INITIALIZERS[underscored_widget_name.to_s.to_sym]&.call(@swt_widget)
166
- @parent_proxy.post_initialize_child(self)
167
- end
168
- @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
+ '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