glimmer 0.1.10.470 → 0.1.11.SWT4.14

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2743000710bdc46b908035ea211c466ff52e32df467e3c0e33298751c15c35d1
4
- data.tar.gz: a5ec8756834000ff7be8efdfdf0d083c3635971a5fe9927b199d10f01526a4ad
3
+ metadata.gz: e969215624dc4b042727fb8cf0ab47397e9b70e499b59df66ce971b45acb54d7
4
+ data.tar.gz: 84f2da9b3e691d1360d7a9db1345b11a7ecbd00adc15f7c8176fd7ed27023db3
5
5
  SHA512:
6
- metadata.gz: 5f16cd9964a83cf1de59bb2ed56a7c526486b99fa7d38fb48f154295699fe86ee36acfe556e375e12113f8f34a069da7c5de894ebb89aff75d3b3decdac8809e
7
- data.tar.gz: 6e2bf4e9bd8f7bc39765a7fca70d78dcf705edf663bb465d504b581d0a35a9e4284c557f997c7376cd391f76ef36642df4adac3a6f542b26d4ae17f289a0197d
6
+ metadata.gz: 3cc0172f53ed935dfe09ce6dd3f154c563e71c3fa6d403a2772acb5c36d6f2cb06143c8fb77b01590eafa2a0dded33a158977cd805244f603f092540326863ee
7
+ data.tar.gz: dee9c61db200aeec1573631c4767ad9488d8b806df361e538c665457380ef7f74cdf919b0ff09366a96a1bad5f66113659262fe5fb041b8ce8c2dbe7198f69d9
@@ -45,7 +45,7 @@ Please follow these instructions to make the `glimmer` command available on your
45
45
 
46
46
  Add the following to `Gemfile`:
47
47
  ```
48
- gem 'glimmer', '~> 0.1.10.470'
48
+ gem 'glimmer', '~> 0.1.11.SWT4.14'
49
49
  ```
50
50
 
51
51
  And, then run:
@@ -57,7 +57,7 @@ bundle install
57
57
 
58
58
  Run this command to get directly:
59
59
  ```
60
- gem install glimmer -v 0.1.10.470
60
+ gem install glimmer -v 0.1.11.SWT4.14
61
61
  ```
62
62
 
63
63
  ## Usage
@@ -109,11 +109,15 @@ Data-binding is done with `bind` command following widget property to bind and t
109
109
 
110
110
  Data-binding examples:
111
111
  - `text bind(contact, :first_name)`
112
+ - `text bind(contact, 'address.street')`
112
113
  - `text bind(contact, :name, computed_by: [:first_name, :last_name])`
113
114
 
114
115
  The first example binds the text property of a widget like `label` to the first name of a contact model.
115
116
 
116
- The second example demonstrates computed value data binding whereby the value of `name` depends on changes to both `first_name` and `last_name`
117
+ The second example binds the text property of a widget like `label` to the nested street of
118
+ the address of a contact. This is called nested property data binding.
119
+
120
+ The third example demonstrates computed value data binding whereby the value of `name` depends on changes to both `first_name` and `last_name`
117
121
 
118
122
  You may learn more about Glimmer's syntax by reading the Eclipse Zone Tutorial mentioned in resources and opening up the samples under the `samples` folder.
119
123
 
@@ -170,6 +174,12 @@ Windows is supported by JRuby and the Eclipse SWT library Glimmer runs on. Howev
170
174
 
171
175
  Same as Windows
172
176
 
177
+ ## Feature Suggestions
178
+
179
+ These features have been suggested. You might see them in a future version of Glimmer. You are welcome to contribute more feature suggestions.
180
+
181
+ - bind_collection: an iterator that enables spawning widgets based on a collection (e.g. spawn 2 `AddressWidget`s if `user.addresses` bind collection contains 2 addresses)
182
+
173
183
  ## Contributing to Glimmer
174
184
 
175
185
  Please follow these instructions if you would like to help us develop Glimmer:
data/RELEASE.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Release Notes
2
2
 
3
+ ** 0.1.11.SWT4.14 **
4
+ - Upgraded SWT to version 4.14
5
+
6
+ ** 0.1.11.470 **
7
+ - Nested property data binding support
8
+
3
9
  ** 0.1.10.470 **
4
10
  - Support Tree data-binding (one-way from model to tree)
5
11
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.10.470
1
+ 0.1.11.SWT4.14
@@ -0,0 +1,5 @@
1
+ SWT:
2
+ '4.14':
3
+ mac_x86_64: http://mirror.csclub.uwaterloo.ca/eclipse/eclipse/downloads/drops4/R-4.14-201912100610/swt-4.14-cocoa-macosx-x86_64.zip
4
+ linux_x86_64: http://mirror.csclub.uwaterloo.ca/eclipse/eclipse/downloads/drops4/R-4.14-201912100610/swt-4.14-gtk-linux-x86_64.zip
5
+ windows_x86_64: http://mirror.csclub.uwaterloo.ca/eclipse/eclipse/downloads/drops4/R-4.14-201912100610/swt-4.14-win32-win32-x86_64.zip
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer 0.1.10.470 ruby lib
5
+ # stub: glimmer 0.1.11.SWT4.14 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer".freeze
9
- s.version = "0.1.10.470"
9
+ s.version = "0.1.11.SWT4.14"
10
10
 
11
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
11
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["AndyMaleh".freeze]
14
- s.date = "2019-11-27"
14
+ s.date = "2020-02-28"
15
15
  s.description = "JRuby Desktop UI DSL + Data-Binding library that enables easy and efficient authoring of user-interfaces using the robust platform-independent Eclipse SWT library".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.executables = ["glimmer".freeze, "girb".freeze]
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
34
34
  "bin/girb",
35
35
  "bin/girb_runner.rb",
36
36
  "bin/glimmer",
37
+ "config/swt.yml",
37
38
  "glimmer.gemspec",
38
39
  "images/Bitter-sweet.jpg",
39
40
  "lib/command_handler.rb",
@@ -44,19 +45,21 @@ Gem::Specification.new do |s|
44
45
  "lib/command_handlers/combo_selection_data_binding_command_handler.rb",
45
46
  "lib/command_handlers/data_binding_command_handler.rb",
46
47
  "lib/command_handlers/list_selection_data_binding_command_handler.rb",
47
- "lib/command_handlers/models/list_observer.rb",
48
- "lib/command_handlers/models/model_observer.rb",
48
+ "lib/command_handlers/models/block_observer.rb",
49
+ "lib/command_handlers/models/list_selection_binding.rb",
50
+ "lib/command_handlers/models/model_binding.rb",
49
51
  "lib/command_handlers/models/observable_array.rb",
50
52
  "lib/command_handlers/models/observable_model.rb",
53
+ "lib/command_handlers/models/observer.rb",
51
54
  "lib/command_handlers/models/r_runnable.rb",
52
55
  "lib/command_handlers/models/r_shell.rb",
53
56
  "lib/command_handlers/models/r_tab_item_composite.rb",
54
57
  "lib/command_handlers/models/r_widget.rb",
55
58
  "lib/command_handlers/models/r_widget_listener.rb",
56
59
  "lib/command_handlers/models/r_widget_packages.rb",
57
- "lib/command_handlers/models/table_items_updater.rb",
58
- "lib/command_handlers/models/tree_items_updater.rb",
59
- "lib/command_handlers/models/widget_observer.rb",
60
+ "lib/command_handlers/models/table_items_binding.rb",
61
+ "lib/command_handlers/models/tree_items_binding.rb",
62
+ "lib/command_handlers/models/widget_binding.rb",
60
63
  "lib/command_handlers/shell_command_handler.rb",
61
64
  "lib/command_handlers/tab_item_command_handler.rb",
62
65
  "lib/command_handlers/table_column_properties_data_binding_command_handler.rb",
@@ -1,9 +1,9 @@
1
1
  require File.dirname(__FILE__) + "/../command_handler"
2
2
  require File.dirname(__FILE__) + "/models/r_widget"
3
- require File.dirname(__FILE__) + "/models/model_observer"
3
+ require File.dirname(__FILE__) + "/models/model_binding"
4
4
 
5
5
  # Responsible for setting up the return value of the bind keyword (command symbol)
6
- # as a ModelObserver. It is then used by another command handler like
6
+ # as a ModelBinding. It is then used by another command handler like
7
7
  # DataBindingCommandHandler for text and selection properties on Text and Spinner
8
8
  # or TableItemsDataBindingCommandHandler for items in a Table
9
9
  class BindCommandHandler
@@ -41,9 +41,17 @@ class BindCommandHandler
41
41
 
42
42
  def do_handle(parent, command_symbol, *args, &block)
43
43
  property_type = args[2] if (args.size == 3) && !args[2].is_a?(Hash)
44
- observer_options = args[2] if args[2].is_a?(Hash)
45
- observer_options = args[3] if args[3].is_a?(Hash)
46
- ModelObserver.new(args[0], args[1].to_s, property_type, observer_options)
44
+ binding_options = args[2] if args[2].is_a?(Hash)
45
+ binding_options = args[3] if args[3].is_a?(Hash)
46
+ ModelBinding.new(args[0], args[1].to_s, property_type, binding_options)
47
+ #TODO replace ModelObserver with something else. It's inaccurate
48
+ # it doesn't observe the model, it updates it, observing the widget
49
+ # yet also contains information about observing the model, like the property
50
+ # to observe. That's the real object spit out here. Must name is something else.
51
+ # Maybe call ModelBinding and spin off Observer as ModelPropertyUpdateCommand
52
+ # similarly create a WidgetPropertyUpdateCommand (execute not update method)
53
+ # Have an observer be a general thin skeleton with an update method instead.
54
+ # In the future, switch observer to a simple block.
47
55
  end
48
56
 
49
57
  end
@@ -4,7 +4,7 @@ require File.dirname(__FILE__) + "/models/r_widget"
4
4
  class ComboSelectionDataBindingCommandHandler
5
5
  include CommandHandler
6
6
  include Glimmer
7
-
7
+
8
8
  include_package 'org.eclipse.swt.widgets'
9
9
 
10
10
  def can_handle?(parent, command_symbol, *args, &block)
@@ -12,28 +12,28 @@ class ComboSelectionDataBindingCommandHandler
12
12
  parent.widget.is_a?(Combo) and
13
13
  command_symbol.to_s == "selection" and
14
14
  args.size == 1 and
15
- args[0].is_a?(ModelObserver) and
15
+ args[0].is_a?(ModelBinding) and
16
16
  args[0].evaluate_options_property.is_a?(Array) and
17
17
  block == nil
18
18
  end
19
-
19
+
20
20
  def do_handle(parent, command_symbol, *args, &block)
21
- model_observer = args[0]
22
- widget_observer = WidgetObserver.new(parent, "items")
23
- widget_observer.update(model_observer.evaluate_options_property)
24
- model = model_observer.model
21
+ model_binding = args[0]
22
+ widget_binding = WidgetBinding.new(parent, "items")
23
+ widget_binding.update(model_binding.evaluate_options_property)
24
+ model = model_binding.base_model
25
25
  model.extend ObservableModel unless model.is_a?(ObservableModel)
26
- model.add_observer(model_observer.options_property_name, widget_observer)
26
+ model.add_observer(model_binding.options_property_name, widget_binding)
27
+
28
+ widget_binding = WidgetBinding.new(parent, "text")
29
+ widget_binding.update(model_binding.evaluate_property)
30
+ model.add_observer(model_binding.property_name_expression, widget_binding)
27
31
 
28
- widget_observer = WidgetObserver.new(parent, "text")
29
- widget_observer.update(model_observer.evaluate_property)
30
- model.add_observer(model_observer.property_name, widget_observer)
31
-
32
32
  add_contents(parent) {
33
33
  on_widget_selected {
34
- model_observer.update(widget_observer.evaluate_property)
34
+ model_binding.update(widget_binding.evaluate_property)
35
35
  }
36
- }
36
+ }
37
37
  end
38
38
 
39
- end
39
+ end
@@ -1,13 +1,13 @@
1
1
  require File.dirname(__FILE__) + "/../command_handler"
2
2
  require File.dirname(__FILE__) + "/models/r_widget"
3
3
  require File.dirname(__FILE__) + "/models/observable_model"
4
- require File.dirname(__FILE__) + "/models/model_observer"
5
- require File.dirname(__FILE__) + "/models/widget_observer"
4
+ require File.dirname(__FILE__) + "/models/model_binding"
5
+ require File.dirname(__FILE__) + "/models/widget_binding"
6
6
 
7
7
  # Responsible for wiring two-way data-binding for text and selection properties
8
8
  # on Text, Button, and Spinner widgets.
9
9
  # Does so by using the output of the bind(model, property) command in the form
10
- # of a ModelObserver, which is then connected to an anonymous widget observer
10
+ # of a ModelBinding, which is then connected to an anonymous widget observer
11
11
  # (aka widget_data_binder as per widget_data_binders array)
12
12
  #
13
13
  # Depends on BindCommandHandler
@@ -19,28 +19,28 @@ class DataBindingCommandHandler
19
19
 
20
20
  @@widget_data_binders = {
21
21
  Java::OrgEclipseSwtWidgets::Text => {
22
- :text => Proc.new do |rwidget, model_observer|
22
+ :text => Proc.new do |rwidget, model_binding|
23
23
  add_contents(rwidget) {
24
24
  on_modify_text { |modify_event|
25
- model_observer.update(rwidget.widget.getText)
25
+ model_binding.update(rwidget.widget.getText)
26
26
  }
27
27
  }
28
28
  end,
29
29
  },
30
30
  Java::OrgEclipseSwtWidgets::Button => {
31
- :selection => Proc.new do |rwidget, model_observer|
31
+ :selection => Proc.new do |rwidget, model_binding|
32
32
  add_contents(rwidget) {
33
33
  on_widget_selected { |selection_event|
34
- model_observer.update(rwidget.widget.getSelection)
34
+ model_binding.update(rwidget.widget.getSelection)
35
35
  }
36
36
  }
37
37
  end
38
38
  },
39
39
  Java::OrgEclipseSwtWidgets::Spinner => {
40
- :selection => Proc.new do |rwidget, model_observer|
40
+ :selection => Proc.new do |rwidget, model_binding|
41
41
  add_contents(rwidget) {
42
42
  on_widget_selected { |selection_event|
43
- model_observer.update(rwidget.widget.getSelection)
43
+ model_binding.update(rwidget.widget.getSelection)
44
44
  }
45
45
  }
46
46
  end
@@ -50,25 +50,24 @@ class DataBindingCommandHandler
50
50
  def can_handle?(parent, command_symbol, *args, &block)
51
51
  (parent.is_a?(RWidget) and
52
52
  args.size == 1 and
53
- args[0].is_a?(ModelObserver))
53
+ args[0].is_a?(ModelBinding))
54
54
  end
55
55
 
56
56
  def do_handle(parent, command_symbol, *args, &block)
57
- model_observer = args[0]
58
- widget_observer_parameters = [parent, command_symbol.to_s]
59
- widget_observer_parameters << proc {|value| model_observer.evaluate_property} if model_observer.observer_options.has_key?(:computed_by)
60
- widget_observer = WidgetObserver.new(*widget_observer_parameters)
61
- widget_observer.update(model_observer.evaluate_property)
62
- model = model_observer.model
63
- model.extend ObservableModel unless model.is_a?(ObservableModel)
64
- model.add_observer(model_observer.property_name, widget_observer)
65
- computed_by_property_names = model_observer.observer_options[:computed_by]
57
+ model_binding = args[0]
58
+ widget_binding_parameters = [parent, command_symbol.to_s]
59
+ widget_binding_parameters << proc {|value| model_binding.evaluate_property} if model_binding.binding_options.has_key?(:computed_by)
60
+ widget_binding = WidgetBinding.new(*widget_binding_parameters)
61
+ widget_binding.update(model_binding.evaluate_property)
62
+ model = model_binding.model
63
+ model_binding.add_observer(widget_binding)
64
+ computed_by_property_names = model_binding.binding_options[:computed_by]
66
65
  computed_by_property_names&.each do |computed_by_property_name|
67
- model.add_observer(computed_by_property_name, widget_observer)
66
+ model.add_observer(computed_by_property_name, widget_binding)
68
67
  end
69
68
  widget_data_binder_map = @@widget_data_binders[parent.widget.class]
70
69
  widget_data_binder = widget_data_binder_map[command_symbol.to_s.to_sym] if widget_data_binder_map
71
- widget_data_binder.call(parent, model_observer) if widget_data_binder
70
+ widget_data_binder.call(parent, model_binding) if widget_data_binder
72
71
  end
73
72
 
74
73
  end
@@ -1,11 +1,11 @@
1
1
  require File.dirname(__FILE__) + "/../command_handler"
2
2
  require File.dirname(__FILE__) + "/models/r_widget"
3
- require File.dirname(__FILE__) + "/models/list_observer"
3
+ require File.dirname(__FILE__) + "/models/list_selection_binding"
4
4
 
5
5
  class ListSelectionDataBindingCommandHandler
6
6
  include CommandHandler
7
7
  include Glimmer
8
-
8
+
9
9
  include_package 'org.eclipse.swt.widgets'
10
10
 
11
11
  def can_handle?(parent, command_symbol, *args, &block)
@@ -13,30 +13,30 @@ class ListSelectionDataBindingCommandHandler
13
13
  parent.widget.is_a?(List) and
14
14
  command_symbol.to_s == "selection" and
15
15
  args.size == 1 and
16
- args[0].is_a?(ModelObserver) and
16
+ args[0].is_a?(ModelBinding) and
17
17
  args[0].evaluate_options_property.is_a?(Array) and
18
18
  block == nil
19
19
  end
20
-
20
+
21
21
  def do_handle(parent, command_symbol, *args, &block)
22
- model_observer = args[0]
23
- widget_observer = WidgetObserver.new(parent, "items")
24
- widget_observer.update(model_observer.evaluate_options_property)
25
- model = model_observer.model
22
+ model_binding = args[0]
23
+ widget_binding = WidgetBinding.new(parent, "items")
24
+ widget_binding.update(model_binding.evaluate_options_property)
25
+ model = model_binding.base_model
26
26
  model.extend ObservableModel unless model.is_a?(ObservableModel)
27
- model.add_observer(model_observer.options_property_name, widget_observer)
27
+ model.add_observer(model_binding.options_property_name, widget_binding)
28
28
 
29
29
  property_type = :string
30
30
  property_type = :array if parent.has_style?(:multi)
31
- list_observer = ListObserver.new(parent, property_type)
32
- list_observer.update(model_observer.evaluate_property)
33
- model.add_observer(model_observer.property_name, list_observer)
34
-
31
+ list_selection_binding = ListSelectionBinding.new(parent, property_type)
32
+ list_selection_binding.update(model_binding.evaluate_property)
33
+ model.add_observer(model_binding.property_name_expression, list_selection_binding)
34
+
35
35
  add_contents(parent) {
36
36
  on_widget_selected {
37
- model_observer.update(list_observer.evaluate_property)
37
+ model_binding.update(list_selection_binding.evaluate_property)
38
38
  }
39
39
  }
40
40
  end
41
41
 
42
- end
42
+ end
@@ -0,0 +1,13 @@
1
+ # Observer that takes an updater block to process updates
2
+
3
+ class BlockObserver
4
+ include Observer
5
+
6
+ def initialize(&updater)
7
+ @updater = updater
8
+ end
9
+
10
+ def update(changed_value)
11
+ @updater.call(changed_value)
12
+ end
13
+ end
@@ -1,25 +1,32 @@
1
- class ListObserver
1
+ require File.dirname(__FILE__) + "/observer"
2
+
3
+ # SWT List widget selection binding
4
+ class ListSelectionBinding
5
+ include Observer
6
+
2
7
  attr_reader :widget
3
8
  @@property_type_updaters = {
4
- :string => lambda { |widget, value| widget.widget.select(widget.widget.index_of(value.to_s)) },
9
+ :string => lambda { |widget, value| widget.widget.select(widget.widget.index_of(value.to_s)) },
5
10
  :array => lambda { |widget, value| widget.widget.selection=((value or []).to_java :string) }
6
11
  }
7
12
  @@property_evaluators = {
8
- :string => lambda do |selection_array|
13
+ :string => lambda do |selection_array|
9
14
  return nil if selection_array.empty?
10
15
  selection_array[0]
11
- end,
12
- :array => lambda do |selection_array|
16
+ end,
17
+ :array => lambda do |selection_array|
13
18
  selection_array
14
19
  end
15
20
  }
21
+ # Initialize with list widget and property_type
22
+ # property_type :string represents default list single selection
23
+ # property_type :array represents list multi selection
16
24
  def initialize(widget, property_type)
17
25
  property_type = :string if property_type.nil? or property_type == :undefined
18
26
  @widget = widget
19
27
  @property_type = property_type
20
28
  end
21
29
  def update(value)
22
- raise "hell" if value == ["Canada"]
23
30
  @@property_type_updaters[@property_type].call(@widget, value) unless evaluate_property == value
24
31
  end
25
32
  def evaluate_property
@@ -28,4 +35,3 @@ class ListObserver
28
35
  return property_value
29
36
  end
30
37
  end
31
-
@@ -0,0 +1,102 @@
1
+ require File.dirname(__FILE__) + "/observer"
2
+
3
+ class ModelBinding
4
+ include Observer
5
+
6
+ attr_reader :property_type, :binding_options
7
+ @@property_type_converters = {
8
+ :undefined => lambda { |value| value },
9
+ :fixnum => lambda { |value| value.to_i },
10
+ :array => lambda { |value| value.to_a }
11
+ }
12
+ def initialize(base_model, property_name_expression, property_type = :undefined, binding_options = nil)
13
+ property_type = :undefined if property_type.nil?
14
+ @base_model = base_model
15
+ @property_name_expression = property_name_expression
16
+ @property_type = property_type
17
+ @binding_options = binding_options || {}
18
+ end
19
+ def model
20
+ nested_property? ? nested_model : base_model
21
+ end
22
+ # e.g. person.address.state returns [person, person.address]
23
+ def nested_models
24
+ @nested_models = [base_model]
25
+ model_property_names.reduce(base_model) do |reduced_model, nested_model_property_name|
26
+ reduced_model.send(nested_model_property_name).tap do |new_reduced_model|
27
+ @nested_models << new_reduced_model
28
+ end
29
+ end
30
+ @nested_models
31
+ end
32
+ def nested_model
33
+ nested_models.last
34
+ end
35
+ def base_model
36
+ @base_model
37
+ end
38
+ def property_name
39
+ nested_property? ? nested_property_name : property_name_expression
40
+ end
41
+ # All nested property names
42
+ # e.g. property name expression "address.state" gives [:address, :state]
43
+ def nested_property_names
44
+ property_name_expression.split(".")
45
+ end
46
+ # Final nested property name
47
+ # e.g. property name expression "address.state" gives :state
48
+ def nested_property_name
49
+ nested_property_names.last
50
+ end
51
+ # Model representing nested property names
52
+ # e.g. property name expression "address.state" gives [:address]
53
+ def model_property_names
54
+ nested_property_names[0...-1]
55
+ end
56
+ def nested_property?
57
+ property_name_expression.include?(".")
58
+ end
59
+ def property_name_expression
60
+ @property_name_expression
61
+ end
62
+ def nested_property_observers_for(observer)
63
+ @nested_property_observers_collection ||= {}
64
+ unless @nested_property_observers_collection.has_key?(observer)
65
+ @nested_property_observers_collection[observer] = nested_property_names.reduce({}) do |output, property_name|
66
+ output.merge(
67
+ property_name => BlockObserver.new do |changed_value|
68
+ # Ensure reattaching observers when a higher level nested property is updated (e.g. person.address changes reattaches person.address.street observer)
69
+ add_observer(observer)
70
+ observer.update(evaluate_property)
71
+ end
72
+ )
73
+ end
74
+ end
75
+ @nested_property_observers_collection[observer]
76
+ end
77
+ def add_observer(observer)
78
+ if nested_property?
79
+ nested_property_observers = nested_property_observers_for(observer)
80
+ nested_models.zip(nested_property_names).each do |model, property_name|
81
+ model.extend ObservableModel unless model.is_a?(ObservableModel)
82
+ model.add_observer(property_name, nested_property_observers[property_name]) unless model.has_observer?(property_name, nested_property_observers[property_name])
83
+ end
84
+ else
85
+ model.extend ObservableModel unless model.is_a?(ObservableModel)
86
+ model.add_observer(property_name, observer)
87
+ end
88
+ end
89
+ def update(value)
90
+ converted_value = @@property_type_converters[@property_type].call(value)
91
+ model.send(property_name + "=", converted_value) unless evaluate_property == converted_value
92
+ end
93
+ def evaluate_property
94
+ model.send(property_name)
95
+ end
96
+ def evaluate_options_property
97
+ model.send(property_name + "_options")
98
+ end
99
+ def options_property_name
100
+ self.property_name + "_options"
101
+ end
102
+ end
@@ -1,10 +1,19 @@
1
1
  require 'set'
2
+
3
+ require File.dirname(__FILE__) + "/block_observer"
4
+
2
5
  module ObservableModel
3
6
 
4
- def add_observer(property_name, observer)
7
+ # Takes observer as an object or a block updater
8
+ def add_observer(property_name, observer = nil, &updater)
9
+ observer ||= BlockObserver.new(&updater)
5
10
  property_observer_list(property_name) << observer
6
11
  end
7
12
 
13
+ def has_observer?(property_name, observer)
14
+ property_observer_list(property_name).include?(observer)
15
+ end
16
+
8
17
  def property_observer_hash
9
18
  @property_observers = Hash.new unless @property_observers
10
19
  @property_observers
@@ -0,0 +1,9 @@
1
+ # Mixin representing Observer trait from Observer Design Pattern
2
+ # Allows classes to include without interfering with their
3
+ # inheritance hierarchy.
4
+
5
+ module Observer
6
+ def update(changed_value)
7
+ raise 'Not implemented!'
8
+ end
9
+ end
@@ -1,18 +1,20 @@
1
1
  require File.dirname(__FILE__) + "/observable_array"
2
2
  require File.dirname(__FILE__) + "/observable_model"
3
+ require File.dirname(__FILE__) + "/observer"
3
4
 
4
- class TableItemsUpdater
5
+ class TableItemsBinding
6
+ include Observer
5
7
  include_package 'org.eclipse.swt'
6
8
  include_package 'org.eclipse.swt.widgets'
7
-
8
- def initialize(parent, model_observer, column_properties)
9
+
10
+ def initialize(parent, model_binding, column_properties)
9
11
  @table = parent
10
- @model_observer = model_observer
12
+ @model_binding = model_binding
11
13
  @column_properties = column_properties
12
- update(@model_observer.evaluate_property)
13
- model = model_observer.model
14
+ update(@model_binding.evaluate_property)
15
+ model = model_binding.base_model
14
16
  model.extend(ObservableModel) unless model.is_a?(ObservableModel)
15
- model.add_observer(model_observer.property_name, self)
17
+ model.add_observer(model_binding.property_name_expression, self)
16
18
  end
17
19
  def update(model_collection=nil)
18
20
  if model_collection and model_collection.is_a?(Array)
@@ -31,6 +33,5 @@ class TableItemsUpdater
31
33
  end
32
34
  end
33
35
  end
34
-
36
+
35
37
  end
36
-
@@ -1,18 +1,20 @@
1
1
  require File.dirname(__FILE__) + "/observable_array"
2
2
  require File.dirname(__FILE__) + "/observable_model"
3
+ require File.dirname(__FILE__) + "/observer"
3
4
 
4
- class TreeItemsUpdater
5
+ class TreeItemsBinding
6
+ include Observer
5
7
  include_package 'org.eclipse.swt'
6
8
  include_package 'org.eclipse.swt.widgets'
7
9
 
8
- def initialize(parent, model_observer, tree_properties)
10
+ def initialize(parent, model_binding, tree_properties)
9
11
  @tree = parent
10
- @model_observer = model_observer
12
+ @model_binding = model_binding
11
13
  @tree_properties = [tree_properties].flatten.first.to_h
12
- update(@model_observer.evaluate_property)
13
- model = model_observer.model
14
+ update(@model_binding.evaluate_property)
15
+ model = model_binding.base_model
14
16
  model.extend(ObservableModel) unless model.is_a?(ObservableModel)
15
- model.add_observer(model_observer.property_name, self)
17
+ model.add_observer(model_binding.property_name_expression, self)
16
18
  end
17
19
  def update(model_tree_root_node=nil)
18
20
  if model_tree_root_node and model_tree_root_node.respond_to?(@tree_properties[:children])
@@ -1,4 +1,8 @@
1
- class WidgetObserver
1
+ require File.dirname(__FILE__) + "/observer"
2
+
3
+ class WidgetBinding
4
+ include Observer
5
+
2
6
  attr_reader :widget, :property
3
7
  @@property_type_converters = {
4
8
  :text => Proc.new { |value| value.to_s },
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + "/../command_handler"
2
2
  require File.dirname(__FILE__) + "/models/r_widget"
3
- require File.dirname(__FILE__) + "/models/table_items_updater"
3
+ require File.dirname(__FILE__) + "/models/table_items_binding"
4
4
 
5
5
  #Depends on BindCommandHandler and TableColumnPropertiesDataBindingCommandHandler
6
6
  class TableItemsDataBindingCommandHandler
@@ -13,16 +13,16 @@ class TableItemsDataBindingCommandHandler
13
13
  parent.widget.is_a?(Table) and
14
14
  command_symbol.to_s == "items" and
15
15
  args.size == 2 and
16
- args[0].is_a?(ModelObserver) and
16
+ args[0].is_a?(ModelBinding) and
17
17
  args[0].evaluate_property.is_a?(Array) and
18
18
  args[1].is_a?(Array) and
19
19
  block == nil
20
20
  end
21
21
 
22
22
  def do_handle(parent, command_symbol, *args, &block)
23
- model_observer = args[0]
23
+ model_binding = args[0]
24
24
  column_properties = args[1]
25
- TableItemsUpdater.new(parent, model_observer, column_properties)
25
+ TableItemsBinding.new(parent, model_binding, column_properties)
26
26
  end
27
27
 
28
28
  end
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + "/../command_handler"
2
2
  require File.dirname(__FILE__) + "/models/r_widget"
3
- require File.dirname(__FILE__) + "/models/tree_items_updater"
3
+ require File.dirname(__FILE__) + "/models/tree_items_binding"
4
4
 
5
5
  class TreeItemsDataBindingCommandHandler
6
6
  include CommandHandler
@@ -12,16 +12,16 @@ class TreeItemsDataBindingCommandHandler
12
12
  (parent.widget.is_a?(Tree)) and
13
13
  (command_symbol.to_s == "items") and
14
14
  (args.size == 2) and
15
- (args[0].is_a?(ModelObserver)) and
15
+ (args[0].is_a?(ModelBinding)) and
16
16
  (!args[0].evaluate_property.is_a?(Array)) and
17
17
  (args[1].is_a?(Array) && !args[1].empty? && args[1].first.is_a?(Hash)) and
18
18
  (block == nil)
19
19
  end
20
20
 
21
21
  def do_handle(parent, command_symbol, *args, &block)
22
- model_observer = args[0]
22
+ model_binding = args[0]
23
23
  tree_properties = args[1]
24
- TreeItemsUpdater.new(parent, model_observer, tree_properties)
24
+ TreeItemsBinding.new(parent, model_binding, tree_properties)
25
25
  end
26
26
 
27
27
  end
@@ -1,22 +1,21 @@
1
1
  require 'net/http'
2
2
  require 'fileutils'
3
3
  require 'os'
4
+ require 'yaml'
4
5
 
5
6
  class GlimmerApplication
6
7
  SWT_ZIP_FILE = File.join(`echo ~`.strip, '.glimmer', 'vendor', 'swt.zip')
7
8
  SWT_JAR_FILE = File.join(File.dirname(SWT_ZIP_FILE), 'swt.jar')
8
9
 
9
10
  OPERATING_SYSTEMS = ["mac", "windows", "linux"]
10
-
11
- SWT_URL = {
12
- "mac_x86_64" => "http://mirror.csclub.uwaterloo.ca/eclipse/eclipse/downloads/drops4/R-4.7-201706120950/swt-4.7-cocoa-macosx-x86_64.zip",
13
- "linux_x86_64" => "http://mirror.cc.vt.edu/pub/eclipse/eclipse/downloads/drops4/R-4.7-201706120950/swt-4.7-gtk-linux-x86_64.zip",
14
- "linux_x86" => "http://mirror.csclub.uwaterloo.ca/eclipse/eclipse/downloads/drops4/R-4.7-201706120950/swt-4.7-gtk-linux-x86.zip",
15
- "windows_x86_64" => "http://mirror.csclub.uwaterloo.ca/eclipse/eclipse/downloads/drops4/R-4.7-201706120950/swt-4.7-win32-win32-x86_64.zip",
16
- "windows_x86" => "http://eclipse.mirror.rafal.ca/eclipse/downloads/drops4/R-4.7-201706120950/swt-4.7-win32-win32-x86.zip"
17
- }
18
-
19
- attr_reader :setup_requested, :application
11
+ REGEX_SETUP = /--setup/
12
+ REGEX_SWT_VERSION = /[0-9]+[.0-9]*/
13
+ REGEX_APPLICATION = /.+/
14
+ REGEX_OPTIONS = [
15
+ REGEX_SETUP, REGEX_SWT_VERSION, REGEX_APPLICATION
16
+ ].map {|re| "(#{re})?"}.join("\s*")
17
+
18
+ attr_reader :setup_requested, :swt_version_specified, :application
20
19
  alias :setup_requested? :setup_requested
21
20
 
22
21
  # Accepts the following options string:
@@ -26,8 +25,10 @@ class GlimmerApplication
26
25
  # or
27
26
  # app_file_path.rb
28
27
  def initialize(options)
29
- @setup_requested = options.first == '--setup'
30
- @application = setup_requested? ? options[1] : options.first
28
+ options_regex_match = options.join(' ').match(REGEX_OPTIONS)
29
+ @setup_requested = !!options_regex_match[1]
30
+ @swt_version_specified = options_regex_match[2]
31
+ @application = options_regex_match[3]
31
32
  end
32
33
 
33
34
  def start
@@ -35,7 +36,7 @@ class GlimmerApplication
35
36
 
36
37
  setup if setup_requested? || !File.exist?(SWT_JAR_FILE)
37
38
 
38
- if application
39
+ if application && File.exist?(SWT_JAR_FILE)
39
40
  puts "Starting Glimmer Application #{application}"
40
41
  system "ruby #{additional_options} -J-classpath \"#{SWT_JAR_FILE}\" #{application}"
41
42
  end
@@ -43,18 +44,21 @@ class GlimmerApplication
43
44
 
44
45
  def usage
45
46
  puts <<-MULTILINE
46
- Usage: glimmer [--setup] [application_ruby_file_path.rb]
47
+ Usage: glimmer [--setup] [SWT_VERSION] [application_ruby_file_path.rb]
47
48
 
48
49
  Example 1: glimmer hello_combo.rb
49
50
  This runs the Glimmer application hello_combo.rb
50
- If the SWT Jar is missing, it downloads it and sets it up first.
51
+ If the SWT Jar is missing, it first downloads the latest version supported.
51
52
 
52
53
  Example 2: glimmer --setup hello_combo.rb
53
54
  This performs setup and then runs the Glimmer application hello_combo.rb
54
- It downloads and sets up the SWT jar whether missing or not.
55
+ It downloads and sets up the latest SWT jar whether missing or not.
55
56
 
56
57
  Example 3: glimmer --setup
57
- This downloads and sets up the SWT jar whether missing or not.
58
+ This downloads and sets up the latest SWT jar whether missing or not.
59
+
60
+ Example 4: glimmer --setup 4.14
61
+ This downloads and sets up the SWT jar specified version 4.14 whether missing or not.
58
62
  MULTILINE
59
63
  end
60
64
 
@@ -65,6 +69,8 @@ This downloads and sets up the SWT jar whether missing or not.
65
69
  puts "Unzipping #{SWT_ZIP_FILE}"
66
70
  `unzip -o #{SWT_ZIP_FILE} -d #{File.dirname(SWT_ZIP_FILE)}`
67
71
  puts "Finished unzipping"
72
+ rescue => e
73
+ puts e.message
68
74
  end
69
75
 
70
76
  def download(file)
@@ -77,7 +83,21 @@ This downloads and sets up the SWT jar whether missing or not.
77
83
  end
78
84
 
79
85
  def platform_swt_url
80
- SWT_URL[platform_swt_url_key]
86
+ swt_config[swt_version][platform_swt_url_key]
87
+ rescue
88
+ raise "SWT version #{swt_version} is not available for download!"
89
+ end
90
+
91
+ def swt_config
92
+ @swt_config ||= YAML.load_file(File.join(__FILE__, '..', '..', 'config', 'swt.yml'))['SWT']
93
+ end
94
+
95
+ def swt_version
96
+ swt_version_specified || swt_latest_version
97
+ end
98
+
99
+ def swt_latest_version
100
+ @swt_latest_version ||= swt_config.keys.max_by {|v| v.split('.').reverse.map(&:to_i).each_with_index.map {|n, i| n*(1000**i)}.sum}
81
101
  end
82
102
 
83
103
  def platform_swt_url_key
@@ -4,18 +4,18 @@ class HelloTab
4
4
  include Glimmer
5
5
  def launch
6
6
  shell {
7
- text "SWT"
7
+ text "Hello Tab"
8
8
  tab_folder {
9
9
  tab_item {
10
- text "Tab 1"
11
- label {
12
- text "Hello World!"
10
+ text "English"
11
+ label {
12
+ text "Hello World!"
13
13
  }
14
14
  }
15
15
  tab_item {
16
- text "Tab 2"
17
- label {
18
- text "Bonjour Univers!"
16
+ text "French"
17
+ label {
18
+ text "Bonjour Univers!"
19
19
  }
20
20
  }
21
21
  }
@@ -23,4 +23,4 @@ class HelloTab
23
23
  end
24
24
  end
25
25
 
26
- HelloTab.new.launch
26
+ HelloTab.new.launch
@@ -31,6 +31,14 @@ describe "Glimmer Data Binding" do
31
31
  end
32
32
  end
33
33
 
34
+ class Address
35
+ attr_accessor :street, :city, :state, :zip
36
+ end
37
+
38
+ class PersonWithNestedProperties
39
+ attr_accessor :address1, :address2
40
+ end
41
+
34
42
  it "tests text widget data binding string property" do
35
43
  person = Person.new
36
44
  person.name = "Bruce Ting"
@@ -284,4 +292,113 @@ describe "Glimmer Data Binding" do
284
292
  expect(person.adult).to eq(true)
285
293
  end
286
294
 
295
+ context "nested data binding" do
296
+
297
+ it "tests text widget data binding to nested string property" do
298
+ person = PersonWithNestedProperties.new
299
+ person.address1 = Address.new
300
+ person.address2 = Address.new
301
+
302
+ person.address1.street = "20 Naper Ave"
303
+ person.address1.city = "Indianapolis"
304
+ person.address1.state = "IN"
305
+ person.address1.zip = "46183"
306
+
307
+ person.address2.street = "101 Confession St"
308
+ person.address2.city = "Denver"
309
+ person.address2.state = "CO"
310
+ person.address2.zip = "80014"
311
+
312
+ @target = shell {
313
+ composite {
314
+ @address1_street_text_widget = text {
315
+ text bind(person, "address1.street")
316
+ }
317
+ @address1_city_text_widget = text {
318
+ text bind(person, "address1.city")
319
+ }
320
+ @address1_state_text_widget = text {
321
+ text bind(person, "address1.state")
322
+ }
323
+ @address1_zip_text_widget = text {
324
+ text bind(person, "address1.zip")
325
+ }
326
+ }
327
+ composite {
328
+ @address2_street_text_widget = text {
329
+ text bind(person, "address2.street")
330
+ }
331
+ @address2_city_text_widget = text {
332
+ text bind(person, "address2.city")
333
+ }
334
+ @address2_state_text_widget = text {
335
+ text bind(person, "address2.state")
336
+ }
337
+ @address2_zip_text_widget = text {
338
+ text bind(person, "address2.zip")
339
+ }
340
+ }
341
+ }
342
+
343
+ expect(@address1_street_text_widget.widget.getText).to eq("20 Naper Ave")
344
+ expect(@address1_city_text_widget.widget.getText).to eq("Indianapolis")
345
+ expect(@address1_state_text_widget.widget.getText).to eq("IN")
346
+ expect(@address1_zip_text_widget.widget.getText).to eq("46183")
347
+
348
+ expect(@address2_street_text_widget.widget.getText).to eq("101 Confession St")
349
+ expect(@address2_city_text_widget.widget.getText).to eq("Denver")
350
+ expect(@address2_state_text_widget.widget.getText).to eq("CO")
351
+ expect(@address2_zip_text_widget.widget.getText).to eq("80014")
352
+
353
+ person.address1.street = "123 Main St"
354
+ person.address1.city = "Chicago"
355
+ person.address1.state = "IL"
356
+ person.address1.zip = "60654"
357
+
358
+ person.address2.street = "100 Park Ave"
359
+ person.address2.city = "San Diego"
360
+ person.address2.state = "CA"
361
+ person.address2.zip = "92014"
362
+
363
+ expect(@address1_street_text_widget.widget.getText).to eq("123 Main St")
364
+ expect(@address1_city_text_widget.widget.getText).to eq("Chicago")
365
+ expect(@address1_state_text_widget.widget.getText).to eq("IL")
366
+ expect(@address1_zip_text_widget.widget.getText).to eq("60654")
367
+
368
+ expect(@address2_street_text_widget.widget.getText).to eq("100 Park Ave")
369
+ expect(@address2_city_text_widget.widget.getText).to eq("San Diego")
370
+ expect(@address2_state_text_widget.widget.getText).to eq("CA")
371
+ expect(@address2_zip_text_widget.widget.getText).to eq("92014")
372
+
373
+ person.address2 = person.address1
374
+
375
+ expect(@address2_street_text_widget.widget.getText).to eq("123 Main St")
376
+ expect(@address2_city_text_widget.widget.getText).to eq("Chicago")
377
+ expect(@address2_state_text_widget.widget.getText).to eq("IL")
378
+ expect(@address2_zip_text_widget.widget.getText).to eq("60654")
379
+
380
+ person.address2 = Address.new
381
+
382
+ person.address2.street = "101 Confession St"
383
+ person.address2.city = "Denver"
384
+ person.address2.state = "CO"
385
+ person.address2.zip = "80014"
386
+
387
+ expect(@address2_street_text_widget.widget.getText).to eq("101 Confession St")
388
+ expect(@address2_city_text_widget.widget.getText).to eq("Denver")
389
+ expect(@address2_state_text_widget.widget.getText).to eq("CO")
390
+ expect(@address2_zip_text_widget.widget.getText).to eq("80014")
391
+
392
+ person.address2.street = "123 Main St"
393
+ person.address2.city = "Chicago"
394
+ person.address2.state = "IL"
395
+ person.address2.zip = "60654"
396
+
397
+ expect(@address2_street_text_widget.widget.getText).to eq("123 Main St")
398
+ expect(@address2_city_text_widget.widget.getText).to eq("Chicago")
399
+ expect(@address2_state_text_widget.widget.getText).to eq("IL")
400
+ expect(@address2_zip_text_widget.widget.getText).to eq("60654")
401
+ end
402
+
403
+ end
287
404
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10.470
4
+ version: 0.1.11.SWT4.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - AndyMaleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-27 00:00:00.000000000 Z
11
+ date: 2020-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -161,6 +161,7 @@ files:
161
161
  - bin/girb
162
162
  - bin/girb_runner.rb
163
163
  - bin/glimmer
164
+ - config/swt.yml
164
165
  - glimmer.gemspec
165
166
  - images/Bitter-sweet.jpg
166
167
  - lib/command_handler.rb
@@ -171,19 +172,21 @@ files:
171
172
  - lib/command_handlers/combo_selection_data_binding_command_handler.rb
172
173
  - lib/command_handlers/data_binding_command_handler.rb
173
174
  - lib/command_handlers/list_selection_data_binding_command_handler.rb
174
- - lib/command_handlers/models/list_observer.rb
175
- - lib/command_handlers/models/model_observer.rb
175
+ - lib/command_handlers/models/block_observer.rb
176
+ - lib/command_handlers/models/list_selection_binding.rb
177
+ - lib/command_handlers/models/model_binding.rb
176
178
  - lib/command_handlers/models/observable_array.rb
177
179
  - lib/command_handlers/models/observable_model.rb
180
+ - lib/command_handlers/models/observer.rb
178
181
  - lib/command_handlers/models/r_runnable.rb
179
182
  - lib/command_handlers/models/r_shell.rb
180
183
  - lib/command_handlers/models/r_tab_item_composite.rb
181
184
  - lib/command_handlers/models/r_widget.rb
182
185
  - lib/command_handlers/models/r_widget_listener.rb
183
186
  - lib/command_handlers/models/r_widget_packages.rb
184
- - lib/command_handlers/models/table_items_updater.rb
185
- - lib/command_handlers/models/tree_items_updater.rb
186
- - lib/command_handlers/models/widget_observer.rb
187
+ - lib/command_handlers/models/table_items_binding.rb
188
+ - lib/command_handlers/models/tree_items_binding.rb
189
+ - lib/command_handlers/models/widget_binding.rb
187
190
  - lib/command_handlers/shell_command_handler.rb
188
191
  - lib/command_handlers/tab_item_command_handler.rb
189
192
  - lib/command_handlers/table_column_properties_data_binding_command_handler.rb
@@ -255,9 +258,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
255
258
  version: '0'
256
259
  required_rubygems_version: !ruby/object:Gem::Requirement
257
260
  requirements:
258
- - - ">="
261
+ - - ">"
259
262
  - !ruby/object:Gem::Version
260
- version: '0'
263
+ version: 1.3.1
261
264
  requirements: []
262
265
  rubyforge_project:
263
266
  rubygems_version: 2.7.10
@@ -1,28 +0,0 @@
1
- class ModelObserver
2
- attr_reader :model, :property_name, :property_type, :observer_options
3
- @@property_type_converters = {
4
- :undefined => lambda { |value| value },
5
- :fixnum => lambda { |value| value.to_i },
6
- :array => lambda { |value| value.to_a }
7
- }
8
- def initialize(model, property_name, property_type = :undefined, observer_options = nil)
9
- property_type = :undefined if property_type.nil?
10
- @model = model
11
- @property_name = property_name
12
- @property_type = property_type
13
- @observer_options = observer_options || {}
14
- end
15
- def update(value)
16
- converted_value = @@property_type_converters[@property_type].call(value)
17
- @model.send(@property_name + "=", converted_value) unless evaluate_property == converted_value
18
- end
19
- def evaluate_property
20
- @model.send(@property_name)
21
- end
22
- def evaluate_options_property
23
- @model.send(@property_name + "_options")
24
- end
25
- def options_property_name
26
- self.property_name + "_options"
27
- end
28
- end