glimmer-dsl-opal 0.7.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +182 -12
  4. data/VERSION +1 -1
  5. data/lib/glimmer-dsl-opal.rb +8 -0
  6. data/lib/glimmer-dsl-opal/ext/class.rb +10 -0
  7. data/lib/{file.rb → glimmer-dsl-opal/ext/file.rb} +0 -0
  8. data/lib/glimmer-dsl-opal/samples/hello/hello_browser.rb +23 -0
  9. data/lib/glimmer-dsl-opal/samples/hello/hello_computed.rb +27 -0
  10. data/lib/glimmer-dsl-opal/samples/hello/hello_list_multi_selection.rb +62 -32
  11. data/lib/glimmer-dsl-opal/samples/hello/hello_list_single_selection.rb +47 -22
  12. data/lib/glimmer-dsl-opal/samples/hello/hello_message_box.rb +37 -0
  13. data/lib/glimmer-dsl-opal/samples/hello/hello_pop_up_context_menu.rb +84 -0
  14. data/lib/glimmer/data_binding/observable_element.rb +1 -1
  15. data/lib/glimmer/dsl/opal/custom_widget_expression.rb +1 -1
  16. data/lib/glimmer/dsl/opal/dsl.rb +2 -0
  17. data/lib/glimmer/dsl/opal/menu_bar_expression.rb +54 -0
  18. data/lib/glimmer/dsl/opal/menu_expression.rb +61 -0
  19. data/lib/glimmer/dsl/opal/widget_expression.rb +3 -2
  20. data/lib/glimmer/dsl/opal/widget_listener_expression.rb +2 -2
  21. data/lib/glimmer/swt/custom/checkbox_group.rb +2 -2
  22. data/lib/glimmer/swt/custom/radio_group.rb +2 -2
  23. data/lib/glimmer/swt/event_listener_proxy.rb +14 -4
  24. data/lib/glimmer/swt/grid_layout_proxy.rb +1 -0
  25. data/lib/glimmer/swt/label_proxy.rb +6 -3
  26. data/lib/glimmer/swt/list_proxy.rb +33 -0
  27. data/lib/glimmer/swt/menu_item_proxy.rb +87 -0
  28. data/lib/glimmer/swt/menu_proxy.rb +162 -0
  29. data/lib/glimmer/swt/message_box_proxy.rb +51 -57
  30. data/lib/glimmer/swt/property_owner.rb +2 -0
  31. data/lib/glimmer/swt/radio_proxy.rb +1 -1
  32. data/lib/glimmer/swt/shell_proxy.rb +32 -187
  33. data/lib/glimmer/swt/tab_folder_proxy.rb +43 -0
  34. data/lib/glimmer/swt/table_column_proxy.rb +3 -2
  35. data/lib/glimmer/swt/table_editor.rb +1 -1
  36. data/lib/glimmer/swt/table_item_proxy.rb +7 -5
  37. data/lib/glimmer/swt/table_proxy.rb +10 -0
  38. data/lib/glimmer/swt/widget_proxy.rb +313 -30
  39. data/lib/glimmer/ui/custom_shell.rb +1 -1
  40. data/lib/glimmer/ui/custom_widget.rb +3 -3
  41. metadata +20 -7
@@ -2,6 +2,8 @@ module Glimmer
2
2
  module SWT
3
3
  # Adapts Glimmer UI classes to SWT JavaBean property owner classes (which are now adapted to Opal)
4
4
  module PropertyOwner
5
+ # TODO consider adding has_attribute?
6
+
5
7
  def get_attribute(attribute_name)
6
8
  send(attribute_getter(attribute_name))
7
9
  end
@@ -4,7 +4,7 @@ module Glimmer
4
4
  module SWT
5
5
  class RadioProxy < WidgetProxy
6
6
  # TODO add a create method that ensures passing :radio style in if not there
7
- STYLE=<<~CSS
7
+ STYLE = <<~CSS
8
8
  .radio {
9
9
  display: inline;
10
10
  }
@@ -1,12 +1,34 @@
1
1
  require 'glimmer/swt/widget_proxy'
2
+ require 'glimmer/swt/layout_proxy'
2
3
  require 'glimmer/swt/display_proxy'
3
4
  require 'glimmer/swt/point'
4
5
 
5
6
  module Glimmer
6
7
  module SWT
7
8
  class ShellProxy < CompositeProxy
9
+ STYLE = <<~CSS
10
+ html {
11
+ width: 100%;
12
+ height: 100%;
13
+ }
14
+ body {
15
+ width: 100%;
16
+ height: 100%;
17
+ margin: 0;
18
+ }
19
+ .shell {
20
+ height: 100%;
21
+ margin: 0;
22
+ }
23
+ .shell iframe {
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+ CSS
28
+
8
29
  # TODO consider renaming to ShellProxy to match SWT API
9
30
  attr_reader :minimum_size
31
+ attr_accessor :menu_bar # TODO implement menu bar rendering
10
32
 
11
33
  WIDTH_MIN = 130
12
34
  HEIGHT_MIN = 0
@@ -52,208 +74,31 @@ module Glimmer
52
74
  .hide {
53
75
  display: none !important;
54
76
  }
55
- .selected, .tabs .tab.selected {
77
+ .selected {
56
78
  background: rgb(80, 116, 211);
57
79
  color: white;
58
80
  }
59
81
  CSS
60
82
  end
61
83
 
62
- def style_dom_shell_css
63
- <<~CSS
64
- html {
65
- width: 100%;
66
- height: 100%;
67
- }
68
- body {
69
- width: 100%;
70
- height: 100%;
71
- margin: 0;
72
- }
73
- .shell {
74
- height: 100%;
75
- margin: 0;
76
- }
77
- .shell iframe {
78
- width: 100%;
79
- height: 100%;
80
- }
81
- CSS
82
- end
83
-
84
- def style_dom_list_css
85
- <<~CSS
86
- ul {
87
- list-style: none;
88
- padding: 0;
89
- }
90
- li {
91
- cursor: default;
92
- padding-left: 10px;
93
- padding-right: 10px;
94
- }
95
- li.empty-list-item {
96
- color: transparent;
97
- }
98
- CSS
99
- end
100
-
101
- def style_dom_tab_css
102
- <<~CSS
103
- .tabs .tab {
104
- background-color: inherit;
105
- float: left;
106
- border: none;
107
- outline: none;
108
- cursor: pointer;
109
- padding: 14px 16px;
110
- transition: 0.3s;
111
- font-size: 17px;
112
- }
113
- .tabs {
114
- overflow: hidden;
115
- border: 1px solid #ccc;
116
- background-color: #f1f1f1;
117
- }
118
- CSS
119
- end
120
-
121
- def style_dom_tab_item_css
122
- <<~CSS
123
- /* Create an selected/current tablink class */
124
- .tabs .tab.selected {
125
- background-color: #ccc;
126
- }
127
- /* Change background color of buttons on hover */
128
- .tabs .tab:hover {
129
- background-color: #ddd;
130
- }
131
- /* Style the tab content */
132
- .tab-item {
133
- padding: 6px 12px;
134
- border: 1px solid #ccc;
135
- border-top: none;
136
- }
137
- CSS
138
- end
139
-
140
- def style_dom_modal_css
141
- <<~CSS
142
- /* The Modal (background) */
143
- .modal {
144
- position: fixed; /* Stay in place */
145
- z-index: 1; /* Sit on top */
146
- padding-top: 100px; /* Location of the box */
147
- left: 0;
148
- top: 0;
149
- width: 100%; /* Full width */
150
- height: 100%; /* Full height */
151
- overflow: auto; /* Enable scroll if needed */
152
- background-color: rgb(0,0,0); /* Fallback color */
153
- background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
154
- text-align: center;
155
- }
156
-
157
- /* Modal Content */
158
- .modal-content {
159
- background-color: #fefefe;
160
- margin: auto;
161
- border: 1px solid #888;
162
- display: inline-block;
163
- min-width: 200px;
164
- }
165
-
166
- .modal-content .text {
167
- background: rgb(80, 116, 211);
168
- color: white;
169
- padding: 5px;
170
- }
171
-
172
- .modal-content .message {
173
- padding: 20px;
174
- }
175
-
176
- /* The Close Button */
177
- .close {
178
- color: #aaaaaa;
179
- float: right;
180
- font-weight: bold;
181
- margin: 5px;
182
- }
183
-
184
- .close:hover,
185
- .close:focus {
186
- color: #000;
187
- text-decoration: none;
188
- cursor: pointer;
189
- }
190
- CSS
191
- end
192
-
193
- def style_dom_table_css
194
- <<~CSS
195
- table {
196
- border-spacing: 0;
197
- }
198
-
199
- table tr th,td {
200
- cursor: default;
201
- }
202
- CSS
203
- end
204
-
205
84
  def dom
206
85
  i = 0
207
86
  body_id = id
208
87
  body_class = ([name] + css_classes.to_a).join(' ')
209
88
  @dom ||= html {
210
89
  div(id: body_id, class: body_class) {
211
- # TODO support the idea of dynamic CSS building on close of shell that adds only as much CSS as needed for widgets that were mentioned
90
+ # TODO consider supporting the idea of dynamic CSS building on close of shell that adds only as much CSS as needed for widgets that were mentioned
212
91
  style(class: 'common-style') {
213
92
  style_dom_css
214
93
  }
215
- style(class: 'shell-style') {
216
- style_dom_shell_css
217
- }
218
- style(class: 'list-style') {
219
- style_dom_list_css
220
- }
221
- style(class: 'tab-style') {
222
- style_dom_tab_css
223
- }
224
- # style(class: 'tab-item-style') {
225
- # style_dom_tab_item_css
226
- # }
227
- # style(class: 'modal-style') {
228
- # style_dom_modal_css
229
- # }
230
- style(class: 'table-style') {
231
- style_dom_table_css
232
- }
233
- style(class: 'fill-layout-style') {
234
- Glimmer::SWT::FillLayoutProxy::STYLE
235
- }
236
- style(class: 'row-layout-style') {
237
- Glimmer::SWT::RowLayoutProxy::STYLE
238
- }
239
- style(class: 'grid-layout-style') {
240
- Glimmer::SWT::GridLayoutProxy::STYLE
241
- }
242
- style(class: 'checkbox-style') {
243
- Glimmer::SWT::CheckboxProxy::STYLE
244
- }
245
- style(class: 'radio-style') {
246
- Glimmer::SWT::RadioProxy::STYLE
247
- }
248
- style(class: 'scrolled-composite-style') {
249
- Glimmer::SWT::ScrolledCompositeProxy::STYLE
250
- }
251
- style(class: 'table-item-style') {
252
- Glimmer::SWT::TableItemProxy::STYLE
253
- }
254
- style(class: 'table-column-style') {
255
- Glimmer::SWT::TableColumnProxy::STYLE
256
- }
94
+ [LayoutProxy, WidgetProxy].map(&:descendants).reduce(:+).each do |style_class|
95
+ if style_class.constants.include?('STYLE')
96
+ style(class: "#{style_class.name.split(':').last.underscore.gsub('_', '-').sub(/-proxy$/, '')}-style") {
97
+ style_class::STYLE
98
+ }
99
+ end
100
+ end
101
+ ''
257
102
  }
258
103
  }.to_s
259
104
  end
@@ -1,8 +1,51 @@
1
+ # Copyright (c) 2020 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
1
22
  require 'glimmer/swt/widget_proxy'
2
23
 
3
24
  module Glimmer
4
25
  module SWT
5
26
  class TabFolderProxy < WidgetProxy
27
+ STYLE = <<~CSS
28
+ .tabs .tab.selected {
29
+ background: rgb(80, 116, 211);
30
+ color: white;
31
+ }
32
+ .tabs .tab {
33
+ background-color: inherit;
34
+ float: left;
35
+ border: none;
36
+ outline: none;
37
+ cursor: pointer;
38
+ padding: 14px 16px;
39
+ transition: 0.3s;
40
+ font-size: 17px;
41
+ }
42
+ .tabs {
43
+ overflow: hidden;
44
+ border: 1px solid #ccc;
45
+ background-color: #f1f1f1;
46
+ }
47
+ CSS
48
+
6
49
  attr_reader :tabs
7
50
 
8
51
  def initialize(parent, args, block)
@@ -4,6 +4,8 @@ require 'glimmer/swt/swt_proxy'
4
4
  module Glimmer
5
5
  module SWT
6
6
  class TableColumnProxy < WidgetProxy
7
+ include Glimmer
8
+
7
9
  STYLE = <<~CSS
8
10
  th.table-column {
9
11
  background: rgb(246, 246, 246);
@@ -15,8 +17,7 @@ module Glimmer
15
17
  float: right;
16
18
  }
17
19
  CSS
18
- include Glimmer
19
-
20
+
20
21
  attr_accessor :sort_block, :sort_by_block
21
22
  attr_reader :text, :width,
22
23
  :no_sort, :sort_property, :editor
@@ -34,7 +34,7 @@ module Glimmer
34
34
  @editor_widget = editor_widget
35
35
  @old_value = table_item.cell_dom_element(table_column_index).html
36
36
  table_item.cell_dom_element(table_column_index).html('')
37
- editor_widget.render(table_item.cell_dom_element(table_column_index))
37
+ editor_widget.render(custom_parent_dom_element: table_item.cell_dom_element(table_column_index))
38
38
  # TODO tweak the width perfectly so it doesn't expand the table cell
39
39
  # editor_widget.dom_element.css('width', 'calc(100% - 20px)')
40
40
  editor_widget.dom_element.css('width', "#{minimumWidth}%") # TODO implement property with pixels (and perhaps derive percentage separately from pixels)
@@ -39,7 +39,7 @@ module Glimmer
39
39
  super(parent, args, block)
40
40
  # TODO check if there is a need to remove this observer when removing widget from table upon items update
41
41
  on_widget_selected { |event|
42
- parent.select(parent.index_of(self), event.meta?)
42
+ parent.select(parent.index_of(self), (event.meta? if event.respond_to?(:meta?)))
43
43
  }
44
44
  end
45
45
 
@@ -147,10 +147,12 @@ module Glimmer
147
147
  end
148
148
  end
149
149
 
150
- def on_widget_selected(&block)
151
- event = 'click'
152
- delegate = $document.on(event, selector, &block)
153
- EventListenerProxy.new(element_proxy: self, event: event, selector: selector, delegate: delegate)
150
+ def observation_request_to_event_mapping
151
+ {
152
+ 'on_widget_selected' => {
153
+ event: 'mouseup',
154
+ }
155
+ }
154
156
  end
155
157
 
156
158
  def max_column_width(column_index)
@@ -27,6 +27,16 @@ require 'glimmer/swt/table_editor'
27
27
  module Glimmer
28
28
  module SWT
29
29
  class TableProxy < CompositeProxy
30
+ STYLE = <<~CSS
31
+ table {
32
+ border-spacing: 0;
33
+ }
34
+
35
+ table tr th,td {
36
+ cursor: default;
37
+ }
38
+ CSS
39
+
30
40
  attr_reader :columns, :selection,
31
41
  :sort_type, :sort_column, :sort_property, :sort_block, :sort_by_block, :additional_sort_properties,
32
42
  :editor, :table_editor
@@ -23,16 +23,20 @@ require 'glimmer/swt/event_listener_proxy'
23
23
  require 'glimmer/swt/property_owner'
24
24
  require 'glimmer/swt/swt_proxy'
25
25
 
26
+ # TODO implement menu (which delays building it till render using add_content_on_render)
27
+
26
28
  module Glimmer
27
29
  module SWT
28
30
  class WidgetProxy
29
31
  include Glimmer
30
32
  include PropertyOwner
31
33
 
32
- attr_reader :parent, :args, :path, :children, :enabled, :foreground, :background, :font, :focus, :disposed?, :rendered
34
+ attr_reader :parent, :args, :path, :children, :enabled, :foreground, :background, :font, :focus, :disposed?, :rendered, :menu_requested
35
+ attr_accessor :menu
33
36
  alias isDisposed disposed?
34
37
  alias is_disposed disposed?
35
38
  alias rendered? rendered
39
+ alias menu_requested? menu_requested
36
40
 
37
41
  class << self
38
42
  # Factory Method that translates a Glimmer DSL keyword into a WidgetProxy object
@@ -76,7 +80,7 @@ module Glimmer
76
80
  end
77
81
 
78
82
  DEFAULT_INITIALIZERS = {
79
- "composite" => lambda do |composite_proxy|
83
+ composite: lambda do |composite_proxy|
80
84
  if composite_proxy.layout.nil?
81
85
  layout = GridLayoutProxy.new(composite_proxy, [])
82
86
  composite_proxy.layout = layout
@@ -84,18 +88,18 @@ module Glimmer
84
88
  layout.margin_height = 15
85
89
  end
86
90
  end,
87
- # "scrolled_composite" => lambda do |scrolled_composite|
91
+ # scrolled_composite: lambda do |scrolled_composite|
88
92
  # scrolled_composite.expand_horizontal = true
89
93
  # scrolled_composite.expand_vertical = true
90
94
  # end,
91
- # "table" => lambda do |table|
95
+ # table: lambda do |table|
92
96
  # table.setHeaderVisible(true)
93
97
  # table.setLinesVisible(true)
94
98
  # end,
95
- "table_column" => lambda do |table_column_proxy|
99
+ table_column: lambda do |table_column_proxy|
96
100
  table_column_proxy.width = 80
97
101
  end,
98
- # "group" => lambda do |group_proxy|
102
+ # group: lambda do |group_proxy|
99
103
  # group_proxy.layout = GridLayoutProxy.new(group_proxy, []) if group.layout.nil?
100
104
  # end,
101
105
  }
@@ -107,7 +111,7 @@ module Glimmer
107
111
  @children = Set.new # TODO consider moving to composite
108
112
  @enabled = true
109
113
  @data = {}
110
- DEFAULT_INITIALIZERS[self.class.underscored_widget_name(self)]&.call(self)
114
+ DEFAULT_INITIALIZERS[self.class.underscored_widget_name(self).to_s.to_sym]&.call(self)
111
115
  @parent.post_initialize_child(self) # TODO rename to post_initialize_child to be closer to glimmer-dsl-swt terminology
112
116
  end
113
117
 
@@ -124,7 +128,19 @@ module Glimmer
124
128
 
125
129
  # Executes at the closing of a parent widget curly braces after all children/properties have been added/set
126
130
  def post_add_content
127
- # No Op by default
131
+ if !menu.nil? && !is_a?(MenuProxy) && !is_a?(MenuItemProxy)
132
+ on_mouse_down { |mouse_event|
133
+ if mouse_event.button == 3 # right-click
134
+ @menu_requested = true
135
+ dom_element.css('position', 'relative')
136
+ menu&.render
137
+ menu.dom_element.css('position', 'absolute')
138
+ menu.dom_element.css('left', mouse_event.x - parent.layout&.margin_width.to_i) # TODO - parent.layout&.margin_left.to_i)
139
+ menu.dom_element.css('top', mouse_event.y - parent.layout&.margin_height.to_i - 5) # TODO - parent.layout&.margin_top.to_i)
140
+ @menu_requested = false
141
+ end
142
+ }
143
+ end
128
144
  end
129
145
 
130
146
  def set_data(key=nil, value)
@@ -148,6 +164,7 @@ module Glimmer
148
164
  Document.find(path).remove
149
165
  parent&.post_dispose_child(self)
150
166
  # TODO fire on_widget_disposed listener
167
+ # children.each(:dispose) # TODO enable this safely
151
168
  @disposed = true
152
169
  end
153
170
 
@@ -160,6 +177,7 @@ module Glimmer
160
177
  event_element_css_selector = mapping[:event_element_css_selector]
161
178
  the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
162
179
  the_listener_dom_element.off(event)
180
+ # TODO improve to precisely remove the listeners that were added, no more no less. (or use the event_listener_proxies method instead or in collaboration)
163
181
  end
164
182
  end
165
183
  end
@@ -215,27 +233,26 @@ module Glimmer
215
233
  Document.find(parent_path)
216
234
  end
217
235
 
218
- def render(custom_parent_dom_element = nil)
236
+ def render(custom_parent_dom_element: nil, brand_new: false)
219
237
  the_parent_dom_element = custom_parent_dom_element || parent_dom_element
220
238
  old_element = dom_element
221
- brand_new = @dom.nil? || old_element.empty?
222
- build_dom(!custom_parent_dom_element) # TODO handle custom parent layout by passing parent instead of parent dom element
239
+ brand_new = @dom.nil? || old_element.empty? || brand_new
240
+ build_dom(layout: !custom_parent_dom_element) # TODO handle custom parent layout by passing parent instead of parent dom element
223
241
  if brand_new
224
242
  the_parent_dom_element.append(@dom)
225
243
  else
226
244
  old_element.replace_with(@dom)
227
245
  end
228
- observation_requests&.clone&.each do |keyword, event_listener_set|
246
+ observation_requests&.each do |keyword, event_listener_set|
229
247
  event_listener_set.each do |event_listener|
230
- observation_requests[keyword].delete(event_listener) # TODO look into the implications of this and if it's needed.
231
- handle_observation_request(keyword, &event_listener)
248
+ handle_observation_request(keyword, event_listener)
232
249
  end
233
250
  end
234
251
  children.each do |child|
235
252
  child.render
236
253
  end
237
254
  @rendered = true
238
- content_on_render_blocks.each { |content_block| content(&content_block) }
255
+ content_on_render_blocks.each { |content_block| content(&content_block) } unless skip_content_on_render_blocks?
239
256
  end
240
257
  alias redraw render
241
258
 
@@ -243,6 +260,10 @@ module Glimmer
243
260
  @content_on_render_blocks ||= []
244
261
  end
245
262
 
263
+ def skip_content_on_render_blocks?
264
+ false
265
+ end
266
+
246
267
  def add_content_on_render(&content_block)
247
268
  if rendered?
248
269
  content_block.call
@@ -251,7 +272,7 @@ module Glimmer
251
272
  end
252
273
  end
253
274
 
254
- def build_dom(layout=true)
275
+ def build_dom(layout: true)
255
276
  # TODO consider passing parent element instead and having table item include a table cell widget only for opal
256
277
  @dom = nil
257
278
  @dom = dom
@@ -273,6 +294,60 @@ module Glimmer
273
294
  end
274
295
 
275
296
  def default_observation_request_to_event_mapping
297
+ mouse_event_handler = -> (event_listener) {
298
+ -> (event) {
299
+ # TODO generalize this solution to all widgets that support key presses
300
+ # TODO support event.location once DOM3 is supported by opal-jquery
301
+ event.define_singleton_method(:button, &event.method(:which))
302
+ event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
303
+ event.define_singleton_method(:x, &event.method(:page_x))
304
+ event.define_singleton_method(:y, &event.method(:page_y))
305
+ doit = true
306
+ event.define_singleton_method(:doit=) do |value|
307
+ doit = value
308
+ end
309
+ event.define_singleton_method(:doit) { doit }
310
+
311
+ if event.which == 1
312
+ # event.prevent # TODO consider if this is needed
313
+ event_listener.call(event)
314
+ end
315
+
316
+ # TODO Imlement doit properly for all different kinds of events
317
+ # unless doit
318
+ # event.prevent
319
+ # event.stop
320
+ # event.stop_immediate
321
+ # end
322
+ }
323
+ }
324
+ context_menu_handler = -> (event_listener) {
325
+ -> (event) {
326
+ # TODO generalize this solution to all widgets that support key presses
327
+ # TODO support event.location once DOM3 is supported by opal-jquery
328
+ event.define_singleton_method(:button, &event.method(:which))
329
+ event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
330
+ event.define_singleton_method(:x, &event.method(:page_x))
331
+ event.define_singleton_method(:y, &event.method(:page_y))
332
+ doit = true
333
+ event.define_singleton_method(:doit=) do |value|
334
+ doit = value
335
+ end
336
+ event.define_singleton_method(:doit) { doit }
337
+
338
+ if event.which == 3
339
+ event.prevent
340
+ event_listener.call(event)
341
+ end
342
+
343
+ # TODO Imlement doit properly for all different kinds of events
344
+ # unless doit
345
+ # event.prevent
346
+ # event.stop
347
+ # event.stop_immediate
348
+ # end
349
+ }
350
+ }
276
351
  {
277
352
  'on_focus_gained' => {
278
353
  event: 'focus',
@@ -280,6 +355,191 @@ module Glimmer
280
355
  'on_focus_lost' => {
281
356
  event: 'blur',
282
357
  },
358
+ 'on_mouse_up' => [
359
+ {
360
+ event: 'mouseup',
361
+ event_handler: mouse_event_handler,
362
+ },
363
+ {
364
+ event: 'contextmenu',
365
+ event_handler: context_menu_handler,
366
+ },
367
+ ],
368
+ 'on_mouse_down' => [
369
+ {
370
+ event: 'mousedown',
371
+ event_handler: mouse_event_handler,
372
+ },
373
+ {
374
+ event: 'contextmenu',
375
+ event_handler: context_menu_handler,
376
+ },
377
+ ],
378
+ 'on_swt_mouseup' => [
379
+ {
380
+ event: 'mouseup',
381
+ event_handler: mouse_event_handler,
382
+ },
383
+ {
384
+ event: 'contextmenu',
385
+ event_handler: context_menu_handler,
386
+ },
387
+ ],
388
+ 'on_swt_mousedown' => [
389
+ {
390
+ event: 'mousedown',
391
+ event_handler: mouse_event_handler,
392
+ },
393
+ {
394
+ event: 'contextmenu',
395
+ event_handler: context_menu_handler,
396
+ },
397
+ ],
398
+ 'on_key_pressed' => {
399
+ event: 'keypress',
400
+ event_handler: -> (event_listener) {
401
+ -> (event) {
402
+ # TODO generalize this solution to all widgets that support key presses
403
+ # TODO support event.location once DOM3 is supported by opal-jquery
404
+ event.define_singleton_method(:keyCode) {event.which}
405
+ event.define_singleton_method(:key_code, &event.method(:keyCode))
406
+ event.define_singleton_method(:character) {event.which.chr}
407
+ event.define_singleton_method(:stateMask) do
408
+ state_mask = 0
409
+ state_mask |= SWTProxy[:alt] if event.alt_key
410
+ state_mask |= SWTProxy[:ctrl] if event.ctrl_key
411
+ state_mask |= SWTProxy[:shift] if event.shift_key
412
+ state_mask |= SWTProxy[:command] if event.meta_key
413
+ state_mask
414
+ end
415
+ event.define_singleton_method(:state_mask, &event.method(:stateMask))
416
+ doit = true
417
+ event.define_singleton_method(:doit=) do |value|
418
+ doit = value
419
+ end
420
+ event.define_singleton_method(:doit) { doit }
421
+ event_listener.call(event)
422
+
423
+ # TODO Fix doit false, it's not stopping input
424
+ unless doit
425
+ event.prevent
426
+ event.prevent_default
427
+ event.stop_propagation
428
+ event.stop_immediate_propagation
429
+ end
430
+
431
+ doit
432
+ }
433
+ } },
434
+ 'on_key_released' => {
435
+ event: 'keydown',
436
+ event_handler: -> (event_listener) {
437
+ -> (event) {
438
+ # TODO generalize this solution to all widgets that support key presses
439
+ # TODO support event.location once DOM3 is supported by opal-jquery
440
+ event.define_singleton_method(:keyCode) {event.which}
441
+ event.define_singleton_method(:key_code, &event.method(:keyCode))
442
+ event.define_singleton_method(:character) {event.which.chr}
443
+ event.define_singleton_method(:stateMask) do
444
+ state_mask = 0
445
+ state_mask |= SWTProxy[:alt] if event.alt_key
446
+ state_mask |= SWTProxy[:ctrl] if event.ctrl_key
447
+ state_mask |= SWTProxy[:shift] if event.shift_key
448
+ state_mask |= SWTProxy[:command] if event.meta_key
449
+ state_mask
450
+ end
451
+ event.define_singleton_method(:state_mask, &event.method(:stateMask))
452
+ doit = true
453
+ event.define_singleton_method(:doit=) do |value|
454
+ doit = value
455
+ end
456
+ event.define_singleton_method(:doit) { doit }
457
+ event_listener.call(event)
458
+
459
+ # TODO Fix doit false, it's not stopping input
460
+ unless doit
461
+ event.prevent
462
+ event.prevent_default
463
+ event.stop_propagation
464
+ event.stop_immediate_propagation
465
+ end
466
+
467
+ doit
468
+ }
469
+ } },
470
+ 'on_swt_keydown' => {
471
+ event: 'keypress',
472
+ event_handler: -> (event_listener) {
473
+ -> (event) {
474
+ # TODO generalize this solution to all widgets that support key presses
475
+ # TODO support event.location once DOM3 is supported by opal-jquery
476
+ event.define_singleton_method(:keyCode) {event.which}
477
+ event.define_singleton_method(:key_code, &event.method(:keyCode))
478
+ event.define_singleton_method(:character) {event.which.chr}
479
+ event.define_singleton_method(:stateMask) do
480
+ state_mask = 0
481
+ state_mask |= SWTProxy[:alt] if event.alt_key
482
+ state_mask |= SWTProxy[:ctrl] if event.ctrl_key
483
+ state_mask |= SWTProxy[:shift] if event.shift_key
484
+ state_mask |= SWTProxy[:command] if event.meta_key
485
+ state_mask
486
+ end
487
+ event.define_singleton_method(:state_mask, &event.method(:stateMask))
488
+ doit = true
489
+ event.define_singleton_method(:doit=) do |value|
490
+ doit = value
491
+ end
492
+ event.define_singleton_method(:doit) { doit }
493
+ event_listener.call(event)
494
+
495
+ # TODO Fix doit false, it's not stopping input
496
+ unless doit
497
+ event.prevent
498
+ event.prevent_default
499
+ event.stop_propagation
500
+ event.stop_immediate_propagation
501
+ end
502
+
503
+ doit
504
+ }
505
+ } },
506
+ 'on_swt_keyup' => {
507
+ event: 'keydown',
508
+ event_handler: -> (event_listener) {
509
+ -> (event) {
510
+ # TODO generalize this solution to all widgets that support key presses
511
+ # TODO support event.location once DOM3 is supported by opal-jquery
512
+ event.define_singleton_method(:keyCode) {event.which}
513
+ event.define_singleton_method(:key_code, &event.method(:keyCode))
514
+ event.define_singleton_method(:character) {event.which.chr}
515
+ event.define_singleton_method(:stateMask) do
516
+ state_mask = 0
517
+ state_mask |= SWTProxy[:alt] if event.alt_key
518
+ state_mask |= SWTProxy[:ctrl] if event.ctrl_key
519
+ state_mask |= SWTProxy[:shift] if event.shift_key
520
+ state_mask |= SWTProxy[:command] if event.meta_key
521
+ state_mask
522
+ end
523
+ event.define_singleton_method(:state_mask, &event.method(:stateMask))
524
+ doit = true
525
+ event.define_singleton_method(:doit=) do |value|
526
+ doit = value
527
+ end
528
+ event.define_singleton_method(:doit) { doit }
529
+ event_listener.call(event)
530
+
531
+ # TODO Fix doit false, it's not stopping input
532
+ unless doit
533
+ event.prevent
534
+ event.prevent_default
535
+ event.stop_propagation
536
+ event.stop_immediate_propagation
537
+ end
538
+
539
+ doit
540
+ }
541
+ }
542
+ },
283
543
  }
284
544
  end
285
545
 
@@ -354,6 +614,14 @@ module Glimmer
354
614
  Document.find(listener_path)
355
615
  end
356
616
 
617
+ def observation_requests
618
+ @observation_requests ||= {}
619
+ end
620
+
621
+ def event_listener_proxies
622
+ @event_listener_proxies ||= []
623
+ end
624
+
357
625
  def can_handle_observation_request?(observation_request)
358
626
  # TODO sort this out for Opal
359
627
  observation_request = observation_request.to_s
@@ -367,32 +635,40 @@ module Glimmer
367
635
  end
368
636
  end
369
637
 
370
- def observation_requests
371
- @observation_requests ||= {}
372
- end
373
-
374
- def handle_observation_request(keyword, &event_listener)
638
+ def handle_observation_request(keyword, original_event_listener)
375
639
  return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
376
640
  event = nil
377
641
  delegate = nil
378
642
  effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
379
643
  observation_requests[keyword] ||= Set.new
380
- observation_requests[keyword] << event_listener
644
+ observation_requests[keyword] << original_event_listener
381
645
  event = mapping[:event]
382
646
  event_handler = mapping[:event_handler]
383
647
  event_element_css_selector = mapping[:event_element_css_selector]
384
- potential_event_listener = event_handler&.call(event_listener)
385
- event_listener = potential_event_listener || event_listener
648
+ potential_event_listener = event_handler&.call(original_event_listener)
649
+ event_listener = potential_event_listener || original_event_listener
386
650
  async_event_listener = lambda do |event|
387
- Async::Task.new do
651
+ # TODO look into the issue with using async::task.new here. maybe put it in event listener (like not being able to call preventDefault or return false successfully )
652
+ # maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
653
+ # Async::Task.new do
388
654
  event_listener.call(event)
389
- end
655
+ # end
390
656
  end
391
657
  the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
392
- delegate = the_listener_dom_element.on(event, &async_event_listener)
658
+ unless the_listener_dom_element.empty?
659
+ the_listener_dom_element.on(event, &async_event_listener)
660
+ # TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
661
+
662
+ event_listener_proxies << EventListenerProxy.new(element_proxy: self, selector: selector, dom_element: the_listener_dom_element, event: event, listener: async_event_listener, original_event_listener: original_event_listener)
663
+ end
393
664
  end
394
- # TODO update code below for new WidgetProxy API
395
- EventListenerProxy.new(element_proxy: self, event: event, selector: selector, delegate: delegate)
665
+ end
666
+
667
+ def remove_event_listener_proxies
668
+ event_listener_proxies.each do |event_listener_proxy|
669
+ event_listener_proxy.unregister
670
+ end
671
+ event_listener_proxies.clear
396
672
  end
397
673
 
398
674
  def add_observer(observer, property_name)
@@ -410,12 +686,17 @@ module Glimmer
410
686
 
411
687
  def method_missing(method, *args, &block)
412
688
  if method.to_s.start_with?('on_')
413
- handle_observation_request(method, &block)
689
+ handle_observation_request(method, block)
414
690
  else
415
691
  super(method, *args, &block)
416
692
  end
417
693
  end
418
694
 
695
+ def swt_widget
696
+ # only added for compatibility/adaptibility with Glimmer DSL for SWT
697
+ self
698
+ end
699
+
419
700
  def apply_property_type_converters(attribute_name, args)
420
701
  if args.count == 1
421
702
  value = args.first
@@ -608,6 +889,8 @@ require 'glimmer/swt/date_time_proxy'
608
889
  require 'glimmer/swt/group_proxy'
609
890
  require 'glimmer/swt/label_proxy'
610
891
  require 'glimmer/swt/list_proxy'
892
+ require 'glimmer/swt/menu_item_proxy'
893
+ require 'glimmer/swt/menu_proxy'
611
894
  require 'glimmer/swt/radio_proxy'
612
895
  require 'glimmer/swt/tab_folder_proxy'
613
896
  require 'glimmer/swt/tab_item_proxy'