glimmer-dsl-swt 4.18.5.5 → 4.18.7.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +4 -4
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +95 -5
  6. data/docs/reference/GLIMMER_PACKAGING_AND_DISTRIBUTION.md +2 -0
  7. data/docs/reference/GLIMMER_SAMPLES.md +67 -0
  8. data/glimmer-dsl-swt.gemspec +18 -6
  9. data/lib/glimmer/dsl/swt/animation_expression.rb +1 -1
  10. data/lib/glimmer/dsl/swt/custom_shape_expression.rb +61 -0
  11. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +1 -1
  12. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  13. data/lib/glimmer/dsl/swt/expand_item_expression.rb +4 -4
  14. data/lib/glimmer/dsl/swt/image_expression.rb +1 -1
  15. data/lib/glimmer/dsl/swt/multiply_expression.rb +1 -1
  16. data/lib/glimmer/dsl/swt/shape_expression.rb +1 -1
  17. data/lib/glimmer/dsl/swt/transform_expression.rb +1 -1
  18. data/lib/glimmer/dsl/swt/widget_expression.rb +2 -1
  19. data/lib/glimmer/swt/color_proxy.rb +1 -1
  20. data/lib/glimmer/swt/custom/shape.rb +173 -53
  21. data/lib/glimmer/swt/custom/shape/cubic.rb +118 -0
  22. data/lib/glimmer/swt/custom/shape/line.rb +47 -4
  23. data/lib/glimmer/swt/custom/shape/path.rb +240 -0
  24. data/lib/glimmer/swt/custom/shape/path_segment.rb +135 -0
  25. data/lib/glimmer/swt/custom/shape/point.rb +33 -0
  26. data/lib/glimmer/swt/custom/shape/polygon.rb +2 -2
  27. data/lib/glimmer/swt/custom/shape/quad.rb +114 -0
  28. data/lib/glimmer/swt/display_proxy.rb +1 -1
  29. data/lib/glimmer/swt/message_box_proxy.rb +1 -1
  30. data/lib/glimmer/swt/proxy_properties.rb +1 -1
  31. data/lib/glimmer/swt/shell_proxy.rb +1 -1
  32. data/lib/glimmer/swt/tab_folder_proxy.rb +52 -0
  33. data/lib/glimmer/swt/transform_proxy.rb +1 -1
  34. data/lib/glimmer/swt/widget_proxy.rb +1 -1
  35. data/lib/glimmer/ui/custom_shape.rb +281 -0
  36. data/samples/elaborate/mandelbrot_fractal.rb +1 -1
  37. data/samples/elaborate/stock_ticker.rb +214 -0
  38. data/samples/hello/hello_canvas.rb +3 -0
  39. data/samples/hello/hello_canvas_data_binding.rb +214 -0
  40. data/samples/hello/hello_canvas_path.rb +120 -0
  41. data/samples/hello/hello_custom_shape.rb +78 -0
  42. data/samples/hello/hello_shape.rb +71 -0
  43. metadata +16 -4
@@ -0,0 +1,135 @@
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/custom/shape'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ module Custom
27
+ class Shape
28
+ # Represents path segments like point, line, quad, and cubic curves
29
+ # Shapes could mix in
30
+ module PathSegment
31
+ def root_path
32
+ current_parent = parent
33
+ until current_parent.class == Path && !current_parent.parent.is_a?(Path)
34
+ current_parent = current_parent.parent
35
+ return current_parent if current_parent.nil?
36
+ end
37
+ current_parent
38
+ end
39
+ def path
40
+ current_parent = parent
41
+ until current_parent.class == Path
42
+ current_parent = current_parent.parent
43
+ return current_parent if current_parent.nil?
44
+ end
45
+ current_parent
46
+ end
47
+ # this is needed to indicate if a shape is part of a path or not (e.g. line and point could be either)
48
+ def part_of_path?
49
+ !!root_path
50
+ end
51
+ # Subclasses must override and implement to indicate method name to invoke on SWT Path object to add segment
52
+ def path_segment_method_name
53
+ nil
54
+ end
55
+ # Subclasses must override and implement to indicate args to pass when invoking SWT Path object method
56
+ def path_segment_args
57
+ []
58
+ end
59
+ # Subclasses must override to indicate expected complete count of args when previous point is NOT connected (e.g. 4 for line, 6 for quad, 8 for cubic)
60
+ def default_path_segment_arg_count
61
+ end
62
+ # Subclasses must override to indicate expected count of args when previous point IS connected (e.g. 2 for line, 4 for quad, 6 for cubic)
63
+ def default_connected_path_segment_arg_count
64
+ end
65
+ # Subclasses may override to provide name of method to invoke for geometry object obtained from the Java AWT library java.awt.geom.Path2D.Double (e.g. curveTo vs cubicTo)
66
+ def path_segment_geometry_method_name
67
+ path_segment_method_name
68
+ end
69
+ # Subclasses must override and implement to indicate args to pass when invoking SWT Path object method
70
+ def path_segment_geometry_args
71
+ path_segment_args
72
+ end
73
+ # Subclasses must override to indicate otherwise
74
+ def previous_point_connected?
75
+ true
76
+ end
77
+
78
+ def dispose(redraw: true)
79
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
80
+ # including classes could override to dispose of resources first
81
+ # afterwards, parent removes from its path segments with post_dispose_content
82
+ parent.post_dispose_content(self) if parent.is_a?(Path)
83
+ if part_of_path?
84
+ drawable.redraw if redraw && !drawable.is_a?(ImageProxy)
85
+ else
86
+ super(redraw: redraw)
87
+ end
88
+ end
89
+ end
90
+
91
+ def first_path_segment?
92
+ parent.path_segments.first == self
93
+ end
94
+
95
+ def previous_path_segment
96
+ parent.path_segments[parent.path_segments.index(self) - 1] || self
97
+ end
98
+
99
+ def add_to_swt_path(swt_path)
100
+ the_path_segment_args = path_segment_args.dup
101
+ if !is_a?(Point) && self.class != Path
102
+ if !previous_point_connected?
103
+ if the_path_segment_args.count == default_path_segment_arg_count
104
+ point = the_path_segment_args.shift, the_path_segment_args.shift
105
+ swt_path.moveTo(*point)
106
+ elsif first_path_segment?
107
+ point = the_path_segment_args[0..1]
108
+ swt_path.moveTo(*point)
109
+ end
110
+ end
111
+ end
112
+ swt_path.send(path_segment_method_name, *the_path_segment_args)
113
+ end
114
+
115
+ def add_to_geometry(geometry)
116
+ the_path_segment_geometry_args = path_segment_geometry_args.dup
117
+ if !is_a?(Point) && self.class != Path
118
+ if !previous_point_connected?
119
+ if the_path_segment_geometry_args.count == default_path_segment_arg_count
120
+ point = the_path_segment_geometry_args.shift, the_path_segment_geometry_args.shift
121
+ geometry.moveTo(point[0], point[1])
122
+ elsif first_path_segment?
123
+ point = the_path_segment_geometry_args[0..1]
124
+ geometry.moveTo(point[0], point[1])
125
+ end
126
+ end
127
+ end
128
+ geometry.send(path_segment_geometry_method_name, *the_path_segment_geometry_args)
129
+ end
130
+
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -20,6 +20,7 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'glimmer/swt/custom/shape'
23
+ require 'glimmer/swt/custom/shape/path_segment'
23
24
  require 'glimmer/swt/swt_proxy'
24
25
  require 'glimmer/swt/display_proxy'
25
26
  require 'glimmer/swt/color_proxy'
@@ -33,6 +34,8 @@ module Glimmer
33
34
  # That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
34
35
  class Shape
35
36
  class Point < Shape
37
+ include PathSegment
38
+
36
39
  def parameter_names
37
40
  [:x, :y]
38
41
  end
@@ -50,6 +53,36 @@ module Glimmer
50
53
  x.to_i.between?(self.absolute_x.to_i - 2, self.absolute_x.to_i + 2) && y.to_i.between?(self.absolute_y.to_i - 2, self.absolute_y.to_i + 2)
51
54
  end
52
55
  alias contain? include?
56
+
57
+ def path_segment_method_name
58
+ 'addRectangle'
59
+ end
60
+
61
+ def path_segment_args
62
+ @args + [1, 1]
63
+ end
64
+
65
+ def path_segment_geometry_method_name
66
+ 'moveTo'
67
+ end
68
+
69
+ def path_segment_geometry_args
70
+ @args
71
+ end
72
+
73
+ def previous_point_connected?
74
+ false
75
+ end
76
+
77
+ def eql?(other)
78
+ other.is_a?(Point) && x == (other && other.respond_to?(:x) && other.x) && y == (other && other.respond_to?(:y) && other.y)
79
+ end
80
+ alias == eql?
81
+
82
+ def hash
83
+ [x, y].hash
84
+ end
85
+
53
86
  end
54
87
  end
55
88
  end
@@ -132,11 +132,11 @@ module Glimmer
132
132
  end
133
133
 
134
134
  def width
135
- bounds.width
135
+ size.x
136
136
  end
137
137
 
138
138
  def height
139
- bounds.height
139
+ size.y
140
140
  end
141
141
 
142
142
  def contain?(x, y)
@@ -0,0 +1,114 @@
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/custom/shape'
23
+ require 'glimmer/swt/custom/shape/path'
24
+ require 'glimmer/swt/custom/shape/path_segment'
25
+ require 'glimmer/swt/swt_proxy'
26
+ require 'glimmer/swt/display_proxy'
27
+ require 'glimmer/swt/color_proxy'
28
+ require 'glimmer/swt/font_proxy'
29
+ require 'glimmer/swt/transform_proxy'
30
+
31
+ module Glimmer
32
+ module SWT
33
+ module Custom
34
+ # Represents a shape (graphics) to be drawn on a control/widget/canvas/display
35
+ # That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
36
+ class Shape
37
+ class Quad < Path
38
+ def parameter_names
39
+ [:point_array]
40
+ end
41
+
42
+ def geometry
43
+ the_point_array = point_array
44
+ if the_point_array != @geometry_point_array
45
+ @geometry_point_array = the_point_array
46
+ @geometry = Java::JavaAwtGeom::Path2D::Double.new
47
+ add_to_geometry(@geometry)
48
+ end
49
+ @geometry
50
+ end
51
+
52
+ def contain?(x, y)
53
+ include?(x, y, filled: true)
54
+ end
55
+
56
+ # checks if drawn or filled rectangle includes the point denoted by x and y (if drawn, it only returns true if point lies on the edge)
57
+ def include?(x, y, filled: nil)
58
+ filled = filled? if filled.nil?
59
+ makeshift_gc = org.eclipse.swt.graphics.GC.new(Glimmer::SWT::DisplayProxy.instance.swt_display)
60
+ swt_path = org.eclipse.swt.graphics.Path.new(Glimmer::SWT::DisplayProxy.instance.swt_display)
61
+ the_path_segment_args = path_segment_args.dup
62
+ if previous_point_connected?
63
+ the_previous_path_segment = previous_path_segment
64
+ swt_path.moveTo(the_previous_path_segment.x, the_previous_path_segment.y)
65
+ else
66
+ swt_path.moveTo(the_path_segment_args.shift, the_path_segment_args.shift)
67
+ end
68
+ swt_path.quadTo(*the_path_segment_args)
69
+ swt_path.contains(x.to_f, y.to_f, makeshift_gc, !filled)
70
+ ensure
71
+ swt_path.dispose
72
+ end
73
+
74
+ def move_by(x_delta, y_delta)
75
+ the_point_array = @args.compact
76
+ the_point_array = the_point_array.first if the_point_array.first.is_a?(Array)
77
+ self.point_array = the_point_array.each_with_index.map {|coordinate, i| i.even? ? coordinate + x_delta : coordinate + y_delta}
78
+ end
79
+
80
+ def path_segment_method_name
81
+ 'quadTo'
82
+ end
83
+
84
+ def path_segment_args
85
+ # TODO make args auto-infer control points if previous_point_connected is true or if there is only a point_array with 1 point
86
+ @args
87
+ end
88
+
89
+ def default_path_segment_arg_count
90
+ 6
91
+ end
92
+
93
+ def default_connected_path_segment_arg_count
94
+ 4
95
+ end
96
+
97
+ def previous_point_connected?
98
+ @args.compact.count == 4 && !first_path_segment?
99
+ end
100
+
101
+ def eql?(other)
102
+ other.is_a?(Quad) && point_array == (other && other.respond_to?(:point_array) && other.point_array)
103
+ end
104
+ alias == eql?
105
+
106
+ def hash
107
+ point_array.hash
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -84,7 +84,7 @@ module Glimmer
84
84
  end
85
85
 
86
86
  def content(&block)
87
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::DisplayExpression.new, &block)
87
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::DisplayExpression.new, 'display', &block)
88
88
  end
89
89
 
90
90
  # asynchronously executes the block (required from threads other than first GUI thread)
@@ -51,7 +51,7 @@ module Glimmer
51
51
  end
52
52
 
53
53
  def content(&block)
54
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::MessageBoxExpression.new, &block)
54
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::MessageBoxExpression.new, 'message_box', &block)
55
55
  end
56
56
 
57
57
  # TODO refactor the following methods to put in a JavaBean mixin or somethin (perhaps contribute to OSS project too)
@@ -73,7 +73,7 @@ module Glimmer
73
73
  Glimmer::SWT::DisplayProxy.instance.auto_exec do
74
74
  result = if proxy_source_object&.respond_to?(attribute_setter(attribute_name))
75
75
  swt_widget_operation = true
76
- proxy_source_object&.send(attribute_setter(attribute_name), *args) unless proxy_source_object&.send(attribute_getter(attribute_name)) == args.first
76
+ proxy_source_object&.send(attribute_setter(attribute_name), *args) unless (proxy_source_object&.respond_to?(attribute_getter(attribute_name)) && proxy_source_object&.send(attribute_getter(attribute_name))) == args.first
77
77
  elsif proxy_source_object&.respond_to?(ruby_attribute_setter(attribute_name))
78
78
  swt_widget_operation = true
79
79
  proxy_source_object&.send(ruby_attribute_setter(attribute_name), args)
@@ -186,7 +186,7 @@ module Glimmer
186
186
  end
187
187
 
188
188
  def content(&block)
189
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShellExpression.new, &block)
189
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShellExpression.new, 'shell', &block)
190
190
  end
191
191
 
192
192
  # (happens as part of `#open`)
@@ -0,0 +1,52 @@
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/widget_proxy'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ # Proxy for org.eclipse.swt.widgets.TabProxy
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class TabFolderProxy < WidgetProxy
30
+ def initialize(underscored_widget_name, parent, args, swt_widget: nil)
31
+ @initialize_tabs_on_select = args.delete(:initialize_tabs_on_select)
32
+ super
33
+ end
34
+
35
+ def post_add_content
36
+ shown = false
37
+ unless @initialize_tabs_on_select
38
+ @show_listener = parent_proxy.on_swt_show do
39
+ unless shown
40
+ shown = true
41
+ self.items.to_a.each do |item|
42
+ self.selection = item
43
+ end
44
+ self.selection = self.items.first unless self.items.first.nil?
45
+ @show_listener.deregister
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -73,7 +73,7 @@ module Glimmer
73
73
  end
74
74
 
75
75
  def content(&block)
76
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::TransformExpression.new, &block)
76
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::TransformExpression.new, 'transform', &block)
77
77
  end
78
78
 
79
79
  def proxy_source_object
@@ -712,7 +712,7 @@ module Glimmer
712
712
 
713
713
  def content(&block)
714
714
  auto_exec do
715
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
715
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, @keyword, &block)
716
716
  end
717
717
  end
718
718
 
@@ -0,0 +1,281 @@
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'
23
+ require 'glimmer/ui'
24
+ require 'glimmer/error'
25
+ require 'glimmer/swt/swt_proxy'
26
+ require 'glimmer/swt/display_proxy'
27
+ require 'glimmer/util/proc_tracker'
28
+ require 'glimmer/data_binding/observer'
29
+ require 'glimmer/data_binding/observable_model'
30
+
31
+ module Glimmer
32
+ module UI
33
+ module CustomShape
34
+ include SuperModule
35
+ include DataBinding::ObservableModel
36
+
37
+ super_module_included do |klass|
38
+ klass.include(Glimmer)
39
+ Glimmer::UI::CustomShape.add_custom_shape_namespaces_for(klass)
40
+ end
41
+
42
+ class << self
43
+ def for(underscored_custom_shape_name)
44
+ unless flyweight_custom_shape_classes.keys.include?(underscored_custom_shape_name)
45
+ begin
46
+ extracted_namespaces = underscored_custom_shape_name.
47
+ to_s.
48
+ split(/__/).map do |namespace|
49
+ namespace.camelcase(:upper)
50
+ end
51
+ custom_shape_namespaces.each do |base|
52
+ extracted_namespaces.reduce(base) do |result, namespace|
53
+ if !result.constants.include?(namespace)
54
+ namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
55
+ end
56
+ begin
57
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = constant = result.const_get(namespace)
58
+ return constant if constant.ancestors.include?(Glimmer::UI::CustomShape)
59
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = constant
60
+ rescue => e
61
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
62
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = result
63
+ end
64
+ end
65
+ end
66
+ raise "#{underscored_custom_shape_name} has no custom shape class!"
67
+ rescue => e
68
+ Glimmer::Config.logger.debug {e.message}
69
+ Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
70
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = nil
71
+ end
72
+ end
73
+ flyweight_custom_shape_classes[underscored_custom_shape_name]
74
+ end
75
+
76
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
77
+ def flyweight_custom_shape_classes
78
+ @flyweight_custom_shape_classes ||= {}
79
+ end
80
+
81
+ # Returns keyword to use for this custom shape
82
+ def keyword
83
+ self.name.underscore.gsub('::', '__')
84
+ end
85
+
86
+ # Returns shortcut keyword to use for this custom shape (keyword minus namespace)
87
+ def shortcut_keyword
88
+ self.name.underscore.gsub('::', '__').split('__').last
89
+ end
90
+
91
+ def add_custom_shape_namespaces_for(klass)
92
+ Glimmer::UI::CustomShape.namespaces_for_class(klass).drop(1).each do |namespace|
93
+ Glimmer::UI::CustomShape.custom_shape_namespaces << namespace
94
+ end
95
+ end
96
+
97
+ def namespaces_for_class(m)
98
+ return [m] if m.name.nil?
99
+ namespace_constants = m.name.split(/::/).map(&:to_sym)
100
+ namespace_constants.reduce([Object]) do |output, namespace_constant|
101
+ output += [output.last.const_get(namespace_constant)]
102
+ end[1..-1].uniq.reverse
103
+ end
104
+
105
+ def custom_shape_namespaces
106
+ @custom_shape_namespaces ||= reset_custom_shape_namespaces
107
+ end
108
+
109
+ def reset_custom_shape_namespaces
110
+ @custom_shape_namespaces = Set[Object, Glimmer::UI]
111
+ end
112
+
113
+ # Allows defining convenience option accessors for an array of option names
114
+ # Example: `options :color1, :color2` defines `#color1` and `#color2`
115
+ # where they return the instance values `options[:color1]` and `options[:color2]`
116
+ # respectively.
117
+ # Can be called multiple times to set more options additively.
118
+ # When passed no arguments, it returns list of all option names captured so far
119
+ def options(*new_options)
120
+ new_options = new_options.compact.map(&:to_s).map(&:to_sym)
121
+ if new_options.empty?
122
+ @options ||= {} # maps options to defaults
123
+ else
124
+ new_options = new_options.reduce({}) {|new_options_hash, new_option| new_options_hash.merge(new_option => nil)}
125
+ @options = options.merge(new_options)
126
+ def_option_attr_accessors(new_options)
127
+ end
128
+ end
129
+
130
+ def option(new_option, default: nil)
131
+ new_option = new_option.to_s.to_sym
132
+ new_options = {new_option => default}
133
+ @options = options.merge(new_options)
134
+ def_option_attr_accessors(new_options)
135
+ end
136
+
137
+ def def_option_attr_accessors(new_options)
138
+ new_options.each do |option, default|
139
+ class_eval <<-end_eval, __FILE__, __LINE__
140
+ def #{option}
141
+ options[:#{option}]
142
+ end
143
+ def #{option}=(option_value)
144
+ self.options[:#{option}] = option_value
145
+ end
146
+ end_eval
147
+ end
148
+ end
149
+
150
+ def before_body(&block)
151
+ @before_body_block = block
152
+ end
153
+
154
+ def body(&block)
155
+ @body_block = block
156
+ end
157
+
158
+ def after_body(&block)
159
+ @after_body_block = block
160
+ end
161
+
162
+ # Current custom shapes being rendered. Useful to yoke all observers evaluated during rendering of their custom shapes for automatical disposal on_shape_disposed
163
+ def current_custom_shapes
164
+ @current_custom_shapes ||= []
165
+ end
166
+ end
167
+
168
+ attr_reader :body_root, :args, :parent, :parent_proxy, :options
169
+
170
+ def initialize(parent, *args, options, &content)
171
+ Glimmer::UI::CustomShape.current_custom_shapes << self
172
+ @parent_proxy = @parent = parent
173
+ @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
174
+ @args = args
175
+ options ||= {}
176
+ @options = self.class.options.merge(options)
177
+ @content = Util::ProcTracker.new(content) if content
178
+ execute_hook('before_body')
179
+ body_block = self.class.instance_variable_get("@body_block")
180
+ raise Glimmer::Error, 'Invalid custom shape for having no body! Please define body block!' if body_block.nil?
181
+ @body_root = instance_exec(&body_block)
182
+ raise Glimmer::Error, 'Invalid custom shape for having an empty body! Please fill body block!' if @body_root.nil?
183
+ auto_exec do
184
+ @body_root.set_data('custom_shape', self)
185
+ end
186
+ execute_hook('after_body')
187
+ end
188
+
189
+ # Subclasses may override to perform post initialization work on an added child
190
+ def post_initialize_child(child)
191
+ # No Op by default
192
+ end
193
+
194
+ def observer_registrations
195
+ @observer_registrations ||= []
196
+ end
197
+
198
+ def has_attribute?(attribute_name, *args)
199
+ has_instance_method?(attribute_setter(attribute_name)) ||
200
+ @body_root.has_attribute?(attribute_name, *args)
201
+ end
202
+
203
+ def set_attribute(attribute_name, *args)
204
+ if has_instance_method?(attribute_setter(attribute_name))
205
+ send(attribute_setter(attribute_name), *args)
206
+ else
207
+ @body_root.set_attribute(attribute_name, *args)
208
+ end
209
+ end
210
+
211
+ # This method ensures it has an instance method not coming from Glimmer DSL
212
+ def has_instance_method?(method_name)
213
+ respond_to?(method_name) and
214
+ !body_root&.respond_to?(method_name) and
215
+ (method(method_name) rescue nil) and
216
+ !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
217
+ !method(method_name)&.source_location&.first&.include?('glimmer/swt/custom/shape.rb')
218
+ end
219
+
220
+ def get_attribute(attribute_name)
221
+ if has_instance_method?(attribute_name)
222
+ send(attribute_name)
223
+ else
224
+ @body_root.get_attribute(attribute_name)
225
+ end
226
+ end
227
+
228
+ def attribute_setter(attribute_name)
229
+ "#{attribute_name}="
230
+ end
231
+
232
+ # TODO see if it is worth it to eliminate duplication of async_exec/sync_exec
233
+ # delegation to DisplayProxy, via a module
234
+
235
+ def async_exec(&block)
236
+ SWT::DisplayProxy.instance.async_exec(&block)
237
+ end
238
+
239
+ def sync_exec(&block)
240
+ SWT::DisplayProxy.instance.sync_exec(&block)
241
+ end
242
+
243
+ def timer_exec(delay_in_millis, &block)
244
+ SWT::DisplayProxy.instance.timer_exec(delay_in_millis, &block)
245
+ end
246
+
247
+ # Returns content block if used as an attribute reader (no args)
248
+ # Otherwise, if a block is passed, it adds it as content to this custom shape
249
+ def content(&block)
250
+ if block_given?
251
+ body_root.content(&block)
252
+ else
253
+ @content
254
+ end
255
+ end
256
+
257
+ def method_missing(method, *args, &block)
258
+ # TODO Consider supporting a glimmer error silencing option for methods defined here
259
+ # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
260
+ body_root.send(method, *args, &block)
261
+ end
262
+
263
+ alias local_respond_to? respond_to?
264
+ def respond_to?(method, *args, &block)
265
+ super or
266
+ body_root.respond_to?(method, *args, &block)
267
+ end
268
+
269
+ private
270
+
271
+ def execute_hook(hook_name)
272
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
273
+ return if hook_block.nil?
274
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
275
+ singleton_class.define_method(temp_method_name, &hook_block)
276
+ send(temp_method_name)
277
+ singleton_class.send(:remove_method, temp_method_name)
278
+ end
279
+ end
280
+ end
281
+ end