glimmer-dsl-swt 4.18.3.1 → 4.18.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +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