glimmer-dsl-swt 4.18.2.4 → 4.18.3.3

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 +60 -0
  3. data/README.md +240 -30
  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/color_expression.rb +4 -4
  10. data/lib/glimmer/dsl/swt/data_binding_expression.rb +3 -3
  11. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  12. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -0
  13. data/lib/glimmer/dsl/swt/property_expression.rb +4 -2
  14. data/lib/glimmer/dsl/swt/shape_expression.rb +2 -4
  15. data/{samples/elaborate/tetris/view/game_over_dialog.rb → lib/glimmer/dsl/swt/transform_expression.rb} +29 -46
  16. data/lib/glimmer/dsl/swt/widget_expression.rb +2 -1
  17. data/lib/glimmer/swt/color_proxy.rb +28 -6
  18. data/lib/glimmer/swt/custom/drawable.rb +8 -0
  19. data/lib/glimmer/swt/custom/shape.rb +66 -26
  20. data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
  21. data/lib/glimmer/swt/display_proxy.rb +26 -5
  22. data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
  23. data/lib/glimmer/swt/shell_proxy.rb +24 -4
  24. data/lib/glimmer/swt/table_proxy.rb +31 -7
  25. data/lib/glimmer/swt/transform_proxy.rb +109 -0
  26. data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
  27. data/lib/glimmer/swt/widget_proxy.rb +35 -20
  28. data/lib/glimmer/ui/custom_shell.rb +11 -9
  29. data/lib/glimmer/ui/custom_widget.rb +65 -39
  30. data/samples/elaborate/meta_sample.rb +81 -24
  31. data/samples/elaborate/tetris.rb +105 -44
  32. data/samples/elaborate/tetris/model/block.rb +1 -1
  33. data/samples/elaborate/tetris/model/game.rb +233 -137
  34. data/samples/elaborate/tetris/model/past_game.rb +26 -0
  35. data/samples/elaborate/tetris/model/tetromino.rb +46 -30
  36. data/samples/elaborate/tetris/view/block.rb +33 -5
  37. data/samples/elaborate/tetris/view/high_score_dialog.rb +133 -0
  38. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  39. data/samples/elaborate/tetris/view/score_lane.rb +11 -11
  40. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +121 -0
  41. data/samples/elaborate/tic_tac_toe.rb +4 -4
  42. data/samples/hello/hello_canvas_transform.rb +40 -0
  43. data/samples/hello/hello_link.rb +1 -1
  44. metadata +11 -5
@@ -22,6 +22,7 @@
22
22
  require 'glimmer'
23
23
  require 'glimmer/dsl/expression'
24
24
  require 'glimmer/dsl/parent_expression'
25
+ require 'glimmer/swt/custom/shape'
25
26
 
26
27
  module Glimmer
27
28
  module DSL
@@ -29,7 +30,7 @@ module Glimmer
29
30
  class WidgetExpression < Expression
30
31
  include ParentExpression
31
32
 
32
- EXCLUDED_KEYWORDS = %w[shell display tab_item]
33
+ EXCLUDED_KEYWORDS = %w[shell display tab_item] + Glimmer::SWT::Custom::Shape.keywords - ['text']
33
34
 
34
35
  def can_interpret?(parent, keyword, *args, &block)
35
36
  result = !EXCLUDED_KEYWORDS.include?(keyword) &&
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2007-2021 Andy Maleh
2
- #
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
5
5
  # "Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@
7
7
  # distribute, sublicense, and/or sell copies of the Software, and to
8
8
  # permit persons to whom the Software is furnished to do so, subject to
9
9
  # the following conditions:
10
- #
10
+ #
11
11
  # The above copyright notice and this permission notice shall be
12
12
  # included in all copies or substantial portions of the Software.
13
- #
13
+ #
14
14
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
15
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
16
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -28,9 +28,20 @@ module Glimmer
28
28
  #
29
29
  # Invoking `#swt_color` returns the SWT Color object wrapped by this proxy
30
30
  #
31
- # Follows the Proxy Design Pattern
31
+ # Follows the Proxy Design Pattern and Flyweight Design Pattern (caching memoization)
32
32
  class ColorProxy
33
33
  include_package 'org.eclipse.swt.graphics'
34
+
35
+ class << self
36
+ def flyweight(*args)
37
+ flyweight_color_proxies[args] ||= new(*args)
38
+ end
39
+
40
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
41
+ def flyweight_color_proxies
42
+ @flyweight_color_proxies ||= {}
43
+ end
44
+ end
34
45
 
35
46
  # Initializes a proxy for an SWT Color object
36
47
  #
@@ -49,6 +60,7 @@ module Glimmer
49
60
  #
50
61
  def initialize(*args)
51
62
  @args = args
63
+ ensure_arg_values_within_valid_bounds
52
64
  end
53
65
 
54
66
  def swt_color
@@ -64,7 +76,7 @@ module Glimmer
64
76
  end
65
77
  when 3..4
66
78
  red, green, blue, alpha = @args
67
- @swt_color = Color.new(DisplayProxy.instance.swt_display, *[red, green, blue, alpha].compact)
79
+ @swt_color = Color.new(*[red, green, blue, alpha].compact)
68
80
  end
69
81
  end
70
82
  @swt_color
@@ -79,7 +91,17 @@ module Glimmer
79
91
 
80
92
  def respond_to?(method, *args, &block)
81
93
  super || swt_color.respond_to?(method, *args, &block)
82
- end
94
+ end
95
+
96
+ private
97
+
98
+ def ensure_arg_values_within_valid_bounds
99
+ if @args.to_a.size >= 3
100
+ @args = @args.map do |value|
101
+ [[value, 255].min, 0].max
102
+ end
103
+ end
104
+ end
83
105
  end
84
106
  end
85
107
  end
@@ -34,6 +34,14 @@ module Glimmer
34
34
  shapes.delete(shape)
35
35
  end
36
36
  end
37
+
38
+ def resetup_shape_paint_listeners
39
+ # TODO consider performance optimization relating to order of shape rendering (affecting only further shapes not previous ones)
40
+ shapes.each do |shape|
41
+ shape.paint_listener_proxy&.unregister
42
+ shape.setup_paint_listener
43
+ end
44
+ end
37
45
  end
38
46
 
39
47
  end
@@ -24,6 +24,7 @@ require 'glimmer/swt/swt_proxy'
24
24
  require 'glimmer/swt/display_proxy'
25
25
  require 'glimmer/swt/color_proxy'
26
26
  require 'glimmer/swt/font_proxy'
27
+ require 'glimmer/swt/transform_proxy'
27
28
 
28
29
  module Glimmer
29
30
  module SWT
@@ -43,7 +44,17 @@ module Glimmer
43
44
  end
44
45
 
45
46
  def gc_instance_methods
46
- org.eclipse.swt.graphics.GC.instance_methods.map(&:to_s)
47
+ @gc_instance_methods ||= org.eclipse.swt.graphics.GC.instance_methods.map(&:to_s)
48
+ end
49
+
50
+ def keywords
51
+ @keywords ||= gc_instance_methods.select do |method_name|
52
+ !method_name.end_with?('=') && (method_name.start_with?('draw_') || method_name.start_with?('fill_'))
53
+ end.reject do |method_name|
54
+ gc_instance_methods.include?("#{method_name}=") || gc_instance_methods.include?("set_#{method_name}")
55
+ end.map do |method_name|
56
+ method_name.gsub(/(draw|fill|gradient|round)_/, '')
57
+ end.uniq.compact.to_a
47
58
  end
48
59
 
49
60
  def arg_options(args, extract: false)
@@ -53,11 +64,19 @@ module Glimmer
53
64
  end
54
65
 
55
66
  def method_name(keyword, args)
56
- keyword = keyword.to_s
57
- gradient = 'gradient_' if arg_options(args)[:gradient]
58
- round = 'round_' if arg_options(args)[:round]
59
- gc_instance_method_name_prefix = !['polyline', 'point', 'image', 'focus'].include?(keyword) && (arg_options(args)[:fill] || arg_options(args)[:gradient]) ? 'fill_' : 'draw_'
60
- "#{gc_instance_method_name_prefix}#{gradient}#{round}#{keyword}"
67
+ method_arg_options = arg_options(args)
68
+ unless flyweight_method_names.keys.include?([keyword, method_arg_options])
69
+ keyword = keyword.to_s
70
+ gradient = 'gradient_' if method_arg_options[:gradient]
71
+ round = 'round_' if method_arg_options[:round]
72
+ gc_instance_method_name_prefix = !['polyline', 'point', 'image', 'focus'].include?(keyword) && (method_arg_options[:fill] || method_arg_options[:gradient]) ? 'fill_' : 'draw_'
73
+ flyweight_method_names[[keyword, method_arg_options]] = "#{gc_instance_method_name_prefix}#{gradient}#{round}#{keyword}"
74
+ end
75
+ flyweight_method_names[[keyword, method_arg_options]]
76
+ end
77
+
78
+ def flyweight_method_names
79
+ @flyweight_method_names ||= {}
61
80
  end
62
81
  end
63
82
 
@@ -92,27 +111,12 @@ module Glimmer
92
111
  end
93
112
 
94
113
  def post_add_content
95
- event_handler = lambda do |event|
96
- @properties['background'] = [@parent.background] if fill? && !@properties.keys.map(&:to_s).include?('background')
97
- @properties['foreground'] = [@parent.foreground] if draw? && !@properties.keys.map(&:to_s).include?('foreground')
98
- @properties.each do |property, args|
99
- method_name = attribute_setter(property)
100
- apply_property_arg_conversions(method_name, args)
101
- event.gc.send(method_name, *args)
102
- end
103
- apply_shape_arg_conversions(@method_name, @args)
104
- apply_shape_arg_defaults(@method_name, @args)
105
- tolerate_shape_extra_args(@method_name, @args)
106
- event.gc.send(@method_name, *@args)
107
- end
108
- if parent.respond_to?(:swt_display)
109
- @paint_listener_proxy = @parent.on_swt_paint(&event_handler)
110
- else
111
- @paint_listener_proxy = @parent.on_paint_control(&event_handler)
112
- end
114
+ setup_paint_listener
115
+ @content_added = true
113
116
  end
114
117
 
115
- def apply_property_arg_conversions(method_name, args)
118
+ def apply_property_arg_conversions(method_name, property, args)
119
+ args = args.dup
116
120
  the_java_method = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.detect {|m| m.name == method_name}
117
121
  if (args.first.is_a?(Symbol) || args.first.is_a?(String))
118
122
  if the_java_method.parameter_types.first == Color.java_class
@@ -131,6 +135,9 @@ module Glimmer
131
135
  if args.first.is_a?(FontProxy)
132
136
  args[0] = args[0].swt_font
133
137
  end
138
+ if args.first.is_a?(TransformProxy)
139
+ args[0] = args[0].swt_transform
140
+ end
134
141
  if ['setBackgroundPattern', 'setForegroundPattern'].include?(method_name.to_s)
135
142
  args.each_with_index do |arg, i|
136
143
  if arg.is_a?(Symbol) || arg.is_a?(String)
@@ -143,6 +150,7 @@ module Glimmer
143
150
  args[0] = org.eclipse.swt.graphics.Pattern.new(*new_args)
144
151
  args[1..-1] = []
145
152
  end
153
+ args
146
154
  end
147
155
 
148
156
  def apply_shape_arg_conversions(method_name, args)
@@ -185,11 +193,43 @@ module Glimmer
185
193
 
186
194
  def set_attribute(attribute_name, *args)
187
195
  @properties[attribute_name] = args
196
+ if @content_added && !@parent.is_disposed
197
+ @parent.resetup_shape_paint_listeners
198
+ @parent.redraw
199
+ end
188
200
  end
189
-
201
+
190
202
  def get_attribute(attribute_name)
191
203
  @properties.symbolize_keys[attribute_name.to_s.to_sym]
192
204
  end
205
+
206
+ def setup_paint_listener
207
+ return if @parent.is_disposed
208
+ if parent.respond_to?(:swt_display)
209
+ @paint_listener_proxy = @parent.on_swt_paint(&method(:paint))
210
+ elsif parent.respond_to?(:swt_widget)
211
+ @paint_listener_proxy = @parent.on_paint_control(&method(:paint))
212
+ end
213
+ end
214
+
215
+ 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')
220
+ @properties.each do |property, args|
221
+ method_name = attribute_setter(property)
222
+ converted_args = apply_property_arg_conversions(method_name, property, args)
223
+ paint_event.gc.send(method_name, *converted_args)
224
+ if property == 'transform' && args.first.is_a?(TransformProxy)
225
+ args.first.swt_transform.dispose
226
+ end
227
+ end
228
+ apply_shape_arg_conversions(@method_name, @args)
229
+ apply_shape_arg_defaults(@method_name, @args)
230
+ tolerate_shape_extra_args(@method_name, @args)
231
+ paint_event.gc.send(@method_name, *@args)
232
+ end
193
233
 
194
234
  end
195
235
 
@@ -47,13 +47,13 @@ module Glimmer
47
47
  style_arg_last_index = args.index(style_args.last)
48
48
  args[style_arg_start_index..style_arg_last_index] = SWTProxy[style_args]
49
49
  end
50
+ if args.first.respond_to?(:swt_widget) && args.first.swt_widget.is_a?(Shell)
51
+ args[0] = args[0].swt_widget
52
+ end
50
53
  if !args.first.is_a?(Shell)
51
54
  current_shell = DisplayProxy.instance.swt_display.shells.first
52
55
  args.unshift(current_shell.nil? ? ShellProxy.new : current_shell)
53
56
  end
54
- if args.first.is_a?(ShellProxy)
55
- args[0] = args[0].swt_widget
56
- end
57
57
  parent = args[0]
58
58
  @parent_proxy = parent.is_a?(Shell) ? ShellProxy.new(swt_widget: parent) : parent
59
59
  @swt_widget = DirectoryDialog.new(*args)
@@ -39,8 +39,20 @@ module Glimmer
39
39
  include_package 'org.eclipse.swt.widgets'
40
40
 
41
41
  include Custom::Drawable
42
-
43
- OBSERVED_MENU_ITEMS = ['about', 'preferences']
42
+
43
+ OBSERVED_MENU_ITEMS = ['about', 'preferences', 'quit']
44
+
45
+ class FilterListener
46
+ include org.eclipse.swt.widgets.Listener
47
+
48
+ def initialize(&listener_block)
49
+ @listener_block = listener_block
50
+ end
51
+
52
+ def handleEvent(event)
53
+ @listener_block.call(event)
54
+ end
55
+ end
44
56
 
45
57
  class << self
46
58
  # Returns singleton instance
@@ -111,7 +123,7 @@ module Glimmer
111
123
  observation_request = observation_request.to_s
112
124
  if observation_request.start_with?('on_swt_')
113
125
  constant_name = observation_request.sub(/^on_swt_/, '')
114
- add_swt_event_listener(constant_name, &block)
126
+ add_swt_event_filter(constant_name, &block)
115
127
  elsif observation_request.start_with?('on_')
116
128
  event_name = observation_request.sub(/^on_/, '')
117
129
  if OBSERVED_MENU_ITEMS.include?(event_name)
@@ -124,10 +136,19 @@ module Glimmer
124
136
  end
125
137
  end
126
138
 
127
- def add_swt_event_listener(swt_constant, &block)
139
+ def add_swt_event_filter(swt_constant, &block)
128
140
  event_type = SWTProxy[swt_constant]
129
- @swt_display.addFilter(event_type, &block)
141
+ @swt_display.addFilter(event_type, FilterListener.new(&block))
130
142
  #WidgetListenerProxy.new(@swt_display.getListeners(event_type).last)
143
+ WidgetListenerProxy.new(
144
+ swt_display: @swt_display,
145
+ event_type: event_type,
146
+ filter: true,
147
+ swt_listener: block,
148
+ widget_add_listener_method: 'addFilter',
149
+ swt_listener_class: FilterListener,
150
+ swt_listener_method: 'handleEvent'
151
+ )
131
152
  end
132
153
  end
133
154
  end
@@ -48,13 +48,13 @@ module Glimmer
48
48
  style_arg_last_index = args.index(style_args.last)
49
49
  args[style_arg_start_index..style_arg_last_index] = SWTProxy[style_args]
50
50
  end
51
+ if args.first.respond_to?(:swt_widget) && args.first.swt_widget.is_a?(Shell)
52
+ args[0] = args[0].swt_widget
53
+ end
51
54
  if !args.first.is_a?(Shell)
52
55
  current_shell = DisplayProxy.instance.swt_display.shells.first
53
56
  args.unshift(current_shell.nil? ? ShellProxy.new : current_shell)
54
57
  end
55
- if args.first.is_a?(ShellProxy)
56
- args[0] = args[0].swt_widget
57
- end
58
58
  parent = args[0]
59
59
  @parent_proxy = parent.is_a?(Shell) ? ShellProxy.new(swt_widget: parent) : parent
60
60
  @swt_widget = FileDialog.new(*args)
@@ -45,11 +45,17 @@ module Glimmer
45
45
  if swt_widget
46
46
  @swt_widget = swt_widget
47
47
  else
48
- if args.first.is_a?(ShellProxy)
48
+ if args.first.respond_to?(:swt_widget) && args.first.swt_widget.is_a?(Shell)
49
49
  @parent_proxy = args[0]
50
50
  args[0] = args[0].swt_widget
51
51
  end
52
- style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
52
+ style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}.map(&:to_sym)
53
+ fill_screen = nil
54
+ if style_args.include?(:fill_screen)
55
+ args.delete(:fill_screen)
56
+ style_args.delete(:fill_screen)
57
+ fill_screen = true
58
+ end
53
59
  if style_args.any?
54
60
  style_arg_start_index = args.index(style_args.first)
55
61
  style_arg_last_index = args.index(style_args.last)
@@ -67,6 +73,7 @@ module Glimmer
67
73
  # TODO make this an option not the default
68
74
  shell_swt_display = Glimmer::SWT::DisplayProxy.instance.swt_display
69
75
  on_swt_show do
76
+ @swt_widget.set_size(@display.bounds.width, @display.bounds.height) if fill_screen
70
77
  Thread.new do
71
78
  sleep(0.25)
72
79
  shell_swt_display.async_exec do
@@ -108,16 +115,29 @@ module Glimmer
108
115
  end
109
116
 
110
117
  def nested?
111
- !parent.nil?
118
+ !swt_widget&.parent.nil?
119
+ end
120
+ alias nested nested?
121
+
122
+ def disposed?
123
+ swt_widget.isDisposed
112
124
  end
125
+ alias disposed disposed?
113
126
 
127
+ # Hides shell. Automatically checks if widget is disposed to avoid crashing.
114
128
  def hide
115
- @swt_widget.setVisible(false)
129
+ @swt_widget.setVisible(false) unless @swt_widget.isDisposed
130
+ end
131
+
132
+ # Closes shell. Automatically checks if widget is disposed to avoid crashing.
133
+ def close
134
+ @swt_widget.close unless @swt_widget.isDisposed
116
135
  end
117
136
 
118
137
  def visible?
119
138
  @swt_widget.isDisposed ? false : @swt_widget.isVisible
120
139
  end
140
+ alias visible visible?
121
141
 
122
142
  # Setting to true opens/shows shell. Setting to false hides the shell.
123
143
  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)
@@ -445,7 +458,6 @@ module Glimmer
445
458
  end
446
459
 
447
460
  def edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil)
448
- require 'facets/hash/symbolize_keys'
449
461
  return if table_item.nil?
450
462
  model = table_item.data
451
463
  property = column_properties[column_index]
@@ -467,7 +479,11 @@ module Glimmer
467
479
  @cancel_in_progress = true
468
480
  @table_editor_widget_proxy&.swt_widget&.dispose
469
481
  @table_editor_widget_proxy = nil
470
- after_cancel&.call
482
+ if after_cancel&.arity == 0
483
+ after_cancel&.call
484
+ else
485
+ after_cancel&.call(table_item)
486
+ end
471
487
  @edit_in_progress = false
472
488
  @cancel_in_progress = false
473
489
  @cancel_edit = nil
@@ -484,14 +500,22 @@ module Glimmer
484
500
  if new_value == model.send(model_editing_property)
485
501
  @cancel_edit.call
486
502
  else
487
- before_write&.call
503
+ if before_write&.arity == 0
504
+ before_write&.call
505
+ else
506
+ before_write&.call(edited_table_item)
507
+ end
488
508
  model.send("#{model_editing_property}=", new_value) # makes table update itself, so must search for selected table item again
489
509
  # Table refresh happens here because of model update triggering observers, so must retrieve table item again
490
510
  edited_table_item = search { |ti| ti.getData == model }.first
491
511
  swt_widget.showItem(edited_table_item)
492
512
  @table_editor_widget_proxy&.swt_widget&.dispose
493
513
  @table_editor_widget_proxy = nil
494
- after_write&.call(edited_table_item)
514
+ if after_write&.arity == 0
515
+ after_write&.call
516
+ else
517
+ after_write&.call(edited_table_item)
518
+ end
495
519
  @edit_in_progress = false
496
520
  end
497
521
  end