glimmer-dsl-swt 4.18.2.5 → 4.18.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/README.md +344 -35
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +13 -7
  6. data/lib/ext/glimmer/config.rb +24 -7
  7. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  8. data/lib/glimmer/data_binding/widget_binding.rb +22 -4
  9. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  10. data/lib/glimmer/dsl/swt/image_expression.rb +14 -6
  11. data/lib/glimmer/dsl/swt/layout_data_expression.rb +4 -4
  12. data/lib/glimmer/dsl/swt/layout_expression.rb +5 -3
  13. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -0
  14. data/lib/glimmer/dsl/swt/property_expression.rb +4 -2
  15. data/{samples/elaborate/tetris/view/game_over_dialog.rb → lib/glimmer/dsl/swt/transform_expression.rb} +29 -46
  16. data/lib/glimmer/swt/custom/drawable.rb +4 -3
  17. data/lib/glimmer/swt/custom/shape.rb +37 -14
  18. data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
  19. data/lib/glimmer/swt/display_proxy.rb +26 -5
  20. data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
  21. data/lib/glimmer/swt/image_proxy.rb +68 -1
  22. data/lib/glimmer/swt/shell_proxy.rb +23 -3
  23. data/lib/glimmer/swt/table_proxy.rb +31 -7
  24. data/lib/glimmer/swt/transform_proxy.rb +109 -0
  25. data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
  26. data/lib/glimmer/swt/widget_proxy.rb +7 -3
  27. data/lib/glimmer/ui/custom_shell.rb +11 -9
  28. data/lib/glimmer/ui/custom_widget.rb +32 -17
  29. data/samples/elaborate/meta_sample.rb +81 -24
  30. data/samples/elaborate/tetris.rb +146 -42
  31. data/samples/elaborate/tetris/model/game.rb +258 -137
  32. data/samples/elaborate/tetris/model/past_game.rb +26 -0
  33. data/samples/elaborate/tetris/model/tetromino.rb +45 -29
  34. data/samples/elaborate/tetris/view/block.rb +8 -13
  35. data/samples/elaborate/tetris/view/high_score_dialog.rb +133 -0
  36. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  37. data/samples/elaborate/tetris/view/score_lane.rb +11 -11
  38. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +139 -0
  39. data/samples/elaborate/tic_tac_toe.rb +4 -4
  40. data/samples/hello/hello_canvas.rb +4 -4
  41. data/samples/hello/hello_canvas_animation.rb +3 -3
  42. data/samples/hello/hello_canvas_transform.rb +40 -0
  43. data/samples/hello/hello_link.rb +1 -1
  44. metadata +11 -5
@@ -0,0 +1,109 @@
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/display_proxy'
23
+ require 'glimmer/swt/properties'
24
+ require 'glimmer/swt/custom/shape'
25
+
26
+ module Glimmer
27
+ module SWT
28
+ # Proxy for org.eclipse.swt.graphics.Transform
29
+ #
30
+ # Follows the Proxy Design Pattern
31
+ class TransformProxy
32
+ include Properties
33
+
34
+ include_package 'org.eclipse.swt.graphics'
35
+ include_package 'org.eclipse.swt.widgets'
36
+
37
+ attr_reader :swt_transform, :parent
38
+
39
+ def initialize(parent, *args, swt_transform: nil, multiply: false)
40
+ @parent = parent
41
+ @multiply = multiply
42
+ if swt_transform.nil?
43
+ if !args.first.is_a?(Display) && !args.first.is_a?(DisplayProxy)
44
+ args.prepend DisplayProxy.instance.swt_display
45
+ end
46
+ if args.first.is_a?(DisplayProxy)
47
+ args[0] = args[0].swt_display
48
+ end
49
+ if args.last.is_a?(TransformProxy)
50
+ args[-1] = args[-1].swt_transform
51
+ end
52
+ if args.last.nil? || args.last.is_a?(Transform)
53
+ @swt_transform = args.last
54
+ @parent&.set_attribute('transform', self)
55
+ else
56
+ @swt_transform = Transform.new(*args)
57
+ end
58
+ else
59
+ @swt_transform = swt_transform
60
+ end
61
+ end
62
+
63
+ def post_add_content
64
+ if @multiply
65
+ @parent.multiply(@swt_transform)
66
+ else
67
+ @parent&.set_attribute('transform', self)
68
+ end
69
+ end
70
+
71
+ def content(&block)
72
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::TransformExpression.new, &block)
73
+ end
74
+
75
+ def has_attribute?(attribute_name, *args)
76
+ @swt_transform.respond_to?(attribute_name) || @swt_transform.respond_to?(attribute_setter(attribute_name))
77
+ end
78
+
79
+ def set_attribute(attribute_name, *args)
80
+ if @swt_transform.respond_to?(attribute_name)
81
+ @swt_transform.send(attribute_name, *args)
82
+ elsif @swt_transform.respond_to?(attribute_setter(attribute_name))
83
+ @swt_transform.send(attribute_setter(attribute_name), *args)
84
+ end
85
+ end
86
+
87
+ def get_attribute(attribute_name)
88
+ if @swt_transform.respond_to?(attribute_getter(attribute_name))
89
+ @swt_transform.send(attribute_getter(attribute_name))
90
+ else
91
+ @swt_transform.send(attribute_name)
92
+ end
93
+ end
94
+
95
+ def method_missing(method_name, *args, &block)
96
+ result = @swt_transform.send(method_name, *args, &block)
97
+ result.nil? ? self : result
98
+ rescue => e
99
+ Glimmer::Config.logger.debug {"Neither MessageBoxProxy nor #{@swt_transform.class.name} can handle the method ##{method}"}
100
+ super
101
+ end
102
+
103
+ def respond_to?(method, *args, &block)
104
+ super ||
105
+ @swt_transform.respond_to?(method, *args, &block)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -26,10 +26,12 @@ module Glimmer
26
26
  # Follows the Proxy Design Pattern
27
27
  class WidgetListenerProxy
28
28
 
29
- attr_reader :swt_widget, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
29
+ attr_reader :swt_widget, :swt_display, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
30
30
 
31
- def initialize(swt_widget:, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil)
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
32
  @swt_widget = swt_widget
33
+ @swt_display = swt_display
34
+ @filter = filter
33
35
  @swt_listener = swt_listener
34
36
  @widget_add_listener_method = widget_add_listener_method
35
37
  @swt_listener_class = swt_listener_class
@@ -42,14 +44,21 @@ module Glimmer
42
44
  @widget_add_listener_method.sub('add', 'remove')
43
45
  end
44
46
 
45
- def unregister
46
- # TODO consider renaming to deregister (and in Observer too)
47
- if @event_type
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
48
56
  @swt_widget.removeListener(@event_type, @swt_listener)
49
57
  else
50
58
  @swt_widget.send(widget_remove_listener_method, @swt_listener)
51
59
  end
52
60
  end
61
+ alias unregister deregister # TODO consider dropping unregister (and in Observer too)
53
62
  end
54
63
  end
55
64
  end
@@ -77,7 +77,7 @@ module Glimmer
77
77
  composite.layout = GridLayout.new if composite.get_layout.nil?
78
78
  end,
79
79
  canvas: lambda do |canvas|
80
- canvas.layout = nil unless canvas.get_layout.nil?
80
+ canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
81
81
  end,
82
82
  scrolled_composite: lambda do |scrolled_composite|
83
83
  scrolled_composite.expand_horizontal = true
@@ -547,6 +547,10 @@ module Glimmer
547
547
  @swt_widget.dispose
548
548
  end
549
549
 
550
+ def disposed?
551
+ @swt_widget.isDisposed
552
+ end
553
+
550
554
  # TODO Consider renaming these methods as they are mainly used for data-binding
551
555
 
552
556
  def can_add_observer?(property_name)
@@ -663,7 +667,7 @@ module Glimmer
663
667
  can_handle_observation_request?(method) ||
664
668
  swt_widget.respond_to?(method, *args, &block)
665
669
  end
666
-
670
+
667
671
  private
668
672
 
669
673
  def style(underscored_widget_name, styles)
@@ -696,7 +700,7 @@ module Glimmer
696
700
  safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
697
701
  listener = listener_class.new(listener_method => safe_block)
698
702
  @swt_widget.send(widget_add_listener_method, listener)
699
- widget_listener_proxy = 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)
703
+ WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
700
704
  end
701
705
 
702
706
  # Looks through SWT class add***Listener methods till it finds one for which
@@ -28,15 +28,12 @@ module Glimmer
28
28
  include Glimmer::UI::CustomWidget
29
29
 
30
30
  class << self
31
- attr_reader :custom_shell
31
+ attr_reader :launched_custom_shell
32
32
 
33
- def launch
34
- @custom_shell = send(self.name.underscore.gsub('::', '__'))
35
- @custom_shell.open
36
- end
37
-
38
- def shutdown
39
- @custom_shell.close
33
+ def launch(*args, &content)
34
+ @launched_custom_shell = send(keyword, *args, &content) if @launched_custom_shell.nil? || @launched_custom_shell.disposed?
35
+ @launched_custom_shell.swt_widget.set_data('launched', true)
36
+ @launched_custom_shell.open
40
37
  end
41
38
  end
42
39
 
@@ -45,7 +42,7 @@ module Glimmer
45
42
  @swt_widget.set_data('custom_shell', self)
46
43
  raise Error, 'Invalid custom shell body root! Must be a shell or another custom shell.' unless body_root.swt_widget.is_a?(org.eclipse.swt.widgets.Shell)
47
44
  end
48
-
45
+
49
46
  # Classes may override
50
47
  def open
51
48
  body_root.open
@@ -56,6 +53,7 @@ module Glimmer
56
53
  open
57
54
  end
58
55
 
56
+ # TODO consider using Forwardable instead
59
57
  def close
60
58
  body_root.close
61
59
  end
@@ -68,6 +66,10 @@ module Glimmer
68
66
  body_root.visible?
69
67
  end
70
68
 
69
+ def disposed?
70
+ swt_widget.is_disposed
71
+ end
72
+
71
73
  def center_within_display
72
74
  body_root.center_within_display
73
75
  end
@@ -77,7 +77,17 @@ module Glimmer
77
77
  def flyweight_custom_widget_classes
78
78
  @flyweight_custom_widget_classes ||= {}
79
79
  end
80
-
80
+
81
+ # Returns keyword to use for this custom widget
82
+ def keyword
83
+ self.name.underscore.gsub('::', '__')
84
+ end
85
+
86
+ # Returns shortcut keyword to use for this custom widget (keyword minus namespace)
87
+ def shortcut_keyword
88
+ self.name.underscore.gsub('::', '__').split('__').last
89
+ end
90
+
81
91
  def add_custom_widget_namespaces_for(klass)
82
92
  Glimmer::UI::CustomWidget.namespaces_for_class(klass).drop(1).each do |namespace|
83
93
  Glimmer::UI::CustomWidget.custom_widget_namespaces << namespace
@@ -138,8 +148,7 @@ module Glimmer
138
148
  end
139
149
 
140
150
  def before_body(&block)
141
- @before_body_blocks ||= []
142
- @before_body_blocks << block
151
+ @before_body_block = block
143
152
  end
144
153
 
145
154
  def body(&block)
@@ -147,29 +156,27 @@ module Glimmer
147
156
  end
148
157
 
149
158
  def after_body(&block)
150
- @after_body_blocks ||= []
151
- @after_body_blocks << block
159
+ @after_body_block = block
152
160
  end
153
161
  end
154
162
 
155
163
  attr_reader :body_root, :swt_widget, :parent, :parent_proxy, :swt_style, :options
156
164
 
157
165
  def initialize(parent, *swt_constants, options, &content)
166
+ @parent_proxy = @parent = parent
167
+ @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
158
168
  @swt_style = SWT::SWTProxy[*swt_constants]
159
169
  options ||= {}
160
170
  @options = self.class.options.merge(options)
161
171
  @content = Util::ProcTracker.new(content) if content
162
- execute_hooks('before_body')
172
+ execute_hook('before_body')
163
173
  body_block = self.class.instance_variable_get("@body_block")
164
174
  raise Glimmer::Error, 'Invalid custom widget for having no body! Please define body block!' if body_block.nil?
165
175
  @body_root = instance_exec(&body_block)
166
176
  raise Glimmer::Error, 'Invalid custom widget for having an empty body! Please fill body block!' if @body_root.nil?
167
177
  @swt_widget = @body_root.swt_widget
168
178
  @swt_widget.set_data('custom_widget', self)
169
- @parent = parent
170
- @parent ||= @swt_widget.parent
171
- @parent_proxy ||= @parent&.get_data('proxy')
172
- execute_hooks('after_body')
179
+ execute_hook('after_body')
173
180
  end
174
181
 
175
182
  # Subclasses may override to perform post initialization work on an added child
@@ -242,10 +249,18 @@ module Glimmer
242
249
  def attribute_setter(attribute_name)
243
250
  "#{attribute_name}="
244
251
  end
252
+
253
+ def disposed?
254
+ swt_widget.isDisposed
255
+ end
245
256
 
246
257
  def has_style?(style)
247
258
  (swt_style & SWT::SWTProxy[style]) == SWT::SWTProxy[style]
248
259
  end
260
+
261
+ def pack(*args)
262
+ body_root.pack(*args)
263
+ end
249
264
 
250
265
  # TODO see if it is worth it to eliminate duplication of async_exec/sync_exec
251
266
  # delegation to DisplayProxy, via a module
@@ -287,13 +302,13 @@ module Glimmer
287
302
 
288
303
  private
289
304
 
290
- def execute_hooks(hook_name)
291
- self.class.instance_variable_get("@#{hook_name}_blocks")&.each do |hook_block|
292
- temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
293
- singleton_class.define_method(temp_method_name, &hook_block)
294
- send(temp_method_name)
295
- singleton_class.send(:remove_method, temp_method_name)
296
- end
305
+ def execute_hook(hook_name)
306
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
307
+ return if hook_block.nil?
308
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
309
+ singleton_class.define_method(temp_method_name, &hook_block)
310
+ send(temp_method_name)
311
+ singleton_class.send(:remove_method, temp_method_name)
297
312
  end
298
313
  end
299
314
  end
@@ -25,6 +25,24 @@ require 'etc'
25
25
  class Sample
26
26
  include Glimmer::DataBinding::ObservableModel
27
27
 
28
+ class << self
29
+ def glimmer_directory
30
+ File.expand_path('../../..', __FILE__)
31
+ end
32
+
33
+ def user_glimmer_directory
34
+ File.join(Etc.getpwuid.dir, '.glimmer-dsl-swt')
35
+ end
36
+
37
+ def ensure_user_glimmer_directory
38
+ unless @ensured_glimmer_directory
39
+ FileUtils.rm_rf(user_glimmer_directory)
40
+ FileUtils.cp_r(glimmer_directory, user_glimmer_directory)
41
+ @ensured_glimmer_directory = true
42
+ end
43
+ end
44
+ end
45
+
28
46
  attr_accessor :sample_directory, :file, :selected
29
47
 
30
48
  def initialize(file, sample_directory: )
@@ -59,25 +77,35 @@ class Sample
59
77
  File.basename(file) != 'meta_sample.rb'
60
78
  end
61
79
  alias launchable editable
80
+
81
+ def file_relative_path
82
+ file.sub(self.class.glimmer_directory, '')
83
+ end
84
+
85
+ def user_file
86
+ File.join(self.class.user_glimmer_directory, file_relative_path)
87
+ end
88
+
89
+ def user_file_parent_directory
90
+ File.dirname(user_file)
91
+ end
62
92
 
93
+ def directory
94
+ file.sub(/\.rb/, '')
95
+ end
96
+
63
97
  def launch(modified_code)
64
- parent_directory = File.basename(File.dirname(file))
65
- modified_file_parent_directory = File.join(Etc.getpwuid.dir, '.glimmer', 'samples', parent_directory)
66
- launch_file = modified_file = File.join(modified_file_parent_directory, File.basename(file))
98
+ launch_file = user_file
67
99
  begin
68
- FileUtils.mkdir_p(modified_file_parent_directory)
69
- FileUtils.cp_r(file, modified_file_parent_directory)
70
- FileUtils.cp_r(file.sub(/\.rb/, ''), modified_file_parent_directory) if File.exist?(file.sub(/\.rb/, ''))
71
- File.write(modified_file, modified_code)
100
+ FileUtils.cp_r(file, user_file_parent_directory)
101
+ FileUtils.cp_r(directory, user_file_parent_directory) if File.exist?(directory)
102
+ File.write(user_file, modified_code)
72
103
  rescue => e
73
104
  puts 'Error writing sample modifications. Launching original sample.'
74
105
  puts e.full_message
75
106
  launch_file = file # load original file if failed to write changes
76
107
  end
77
108
  load(launch_file)
78
- ensure
79
- FileUtils.rm_rf(modified_file)
80
- FileUtils.rm_rf(modified_file.sub(/\.rb/, ''))
81
109
  end
82
110
  end
83
111
 
@@ -162,17 +190,18 @@ class SampleDirectory
162
190
  end
163
191
 
164
192
  class MetaSampleApplication
165
- include Glimmer
193
+ include Glimmer::UI::CustomShell
166
194
 
167
- def initialize
195
+ before_body {
196
+ Sample.ensure_user_glimmer_directory
168
197
  selected_sample_directory = SampleDirectory.sample_directories.first
169
198
  selected_sample = selected_sample_directory.samples.first
170
199
  selected_sample_directory.selected_sample_name = selected_sample.name
171
- end
172
-
173
- def launch
174
200
  Display.app_name = 'Glimmer Meta-Sample'
175
- shell {
201
+ }
202
+
203
+ body {
204
+ shell(:fill_screen) {
176
205
  minimum_size 1280, 768
177
206
  text 'Glimmer Meta-Sample (The Sample of Samples)'
178
207
  image File.expand_path('../../icons/scaffold_app.png', __dir__)
@@ -218,11 +247,8 @@ class MetaSampleApplication
218
247
  on_widget_selected {
219
248
  begin
220
249
  SampleDirectory.selected_sample.launch(@code_text.text)
221
- rescue StandardError, SyntaxError => launch_error
222
- message_box {
223
- text 'Error Launching'
224
- message launch_error.full_message
225
- }.open
250
+ rescue LoadError, StandardError, SyntaxError => launch_error
251
+ error_dialog(message: launch_error.full_message).open
226
252
  end
227
253
  }
228
254
  }
@@ -243,10 +269,41 @@ class MetaSampleApplication
243
269
  editable bind(SampleDirectory, 'selected_sample.editable')
244
270
  }
245
271
 
246
- weights 4, 9
272
+ weights 4, 11
273
+ }
274
+ }
275
+ }
276
+
277
+ # Method-based error_dialog custom widget
278
+ def error_dialog(message:)
279
+ return if message.nil?
280
+ dialog(body_root) { |dialog_proxy|
281
+ row_layout(:vertical) {
282
+ center true
283
+ }
284
+
285
+ text 'Error Launching'
286
+
287
+ styled_text(:border, :h_scroll, :v_scroll) {
288
+ layout_data {
289
+ width body_root.bounds.width*0.75
290
+ height body_root.bounds.height*0.75
291
+ }
292
+
293
+ text message
294
+ editable false
295
+ caret nil
296
+ }
297
+
298
+ button {
299
+ text 'Close'
300
+
301
+ on_widget_selected {
302
+ dialog_proxy.close
303
+ }
247
304
  }
248
- }.open
305
+ }
249
306
  end
250
307
  end
251
308
 
252
- MetaSampleApplication.new.launch
309
+ MetaSampleApplication.launch