glimmer 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +109 -2
  3. data/bin/girb +4 -2
  4. data/bin/glimmer +3 -2
  5. data/lib/glimmer.rb +6 -3
  6. data/lib/glimmer/command_handlers.rb +37 -34
  7. data/lib/glimmer/ext/module.rb +20 -0
  8. data/lib/glimmer/launcher.rb +75 -0
  9. data/lib/glimmer/swt/command_handlers/bind_command_handler.rb +55 -0
  10. data/lib/glimmer/swt/command_handlers/color_command_handler.rb +30 -0
  11. data/lib/glimmer/swt/command_handlers/combo_selection_data_binding_command_handler.rb +43 -0
  12. data/lib/glimmer/swt/command_handlers/custom_widget_command_handler.rb +24 -0
  13. data/lib/glimmer/swt/command_handlers/data_binding_command_handler.rb +72 -0
  14. data/lib/glimmer/swt/command_handlers/display_command_handler.rb +20 -0
  15. data/lib/glimmer/swt/command_handlers/layout_command_handler.rb +27 -0
  16. data/lib/glimmer/swt/command_handlers/layout_data_command_handler.rb +26 -0
  17. data/lib/glimmer/swt/command_handlers/list_selection_data_binding_command_handler.rb +48 -0
  18. data/lib/glimmer/swt/command_handlers/property_command_handler.rb +24 -0
  19. data/lib/glimmer/swt/command_handlers/shell_command_handler.rb +20 -0
  20. data/lib/glimmer/swt/command_handlers/tab_item_command_handler.rb +24 -0
  21. data/lib/glimmer/swt/command_handlers/table_column_properties_data_binding_command_handler.rb +28 -0
  22. data/lib/glimmer/swt/command_handlers/table_items_data_binding_command_handler.rb +33 -0
  23. data/lib/glimmer/swt/command_handlers/tree_items_data_binding_command_handler.rb +32 -0
  24. data/lib/glimmer/swt/command_handlers/tree_properties_data_binding_command_handler.rb +28 -0
  25. data/lib/glimmer/swt/command_handlers/widget_command_handler.rb +26 -0
  26. data/lib/glimmer/swt/command_handlers/widget_listener_command_handler.rb +43 -0
  27. data/lib/glimmer/swt/custom_widget.rb +91 -0
  28. data/lib/glimmer/swt/g_color.rb +39 -0
  29. data/lib/glimmer/swt/g_display.rb +28 -0
  30. data/lib/glimmer/swt/g_font.rb +75 -0
  31. data/lib/glimmer/swt/g_layout.rb +72 -0
  32. data/lib/glimmer/swt/g_layout_data.rb +56 -0
  33. data/lib/glimmer/swt/g_runnable.rb +15 -0
  34. data/lib/glimmer/swt/g_shell.rb +50 -0
  35. data/lib/glimmer/swt/g_swt.rb +65 -0
  36. data/lib/glimmer/swt/g_tab_item_composite.rb +34 -0
  37. data/lib/glimmer/swt/g_widget.rb +213 -0
  38. data/lib/glimmer/swt/g_widget_listener.rb +12 -0
  39. data/lib/glimmer/swt/list_selection_binding.rb +49 -0
  40. data/lib/glimmer/swt/model_binding.rb +208 -0
  41. data/lib/glimmer/swt/observable.rb +13 -0
  42. data/lib/glimmer/swt/observable_array.rb +105 -0
  43. data/lib/glimmer/swt/observable_model.rb +107 -0
  44. data/lib/glimmer/swt/observer.rb +117 -0
  45. data/lib/glimmer/swt/proc_tracker.rb +16 -0
  46. data/lib/glimmer/swt/table_items_binding.rb +47 -0
  47. data/lib/glimmer/swt/tree_items_binding.rb +51 -0
  48. data/lib/glimmer/swt/widget_binding.rb +31 -0
  49. data/lib/glimmer/xml/command_handlers/html_command_handler.rb +50 -0
  50. data/lib/glimmer/xml/command_handlers/xml_command_handler.rb +23 -0
  51. data/lib/glimmer/xml/command_handlers/xml_name_space_command_handler.rb +36 -0
  52. data/lib/glimmer/xml/command_handlers/xml_tag_command_handler.rb +28 -0
  53. data/lib/glimmer/xml/command_handlers/xml_text_command_handler.rb +24 -0
  54. data/lib/glimmer/xml/depth_first_search_iterator.rb +20 -0
  55. data/lib/glimmer/xml/name_space_visitor.rb +21 -0
  56. data/lib/glimmer/xml/node.rb +84 -0
  57. data/lib/glimmer/xml/node_visitor.rb +13 -0
  58. data/lib/glimmer/xml/xml_visitor.rb +63 -0
  59. data/lib/glimmer/xml_command_handlers.rb +11 -10
  60. metadata +68 -50
  61. data/lib/glimmer/command_handlers/bind_command_handler.rb +0 -51
  62. data/lib/glimmer/command_handlers/color_command_handler.rb +0 -26
  63. data/lib/glimmer/command_handlers/combo_selection_data_binding_command_handler.rb +0 -40
  64. data/lib/glimmer/command_handlers/data_binding_command_handler.rb +0 -69
  65. data/lib/glimmer/command_handlers/display_command_handler.rb +0 -16
  66. data/lib/glimmer/command_handlers/layout_command_handler.rb +0 -23
  67. data/lib/glimmer/command_handlers/layout_data_command_handler.rb +0 -22
  68. data/lib/glimmer/command_handlers/list_selection_data_binding_command_handler.rb +0 -45
  69. data/lib/glimmer/command_handlers/models/g_color.rb +0 -34
  70. data/lib/glimmer/command_handlers/models/g_display.rb +0 -26
  71. data/lib/glimmer/command_handlers/models/g_font.rb +0 -73
  72. data/lib/glimmer/command_handlers/models/g_layout.rb +0 -71
  73. data/lib/glimmer/command_handlers/models/g_layout_data.rb +0 -55
  74. data/lib/glimmer/command_handlers/models/g_runnable.rb +0 -13
  75. data/lib/glimmer/command_handlers/models/g_shell.rb +0 -49
  76. data/lib/glimmer/command_handlers/models/g_swt.rb +0 -63
  77. data/lib/glimmer/command_handlers/models/g_tab_item_composite.rb +0 -33
  78. data/lib/glimmer/command_handlers/models/g_widget.rb +0 -214
  79. data/lib/glimmer/command_handlers/models/g_widget_listener.rb +0 -11
  80. data/lib/glimmer/command_handlers/models/list_selection_binding.rb +0 -47
  81. data/lib/glimmer/command_handlers/models/model_binding.rb +0 -206
  82. data/lib/glimmer/command_handlers/models/observable.rb +0 -11
  83. data/lib/glimmer/command_handlers/models/observable_array.rb +0 -104
  84. data/lib/glimmer/command_handlers/models/observable_model.rb +0 -105
  85. data/lib/glimmer/command_handlers/models/observer.rb +0 -115
  86. data/lib/glimmer/command_handlers/models/table_items_binding.rb +0 -45
  87. data/lib/glimmer/command_handlers/models/tree_items_binding.rb +0 -49
  88. data/lib/glimmer/command_handlers/models/widget_binding.rb +0 -29
  89. data/lib/glimmer/command_handlers/property_command_handler.rb +0 -21
  90. data/lib/glimmer/command_handlers/shell_command_handler.rb +0 -17
  91. data/lib/glimmer/command_handlers/tab_item_command_handler.rb +0 -21
  92. data/lib/glimmer/command_handlers/table_column_properties_data_binding_command_handler.rb +0 -25
  93. data/lib/glimmer/command_handlers/table_items_data_binding_command_handler.rb +0 -30
  94. data/lib/glimmer/command_handlers/tree_items_data_binding_command_handler.rb +0 -29
  95. data/lib/glimmer/command_handlers/tree_properties_data_binding_command_handler.rb +0 -25
  96. data/lib/glimmer/command_handlers/widget_command_handler.rb +0 -22
  97. data/lib/glimmer/command_handlers/widget_listener_command_handler.rb +0 -39
  98. data/lib/glimmer/xml_command_handlers/html_command_handler.rb +0 -47
  99. data/lib/glimmer/xml_command_handlers/models/depth_first_search_iterator.rb +0 -19
  100. data/lib/glimmer/xml_command_handlers/models/name_space_visitor.rb +0 -20
  101. data/lib/glimmer/xml_command_handlers/models/node.rb +0 -82
  102. data/lib/glimmer/xml_command_handlers/models/node_visitor.rb +0 -11
  103. data/lib/glimmer/xml_command_handlers/models/xml_visitor.rb +0 -61
  104. data/lib/glimmer/xml_command_handlers/xml_command_handler.rb +0 -20
  105. data/lib/glimmer/xml_command_handlers/xml_name_space_command_handler.rb +0 -33
  106. data/lib/glimmer/xml_command_handlers/xml_tag_command_handler.rb +0 -25
  107. data/lib/glimmer/xml_command_handlers/xml_text_command_handler.rb +0 -21
  108. data/lib/glimmer_application.rb +0 -65
@@ -0,0 +1,50 @@
1
+ require_relative 'g_widget'
2
+ require_relative 'g_display'
3
+
4
+ module Glimmer
5
+ module SWT
6
+ class GShell < GWidget
7
+ WIDTH_MIN = 130
8
+ HEIGHT_MIN = 0
9
+
10
+ include_package 'org.eclipse.swt.layout'
11
+ include_package 'org.eclipse.swt.widgets'
12
+
13
+ attr_reader :display
14
+
15
+ # Instantiates shell with same arguments expected by SWT Shell
16
+ def initialize(*args)
17
+ if !args.first.is_a?(Display) && !args.first.is_a?(Shell)
18
+ @display = GDisplay.instance.display
19
+ args = [@display] + args
20
+ end
21
+ args = GSWT.constantify_args(args)
22
+ @widget = Shell.new(*args)
23
+ @display ||= @widget.getDisplay
24
+ @widget.setLayout(FillLayout.new)
25
+ @widget.setMinimumSize(WIDTH_MIN, HEIGHT_MIN)
26
+ end
27
+
28
+ # Centers shell within screen
29
+ def center
30
+ primary_monitor = @display.getPrimaryMonitor()
31
+ monitor_bounds = primary_monitor.getBounds()
32
+ shell_bounds = @widget.getBounds()
33
+ location_x = monitor_bounds.x + (monitor_bounds.width - shell_bounds.width) / 2
34
+ location_y = monitor_bounds.y + (monitor_bounds.height - shell_bounds.height) / 2
35
+ @widget.setLocation(location_x, location_y)
36
+ end
37
+
38
+ # Opens shell and starts SWT's UI thread event loop
39
+ def open
40
+ @widget.pack
41
+ center
42
+ @widget.open
43
+ until @widget.isDisposed
44
+ @display.sleep unless @display.readAndDispatch
45
+ end
46
+ @display.dispose
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,65 @@
1
+ module Glimmer
2
+ module SWT #TODO refactor so GSWT and SWT are one and the same
3
+ class GSWT
4
+ class << self
5
+ ERROR_INVALID_STYLE = " is an invalid SWT style! Please choose a style from org.eclipse.swt.SWT class constants."
6
+ java_import 'org.eclipse.swt.SWT'
7
+
8
+ # Gets SWT constants as if calling SWT::CONSTANT where constant is
9
+ # passed in as a lower case symbol
10
+ def [](*symbols)
11
+ symbols.compact.reduce(0) do |output, symbol|
12
+ constant_value = constant(symbol)
13
+ if constant_value.is_a?(Integer)
14
+ output | constant(symbol)
15
+ else
16
+ raise symbol.to_s + ERROR_INVALID_STYLE
17
+ end
18
+ end
19
+ end
20
+
21
+ # Returns SWT style integer value for passed in symbol or allows
22
+ # passed in object to pass through (e.g. Integer). This makes is convenient
23
+ # to use symbols or actual SWT style integers in Glimmer
24
+ # Does not raise error for invalid values. Just lets them pass as is.
25
+ # (look into [] operator if you want an error raised on invalid values)
26
+ def constant(symbol)
27
+ return symbol unless symbol.is_a?(Symbol) || symbol.is_a?(String)
28
+ symbol_string = symbol.to_s
29
+ swt_constant_symbol = symbol_string.downcase == symbol_string ? symbol_string.upcase.to_sym : symbol_string.to_sym
30
+ SWT.const_get(swt_constant_symbol)
31
+ rescue
32
+ begin
33
+ alternative_swt_constant_symbol = SWT.constants.find {|c| c.to_s.upcase == swt_constant_symbol.to_s.upcase}
34
+ SWT.const_get(alternative_swt_constant_symbol)
35
+ rescue
36
+ EXTRA_STYLES[swt_constant_symbol] || symbol
37
+ end
38
+ end
39
+
40
+ def has_constant?(symbol)
41
+ return false unless symbol.is_a?(Symbol) || symbol.is_a?(String)
42
+ constant(symbol).is_a?(Integer)
43
+ end
44
+
45
+ def constantify_args(args)
46
+ args.map {|arg| constant(arg)}
47
+ end
48
+
49
+ # Deconstructs a style integer into symbols
50
+ # Useful for debugging
51
+ def deconstruct(integer)
52
+ SWT.constants.reduce([]) do |found, c|
53
+ constant_value = SWT.const_get(c) rescue -1
54
+ is_found = constant_value.is_a?(Integer) && (constant_value & style) == constant_value
55
+ is_found ? found += [c] : found
56
+ end
57
+ end
58
+ end
59
+
60
+ EXTRA_STYLES = {
61
+ NO_RESIZE: GSWT[:shell_trim] & (~GSWT[:resize]) & (~GSWT[:max])
62
+ }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + "/g_widget_listener"
2
+ require File.dirname(__FILE__) + "/g_runnable"
3
+
4
+ module Glimmer
5
+ module SWT
6
+ class GTabItemComposite < GWidget
7
+ include_package 'org.eclipse.swt.widgets'
8
+
9
+ attr_reader :tab_item
10
+ def initialize(tab_item, parent, style, &contents)
11
+ super("composite", parent, style, &contents)
12
+ @tab_item = tab_item
13
+ @tab_item.widget.control = self.widget
14
+ end
15
+
16
+ def has_attribute?(attribute_name, *args)
17
+ if attribute_name.to_s == "text"
18
+ true
19
+ else
20
+ super(attribute_name, *args)
21
+ end
22
+ end
23
+
24
+ def set_attribute(attribute_name, *args)
25
+ if attribute_name.to_s == "text"
26
+ text_value = args[0]
27
+ @tab_item.widget.text = text_value
28
+ else
29
+ super(attribute_name, *args)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,213 @@
1
+ require_relative 'g_widget_listener'
2
+ require_relative 'g_runnable'
3
+ require_relative 'g_color'
4
+ require_relative 'g_font'
5
+ require_relative 'g_swt'
6
+ require_relative '../parent'
7
+
8
+ module Glimmer
9
+ module SWT
10
+ class GWidget
11
+ include_package 'org.eclipse.swt'
12
+ include_package 'org.eclipse.swt.widgets'
13
+ include_package 'org.eclipse.swt.layout'
14
+ include_package 'org.eclipse.swt.graphics'
15
+ include Parent
16
+
17
+ attr_reader :widget
18
+
19
+ #TODO externalize
20
+ @@default_styles = {
21
+ "text" => GSWT[:border],
22
+ "table" => GSWT[:border],
23
+ "spinner" => GSWT[:border],
24
+ "list" => GSWT[:border, :v_scroll],
25
+ "button" => GSWT[:push],
26
+ }
27
+
28
+ #TODO externalize
29
+ @@default_initializers = {
30
+ "composite" => Proc.new {|composite| composite.setLayout(GridLayout.new) },
31
+ "table" => Proc.new do |table|
32
+ table.setHeaderVisible(true)
33
+ table.setLinesVisible(true)
34
+ end,
35
+ "table_column" => Proc.new { |table_column| table_column.setWidth(80) },
36
+ "group" => Proc.new {|group| group.setLayout(GridLayout.new) },
37
+ }
38
+
39
+ #styles is a comma separate list of symbols representing SWT styles in lower case
40
+ def initialize(underscored_widget_name, parent, styles, &contents)
41
+ @widget = self.class.swt_widget_class_for(underscored_widget_name).new(parent, style(underscored_widget_name, styles))
42
+ @@default_initializers[underscored_widget_name].call(@widget) if @@default_initializers[underscored_widget_name]
43
+ end
44
+
45
+ def has_style?(swt_style)
46
+ pd(pd(@widget.getStyle) & swt_style) == pd(swt_style)
47
+ end
48
+
49
+ def has_attribute?(attribute_name, *args)
50
+ @widget.respond_to?(attribute_setter(attribute_name), args)
51
+ end
52
+
53
+ def set_attribute(attribute_name, *args)
54
+ apply_property_type_converters(attribute_name, args)
55
+ @widget.send(attribute_setter(attribute_name), *args)
56
+ end
57
+
58
+ def property_type_converters
59
+ color_converter = Proc.new do |value|
60
+ if value.is_a?(Symbol) || value.is_a?(String)
61
+ GColor.color_for(widget.getDisplay, value)
62
+ else
63
+ value
64
+ end
65
+ end
66
+ @property_type_converters ||= {
67
+ :text => Proc.new { |value| value.to_s },
68
+ :items => Proc.new { |value| value.to_java :string},
69
+ :visible => Proc.new { |value| !!value},
70
+ :background => color_converter,
71
+ :foreground => color_converter,
72
+ :font => Proc.new do |value|
73
+ if value.is_a?(Hash)
74
+ font_properties = value
75
+ GFont.for(self).font(font_properties)
76
+ else
77
+ value
78
+ end
79
+ end,
80
+ }
81
+ end
82
+
83
+ def apply_property_type_converters(attribute_name, args)
84
+ if args.count == 1
85
+ value = args.first
86
+ converter = property_type_converters[attribute_name.to_sym]
87
+ args[0] = converter.call(value) if converter
88
+ end
89
+ if args.count == 1 && args.first.is_a?(GColor)
90
+ g_color = args.first
91
+ g_color.display = widget.display if g_color.display.nil? || g_color.display != widget.display
92
+ args[0] = g_color.color
93
+ end
94
+ end
95
+
96
+ def self.widget_exists?(underscored_widget_name)
97
+ !!swt_widget_class_for(underscored_widget_name)
98
+ end
99
+
100
+ # This supports widgets in and out of basic SWT
101
+ def self.swt_widget_class_for(underscored_widget_name)
102
+ swt_widget_name = underscored_widget_name.camelcase(:upper)
103
+ swt_widget_class = eval(swt_widget_name)
104
+ unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
105
+ Glimmer.logger.debug("Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget")
106
+ return nil
107
+ end
108
+ swt_widget_class
109
+ rescue NameError => e
110
+ Glimmer.logger.debug("#{e.message}\n#{e.backtrace.join("\n")}")
111
+ nil
112
+ rescue => e
113
+ Glimmer.logger.debug("#{e.message}\n#{e.backtrace.join("\n")}")
114
+ nil
115
+ end
116
+
117
+ def widget_listener_exists?(underscored_listener_name)
118
+ listener_method_name = underscored_listener_name.listener_method_name(:lower)
119
+ @widget.getClass.getMethods.each do |widget_method|
120
+ if widget_method.getName.match(/add.*Listener/)
121
+ widget_method.getParameterTypes.each do |listener_type|
122
+ listener_type.getMethods.each do |listener_method|
123
+ if (listener_method.getName == listener_method_name)
124
+ return true
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ return false
131
+ end
132
+
133
+ def can_add_listener?(underscored_listener_name)
134
+ listener_method_name = underscored_listener_name.camelcase(:lower)
135
+ @widget.getClass.getMethods.each do |widget_method|
136
+ if widget_method.getName.match(/add.*Listener/)
137
+ widget_method.getParameterTypes.each do |listener_type|
138
+ listener_type.getMethods.each do |listener_method|
139
+ if (listener_method.getName == listener_method_name)
140
+ return true
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ return false
147
+ end
148
+
149
+ def add_listener(underscored_listener_name, &block)
150
+ listener_method_name = underscored_listener_name.camelcase(:lower)
151
+ @widget.getClass.getMethods.each do |widget_method|
152
+ if widget_method.getName.match(/add.*Listener/)
153
+ widget_method.getParameterTypes.each do |listener_type|
154
+ listener_type.getMethods.each do |listener_method|
155
+ if (listener_method.getName == listener_method_name)
156
+ listener_class = Class.new(Object)
157
+ listener_class.send :include, (eval listener_type.to_s.sub("interface", ""))
158
+ listener = listener_class.new
159
+ listener_type.getMethods.each do |t_method|
160
+ eval "def listener.#{t_method.getName}(event) end"
161
+ end
162
+ def listener.block=(block)
163
+ @block = block
164
+ end
165
+ listener.block=block
166
+ eval "def listener.#{listener_method.getName}(event) @block.call(event) if @block end"
167
+ @widget.send(widget_method.getName, listener)
168
+ return GWidgetListener.new(listener)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ def process_block(block)
177
+ block.call(@widget)
178
+ end
179
+
180
+ def async_exec(&block)
181
+ @widget.getDisplay.asyncExec(GRunnable.new(&block))
182
+ end
183
+
184
+ def sync_exec(&block)
185
+ @widget.getDisplay.syncExec(GRunnable.new(&block))
186
+ end
187
+
188
+ def has_style?(style)
189
+ (widget.style & GSWT[style]) == GSWT[style]
190
+ end
191
+
192
+ def dispose
193
+ @widget.dispose
194
+ end
195
+
196
+ private
197
+
198
+ def style(underscored_widget_name, styles)
199
+ styles.empty? ? default_style(underscored_widget_name) : GSWT[*styles]
200
+ end
201
+
202
+ def default_style(underscored_widget_name)
203
+ style = @@default_styles[underscored_widget_name] if @@default_styles[underscored_widget_name]
204
+ style = GSWT[:none] unless style
205
+ style
206
+ end
207
+
208
+ def attribute_setter(attribute_name)
209
+ "set#{attribute_name.to_s.camelcase(:upper)}"
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,12 @@
1
+ module Glimmer
2
+ module SWT
3
+ class GWidgetListener
4
+
5
+ attr_reader :listener
6
+
7
+ def initialize(listener)
8
+ @listener = listener
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ require_relative 'observable'
2
+ require_relative 'observer'
3
+
4
+ module Glimmer
5
+ module SWT
6
+ # SWT List widget selection binding
7
+ class ListSelectionBinding
8
+ include Glimmer
9
+ include Observable
10
+ include Observer
11
+
12
+ attr_reader :widget
13
+ @@property_type_updaters = {
14
+ :string => lambda { |widget, value| widget.widget.select(widget.widget.index_of(value.to_s)) },
15
+ :array => lambda { |widget, value| widget.widget.selection=((value or []).to_java :string) }
16
+ }
17
+ @@property_evaluators = {
18
+ :string => lambda do |selection_array|
19
+ return nil if selection_array.empty?
20
+ selection_array[0]
21
+ end,
22
+ :array => lambda do |selection_array|
23
+ selection_array
24
+ end
25
+ }
26
+ # Initialize with list widget and property_type
27
+ # property_type :string represents default list single selection
28
+ # property_type :array represents list multi selection
29
+ def initialize(widget, property_type)
30
+ property_type = :string if property_type.nil? or property_type == :undefined
31
+ @widget = widget
32
+ @property_type = property_type
33
+ add_contents(@widget) {
34
+ on_widget_disposed { |dispose_event|
35
+ unregister_all_observables
36
+ }
37
+ }
38
+ end
39
+ def call(value)
40
+ @@property_type_updaters[@property_type].call(@widget, value) unless evaluate_property == value
41
+ end
42
+ def evaluate_property
43
+ selection_array = @widget.widget.send("selection").to_a
44
+ property_value = @@property_evaluators[@property_type].call(selection_array)
45
+ return property_value
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,208 @@
1
+ require_relative 'observable'
2
+ require_relative 'observer'
3
+
4
+ module Glimmer
5
+ module SWT
6
+ class ModelBinding
7
+ include Observable
8
+ include Observer
9
+
10
+ attr_reader :property_type, :binding_options
11
+ @@property_type_converters = {
12
+ :undefined => lambda { |value| value },
13
+ :fixnum => lambda { |value| value.to_i },
14
+ :array => lambda { |value| value.to_a }
15
+ }
16
+ def initialize(base_model, property_name_expression, property_type = :undefined, binding_options = nil)
17
+ property_type = :undefined if property_type.nil?
18
+ @base_model = base_model
19
+ @property_name_expression = property_name_expression
20
+ @property_type = property_type
21
+ @binding_options = binding_options || {}
22
+ if computed?
23
+ @computed_model_bindings = computed_by.map do |computed_by_property_expression|
24
+ self.class.new(base_model, computed_by_property_expression, :undefined, computed_binding_options)
25
+ end
26
+ end
27
+ end
28
+ def model
29
+ nested_property? ? nested_model : base_model
30
+ end
31
+ # e.g. person.address.state returns [person, person.address]
32
+ def nested_models
33
+ @nested_models = [base_model]
34
+ model_property_names.reduce(base_model) do |reduced_model, nested_model_property_name|
35
+ if reduced_model.nil?
36
+ nil
37
+ else
38
+ invoke_property_reader(reduced_model, nested_model_property_name).tap do |new_reduced_model|
39
+ @nested_models << new_reduced_model
40
+ end
41
+ end
42
+ end
43
+ @nested_models
44
+ end
45
+ def nested_model
46
+ nested_models.last
47
+ end
48
+ def base_model
49
+ @base_model
50
+ end
51
+ def property_name
52
+ nested_property? ? nested_property_name : property_name_expression
53
+ end
54
+ # All nested property names
55
+ # e.g. property name expression "address.state" gives ['address', 'state']
56
+ # If there are any indexed property names, this returns indexes as properties.
57
+ # e.g. property name expression "addresses[1].state" gives ['addresses', '[1]', 'state']
58
+ def nested_property_names
59
+ @nested_property_names ||= property_name_expression.split(".").map {|pne| pne.match(/([^\[]+)(\[[^\]]+\])?/).to_a.drop(1)}.flatten.compact
60
+ end
61
+ # Final nested property name
62
+ # e.g. property name expression "address.state" gives :state
63
+ def nested_property_name
64
+ nested_property_names.last
65
+ end
66
+ # Model representing nested property names
67
+ # e.g. property name expression "address.state" gives [:address]
68
+ def model_property_names
69
+ nested_property_names[0...-1]
70
+ end
71
+ def nested_property?
72
+ property_name_expression.match(/[.\[]/)
73
+ end
74
+ def property_name_expression
75
+ @property_name_expression
76
+ end
77
+ def computed?
78
+ !computed_by.empty?
79
+ end
80
+ def computed_by
81
+ [@binding_options[:computed_by]].flatten.compact
82
+ end
83
+ def computed_binding_options
84
+ @binding_options.reject {|k,v| k == :computed_by}
85
+ end
86
+ def nested_property_observers_for(observer)
87
+ @nested_property_observers_collection ||= {}
88
+ unless @nested_property_observers_collection.has_key?(observer)
89
+ @nested_property_observers_collection[observer] = nested_property_names.reduce({}) do |output, property_name|
90
+ output.merge(
91
+ property_name => Observer.proc do |new_value|
92
+ # Ensure reattaching observers when a higher level nested property is updated (e.g. person.address changes reattaches person.address.street observer)
93
+ add_observer(observer)
94
+ observer.call(evaluate_property)
95
+ end
96
+ )
97
+ end
98
+ end
99
+ # @nested_property_observers_collection[observer].keys.each_with_index do |property_name, i|
100
+ # previous_property_name = nested_property_names[i-1]
101
+ # previous_observer = @nested_property_observers_collection[observer][previous_property_name]
102
+ # nested_property_observer = @nested_property_observers_collection[observer][property_name]
103
+ # previous_observer.add_dependent(nested_property_observer) unless previous_observer.nil?
104
+ # end
105
+ # TODO remove this brainstorming
106
+ # person.addresses[1].streets[2].number
107
+ # person.addresses[1] = ...
108
+ @nested_property_observers_collection[observer]
109
+ end
110
+ def add_observer(observer)
111
+ if computed?
112
+ add_computed_observers(observer)
113
+ elsif nested_property?
114
+ add_nested_observers(observer)
115
+ else
116
+ observer.observe(model, property_name)
117
+ observer.add_dependent([self, nil] => [observer, model, property_name])
118
+ end
119
+ end
120
+ def remove_observer(observer)
121
+ if computed?
122
+ @computed_model_bindings.each do |computed_model_binding|
123
+ computed_observer_for(observer).unobserve(computed_model_binding)
124
+ end
125
+ @computed_observer_collection.delete(observer)
126
+ elsif nested_property?
127
+ nested_property_observers_for(observer).clear
128
+ else
129
+ observer.unobserve(model, property_name)
130
+ end
131
+ end
132
+ def computed_observer_for(observer)
133
+ @computed_observer_collection ||= {}
134
+ unless @computed_observer_collection.has_key?(observer)
135
+ @computed_observer_collection[observer] = Observer.proc do |new_value|
136
+ observer.call(evaluate_property)
137
+ end
138
+ end
139
+ @computed_observer_collection[observer]
140
+ end
141
+ def add_computed_observers(observer)
142
+ @computed_model_bindings.each do |computed_model_binding|
143
+ computed_observer_for(observer).observe(computed_model_binding)
144
+ observer.add_dependent([self, nil] => [computed_observer_for(observer), computed_model_binding, nil])
145
+ end
146
+ end
147
+ def add_nested_observers(observer)
148
+ nested_property_observers = nested_property_observers_for(observer)
149
+ nested_models.zip(nested_property_names).each_with_index do |zip, i|
150
+ model, property_name = zip
151
+ nested_property_observer = nested_property_observers[property_name]
152
+ previous_index = i - 1
153
+ parent_model = previous_index.negative? ? self : nested_models[previous_index]
154
+ parent_property_name = previous_index.negative? ? nil : nested_property_names[previous_index]
155
+ parent_observer = previous_index.negative? ? observer : nested_property_observers[parent_property_name]
156
+ parent_property_name = nil if parent_property_name.to_s.start_with?('[')
157
+ unless model.nil?
158
+ if property_indexed?(property_name)
159
+ # TODO figure out a way to deal with this more uniformly
160
+ nested_property_observer.observe(model)
161
+ parent_observer.add_dependent([parent_model, parent_property_name] => [nested_property_observer, model, nil])
162
+ else
163
+ nested_property_observer.observe(model, property_name)
164
+ parent_observer.add_dependent([parent_model, parent_property_name] => [nested_property_observer, model, property_name])
165
+ end
166
+ end
167
+ end
168
+ end
169
+ def call(value)
170
+ return if model.nil?
171
+ converted_value = @@property_type_converters[@property_type].call(value)
172
+ invoke_property_writer(model, "#{property_name}=", converted_value) unless evaluate_property == converted_value
173
+ end
174
+ def evaluate_property
175
+ invoke_property_reader(model, property_name) unless model.nil?
176
+ end
177
+ def evaluate_options_property
178
+ model.send(options_property_name) unless model.nil?
179
+ end
180
+ def options_property_name
181
+ self.property_name + "_options"
182
+ end
183
+ def property_indexed?(property_expression)
184
+ property_expression.start_with?('[')
185
+ end
186
+ def invoke_property_reader(object, property_expression)
187
+ if property_indexed?(property_expression)
188
+ property_method = '[]'
189
+ property_argument = property_expression[1...-1]
190
+ property_argument = property_argument.to_i if property_argument.match(/\d+/)
191
+ object.send(property_method, property_argument)
192
+ else
193
+ object.send(property_expression)
194
+ end
195
+ end
196
+ def invoke_property_writer(object, property_expression, value)
197
+ if property_indexed?(property_expression)
198
+ property_method = '[]='
199
+ property_argument = property_expression[1...-2]
200
+ property_argument = property_argument.to_i if property_argument.match(/\d+/)
201
+ object.send(property_method, property_argument, value)
202
+ else
203
+ object.send(property_expression, value)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end