glimmer-dsl-opal 0.7.5 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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'