glimmer 0.7.1 → 0.7.6

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: 31b586dfb4cb23af36ebd26ad97a46625fe122c64d9725b237c8f88f0c72ce71
4
- data.tar.gz: 14ff7f5996588c4261cf4d102d5ffca28fc59cef5bd151d07c273038efae3da5
3
+ metadata.gz: 94497522f529a193db5b827f739dcf4eab4a23c0c0ac583d7352cd7bd8270a83
4
+ data.tar.gz: a7a6a6ff9816f5be2422b5bd53c020f8152b47a436cd2cc805690e3af3cb5612
5
5
  SHA512:
6
- metadata.gz: 6eb266dffd64ba21842038fd272c06a3342f3399b5729d178b966b9543dfdbb9eb45eea079782d0db205e68cc9b63a5a0a4222a9d77c9f5df2cacc01126fed51
7
- data.tar.gz: 123a861f812ac0e4ee2e4c802255ff017c2459a65de79cb52863897a0b017fc8037d9b57eb8e041dc75005d68ee4a3835b07a7cd03f87c30f524c5fef67717ca
6
+ metadata.gz: '095cae0665dbdb73713608a2b2ba9029e0693a33796e4e61ee8463f7f4dcdb385ef04f6fa5f1b3ef2cc48db11492b6bb1c6adff6eeb9de89639a7a1993c3ae1a'
7
+ data.tar.gz: 5513aac74d33b07ee14dbc430938e1f059a5a76a842d6dbbfef2b29c5fede52f2c57a3aea2053835b411da346bd3c7dca9bdddea385742363a6bf6ed8dbb7d40
@@ -1,12 +1,16 @@
1
- # Glimmer 0.7.1 Beta (JRuby Desktop UI DSL + Data-Binding)
1
+ # Glimmer 0.7.6 Beta (Desktop Development Library for Ruby)
2
2
  [![Gem Version](https://badge.fury.io/rb/glimmer.svg)](http://badge.fury.io/rb/glimmer)
3
- [![Coverage Status](https://coveralls.io/repos/github/AndyObtiva/glimmer/badge.svg?branch=master)](https://coveralls.io/github/AndyObtiva/glimmer?branch=master)
3
+ [![Travis CI](https://travis-ci.com/AndyObtiva/glimmer.svg?branch=master)](https://travis-ci.com/github/AndyObtiva/glimmer)
4
+ <!-- [![Coverage Status](https://coveralls.io/repos/github/AndyObtiva/glimmer/badge.svg?branch=master)](https://coveralls.io/github/AndyObtiva/glimmer?branch=master) -->
4
5
 
5
- Glimmer is a native-UI cross-platform desktop development library written in Ruby. Glimmer's main innovation is a JRuby DSL that enables productive and efficient authoring of desktop application user-interfaces while relying on the robust Eclipse SWT library. Glimmer additionally innovates by having built-in data-binding support to greatly facilitate synchronizing the UI with domain models. As a result, that achieves true decoupling of object oriented components, enabling developers to solve business problems without worrying about UI concerns, or alternatively drive development UI-first, and then write clean business components test-first afterwards.
6
+ Glimmer is a native-UI cross-platform desktop development library written in Ruby. Glimmer's main innovation is a JRuby DSL that enables productive and efficient authoring of desktop application user-interfaces while relying on the robust Eclipse SWT library. Glimmer additionally innovates by having built-in data-binding support to greatly facilitate synchronizing the UI with domain models. As a result, that achieves true decoupling of object oriented components, enabling developers to solve business problems without worrying about UI concerns, or alternatively drive development UI-first, and then write clean business models test-first afterwards.
7
+
8
+ [<img src="https://covers.oreillystatic.com/images/9780596519650/lrg.jpg" width=105 /><br />
9
+ Featured in<br />JRuby Cookbook](http://shop.oreilly.com/product/9780596519650.do)
6
10
 
7
11
  ## Examples
8
12
 
9
- ### Hello World
13
+ ### Hello, World!
10
14
 
11
15
  Glimmer code (from `samples/hello/hello_world.rb`):
12
16
  ```ruby
@@ -70,12 +74,10 @@ NOTE: Glimmer is in beta mode. Please help make better by adopting for small or
70
74
 
71
75
  ## Table of Contents
72
76
 
73
- <!-- TOC START min:1 max:3 link:true asterisk:false update:true -->
74
- - [Glimmer 0.7.1 Beta (JRuby Desktop UI DSL + Data-Binding)](#glimmer-058-beta-jruby-desktop-ui-dsl--data-binding)
77
+ - [Glimmer 0.7.6 Beta (Desktop Development Library for Ruby)](#glimmer-075-beta-desktop-development-library-for-ruby)
75
78
  - [Examples](#examples)
76
- - [Hello World](#hello-world)
79
+ - [Hello, World!](#hello-world)
77
80
  - [Tic Tac Toe](#tic-tac-toe)
78
- - [Table of Contents](#table-of-contents)
79
81
  - [Background](#background)
80
82
  - [Platform Support](#platform-support)
81
83
  - [Pre-requisites](#pre-requisites)
@@ -85,6 +87,7 @@ NOTE: Glimmer is in beta mode. Please help make better by adopting for small or
85
87
  - [Glimmer Command](#glimmer-command)
86
88
  - [Basic Usage](#basic-usage)
87
89
  - [Advanced Usage](#advanced-usage)
90
+ - [Scaffolding](#scaffolding)
88
91
  - [Girb (Glimmer irb) Command](#girb-glimmer-irb-command)
89
92
  - [Glimmer DSL Syntax](#glimmer-dsl-syntax)
90
93
  - [Widgets](#widgets)
@@ -99,6 +102,9 @@ NOTE: Glimmer is in beta mode. Please help make better by adopting for small or
99
102
  - [Miscellaneous](#miscellaneous)
100
103
  - [Glimmer Style Guide](#glimmer-style-guide)
101
104
  - [Samples](#samples)
105
+ - [Hello Samples](#hello-samples)
106
+ - [Elaborate Samples](#elaborate-samples)
107
+ - [In Production](#in-production)
102
108
  - [SWT Reference](#swt-reference)
103
109
  - [SWT Packages](#swt-packages)
104
110
  - [Logging](#logging)
@@ -111,15 +117,14 @@ NOTE: Glimmer is in beta mode. Please help make better by adopting for small or
111
117
  - [Self Signed Certificate](#self-signed-certificate)
112
118
  - [Gotchas](#gotchas)
113
119
  - [Resources](#resources)
120
+ - [Help](#help)
121
+ - [Issues](#issues)
122
+ - [IRC Channel](#irc-channel)
114
123
  - [Feature Suggestions](#feature-suggestions)
115
124
  - [Change Log](#change-log)
116
125
  - [Contributing](#contributing)
117
126
  - [Contributors](#contributors)
118
127
  - [License](#license)
119
- <!-- TOC END -->
120
-
121
-
122
-
123
128
  ## Background
124
129
 
125
130
  Ruby is a dynamically-typed object-oriented language, which provides great productivity gains due to its powerful expressive syntax and dynamic nature. While it is proven by the Ruby on Rails framework for web development, it currently lacks a robust platform-independent framework for building desktop applications. Given that Java libraries can now be utilized in Ruby code through JRuby, Eclipse technologies, such as SWT, JFace, and RCP can help fill the gap of desktop application development with Ruby.
@@ -164,7 +169,7 @@ Please follow these instructions to make the `glimmer` command available on your
164
169
 
165
170
  Run this command to install directly:
166
171
  ```
167
- jgem install glimmer -v 0.7.1
172
+ jgem install glimmer -v 0.7.6
168
173
  ```
169
174
 
170
175
  `jgem` is JRuby's version of `gem` command.
@@ -175,7 +180,7 @@ Otherwise, you may also run `jruby -S gem install ...`
175
180
 
176
181
  Add the following to `Gemfile`:
177
182
  ```
178
- gem 'glimmer', '~> 0.7.1'
183
+ gem 'glimmer', '~> 0.7.6'
179
184
  ```
180
185
 
181
186
  And, then run:
@@ -734,6 +739,7 @@ Glimmer ships with SWT style **smart defaults** so you wouldn't have to set them
734
739
 
735
740
  - `text(:border)`
736
741
  - `table(:border)`
742
+ - `tree(:border, :virtual, :v_scroll, :h_scroll)`
737
743
  - `spinner(:border)`
738
744
  - `list(:border, :v_scroll)`
739
745
  - `button(:push)`
@@ -1075,7 +1081,7 @@ https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/
1075
1081
 
1076
1082
  Data-binding is done with `bind` command following widget property to bind and taking model and bindable attribute as arguments.
1077
1083
 
1078
- Data-binding examples:
1084
+ #### General data-binding examples:
1079
1085
 
1080
1086
  `text bind(contact, :first_name)`
1081
1087
 
@@ -1098,6 +1104,10 @@ This example also specifies a converter on read of the model property, but via a
1098
1104
 
1099
1105
  This is a block shortcut version of the syntax above it. It facilitates formatting model data for read-only widgets since it's a very common view concern. It also saves the developer from having to create a separate formatter/presenter for the model when the view can be an active view that handles common simple formatting operations directly.
1100
1106
 
1107
+ `text bind(contact, 'address.street', read_only: true)
1108
+
1109
+ This is read-ohly data-binding. It doesn't update contact.address.street when widget text property is changed.
1110
+
1101
1111
  `text bind(contact, 'addresses[1].street')`
1102
1112
 
1103
1113
  This example binds the text property of a widget like `label` to the nested indexed address street of a contact. This is called nested indexed property data binding.
@@ -1116,6 +1126,8 @@ This example demonstrates nested indexed computed value data binding whereby the
1116
1126
 
1117
1127
  Example from [samples/hello/hello_combo.rb](samples/hello_combo.rb) sample (you may copy/paste in [`girb`](#girb-glimmer-irb-command)):
1118
1128
 
1129
+ #### Combo
1130
+
1119
1131
  ![Hello Combo](images/glimmer-hello-combo.png)
1120
1132
 
1121
1133
  ![Hello Combo](images/glimmer-hello-combo-expanded.png)
@@ -1159,6 +1171,8 @@ HelloCombo.new.launch
1159
1171
 
1160
1172
  `combo` widget is data-bound to the country of a person. Note that it expects `person` object to have `:country` attribute and `:country_options` attribute containing all available countries.
1161
1173
 
1174
+ #### List
1175
+
1162
1176
  Example from [samples/hello/hello_list_single_selection.rb](samples/hello_list_single_selection.rb) sample:
1163
1177
 
1164
1178
  ![Hello List Single Selection](images/glimmer-hello-list-single-selection.png)
@@ -1240,6 +1254,45 @@ Note that in all the data-binding examples above, there was also an observer att
1240
1254
 
1241
1255
  You may learn more about Glimmer's data-binding syntax by reading the [Eclipse Zone Tutorial](http://eclipse.dzone.com/articles/an-introduction-glimmer) mentioned in resources and opening up the samples under the [samples](samples) directory.
1242
1256
 
1257
+ #### Tree
1258
+
1259
+ The SWT Tree widget visualizes a tree data-structure, such as an employment or composition hierarchy.
1260
+
1261
+ To data-bind a Tree, you need the root model, the children querying method, and the text display attribute on each child.
1262
+
1263
+ This involves using the `bind` keyword mentioned above in addition to a special `tree_properties` keyword that takes the children and text attribute methods.
1264
+
1265
+ Example:
1266
+
1267
+ ```ruby
1268
+ shell {
1269
+ @tree = tree {
1270
+ items bind(company, :owner), tree_properties(children: :coworkers, text: :name)
1271
+ selection bind(company, :selected_coworker)
1272
+ }
1273
+ }
1274
+ ```
1275
+
1276
+ The code above includes two data-bindings:
1277
+ - Tree `items`, which first bind to the root node (company.owner), and then dig down via `coworkers` `children` method, using the `name` `text` attribute for displaying each tree item.
1278
+ - Tree `selection`, which binds the single tree item selected by the user to the attribute denoted by the `bind` keyword
1279
+
1280
+ Additionally, Tree `items` data-binding automatically stores each node model unto the SWT TreeItem object via `setData` method. This enables things like searchability.
1281
+
1282
+ The tree widget in Glimmer is represented by a subclass of `WidgetProxy` called `TreeProxy`.
1283
+ TreeProxy includes a `depth_first_search` method that takes a block to look for a tree item.
1284
+
1285
+ Example:
1286
+
1287
+ ```ruby
1288
+ found_array = @tree.depth_first_search { |tree_item| tree_item.getData == company.owner }
1289
+ ```
1290
+
1291
+ This finds the root node. The array is a Java array. This enables easy passing of it to SWT `Tree#setSelection` method, which expects a Java array of `TreeItem` objects.
1292
+
1293
+ To edit a tree, you must invoke `TreeProxy#edit_selected_tree_item` or `TreeProxy#edit_tree_item`. This automatically leverages the SWT TreeEditor custom class behind the scenes, displaying
1294
+ a text widget to the user to change the selected or passed tree item text into something else. It automatically persists the change to `items` data-bound model on ENTER/FOCUS-OUT or cancels on ESC/NO-CHANGE.
1295
+
1243
1296
  ### Observer
1244
1297
 
1245
1298
  Glimmer comes with `Observer` module, which is used internally for data-binding, but can also be used externally for custom use of the Observer Pattern. It is hidden when observing widgets, and used explicitly when observing models.
@@ -1925,7 +1978,7 @@ glimmer samples/elaborate/contact_manager.rb # demonstrates table data-binding
1925
1978
  glimmer samples/elaborate/tic_tac_toe.rb # demonstrates a full MVC application
1926
1979
  ```
1927
1980
 
1928
- ![Gladiator](images/glimmer-gladiator.png)
1981
+ ![Gladiator](https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/v0.1.5/images/glimmer-gladiator.png)
1929
1982
 
1930
1983
  [Gladiator](https://github.com/AndyObtiva/glimmer-cs-gladiator) (short for Glimmer Editor) is a Glimmer sample project under on-going development.
1931
1984
  You may check it out to learn how to build a Glimmer Custom Shell gem.
@@ -2194,10 +2247,25 @@ Exec failed with code 2 command [[/usr/bin/SetFile, -c, icnC, /var/folders/4_/g1
2194
2247
 
2195
2248
  ## Resources
2196
2249
 
2250
+ * [Code Master Blog](http://andymaleh.blogspot.com/search/label/Glimmer)
2197
2251
  * [Eclipse Zone Tutorial](http://eclipse.dzone.com/articles/an-introduction-glimmer)
2198
2252
  * [InfoQ Article](http://www.infoq.com/news/2008/02/glimmer-jruby-swt)
2199
2253
  * [RubyConf 2008 Video](https://confreaks.tv/videos/rubyconf2008-desktop-development-with-glimmer)
2200
- * [Code Blog](http://andymaleh.blogspot.com/search/label/Glimmer)
2254
+ * [JRuby Cookbook by Justin Edelson & Henry Liu](http://shop.oreilly.com/product/9780596519650.do)
2255
+
2256
+ ## Help
2257
+
2258
+ ### Issues
2259
+
2260
+ You may submit [issues](https://github.com/AndyObtiva/glimmer/issues) on [GitHub](https://github.com/AndyObtiva/glimmer/issues).
2261
+
2262
+ [Click here to submit an issue.](https://github.com/AndyObtiva/glimmer/issues)
2263
+
2264
+ ### IRC Channel
2265
+
2266
+ If you need live help, try the [#glimmer](http://widget.mibbit.com/?settings=7514b8a196f8f1de939a351245db7aa8&server=irc.mibbit.net&channel=%23glimmer) IRC channel on [irc.mibbit.net](http://widget.mibbit.com/?settings=7514b8a196f8f1de939a351245db7aa8&server=irc.mibbit.net&channel=%23glimmer). If no one was available, you may [leave a GitHub issue](https://github.com/AndyObtiva/glimmer/issues) to schedule a meetup on IRC.
2267
+
2268
+ [Click here to connect to #glimmer IRC channel immediately via a web interface.](http://widget.mibbit.com/?settings=7514b8a196f8f1de939a351245db7aa8&server=irc.mibbit.net&channel=%23glimmer)
2201
2269
 
2202
2270
  ## Feature Suggestions
2203
2271
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.1
1
+ 0.7.6
@@ -203,10 +203,9 @@ module Glimmer
203
203
  end
204
204
 
205
205
  def evaluate_property
206
- unless model.nil?
207
- value = invoke_property_reader(model, property_name)
208
- convert_on_read(value)
209
- end
206
+ value = nil
207
+ value = invoke_property_reader(model, property_name) unless model.nil?
208
+ convert_on_read(value)
210
209
  end
211
210
 
212
211
  def evaluate_options_property
@@ -233,6 +232,7 @@ module Glimmer
233
232
  end
234
233
 
235
234
  def invoke_property_writer(object, property_expression, value)
235
+ return if @binding_options[:read_only]
236
236
  value = convert_on_write(value)
237
237
  if property_indexed?(property_expression)
238
238
  property_method = '[]='
@@ -40,9 +40,7 @@ module Glimmer
40
40
  end
41
41
 
42
42
  def property_observer_hash
43
- # TODO simplify with ||=
44
- @property_observers = Hash.new unless @property_observers
45
- @property_observers
43
+ @property_observers ||= Hash.new
46
44
  end
47
45
 
48
46
  def property_observer_list(property_name)
@@ -26,6 +26,7 @@ module Glimmer
26
26
 
27
27
  def call(model_collection=nil)
28
28
  if model_collection and model_collection.is_a?(Array)
29
+ # TODO clean observer registrations
29
30
  observe(model_collection, @column_properties)
30
31
  @model_collection = model_collection
31
32
  end
@@ -17,7 +17,12 @@ module Glimmer
17
17
  @tree = parent
18
18
  @model_binding = model_binding
19
19
  @tree_properties = [tree_properties].flatten.first.to_h
20
- call(@model_binding.evaluate_property)
20
+ if @tree.respond_to?(:tree_properties=)
21
+ @tree.tree_properties = @tree_properties
22
+ else # assume custom widget
23
+ @tree.body_root.tree_properties = @tree_properties
24
+ end
25
+ call
21
26
  model = model_binding.base_model
22
27
  observe(model, model_binding.property_name_expression)
23
28
  @tree.on_widget_disposed do |dispose_event|
@@ -25,26 +30,40 @@ module Glimmer
25
30
  end
26
31
  end
27
32
 
28
- def call(model_tree_root_node=nil)
29
- if model_tree_root_node and model_tree_root_node.respond_to?(@tree_properties[:children])
30
- observe(model_tree_root_node, @tree_properties[:text])
31
- observe(model_tree_root_node, @tree_properties[:children])
32
- @model_tree_root_node = model_tree_root_node
33
- end
33
+ def call(new_value=nil)
34
+ @model_tree_root_node = @model_binding.evaluate_property
34
35
  populate_tree(@model_tree_root_node, @tree, @tree_properties)
35
36
  end
36
37
 
37
38
  def populate_tree(model_tree_root_node, parent, tree_properties)
39
+ # TODO make it change things by delta instead of removing all
40
+ selected_tree_item_model = parent.swt_widget.getSelection.map(&:getData).first
41
+ old_tree_items = parent.all_tree_items
42
+ old_tree_item_expansion_by_data = old_tree_items.reduce({}) {|hash, ti| hash.merge(ti.getData => ti.getExpanded)}
43
+ old_tree_items.each do |tree_item|
44
+ tree_item.getData('observer_registrations').each do |key, observer_registration|
45
+ observer_registration.unregister
46
+ end
47
+ end
38
48
  parent.swt_widget.removeAll
39
49
  populate_tree_node(model_tree_root_node, parent.swt_widget, tree_properties)
50
+ parent.all_tree_items.each { |ti| ti.setExpanded(!!old_tree_item_expansion_by_data[ti.getData]) }
51
+ tree_item_to_select = parent.depth_first_search {|ti| ti.getData == selected_tree_item_model}
52
+ parent.swt_widget.setSelection(tree_item_to_select)
40
53
  end
41
54
 
42
55
  def populate_tree_node(model_tree_node, parent, tree_properties)
43
- table_item = TreeItem.new(parent, SWT::SWTProxy[:none])
44
- table_item.setText((model_tree_node && model_tree_node.send(tree_properties[:text])).to_s)
56
+ return if model_tree_node.nil?
57
+ # TODO anticipate default tree properties if none were passed (like literal values text and children)
58
+ tree_item = TreeItem.new(parent, SWT::SWTProxy[:none])
59
+ observer_registrations = @tree_properties.reduce({}) do |hash, key_value_pair|
60
+ hash.merge(key_value_pair.first => observe(model_tree_node, key_value_pair.last))
61
+ end
62
+ tree_item.setData('observer_registrations', observer_registrations)
63
+ tree_item.setData(model_tree_node)
64
+ tree_item.setText((model_tree_node && model_tree_node.send(tree_properties[:text])).to_s)
45
65
  [model_tree_node && model_tree_node.send(tree_properties[:children])].flatten.to_a.compact.each do |child|
46
- observe(child, @tree_properties[:text])
47
- populate_tree_node(child, table_item, tree_properties)
66
+ populate_tree_node(child, tree_item, tree_properties)
48
67
  end
49
68
  end
50
69
  end
@@ -100,7 +100,7 @@ module Glimmer
100
100
  return Glimmer::DSL::Engine.interpret(keyword, *args, &block)
101
101
  rescue => e
102
102
  Glimmer::DSL::Engine.reset
103
- raise e if static_expression_dsl.nil?
103
+ raise e if static_expression_dsl.nil? || !Glimmer::DSL::Engine.static_expressions[keyword][static_expression_dsl].is_a?(TopLevelExpression)
104
104
  end
105
105
  end
106
106
  raise Glimmer::Error, "Unsupported keyword: #{keyword}" unless static_expression_dsl || retrieved_static_expression
@@ -153,12 +153,13 @@ module Glimmer
153
153
  #
154
154
  # For example, a shell widget would get properties set and children added
155
155
  def add_content(parent, expression, &block)
156
- time = Time.now.to_f
157
- dsl_stack.push(expression.class.dsl)
158
- parent_stack.push(parent) if expression.is_a?(ParentExpression)
159
- expression.add_content(parent, &block) if block_given?
160
- parent_stack.pop if expression.is_a?(ParentExpression)
161
- dsl_stack.pop
156
+ if block_given? && expression.is_a?(ParentExpression)
157
+ dsl_stack.push(expression.class.dsl)
158
+ parent_stack.push(parent)
159
+ expression.add_content(parent, &block)
160
+ parent_stack.pop
161
+ dsl_stack.pop
162
+ end
162
163
  end
163
164
 
164
165
  # Current parent while evaluating Glimmer DSL (nil if just started or done evaluatiing)
@@ -17,7 +17,13 @@ module Glimmer
17
17
  end
18
18
 
19
19
  def interpret(parent, keyword, *args, &block)
20
- Glimmer::SWT::WidgetProxy.new(keyword, parent, args)
20
+ begin
21
+ class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
22
+ widget_class = Glimmer::SWT.const_get(class_name)
23
+ rescue
24
+ widget_class = Glimmer::SWT::WidgetProxy
25
+ end
26
+ widget_class.new(keyword, parent, args)
21
27
  end
22
28
  end
23
29
  end
@@ -25,3 +31,4 @@ module Glimmer
25
31
  end
26
32
 
27
33
  require 'glimmer/swt/widget_proxy'
34
+ require 'glimmer/swt/tree_proxy'
@@ -116,6 +116,11 @@ module Glimmer
116
116
  end
117
117
  end
118
118
  end
119
+
120
+ attr_reader :application_paths
121
+ attr_reader :env_vars
122
+ attr_reader :glimmer_options
123
+ attr_reader :jruby_options
119
124
 
120
125
  def initialize(raw_options)
121
126
  @application_paths = extract_application_paths(raw_options)
@@ -135,6 +140,7 @@ module Glimmer
135
140
  private
136
141
 
137
142
  def launch_application
143
+ load File.expand_path('./Rakefile') if File.exist?(File.expand_path('./Rakefile'))
138
144
  threads = @application_paths.map do |application_path|
139
145
  Thread.new do
140
146
  self.class.launch(
@@ -286,8 +286,6 @@ class Scaffold
286
286
  app_view.open
287
287
  end
288
288
  end
289
-
290
- #{class_name(app_name)}.new.open
291
289
  MULTI_LINE_STRING
292
290
  end
293
291
 
@@ -307,8 +305,10 @@ class Scaffold
307
305
  def app_bin_file(app_name)
308
306
  <<~MULTI_LINE_STRING
309
307
  #!/usr/bin/env ruby
310
-
308
+
311
309
  require_relative '../app/#{file_name(app_name)}'
310
+
311
+ #{class_name(app_name)}.new.open
312
312
  MULTI_LINE_STRING
313
313
  end
314
314
 
@@ -20,33 +20,39 @@ module Glimmer
20
20
  alias opened_before? opened_before
21
21
 
22
22
  # Instantiates ShellProxy with same arguments expected by SWT Shell
23
- def initialize(*args)
24
- if args.first.is_a?(ShellProxy)
25
- args[0] = args[0].swt_widget
26
- end
27
- style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
28
- if style_args.any?
29
- style_arg_start_index = args.index(style_args.first)
30
- style_arg_last_index = args.index(style_args.last)
31
- args[style_arg_start_index..style_arg_last_index] = SWTProxy[style_args]
32
- end
33
- if args.first.nil? || (!args.first.is_a?(Display) && !args.first.is_a?(Shell))
34
- @display = DisplayProxy.instance.swt_display
35
- args = [@display] + args
36
- end
37
- args = args.compact
38
- @swt_widget = Shell.new(*args)
39
- @display ||= @swt_widget.getDisplay
40
- @swt_widget.setLayout(FillLayout.new)
41
- @swt_widget.setMinimumSize(WIDTH_MIN, HEIGHT_MIN)
42
- on_event_show do
43
- Thread.new do
44
- sleep(0.25)
45
- async_exec do
46
- @swt_widget.setActive
23
+ # if swt_widget keyword arg was passed, then it is assumed the shell has already been instantiated
24
+ # and the proxy wraps it instead of creating a new one.
25
+ def initialize(*args, swt_widget: nil)
26
+ if swt_widget
27
+ @swt_widget = swt_widget
28
+ else
29
+ if args.first.is_a?(ShellProxy)
30
+ args[0] = args[0].swt_widget
31
+ end
32
+ style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
33
+ if style_args.any?
34
+ style_arg_start_index = args.index(style_args.first)
35
+ style_arg_last_index = args.index(style_args.last)
36
+ args[style_arg_start_index..style_arg_last_index] = SWTProxy[style_args]
37
+ end
38
+ if args.first.nil? || (!args.first.is_a?(Display) && !args.first.is_a?(Shell))
39
+ @display = DisplayProxy.instance.swt_display
40
+ args = [@display] + args
41
+ end
42
+ args = args.compact
43
+ @swt_widget = Shell.new(*args)
44
+ @swt_widget.setLayout(FillLayout.new)
45
+ @swt_widget.setMinimumSize(WIDTH_MIN, HEIGHT_MIN)
46
+ on_event_show do
47
+ Thread.new do
48
+ sleep(0.25)
49
+ async_exec do
50
+ @swt_widget.setActive unless @swt_widget.isDisposed
51
+ end
47
52
  end
48
53
  end
49
54
  end
55
+ @display ||= @swt_widget.getDisplay
50
56
  end
51
57
 
52
58
  # Centers shell within monitor it is in
@@ -96,16 +102,14 @@ module Glimmer
96
102
  end
97
103
 
98
104
  def pack_same_size
99
- minimum_size = @swt_widget.getMinimumSize
100
105
  bounds = @swt_widget.getBounds
101
- @swt_widget.setMinimumSize(bounds.width, bounds.height)
102
- listener = on_control_resized {
103
- @swt_widget.setSize(bounds.width, bounds.height)
104
- @swt_widget.setLocation(bounds.x, bounds.y)
105
- }
106
+ width = @swt_widget.getBounds.width
107
+ height = @swt_widget.getBounds.height
108
+ x = @swt_widget.getBounds.x
109
+ y = @swt_widget.getBounds.y
106
110
  @swt_widget.pack
107
- @swt_widget.removeControlListener(listener.swt_listener)
108
- @swt_widget.setMinimumSize(minimum_size)
111
+ @swt_widget.setSize(width, height)
112
+ @swt_widget.setLocation(x, y)
109
113
  end
110
114
 
111
115
  def content(&block)
@@ -54,6 +54,12 @@ module Glimmer
54
54
  super(attribute_name)
55
55
  end
56
56
  end
57
+
58
+ def dispose
59
+ swt_tab_item.setControl(nil)
60
+ swt_widget.dispose
61
+ swt_tab_item.dispose
62
+ end
57
63
  end
58
64
  end
59
65
  end
@@ -0,0 +1,120 @@
1
+ require 'glimmer/swt/widget_proxy'
2
+
3
+ module Glimmer
4
+ module SWT
5
+ class TreeProxy < Glimmer::SWT::WidgetProxy
6
+ include Glimmer
7
+
8
+ attr_reader :tree_editor, :tree_editor_text_proxy
9
+ attr_accessor :tree_properties
10
+
11
+ def initialize(underscored_widget_name, parent, args)
12
+ super
13
+ @tree_editor = TreeEditor.new(swt_widget)
14
+ @tree_editor.horizontalAlignment = SWTProxy[:left]
15
+ @tree_editor.grabHorizontal = true
16
+ @tree_editor.minimumHeight = 20
17
+ end
18
+
19
+ # Performs depth first search for tree items matching block condition
20
+ # If no condition block is passed, returns all tree items
21
+ # Returns a Java TreeItem array to easily set as selection on org.eclipse.swt.Tree if needed
22
+ def depth_first_search(&condition)
23
+ found = []
24
+ recursive_depth_first_search(swt_widget.getItems.first, found, &condition)
25
+ found.to_java(TreeItem)
26
+ end
27
+
28
+ # Returns all tree items including descendants
29
+ def all_tree_items
30
+ depth_first_search
31
+ end
32
+
33
+ def widget_property_listener_installers
34
+ super.merge({
35
+ Java::OrgEclipseSwtWidgets::Tree => {
36
+ selection: lambda do |observer|
37
+ on_widget_selected { |selection_event|
38
+ observer.call(@swt_widget.getSelection)
39
+ }
40
+ end
41
+ },
42
+ })
43
+ end
44
+
45
+ def edit_in_progress?
46
+ !!@edit_in_progress
47
+ end
48
+
49
+ def edit_selected_tree_item(before_write: nil, after_write: nil, after_cancel: nil)
50
+ edit_tree_item(swt_widget.getSelection.first, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
51
+ end
52
+
53
+ def edit_tree_item(tree_item, before_write: nil, after_write: nil, after_cancel: nil)
54
+ return if tree_item.nil?
55
+ content {
56
+ @tree_editor_text_proxy = text {
57
+ focus true
58
+ text tree_item.getText
59
+ action_taken = false
60
+ cancel = lambda {
61
+ @tree_editor_text_proxy.swt_widget.dispose
62
+ @tree_editor_text_proxy = nil
63
+ after_cancel&.call
64
+ @edit_in_progress = false
65
+ }
66
+ action = lambda { |event|
67
+ if !action_taken && !@edit_in_progress
68
+ action_taken = true
69
+ @edit_in_progress = true
70
+ new_text = @tree_editor_text_proxy.swt_widget.getText
71
+ if new_text == tree_item.getText
72
+ cancel.call
73
+ else
74
+ before_write&.call
75
+ tree_item.setText(new_text)
76
+ model = tree_item.getData
77
+ model.send("#{tree_properties[:text]}=", new_text) # makes tree update itself, so must search for selected tree item again
78
+ edited_tree_item = depth_first_search { |ti| ti.getData == model }.first
79
+ swt_widget.showItem(edited_tree_item)
80
+ @tree_editor_text_proxy.swt_widget.dispose
81
+ @tree_editor_text_proxy = nil
82
+ after_write&.call(edited_tree_item)
83
+ @edit_in_progress = false
84
+ end
85
+ end
86
+ }
87
+ on_focus_lost(&action)
88
+ on_key_pressed { |key_event|
89
+ if key_event.keyCode == swt(:cr)
90
+ action.call(key_event)
91
+ elsif key_event.keyCode == swt(:esc)
92
+ cancel.call
93
+ end
94
+ }
95
+ }
96
+ @tree_editor_text_proxy.swt_widget.selectAll
97
+ }
98
+ @tree_editor.setEditor(@tree_editor_text_proxy.swt_widget, tree_item);
99
+ end
100
+
101
+ private
102
+
103
+ def recursive_depth_first_search(tree_item, found, &condition)
104
+ return if tree_item.nil?
105
+ found << tree_item if condition.nil? || condition.call(tree_item)
106
+ tree_item.getItems.each do |child_tree_item|
107
+ recursive_depth_first_search(child_tree_item, found, &condition)
108
+ end
109
+ end
110
+
111
+ def property_type_converters
112
+ super.merge({
113
+ selection: lambda do |value|
114
+ depth_first_search {|ti| ti.getData == value}
115
+ end,
116
+ })
117
+ end
118
+ end
119
+ end
120
+ end
@@ -5,12 +5,29 @@ module Glimmer
5
5
  # Follows the Proxy Design Pattern
6
6
  class WidgetListenerProxy
7
7
 
8
- attr_reader :swt_listener
8
+ attr_reader :swt_widget, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
9
9
 
10
- # TODO capture its widget and support unregistering
11
-
12
- def initialize(swt_listener)
10
+ def initialize(swt_widget:, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil)
11
+ @swt_widget = swt_widget
13
12
  @swt_listener = swt_listener
13
+ @widget_add_listener_method = widget_add_listener_method
14
+ @swt_listener_class = swt_listener_class
15
+ @swt_listener_method = swt_listener_method
16
+ @event_type = event_type
17
+ @swt_constant = swt_constant
18
+ end
19
+
20
+ def widget_remove_listener_method
21
+ @widget_add_listener_method.sub('add', 'remove')
22
+ end
23
+
24
+ def unregister
25
+ # TODO consider renaming to deregister (and in Observer too)
26
+ if @event_type
27
+ @swt_widget.removeListener(@event_type, @swt_listener)
28
+ else
29
+ @swt_widget.send(widget_remove_listener_method, @swt_listener)
30
+ end
14
31
  end
15
32
  end
16
33
  end
@@ -5,6 +5,8 @@ require 'glimmer/swt/font_proxy'
5
5
  require 'glimmer/swt/swt_proxy'
6
6
  require 'glimmer/data_binding/observable_widget'
7
7
 
8
+ # TODO refactor to make file smaller and extract sub-widget-proxies out of this
9
+
8
10
  module Glimmer
9
11
  module SWT
10
12
  # Proxy for SWT Widget objects
@@ -23,6 +25,7 @@ module Glimmer
23
25
  DEFAULT_STYLES = {
24
26
  "text" => [:border],
25
27
  "table" => [:border],
28
+ "tree" => [:virtual, :border, :h_scroll, :v_scroll],
26
29
  "spinner" => [:border],
27
30
  "styled_text" => [:border],
28
31
  "list" => [:border, :v_scroll],
@@ -31,17 +34,17 @@ module Glimmer
31
34
  }
32
35
 
33
36
  DEFAULT_INITIALIZERS = {
34
- "composite" => proc do |composite|
37
+ "composite" => lambda do |composite|
35
38
  composite.setLayout(GridLayout.new)
36
39
  end,
37
- "table" => proc do |table|
40
+ "table" => lambda do |table|
38
41
  table.setHeaderVisible(true)
39
42
  table.setLinesVisible(true)
40
43
  end,
41
- "table_column" => proc do |table_column|
44
+ "table_column" => lambda do |table_column|
42
45
  table_column.setWidth(80)
43
46
  end,
44
- "group" => proc do |group|
47
+ "group" => lambda do |group|
45
48
  group.setLayout(GridLayout.new)
46
49
  end,
47
50
  }
@@ -102,10 +105,24 @@ module Glimmer
102
105
  end
103
106
  end
104
107
 
108
+ def pack_same_size
109
+ bounds = @swt_widget.getBounds
110
+ listener = on_control_resized {
111
+ @swt_widget.setSize(bounds.width, bounds.height)
112
+ @swt_widget.setLocation(bounds.x, bounds.y)
113
+ }
114
+ if @swt_widget.is_a?(Composite)
115
+ @swt_widget.layout(true, true)
116
+ else
117
+ @swt_widget.pack(true)
118
+ end
119
+ @swt_widget.removeControlListener(listener.swt_listener)
120
+ end
121
+
105
122
  def widget_property_listener_installers
106
123
  @swt_widget_property_listener_installers ||= {
107
124
  Java::OrgEclipseSwtWidgets::Control => {
108
- :focus => proc do |observer|
125
+ :focus => lambda do |observer|
109
126
  on_focus_gained { |focus_event|
110
127
  observer.call(true)
111
128
  }
@@ -115,12 +132,12 @@ module Glimmer
115
132
  end,
116
133
  },
117
134
  Java::OrgEclipseSwtWidgets::Text => {
118
- :text => proc do |observer|
135
+ :text => lambda do |observer|
119
136
  on_modify_text { |modify_event|
120
137
  observer.call(@swt_widget.getText)
121
138
  }
122
139
  end,
123
- :caret_position => proc do |observer|
140
+ :caret_position => lambda do |observer|
124
141
  on_event_keydown { |event|
125
142
  observer.call(@swt_widget.getCaretPosition)
126
143
  }
@@ -134,7 +151,21 @@ module Glimmer
134
151
  observer.call(@swt_widget.getCaretPosition)
135
152
  }
136
153
  end,
137
- :selection_count => proc do |observer|
154
+ :selection => lambda do |observer|
155
+ on_event_keydown { |event|
156
+ observer.call(@swt_widget.getSelection)
157
+ }
158
+ on_event_keyup { |event|
159
+ observer.call(@swt_widget.getSelection)
160
+ }
161
+ on_event_mousedown { |event|
162
+ observer.call(@swt_widget.getSelection)
163
+ }
164
+ on_event_mouseup { |event|
165
+ observer.call(@swt_widget.getSelection)
166
+ }
167
+ end,
168
+ :selection_count => lambda do |observer|
138
169
  on_event_keydown { |event|
139
170
  observer.call(@swt_widget.getSelectionCount)
140
171
  }
@@ -148,7 +179,7 @@ module Glimmer
148
179
  observer.call(@swt_widget.getSelectionCount)
149
180
  }
150
181
  end,
151
- :top_index => proc do |observer|
182
+ :top_index => lambda do |observer|
152
183
  @last_top_index = @swt_widget.getTopIndex
153
184
  on_paint_control { |event|
154
185
  if @swt_widget.getTopIndex != @last_top_index
@@ -159,33 +190,33 @@ module Glimmer
159
190
  end,
160
191
  },
161
192
  Java::OrgEclipseSwtCustom::StyledText => {
162
- :text => proc do |observer|
193
+ :text => lambda do |observer|
163
194
  on_modify_text { |modify_event|
164
195
  observer.call(@swt_widget.getText)
165
196
  }
166
197
  end,
167
198
  },
168
199
  Java::OrgEclipseSwtWidgets::Button => {
169
- :selection => proc do |observer|
200
+ :selection => lambda do |observer|
170
201
  on_widget_selected { |selection_event|
171
202
  observer.call(@swt_widget.getSelection)
172
203
  }
173
204
  end
174
205
  },
175
206
  Java::OrgEclipseSwtWidgets::MenuItem => {
176
- :selection => proc do |observer|
207
+ :selection => lambda do |observer|
177
208
  on_widget_selected { |selection_event|
178
209
  observer.call(@swt_widget.getSelection)
179
210
  }
180
211
  end
181
212
  },
182
213
  Java::OrgEclipseSwtWidgets::Spinner => {
183
- :selection => proc do |observer|
214
+ :selection => lambda do |observer|
184
215
  on_widget_selected { |selection_event|
185
216
  observer.call(@swt_widget.getSelection)
186
217
  }
187
218
  end
188
- }
219
+ },
189
220
  }
190
221
  end
191
222
 
@@ -307,10 +338,12 @@ module Glimmer
307
338
  end
308
339
 
309
340
  def add_listener(underscored_listener_name, &block)
310
- widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
311
- listener = listener_class.new(listener_method => block)
341
+ widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
342
+ widget_listener_proxy = nil
343
+ safe_block = lambda { |event| block.call(event) unless @swt_widget.isDisposed }
344
+ listener = listener_class.new(listener_method => safe_block)
312
345
  @swt_widget.send(widget_add_listener_method, listener)
313
- WidgetListenerProxy.new(listener)
346
+ widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
314
347
  end
315
348
 
316
349
  # Looks through SWT class add***Listener methods till it finds one for which
@@ -361,8 +394,10 @@ module Glimmer
361
394
 
362
395
  def add_swt_event_listener(swt_constant, &block)
363
396
  event_type = SWTProxy[swt_constant]
364
- @swt_widget.addListener(event_type, &block)
365
- WidgetListenerProxy.new(@swt_widget.getListeners(event_type).last)
397
+ widget_listener_proxy = nil
398
+ safe_block = lambda { |event| block.call(event) unless @swt_widget.isDisposed }
399
+ @swt_widget.addListener(event_type, &safe_block)
400
+ widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: @swt_widget.getListeners(event_type).last, event_type: event_type, swt_constant: swt_constant)
366
401
  end
367
402
 
368
403
  def widget_custom_attribute_mapping
@@ -395,7 +430,7 @@ module Glimmer
395
430
  end
396
431
 
397
432
  def property_type_converters
398
- color_converter = proc do |value|
433
+ color_converter = lambda do |value|
399
434
  if value.is_a?(Symbol) || value.is_a?(String)
400
435
  ColorProxy.new(value).swt_color
401
436
  else
@@ -405,7 +440,7 @@ module Glimmer
405
440
  # TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
406
441
  @property_type_converters ||= {
407
442
  :background => color_converter,
408
- :background_image => proc do |value|
443
+ :background_image => lambda do |value|
409
444
  if value.is_a?(String)
410
445
  if value.start_with?('uri:classloader')
411
446
  value = value.sub(/^uri\:classloader\:\//, '')
@@ -426,7 +461,7 @@ module Glimmer
426
461
  end
427
462
  end,
428
463
  :foreground => color_converter,
429
- :font => proc do |value|
464
+ :font => lambda do |value|
430
465
  if value.is_a?(Hash)
431
466
  font_properties = value
432
467
  FontProxy.new(self, font_properties).swt_font
@@ -434,17 +469,17 @@ module Glimmer
434
469
  value
435
470
  end
436
471
  end,
437
- :items => proc do |value|
472
+ :items => lambda do |value|
438
473
  value.to_java :string
439
474
  end,
440
- :text => proc do |value|
475
+ :text => lambda do |value|
441
476
  if swt_widget.is_a?(Browser)
442
477
  value.to_s
443
478
  else
444
479
  value.to_s
445
480
  end
446
481
  end,
447
- :visible => proc do |value|
482
+ :visible => lambda do |value|
448
483
  !!value
449
484
  end,
450
485
  }
@@ -224,6 +224,10 @@ module Glimmer
224
224
  def dispose
225
225
  body_root.dispose
226
226
  end
227
+
228
+ def method_missing(method, *args, &block)
229
+ body_root.send(method, *args, &block)
230
+ end
227
231
 
228
232
  private
229
233
 
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.7.1
4
+ version: 0.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - AndyMaleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-11 00:00:00.000000000 Z
11
+ date: 2020-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -192,9 +192,7 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: 0.8.1
195
- description: JRuby Desktop UI DSL + Data-Binding native-UI cross-platform library
196
- that enables productive and efficient authoring of desktop user-interfaces using
197
- the robust Eclipse SWT library
195
+ description: Desktop Development Library for Ruby
198
196
  email: andy.am@gmail.com
199
197
  executables:
200
198
  - glimmer
@@ -202,10 +200,10 @@ executables:
202
200
  extensions: []
203
201
  extra_rdoc_files:
204
202
  - LICENSE.txt
205
- - README.markdown
203
+ - README.md
206
204
  files:
207
205
  - LICENSE.txt
208
- - README.markdown
206
+ - README.md
209
207
  - RUBY_VERSION
210
208
  - VERSION
211
209
  - bin/girb
@@ -292,6 +290,7 @@ files:
292
290
  - lib/glimmer/swt/shell_proxy.rb
293
291
  - lib/glimmer/swt/swt_proxy.rb
294
292
  - lib/glimmer/swt/tab_item_proxy.rb
293
+ - lib/glimmer/swt/tree_proxy.rb
295
294
  - lib/glimmer/swt/widget_listener_proxy.rb
296
295
  - lib/glimmer/swt/widget_proxy.rb
297
296
  - lib/glimmer/ui/custom_shell.rb
@@ -327,5 +326,5 @@ requirements: []
327
326
  rubygems_version: 3.0.6
328
327
  signing_key:
329
328
  specification_version: 4
330
- summary: Desktop application development library
329
+ summary: Desktop Development Library for Ruby
331
330
  test_files: []