glimmer 0.1.10.470 → 0.1.11.SWT4.14

Sign up to get free protection for your applications and to get access to all the features.
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