glimmer-dsl-swt 4.18.3.1 → 4.18.4.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 +55 -1
  3. data/README.md +739 -324
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +9 -4
  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 +171 -53
  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/shell_proxy.rb +13 -1
  21. data/lib/glimmer/swt/table_proxy.rb +43 -15
  22. data/lib/glimmer/swt/widget_proxy.rb +11 -2
  23. data/lib/glimmer/ui/custom_shell.rb +1 -0
  24. data/lib/glimmer/ui/custom_widget.rb +10 -3
  25. data/samples/elaborate/meta_sample.rb +1 -1
  26. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
  27. data/samples/elaborate/tetris.rb +90 -17
  28. data/samples/elaborate/tetris/model/game.rb +89 -8
  29. data/samples/elaborate/tetris/{view/game_over_dialog.rb → model/past_game.rb} +12 -41
  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 +131 -0
  33. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  34. data/samples/elaborate/tetris/view/score_lane.rb +6 -6
  35. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +70 -3
  36. data/samples/hello/hello_canvas.rb +10 -9
  37. data/samples/hello/hello_canvas_animation.rb +5 -5
  38. data/samples/hello/hello_code_text.rb +92 -0
  39. data/samples/hello/hello_table.rb +6 -4
  40. data/samples/hello/hello_table/baseball_park.png +0 -0
  41. metadata +8 -3
@@ -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
@@ -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
 
@@ -117,18 +120,27 @@ module Glimmer
117
120
  def nested?
118
121
  !swt_widget&.parent.nil?
119
122
  end
123
+ alias nested nested?
120
124
 
121
125
  def disposed?
122
126
  swt_widget.isDisposed
123
127
  end
128
+ alias disposed disposed?
124
129
 
130
+ # Hides shell. Automatically checks if widget is disposed to avoid crashing.
125
131
  def hide
126
- @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
127
138
  end
128
139
 
129
140
  def visible?
130
141
  @swt_widget.isDisposed ? false : @swt_widget.isVisible
131
142
  end
143
+ alias visible visible?
132
144
 
133
145
  # Setting to true opens/shows shell. Setting to false hides the shell.
134
146
  def visible=(visibility)
@@ -265,10 +265,18 @@ module Glimmer
265
265
  end
266
266
  end
267
267
 
268
+ def items
269
+ swt_widget.get_items
270
+ end
271
+
268
272
  def model_binding
269
273
  swt_widget.data
270
274
  end
271
275
 
276
+ def table_items_binding
277
+ swt_widget.get_data('table_items_binding')
278
+ end
279
+
272
280
  def sort_block=(comparator)
273
281
  @sort_block = comparator
274
282
  end
@@ -353,9 +361,9 @@ module Glimmer
353
361
  @additional_sort_properties = args unless args.empty?
354
362
  end
355
363
 
356
- def sort!
364
+ def sort!(internal_sort: false)
357
365
  return unless sort_property && (sort_type || sort_block || sort_by_block)
358
- array = model_binding.evaluate_property
366
+ original_array = array = model_binding.evaluate_property
359
367
  array = array.sort_by(&:hash) # this ensures consistent subsequent sorting in case there are equivalent sorts to avoid an infinite loop
360
368
  # Converting value to_s first to handle nil cases. Should work with numeric, boolean, and date fields
361
369
  if sort_block
@@ -379,7 +387,12 @@ module Glimmer
379
387
  end
380
388
  end
381
389
  sorted_array = sorted_array.reverse if sort_direction == :descending
382
- 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
383
396
  end
384
397
 
385
398
  def editor=(args)
@@ -444,8 +457,7 @@ module Glimmer
444
457
  edit_table_item(swt_widget.getSelection.first, column_index, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
445
458
  end
446
459
 
447
- def edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil)
448
- require 'facets/hash/symbolize_keys'
460
+ def edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil, write_on_cancel: false)
449
461
  return if table_item.nil?
450
462
  model = table_item.data
451
463
  property = column_properties[column_index]
@@ -464,14 +476,22 @@ module Glimmer
464
476
  widget_value_property = TableProxy::editors.symbolize_keys[editor_widget][:widget_value_property]
465
477
 
466
478
  @cancel_edit = lambda do |event=nil|
467
- @cancel_in_progress = true
468
- @table_editor_widget_proxy&.swt_widget&.dispose
469
- @table_editor_widget_proxy = nil
470
- after_cancel&.call
471
- @edit_in_progress = false
472
- @cancel_in_progress = false
473
- @cancel_edit = nil
474
- @edit_mode = false
479
+ if write_on_cancel
480
+ @finish_edit.call(event)
481
+ else
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
494
+ end
475
495
  end
476
496
 
477
497
  @finish_edit = lambda do |event=nil|
@@ -484,14 +504,22 @@ module Glimmer
484
504
  if new_value == model.send(model_editing_property)
485
505
  @cancel_edit.call
486
506
  else
487
- before_write&.call
507
+ if before_write&.arity == 0
508
+ before_write&.call
509
+ else
510
+ before_write&.call(edited_table_item)
511
+ end
488
512
  model.send("#{model_editing_property}=", new_value) # makes table update itself, so must search for selected table item again
489
513
  # Table refresh happens here because of model update triggering observers, so must retrieve table item again
490
514
  edited_table_item = search { |ti| ti.getData == model }.first
491
515
  swt_widget.showItem(edited_table_item)
492
516
  @table_editor_widget_proxy&.swt_widget&.dispose
493
517
  @table_editor_widget_proxy = nil
494
- after_write&.call(edited_table_item)
518
+ if after_write&.arity == 0
519
+ after_write&.call
520
+ else
521
+ after_write&.call(edited_table_item)
522
+ end
495
523
  @edit_in_progress = false
496
524
  end
497
525
  end