glimmer-dsl-swt 4.18.3.2 → 4.18.4.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +674 -323
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +7 -3
  6. data/lib/ext/rouge/themes/glimmer.rb +29 -0
  7. data/lib/glimmer-dsl-swt.rb +0 -1
  8. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  9. data/lib/glimmer/data_binding/widget_binding.rb +9 -1
  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/swt/custom/code_text.rb +196 -51
  14. data/lib/glimmer/swt/custom/drawable.rb +4 -6
  15. data/lib/glimmer/swt/custom/shape.rb +69 -12
  16. data/lib/glimmer/swt/date_time_proxy.rb +1 -3
  17. data/lib/glimmer/swt/display_proxy.rb +12 -1
  18. data/lib/glimmer/swt/font_proxy.rb +1 -0
  19. data/lib/glimmer/swt/image_proxy.rb +79 -1
  20. data/lib/glimmer/swt/layout_proxy.rb +4 -1
  21. data/lib/glimmer/swt/shell_proxy.rb +10 -1
  22. data/lib/glimmer/swt/table_proxy.rb +27 -14
  23. data/lib/glimmer/swt/widget_proxy.rb +12 -2
  24. data/lib/glimmer/ui/custom_widget.rb +6 -2
  25. data/samples/elaborate/meta_sample.rb +5 -2
  26. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
  27. data/samples/elaborate/tetris.rb +60 -7
  28. data/samples/elaborate/tetris/model/game.rb +36 -7
  29. data/samples/elaborate/tetris/model/past_game.rb +14 -1
  30. data/samples/elaborate/tetris/model/tetromino.rb +18 -5
  31. data/samples/elaborate/tetris/view/block.rb +8 -13
  32. data/samples/elaborate/tetris/view/high_score_dialog.rb +23 -6
  33. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  34. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +22 -6
  35. data/samples/hello/hello_canvas.rb +10 -9
  36. data/samples/hello/hello_canvas_animation.rb +5 -5
  37. data/samples/hello/hello_code_text.rb +104 -0
  38. data/samples/hello/hello_table.rb +6 -4
  39. data/samples/hello/hello_table/baseball_park.png +0 -0
  40. metadata +6 -2
@@ -29,17 +29,15 @@ module Glimmer
29
29
  end
30
30
 
31
31
  def clear_shapes
32
- shapes.dup.each do |shape|
33
- shape.paint_listener_proxy&.unregister
34
- shapes.delete(shape)
35
- end
32
+ shapes.dup.each(&:dispose)
36
33
  end
37
34
 
38
- def resetup_shape_paint_listeners
35
+ def resetup_shape_painting
39
36
  # TODO consider performance optimization relating to order of shape rendering (affecting only further shapes not previous ones)
37
+ reset_gc if respond_to?(:reset_gc)
40
38
  shapes.each do |shape|
41
39
  shape.paint_listener_proxy&.unregister
42
- shape.setup_paint_listener
40
+ shape.setup_painting
43
41
  end
44
42
  end
45
43
  end
@@ -30,7 +30,6 @@ module Glimmer
30
30
  module SWT
31
31
  module Custom
32
32
  # Represents a shape (graphics) to be drawn on a control/widget/canvas/display
33
- # swt_widget returns the parent (e.g. a `canvas` WidgetProxy), equivalent to `parent.swt_widget`
34
33
  # That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
35
34
  class Shape
36
35
  include Packages
@@ -78,9 +77,21 @@ module Glimmer
78
77
  def flyweight_method_names
79
78
  @flyweight_method_names ||= {}
80
79
  end
80
+
81
+ def pattern(*args)
82
+ found_pattern = flyweigh_patterns[args]
83
+ if found_pattern.nil? || found_pattern.is_disposed
84
+ found_pattern = flyweigh_patterns[args] = org.eclipse.swt.graphics.Pattern.new(*args)
85
+ end
86
+ found_pattern
87
+ end
88
+
89
+ def flyweigh_patterns
90
+ @flyweigh_patterns ||= {}
91
+ end
81
92
  end
82
93
 
83
- attr_reader :parent, :name, :args, :options, :swt_widget, :paint_listener_proxy
94
+ attr_reader :parent, :name, :args, :options, :paint_listener_proxy
84
95
 
85
96
  def initialize(parent, keyword, *args, &property_block)
86
97
  @parent = parent
@@ -88,7 +99,6 @@ module Glimmer
88
99
  @method_name = self.class.method_name(keyword, args)
89
100
  @options = self.class.arg_options(args, extract: true)
90
101
  @args = args
91
- @swt_widget = parent.respond_to?(:swt_display) ? parent.swt_display : parent.swt_widget
92
102
  @properties = {}
93
103
  @parent.shapes << self
94
104
  post_add_content if property_block.nil?
@@ -110,8 +120,17 @@ module Glimmer
110
120
  @options[:round]
111
121
  end
112
122
 
123
+ def has_some_background?
124
+ @properties.keys.map(&:to_s).include?('background') || @properties.keys.map(&:to_s).include?('background_pattern')
125
+ end
126
+
127
+ def has_some_foreground?
128
+ @properties.keys.map(&:to_s).include?('foreground') || @properties.keys.map(&:to_s).include?('foreground_pattern')
129
+ end
130
+
113
131
  def post_add_content
114
- setup_paint_listener
132
+ amend_method_name_options_based_on_properties
133
+ setup_painting
115
134
  @content_added = true
116
135
  end
117
136
 
@@ -147,7 +166,7 @@ module Glimmer
147
166
  end
148
167
  end
149
168
  new_args = [DisplayProxy.instance.swt_display] + args
150
- args[0] = org.eclipse.swt.graphics.Pattern.new(*new_args)
169
+ args[0] = pattern(*new_args, type: method_name.to_s.match(/set(.+)Pattern/)[1])
151
170
  args[1..-1] = []
152
171
  end
153
172
  args
@@ -172,7 +191,7 @@ module Glimmer
172
191
  args[0] = ImageProxy.new(args[0])
173
192
  end
174
193
  if method_name.include?('image') && args.first.is_a?(ImageProxy)
175
- args[0] = args[0].swt_image
194
+ @image = args[0] = args[0].swt_image
176
195
  end
177
196
  end
178
197
 
@@ -186,6 +205,21 @@ module Glimmer
186
205
  args[the_java_method_arg_count..-1] = []
187
206
  end
188
207
  end
208
+
209
+ def amend_method_name_options_based_on_properties
210
+ if has_some_background? && !has_some_foreground?
211
+ @options[:fill] = true
212
+ elsif !has_some_background? && has_some_foreground?
213
+ @options[:fill] = false
214
+ elsif @name == 'rectangle' && has_some_background? && has_some_foreground?
215
+ @options[:fill] = true
216
+ @options[:gradient] = true
217
+ end
218
+ if @name == 'rectangle' && @args.size > 4 && @args.last.is_a?(Numeric)
219
+ @options[:round] = true
220
+ end
221
+ @method_name = self.class.method_name(@name, @args + [@options])
222
+ end
189
223
 
190
224
  def has_attribute?(attribute_name, *args)
191
225
  self.class.gc_instance_methods.include?(attribute_setter(attribute_name))
@@ -194,7 +228,7 @@ module Glimmer
194
228
  def set_attribute(attribute_name, *args)
195
229
  @properties[attribute_name] = args
196
230
  if @content_added && !@parent.is_disposed
197
- @parent.resetup_shape_paint_listeners
231
+ @parent.resetup_shape_painting
198
232
  @parent.redraw
199
233
  end
200
234
  end
@@ -203,20 +237,43 @@ module Glimmer
203
237
  @properties.symbolize_keys[attribute_name.to_s.to_sym]
204
238
  end
205
239
 
206
- def setup_paint_listener
240
+ def pattern(*args, type: nil)
241
+ instance_variable_name = "@#{type}_pattern"
242
+ the_pattern = instance_variable_get(instance_variable_name)
243
+ if the_pattern.nil?
244
+ the_pattern = self.class.pattern(*args)
245
+ end
246
+ the_pattern
247
+ end
248
+
249
+ def dispose
250
+ paint_listener_proxy&.unregister
251
+ @background_pattern&.dispose
252
+ @background_pattern = nil
253
+ @foreground_pattern&.dispose
254
+ @foreground_pattern = nil
255
+ @image&.dispose
256
+ @image = nil
257
+ @parent.shapes.delete(self)
258
+ end
259
+
260
+ def setup_painting
261
+ # TODO consider moving this method to parent (making the logic polymorphic)
207
262
  return if @parent.is_disposed
208
263
  if parent.respond_to?(:swt_display)
209
264
  @paint_listener_proxy = @parent.on_swt_paint(&method(:paint))
265
+ elsif parent.respond_to?(:swt_image)
266
+ paint(parent) # treat parent as paint event since you don't do repaints with images, it's a one time deal.
210
267
  elsif parent.respond_to?(:swt_widget)
211
268
  @paint_listener_proxy = @parent.on_paint_control(&method(:paint))
212
269
  end
213
270
  end
214
271
 
215
272
  def paint(paint_event)
216
- @properties['background'] = [@parent.background] if fill? && !@properties.keys.map(&:to_s).include?('background')
217
- @properties['foreground'] = [@parent.foreground] if draw? && !@properties.keys.map(&:to_s).include?('foreground')
218
- @properties['font'] = [@parent.font] if draw? && !@properties.keys.map(&:to_s).include?('font')
219
- @properties['transform'] = [nil] if !@properties.keys.map(&:to_s).include?('transform')
273
+ @properties['background'] = [@parent.background] if fill? && !has_some_background?
274
+ @properties['foreground'] = [@parent.foreground] if @parent.respond_to?(:foreground) && draw? && !has_some_foreground?
275
+ @properties['font'] = [@parent.font] if @parent.respond_to?(:font) && draw? && !@properties.keys.map(&:to_s).include?('font')
276
+ @properties['transform'] = [nil] if @parent.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
220
277
  @properties.each do |property, args|
221
278
  method_name = attribute_setter(property)
222
279
  converted_args = apply_property_arg_conversions(method_name, property, args)
@@ -43,9 +43,7 @@ module Glimmer
43
43
  end
44
44
 
45
45
  def date=(date_value)
46
- self.year = date_value.year
47
- self.month = date_value.month
48
- self.day = date_value.day
46
+ swt_widget.setDate(date_value.year, date_value.month - 1, date_value.day)
49
47
  end
50
48
 
51
49
  def time
@@ -40,7 +40,7 @@ module Glimmer
40
40
 
41
41
  include Custom::Drawable
42
42
 
43
- OBSERVED_MENU_ITEMS = ['about', 'preferences']
43
+ OBSERVED_MENU_ITEMS = ['about', 'preferences', 'quit']
44
44
 
45
45
  class FilterListener
46
46
  include org.eclipse.swt.widgets.Listener
@@ -71,6 +71,9 @@ module Glimmer
71
71
  Display.app_name ||= 'Glimmer'
72
72
  @swt_display = Display.new(*args)
73
73
  @swt_display.set_data('proxy', self)
74
+ on_swt_Dispose {
75
+ clear_shapes
76
+ }
74
77
  end
75
78
 
76
79
  def content(&block)
@@ -88,6 +91,14 @@ module Glimmer
88
91
  def timer_exec(&block)
89
92
  @swt_display.timer_exec(&block)
90
93
  end
94
+
95
+ def on_widget_disposed(&block)
96
+ on_swt_Dispose(&block)
97
+ end
98
+
99
+ def disposed?
100
+ @swt_display.isDisposed
101
+ end
91
102
 
92
103
  def method_missing(method, *args, &block)
93
104
  if can_handle_observation_request?(method)
@@ -43,6 +43,7 @@ module Glimmer
43
43
 
44
44
  attr_reader :widget_proxy, :swt_font, :font_properties
45
45
 
46
+
46
47
  # Builds a new font proxy from passed in widget_proxy and font_properties hash,
47
48
  #
48
49
  # It begins with existing SWT widget font and amends it with font properties.
@@ -19,6 +19,9 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ require 'glimmer/swt/custom/drawable'
23
+ require 'glimmer/swt/properties'
24
+
22
25
  module Glimmer
23
26
  module SWT
24
27
  # Proxy for org.eclipse.swt.graphics.Image
@@ -27,6 +30,9 @@ module Glimmer
27
30
  #
28
31
  # Follows the Proxy Design Pattern
29
32
  class ImageProxy
33
+ include Custom::Drawable
34
+ include Properties
35
+
30
36
  class << self
31
37
  def create(*args)
32
38
  if args.size == 1 && args.first.is_a?(ImageProxy)
@@ -37,6 +43,7 @@ module Glimmer
37
43
  end
38
44
  end
39
45
 
46
+ include_package 'org.eclipse.swt.widgets'
40
47
  include_package 'org.eclipse.swt.graphics'
41
48
 
42
49
  attr_reader :file_path, :jar_file_path, :image_data, :swt_image
@@ -46,8 +53,13 @@ module Glimmer
46
53
  # Takes the same args as the SWT Image class
47
54
  # Alternatively, takes a file path string or a uri:classloader file path string (generated by JRuby when invoking `File.expand_path` inside a JAR file)
48
55
  # and returns an image object.
49
- def initialize(*args)
56
+ def initialize(*args, &content)
50
57
  @args = args
58
+ @parent_proxy = nil
59
+ if @args.first.is_a?(WidgetProxy)
60
+ @parent_proxy = @args.shift
61
+ @parent = @parent_proxy.swt_widget
62
+ end
51
63
  options = @args.last.is_a?(Hash) ? @args.delete_at(-1) : {}
52
64
  options[:swt_image] = @args.first if @args.size == 1 && @args.first.is_a?(Image)
53
65
  @file_path = @args.first if @args.size == 1 && @args.first.is_a?(String)
@@ -55,6 +67,9 @@ module Glimmer
55
67
  if options&.keys&.include?(:swt_image)
56
68
  @swt_image = options[:swt_image]
57
69
  @original_image_data = @image_data = @swt_image.image_data
70
+ elsif args.size == 1 && args.first.is_a?(ImageProxy)
71
+ @swt_image = @args.first.swt_image
72
+ @original_image_data = @image_data = @swt_image.image_data
58
73
  elsif @file_path
59
74
  @original_image_data = @image_data = ImageData.new(input_stream || @file_path)
60
75
  @swt_image = Image.new(DisplayProxy.instance.swt_display, @image_data)
@@ -64,9 +79,22 @@ module Glimmer
64
79
  width = (@image_data.width.to_f / @image_data.height.to_f)*height.to_f if !height.nil? && width.nil?
65
80
  scale_to(width, height) unless width.nil? || height.nil?
66
81
  else
82
+ @args.prepend(DisplayProxy.instance.swt_display) unless @args.first.is_a?(Display)
67
83
  @swt_image = Image.new(*@args)
68
84
  @original_image_data = @image_data = @swt_image.image_data
69
85
  end
86
+ @swt_image.singleton_class.alias_method(:dispose_without_glimmer, :dispose)
87
+ proxy = self
88
+ # TODO consider adding a get_data/set_data method to conform with other SWT widgets
89
+ @swt_image.singleton_class.define_method(:dispose) do
90
+ proxy.clear_shapes
91
+ dispose_without_glimmer
92
+ end
93
+ post_add_content if content.nil?
94
+ end
95
+
96
+ def post_add_content
97
+ @parent&.image = swt_image
70
98
  end
71
99
 
72
100
  def input_stream
@@ -91,6 +119,56 @@ module Glimmer
91
119
  self
92
120
  end
93
121
 
122
+ def gc
123
+ @gc ||= reset_gc
124
+ end
125
+
126
+ def reset_gc
127
+ @gc = org.eclipse.swt.graphics.GC.new(swt_image)
128
+ end
129
+
130
+ def disposed?
131
+ @swt_image.isDisposed
132
+ end
133
+
134
+ def has_attribute?(attribute_name, *args)
135
+ @swt_image.respond_to?(attribute_setter(attribute_name), args) || respond_to?(ruby_attribute_setter(attribute_name), args)
136
+ end
137
+
138
+ def set_attribute(attribute_name, *args)
139
+ # TODO consider refactoring/unifying this code with WidgetProxy and elsewhere
140
+ if args.count == 1
141
+ if args.first.is_a?(Symbol) || args.first.is_a?(String)
142
+ args[0] = ColorProxy.new(args.first).swt_color
143
+ end
144
+ if args.first.is_a?(ColorProxy)
145
+ args[0] = args.first.swt_color
146
+ end
147
+ end
148
+
149
+ if @swt_image.respond_to?(attribute_setter(attribute_name))
150
+ @swt_image.send(attribute_setter(attribute_name), *args) unless @swt_image.send(attribute_getter(attribute_name)) == args.first
151
+ elsif @swt_image.respond_to?(ruby_attribute_setter(attribute_name))
152
+ @swt_image.send(ruby_attribute_setter(attribute_name), args)
153
+ else
154
+ send(ruby_attribute_setter(attribute_name), args)
155
+ end
156
+ end
157
+
158
+ def get_attribute(attribute_name)
159
+ if @swt_image.respond_to?(attribute_getter(attribute_name))
160
+ @swt_image.send(attribute_getter(attribute_name))
161
+ elsif @swt_image.respond_to?(ruby_attribute_getter(attribute_name))
162
+ @swt_image.send(ruby_attribute_getter(attribute_name))
163
+ elsif @swt_image.respond_to?(attribute_name)
164
+ @swt_image.send(attribute_name)
165
+ elsif respond_to?(ruby_attribute_getter(attribute_name))
166
+ send(ruby_attribute_getter(attribute_name))
167
+ else
168
+ send(attribute_name)
169
+ end
170
+ end
171
+
94
172
  def method_missing(method, *args, &block)
95
173
  swt_image.send(method, *args, &block)
96
174
  rescue => e
@@ -72,7 +72,9 @@ module Glimmer
72
72
  @swt_layout.marginRight = 0 if @swt_layout.respond_to?(:marginRight)
73
73
  @swt_layout.marginBottom = 0 if @swt_layout.respond_to?(:marginBottom)
74
74
  @swt_layout.marginLeft = 0 if @swt_layout.respond_to?(:marginLeft)
75
+ old_layout = @widget_proxy.swt_widget.getLayout
75
76
  @widget_proxy.swt_widget.setLayout(@swt_layout)
77
+ @widget_proxy.swt_widget.layout if old_layout
76
78
  end
77
79
 
78
80
  def has_attribute?(attribute_name, *args)
@@ -83,7 +85,8 @@ module Glimmer
83
85
  apply_property_type_converters(attribute_name, args)
84
86
  if args.first != @swt_layout.send(attribute_getter(attribute_name))
85
87
  @swt_layout.send(attribute_setter(attribute_name), *args)
86
- @widget_proxy.swt_widget.getShell.pack
88
+ @widget_proxy.swt_widget.layout
89
+ @widget_proxy.swt_widget.getShell.layout
87
90
  end
88
91
  end
89
92
 
@@ -82,6 +82,9 @@ module Glimmer
82
82
  end
83
83
  end
84
84
  end
85
+ on_widget_disposed {
86
+ clear_shapes
87
+ }
85
88
  @display ||= @swt_widget.getDisplay
86
89
  end
87
90
 
@@ -124,8 +127,14 @@ module Glimmer
124
127
  end
125
128
  alias disposed disposed?
126
129
 
130
+ # Hides shell. Automatically checks if widget is disposed to avoid crashing.
127
131
  def hide
128
- @swt_widget.setVisible(false)
132
+ @swt_widget.setVisible(false) unless @swt_widget.isDisposed
133
+ end
134
+
135
+ # Closes shell. Automatically checks if widget is disposed to avoid crashing.
136
+ def close
137
+ @swt_widget.close unless @swt_widget.isDisposed
129
138
  end
130
139
 
131
140
  def visible?
@@ -273,6 +273,10 @@ module Glimmer
273
273
  swt_widget.data
274
274
  end
275
275
 
276
+ def table_items_binding
277
+ swt_widget.get_data('table_items_binding')
278
+ end
279
+
276
280
  def sort_block=(comparator)
277
281
  @sort_block = comparator
278
282
  end
@@ -357,9 +361,9 @@ module Glimmer
357
361
  @additional_sort_properties = args unless args.empty?
358
362
  end
359
363
 
360
- def sort!
364
+ def sort!(internal_sort: false)
361
365
  return unless sort_property && (sort_type || sort_block || sort_by_block)
362
- array = model_binding.evaluate_property
366
+ original_array = array = model_binding.evaluate_property
363
367
  array = array.sort_by(&:hash) # this ensures consistent subsequent sorting in case there are equivalent sorts to avoid an infinite loop
364
368
  # Converting value to_s first to handle nil cases. Should work with numeric, boolean, and date fields
365
369
  if sort_block
@@ -383,7 +387,12 @@ module Glimmer
383
387
  end
384
388
  end
385
389
  sorted_array = sorted_array.reverse if sort_direction == :descending
386
- model_binding.call(sorted_array)
390
+ if model_binding.binding_options.symbolize_keys[:read_only_sort]
391
+ table_items_binding.call(sorted_array, internal_sort: true) unless internal_sort
392
+ else
393
+ model_binding.call(sorted_array)
394
+ end
395
+ sorted_array
387
396
  end
388
397
 
389
398
  def editor=(args)
@@ -448,7 +457,7 @@ module Glimmer
448
457
  edit_table_item(swt_widget.getSelection.first, column_index, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
449
458
  end
450
459
 
451
- def edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil)
460
+ def edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil, write_on_cancel: false)
452
461
  return if table_item.nil?
453
462
  model = table_item.data
454
463
  property = column_properties[column_index]
@@ -467,18 +476,22 @@ module Glimmer
467
476
  widget_value_property = TableProxy::editors.symbolize_keys[editor_widget][:widget_value_property]
468
477
 
469
478
  @cancel_edit = lambda do |event=nil|
470
- @cancel_in_progress = true
471
- @table_editor_widget_proxy&.swt_widget&.dispose
472
- @table_editor_widget_proxy = nil
473
- if after_cancel&.arity == 0
474
- after_cancel&.call
479
+ if write_on_cancel
480
+ @finish_edit.call(event)
475
481
  else
476
- after_cancel&.call(table_item)
482
+ @cancel_in_progress = true
483
+ @table_editor_widget_proxy&.swt_widget&.dispose
484
+ @table_editor_widget_proxy = nil
485
+ if after_cancel&.arity == 0
486
+ after_cancel&.call
487
+ else
488
+ after_cancel&.call(table_item)
489
+ end
490
+ @edit_in_progress = false
491
+ @cancel_in_progress = false
492
+ @cancel_edit = nil
493
+ @edit_mode = false
477
494
  end
478
- @edit_in_progress = false
479
- @cancel_in_progress = false
480
- @cancel_edit = nil
481
- @edit_mode = false
482
495
  end
483
496
 
484
497
  @finish_edit = lambda do |event=nil|