glimmer-dsl-swt 4.20.0.4 → 4.20.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_SAMPLES.md +13 -0
  6. data/docs/reference/GLIMMER_STYLE_GUIDE.md +4 -3
  7. data/glimmer-dsl-swt.gemspec +0 -0
  8. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +3 -3
  9. data/lib/glimmer/swt/display_proxy.rb +11 -8
  10. data/lib/glimmer/swt/widget_proxy.rb +1 -1
  11. data/lib/glimmer/ui/custom_shell.rb +3 -3
  12. data/lib/glimmer/ui/custom_widget.rb +5 -2
  13. data/samples/elaborate/calculator.rb +116 -0
  14. data/samples/elaborate/calculator/model/command.rb +105 -0
  15. data/samples/elaborate/calculator/model/command/all_clear.rb +17 -0
  16. data/samples/elaborate/calculator/model/command/command_history.rb +0 -0
  17. data/samples/elaborate/calculator/model/command/equals.rb +18 -0
  18. data/samples/elaborate/calculator/model/command/number.rb +20 -0
  19. data/samples/elaborate/calculator/model/command/operation.rb +27 -0
  20. data/samples/elaborate/calculator/model/command/operation/add.rb +15 -0
  21. data/samples/elaborate/calculator/model/command/operation/divide.rb +15 -0
  22. data/samples/elaborate/calculator/model/command/operation/multiply.rb +15 -0
  23. data/samples/elaborate/calculator/model/command/operation/subtract.rb +15 -0
  24. data/samples/elaborate/calculator/model/command/point.rb +20 -0
  25. data/samples/elaborate/calculator/model/presenter.rb +30 -0
  26. data/samples/elaborate/tetris.rb +4 -4
  27. data/samples/elaborate/timer.rb +233 -0
  28. data/samples/elaborate/timer/alarm1.wav +0 -0
  29. data/samples/elaborate/timer/sounds/alarm1.wav +0 -0
  30. data/samples/elaborate/user_profile.rb +4 -2
  31. data/samples/elaborate/weather.rb +1 -1
  32. data/samples/hello/hello_cool_bar.rb +4 -4
  33. data/samples/hello/hello_text.rb +120 -0
  34. data/samples/hello/hello_tool_bar.rb +4 -4
  35. metadata +18 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76c2288c83bde07fd0e5db24556bd986b52c868db3db8dffd1f4cb4a82bd8085
4
- data.tar.gz: 33f8584fd2ce2efac14d188a5d4753f6a807851529d663b1dbab1b57a4838db8
3
+ metadata.gz: b539c452b30e337ec632c8e4e034a2ef0585000eb7fdad70afe77ec5851f2d49
4
+ data.tar.gz: a562eca963b5d39cbb8c7b56219dae2bc23ad93e2f8b76cb1853c38077e07c93
5
5
  SHA512:
6
- metadata.gz: cbb93aa7d256686e409becd45a4e4a8bc4ca2597923a8b66118f3711aa8922a1b5d2b6ad62d615b6affdcf430533b1539118313c5eea95c81f94766c21a90486
7
- data.tar.gz: 1c79ebfc57bf52c2d586ce65badaf0643ce37c534261943597d308bbd702e1fa01840f9d75164c80e9597c8e73f26d438ec95528321a8ba8f16b0d2048412814
6
+ metadata.gz: cea713cd788b554522e83e7e72da27e942f962fb3996a415ed2d06b803f6a95345265a7e81d9fcd3dc7e6567b4951fdee1dbeedb908ecb146cdcfb70f17c6f7f
7
+ data.tar.gz: 4a15d50266466f54713a440f563d203137c5ea9a2f290d2f6f2e44f752a70ddcc772d901152aeb6fc31957b7d7d9cf6ef04df82f45db4a3964c14ed467a32c94
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Change Log
2
2
 
3
+ ### 4.20.0.5
4
+
5
+ - Hello, Text! sample
6
+ - Timer elaborate sample (copied and simplified from external sample)
7
+ - Calculator elaborate sample (copied and simplified from external sample)
8
+ - Fixed issue relating to cleaning up display listeners
9
+
3
10
  ### 4.20.0.4
4
11
 
5
12
  - Weather elaborate sample
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for SWT 4.20.0.4
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for SWT 4.20.0.5
2
2
  ## JRuby Desktop Development GUI Framework
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-swt.svg)](http://badge.fury.io/rb/glimmer-dsl-swt)
4
4
  [![Travis CI](https://travis-ci.com/AndyObtiva/glimmer-dsl-swt.svg?branch=master)](https://travis-ci.com/github/AndyObtiva/glimmer-dsl-swt)
@@ -15,7 +15,7 @@
15
15
  [<img src="https://covers.oreillystatic.com/images/9780596519650/lrg.jpg" width=105 /><br />
16
16
  Featured in JRuby Cookbook](http://shop.oreilly.com/product/9780596519650.do) and [Chalmers/Gothenburg University Software Engineering Master's Lecture Material](http://www.cse.chalmers.se/~bergert/slides/guest_lecture_DSLs.pdf)
17
17
 
18
- [Glimmer DSL for SWT](https://rubygems.org/gems/glimmer-dsl-swt) 4.20.0.4 includes [SWT 4.20](https://download.eclipse.org/eclipse/downloads/drops4/R-4.20-202106111600/), which was released on June 11, 2021. Gem version numbers are in sync with the SWT library versions. The first two digits represent the SWT version number. The last two digits represent the minor and patch versions of Glimmer DSL for SWT. Note that SWT now supports AARCH64 on Mac and Linux, but it is not fully tested in Glimmer DSL for SWT yet, so deem its support experimental for the time being without guarantees for functionality until declared otherwise (report any issues you may encounter).
18
+ [Glimmer DSL for SWT](https://rubygems.org/gems/glimmer-dsl-swt) 4.20.0.5 includes [SWT 4.20](https://download.eclipse.org/eclipse/downloads/drops4/R-4.20-202106111600/), which was released on June 11, 2021. Gem version numbers are in sync with the SWT library versions. The first two digits represent the SWT version number. The last two digits represent the minor and patch versions of Glimmer DSL for SWT. Note that SWT now supports AARCH64 on Mac and Linux, but it is not fully tested in Glimmer DSL for SWT yet, so deem its support experimental for the time being without guarantees for functionality until declared otherwise (report any issues you may encounter).
19
19
 
20
20
  **Starting in version 4.20.0.0, [Glimmer DSL for SWT](https://rubygems.org/gems/glimmer-dsl-swt) comes with the new [***Shine***](/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#shine) syntax** for highly intuitive and visually expressive View/Model Attribute Mapping, relying on `<=>` for bidirectional (two-way) data-binding and `<=` for unidirectional (one-way) data-binding, providing an alternative to the `bind` keyword (keep in mind that it is still an early alpha, so default back to `bind` whenever needed).
21
21
 
@@ -341,7 +341,7 @@ jgem install glimmer-dsl-swt
341
341
 
342
342
  Or this command if you want a specific version:
343
343
  ```
344
- jgem install glimmer-dsl-swt -v 4.20.0.4
344
+ jgem install glimmer-dsl-swt -v 4.20.0.5
345
345
  ```
346
346
 
347
347
  `jgem` is JRuby's version of `gem` command.
@@ -369,7 +369,7 @@ Note: if you're using activerecord or activesupport, keep in mind that Glimmer u
369
369
 
370
370
  Add the following to `Gemfile`:
371
371
  ```
372
- gem 'glimmer-dsl-swt', '~> 4.20.0.4'
372
+ gem 'glimmer-dsl-swt', '~> 4.20.0.5'
373
373
  ```
374
374
 
375
375
  And, then run:
@@ -390,7 +390,7 @@ glimmer
390
390
  ```
391
391
 
392
392
  ```
393
- Glimmer (JRuby Desktop Development GUI Framework) - JRuby Gem: glimmer-dsl-swt v4.20.0.4
393
+ Glimmer (JRuby Desktop Development GUI Framework) - JRuby Gem: glimmer-dsl-swt v4.20.0.5
394
394
 
395
395
  Usage: glimmer [--bundler] [--pd] [--quiet] [--debug] [--log-level=VALUE] [[ENV_VAR=VALUE]...] [[-jruby-option]...] (application.rb or task[task_args]) [[application2.rb]...]
396
396
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.20.0.4
1
+ 4.20.0.5
@@ -2,6 +2,7 @@
2
2
  - [Hello Samples](#hello-samples)
3
3
  - [Hello, World!](#hello-world)
4
4
  - [Hello, Button!](#hello-button)
5
+ - [Hello, Text!](#hello-text)
5
6
  - [Hello, Composite!](#hello-composite)
6
7
  - [Hello, Layout!](#hello-layout)
7
8
  - [Hello, Shell!](#hello-shell)
@@ -116,6 +117,18 @@ Hello, Button! Incremented 7 times!
116
117
 
117
118
  ![Hello Button Incremented](/images/glimmer-hello-button-incremented.png)
118
119
 
120
+ #### Hello, Text!
121
+
122
+ This sample demonstrates the use of the `text` widget in Glimmer, including data-binding (e.g. via the `<=>` operator) and event handling.
123
+
124
+ Code:
125
+
126
+ [samples/hello/hello_text.rb](/samples/hello/hello_text.rb)
127
+
128
+ Hello, Text!
129
+
130
+ ![Hello Text](/images/glimmer-hello-text.png)
131
+
119
132
  #### Hello, Composite!
120
133
 
121
134
  This sample demonstrates the `composite` widget, which is simply used as a container for visual layout and organization.
@@ -7,8 +7,9 @@
7
7
  - Widget properties are declared with underscored lowercase versions of the SWT properties
8
8
  - Widget property declarations always have arguments and never take a block
9
9
  - Widget property arguments are never wrapped inside parentheses
10
- - Widget listeners are always declared starting with `on_` prefix and affixing listener event method name afterwards in underscored lowercase form
10
+ - Widget listeners are always declared starting with `on_` prefix and affixing listener event method name afterwards in underscored lowercase form. Their multi-line blocks rely on the `do; end` style.
11
11
  - Widget listeners are always followed by a block using curly braces (Only when declared in DSL. When invoked on widget object directly outside of GUI declarations, standard Ruby conventions apply)
12
12
  - Data-binding is done via `bind` keyword, which always takes arguments wrapped in parentheses
13
- - Custom widget body, before_body, and after_body blocks open their blocks and close them with curly braces.
14
- - Custom widgets receive additional arguments to SWT style called options. These are passed as the last argument inside the parentheses, a hash of option names pointing to values.
13
+ - Custom widget `body`, `before_body`, and `after_body` blocks open their blocks and close them with curly braces.
14
+ - Custom widgets receive additional keyword arguments called options, which come after the SWT styles.
15
+ - Pure logic multi-line blocks that do not constitute GUI DSL view elements (such as `Thread.new`, `loop`, `each` and `observe` blocks) rely on the `do; end` style to clearly separate logic code from view code.
Binary file
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2007-2021 Andy Maleh
2
- #
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
5
5
  # "Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@
7
7
  # distribute, sublicense, and/or sell copies of the Software, and to
8
8
  # permit persons to whom the Software is furnished to do so, subject to
9
9
  # the following conditions:
10
- #
10
+ #
11
11
  # The above copyright notice and this permission notice shall be
12
12
  # included in all copies or substantial portions of the Software.
13
- #
13
+ #
14
14
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
15
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
16
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -42,7 +42,7 @@ module Glimmer
42
42
 
43
43
  OBSERVED_MENU_ITEMS = ['about', 'preferences', 'quit']
44
44
 
45
- class FilterListener
45
+ class ConcreteListener
46
46
  include org.eclipse.swt.widgets.Listener
47
47
 
48
48
  def initialize(&listener_block)
@@ -194,15 +194,17 @@ module Glimmer
194
194
  observation_request = observation_request.to_s
195
195
  if observation_request.start_with?('on_swt_')
196
196
  constant_name = observation_request.sub(/^on_swt_/, '')
197
- add_swt_event_filter(constant_name, &block)
197
+ swt_event_reg = add_swt_event_filter(constant_name, &block)
198
+ Glimmer::UI::CustomWidget.current_custom_widgets.last&.observer_registrations&.push(swt_event_reg)
199
+ swt_event_reg
198
200
  elsif observation_request.start_with?('on_')
199
201
  event_name = observation_request.sub(/^on_/, '')
200
202
  if OBSERVED_MENU_ITEMS.include?(event_name) && OS.mac?
201
203
  system_menu = swt_display.getSystemMenu
202
204
  menu_item = system_menu.getItems.find {|menu_item| menu_item.getID == SWTProxy["ID_#{event_name.upcase}"]}
203
- display_mac_event_registration = menu_item.addListener(SWTProxy[:Selection], &block)
204
- # TODO enable this code and test on the Mac to ensure automatic cleanup of mac event registrations in custom widgets
205
- # Glimmer::UI::CustomWidget.current_custom_widgets.last&.observer_registrations&.push(display_mac_event_registration)
205
+ listener = ConcreteListener.new(&block)
206
+ display_mac_event_registration = menu_item.addListener(SWTProxy[:Selection], listener)
207
+ Glimmer::UI::CustomWidget.current_custom_widgets.last&.observer_registrations&.push(display_mac_event_registration)
206
208
  display_mac_event_registration
207
209
  end
208
210
  end
@@ -210,15 +212,16 @@ module Glimmer
210
212
 
211
213
  def add_swt_event_filter(swt_constant, &block)
212
214
  event_type = SWTProxy[swt_constant]
213
- @swt_display.addFilter(event_type, FilterListener.new(&block))
215
+ swt_listener = ConcreteListener.new(&block)
216
+ @swt_display.addFilter(event_type, swt_listener)
214
217
  #WidgetListenerProxy.new(@swt_display.getListeners(event_type).last)
215
218
  WidgetListenerProxy.new(
216
219
  swt_display: @swt_display,
217
220
  event_type: event_type,
218
221
  filter: true,
219
- swt_listener: block,
222
+ swt_listener: swt_listener,
220
223
  widget_add_listener_method: 'addFilter',
221
- swt_listener_class: FilterListener,
224
+ swt_listener_class: ConcreteListener,
222
225
  swt_listener_method: 'handleEvent'
223
226
  )
224
227
  end
@@ -838,7 +838,7 @@ module Glimmer
838
838
  widget_listener_proxy = nil
839
839
  safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
840
840
  @swt_widget.addListener(event_type, &safe_block)
841
- 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)
841
+ WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: @swt_widget.getListeners(event_type).last, event_type: event_type, swt_constant: swt_constant)
842
842
  end
843
843
  end
844
844
 
@@ -31,9 +31,9 @@ module Glimmer
31
31
  class << self
32
32
  def launch(*args, &content)
33
33
  auto_exec do
34
- @launched_custom_shell = send(keyword, *args, &content)
35
- @launched_custom_shell.swt_widget.set_data('launched', true)
36
- @launched_custom_shell.open
34
+ launched_custom_shell = send(keyword, *args, &content)
35
+ launched_custom_shell.swt_widget.set_data('launched', true)
36
+ launched_custom_shell.open
37
37
  end
38
38
  end
39
39
  end
@@ -186,8 +186,11 @@ module Glimmer
186
186
  @swt_widget.set_data('custom_widget', self)
187
187
  end
188
188
  execute_hook('after_body')
189
- @dispose_listener_registration = @body_root.on_widget_disposed do
190
- observer_registrations.each(&:deregister)
189
+ auto_exec do
190
+ @dispose_listener_registration = @body_root.on_widget_disposed do
191
+ observer_registrations.compact.each(&:deregister)
192
+ observer_registrations.clear
193
+ end
191
194
  end
192
195
  end
193
196
 
@@ -0,0 +1,116 @@
1
+ require 'glimmer-dsl-swt'
2
+ require 'bigdecimal'
3
+
4
+ require_relative 'calculator/model/presenter'
5
+
6
+ # This sample demonstrates use of MVP (Model-View-Presenter) Architectural Pattern
7
+ # to data-bind View widgets to object-oriented Models taking advantage of design patterns
8
+ # like Command Design Pattern.
9
+ class Calculator
10
+ include Glimmer::UI::CustomShell
11
+
12
+ BUTTON_FONT = {height: 14}
13
+ BUTTON_FONT_OPERATION = {height: 18}
14
+ BUTTON_FONT_BIG = {height: 28}
15
+
16
+ attr_reader :presenter
17
+
18
+ before_body {
19
+ @presenter = Model::Presenter.new
20
+
21
+ Display.setAppName('Glimmer Calculator')
22
+
23
+ display {
24
+ on_swt_keydown { |key_event|
25
+ char = key_event.character.chr rescue nil
26
+ @presenter.press(char)
27
+ }
28
+
29
+ on_about {
30
+ display_about_dialog
31
+ }
32
+ }
33
+ }
34
+
35
+ body {
36
+ shell {
37
+ grid_layout 4, true
38
+
39
+ minimum_size (OS.mac? ? 320 : (OS.windows? ? 390 : 520)), 240
40
+ text "Glimmer Calculator"
41
+
42
+ on_shell_closed do
43
+ @presenter.purge_command_history
44
+ end
45
+
46
+ # Setting styled_text to multi in order for alignment options to activate
47
+ styled_text(:multi, :wrap, :border) {
48
+ text bind(@presenter, :result)
49
+ alignment swt(:right)
50
+ right_margin 5
51
+ font height: 40
52
+ layout_data(:fill, :fill, true, true) {
53
+ horizontal_span 4
54
+ }
55
+ editable false
56
+ caret nil
57
+ }
58
+ command_button('AC')
59
+ operation_button('÷')
60
+ operation_button('×')
61
+ operation_button('−')
62
+ (7..9).each { |number|
63
+ number_button(number)
64
+ }
65
+ operation_button('+', font: BUTTON_FONT_BIG, vertical_span: 2)
66
+ (4..6).each { |number|
67
+ number_button(number)
68
+ }
69
+ (1..3).each { |number|
70
+ number_button(number)
71
+ }
72
+ command_button('=', font: BUTTON_FONT_BIG, vertical_span: 2)
73
+ number_button(0, horizontal_span: 2)
74
+ operation_button('.')
75
+ }
76
+ }
77
+
78
+ def number_button(number, options = {})
79
+ command_button(number, options)
80
+ end
81
+
82
+ def operation_button(operation, options = {})
83
+ command_button(operation, options.merge(font: BUTTON_FONT_OPERATION))
84
+ end
85
+
86
+ def command_button(command, options = {})
87
+ command = command.to_s
88
+ options[:font] ||= BUTTON_FONT
89
+ options[:horizontal_span] ||= 1
90
+ options[:vertical_span] ||= 1
91
+
92
+ button { |proxy|
93
+ text command
94
+ font options[:font]
95
+
96
+ layout_data(:fill, :fill, true, true) {
97
+ horizontal_span options[:horizontal_span]
98
+ vertical_span options[:vertical_span]
99
+ }
100
+
101
+ on_widget_selected {
102
+ @presenter.press(command)
103
+ }
104
+ }
105
+ end
106
+
107
+ def display_about_dialog
108
+ message_box(body_root) {
109
+ text 'About'
110
+ message "Glimmer - Calculator\n\nCopyright (c) 2007-2021 Andy Maleh"
111
+ }.open
112
+ end
113
+
114
+ end
115
+
116
+ Calculator.launch
@@ -0,0 +1,105 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class << self
5
+ attr_accessor :number1, :number2, :operation
6
+
7
+ # Keyword string representing calculator command (e.g. '+' for Add command)
8
+ # Subclasses must call to define a single keyword
9
+ def keyword(keyword_text)
10
+ Command.keyword_to_command_class_mapping[keyword_text] = self
11
+ end
12
+
13
+ # Keyword string array representing calculator command (e.g. ('0'..'9').to_a)
14
+ # Subclasses must call to define multiple keywords
15
+ def keywords(*keyword_text_array)
16
+ keyword_text_array.flatten.each do |keyword_text|
17
+ keyword(keyword_text)
18
+ end
19
+ end
20
+
21
+ def keyword_to_command_class_mapping
22
+ @keyword_to_command_class_mapping ||= {}
23
+ end
24
+
25
+ def command_history
26
+ @command_history ||= []
27
+ end
28
+
29
+ def purge_command_history
30
+ Command.command_history.clear
31
+ self.number1 = nil
32
+ self.number2 = nil
33
+ self.operation = nil
34
+ end
35
+
36
+ def for(button)
37
+ command_class = keyword_to_command_class_mapping[button]
38
+ command_class&.new(button)&.tap do |command|
39
+ command.execute
40
+ command_history << command
41
+ end
42
+ end
43
+ end
44
+
45
+ attr_reader :button
46
+ attr_accessor :result
47
+
48
+ def initialize(button)
49
+ @button = button
50
+ end
51
+
52
+ def number1
53
+ Command.number1
54
+ end
55
+
56
+ def number1=(value)
57
+ Command.number1 = value.to_f
58
+ end
59
+
60
+ def number2
61
+ Command.number2
62
+ end
63
+
64
+ def number2=(value)
65
+ Command.number2 = value.to_f
66
+ end
67
+
68
+ def operation
69
+ Command.operation
70
+ end
71
+
72
+ def operation=(op)
73
+ Command.operation = op
74
+ end
75
+
76
+ def last_result
77
+ last_command&.result
78
+ end
79
+
80
+ def last_command
81
+ command_history.last
82
+ end
83
+
84
+ def command_history
85
+ Command.command_history
86
+ end
87
+
88
+ def execute
89
+ raise 'Not implemented! Please override in a subclass.'
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # Dir[File.join(File.dirname(__FILE__), 'command', '**', '*.rb')].each {|f| require(f)} # disabled for Opal compatibility
96
+
97
+ require_relative 'command/all_clear'
98
+ require_relative 'command/equals'
99
+ require_relative 'command/number'
100
+ require_relative 'command/operation'
101
+ require_relative 'command/point'
102
+ require_relative 'command/operation/add'
103
+ require_relative 'command/operation/divide'
104
+ require_relative 'command/operation/multiply'
105
+ require_relative 'command/operation/subtract'
@@ -0,0 +1,17 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class AllClear < Command
5
+ keywords 'AC', 'c', 'C', 8.chr, 27.chr, 127.chr
6
+
7
+ def execute
8
+ self.result = '0'
9
+ self.number1 = nil
10
+ self.number2 = nil
11
+ self.operation = nil
12
+ command_history.clear
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Equals < Command
5
+ keywords '=', "\r"
6
+
7
+ def execute
8
+ if number1 && number2 && operation
9
+ self.result = operation.calculate.to_s
10
+ self.number1 = self.result
11
+ else
12
+ self.result = last_result || '0'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Number < Command
5
+ keywords ('0'..'9').to_a
6
+
7
+ def execute
8
+ self.result = last_result.nil? || (!last_command.is_a?(Number) && !last_command.is_a?(Point)) ? button : last_result + button
9
+ if operation.nil? || last_command.is_a?(Equals)
10
+ self.number1 = self.result
11
+ self.number2 = nil
12
+ self.operation = nil
13
+ else
14
+ self.number2 = self.result
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Operation < Command
5
+ def execute
6
+ if number1 && number2 && operation && !last_command.is_a?(Equals)
7
+ self.result = operation.calculate.to_s
8
+ self.number1 = self.result
9
+ else
10
+ self.result = last_result || '0'
11
+ self.operation = self
12
+ end
13
+ end
14
+
15
+ def calculate
16
+ calculation_result = BigDecimal.new(number1.to_s).send(operation_method, BigDecimal.new(number2.to_s)).to_f
17
+ calculation_result.to_s.match(/\.0+$/) ? calculation_result.to_i : calculation_result
18
+ end
19
+
20
+ # Subclasses must implement to indicate operation method on number (e.g. :+ for addition)
21
+ def operation_method
22
+ raise 'Not implemented!'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Operation < Command
5
+ class Add < Operation
6
+ keyword '+'
7
+
8
+ def operation_method
9
+ :+
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Operation < Command
5
+ class Divide < Operation
6
+ keywords '÷', '/'
7
+
8
+ def operation_method
9
+ :/
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Operation < Command
5
+ class Multiply < Operation
6
+ keywords '×', '*'
7
+
8
+ def operation_method
9
+ :*
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Operation < Command
5
+ class Subtract < Operation
6
+ keywords '−', '-'
7
+
8
+ def operation_method
9
+ :-
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ class Calculator
2
+ module Model
3
+ class Command
4
+ class Point < Command
5
+ keyword '.'
6
+
7
+ def execute
8
+ self.result = last_result.nil? || !last_command.is_a?(Number) ? '0.' : "#{last_result}."
9
+ if operation.nil? || last_command.is_a?(Equals)
10
+ self.number1 = self.result
11
+ self.number2 = nil
12
+ self.operation = nil
13
+ else
14
+ self.number2 = self.result
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'command'
2
+
3
+ class Calculator
4
+ module Model
5
+ class Presenter
6
+ FORMATTER = {
7
+ nil => '0',
8
+ 'NaN' => 'Not a number'
9
+ }
10
+
11
+ attr_accessor :result
12
+
13
+ def initialize
14
+ self.result = '0'
15
+ end
16
+
17
+ def press(button)
18
+ command = Command.for(button)
19
+ if command
20
+ new_result = command.result
21
+ self.result = FORMATTER[new_result] || new_result
22
+ end
23
+ end
24
+
25
+ def purge_command_history
26
+ Command.purge_command_history
27
+ end
28
+ end
29
+ end
30
+ end
@@ -54,7 +54,7 @@ class Tetris
54
54
  Display.app_name = 'Glimmer Tetris'
55
55
 
56
56
  display {
57
- @keyboard_down_listener = on_swt_keydown { |key_event|
57
+ on_swt_keydown { |key_event|
58
58
  case key_event.keyCode
59
59
  when swt(:arrow_down), 's'.bytes.first
60
60
  game.down! if OS.mac?
@@ -82,7 +82,7 @@ class Tetris
82
82
 
83
83
  # invoke game.down! on keyup with Windows/Linux since they seem to group-render similar events, preventing intermediate renders (causing invisiblity while holding keys)
84
84
  if !OS.mac?
85
- @keyboard_up_listener = on_swt_keyup { |key_event|
85
+ on_swt_keyup { |key_event|
86
86
  case key_event.keyCode
87
87
  when swt(:arrow_down), 's'.bytes.first
88
88
  game.down!
@@ -91,11 +91,11 @@ class Tetris
91
91
  end
92
92
 
93
93
  # if running in app mode, set the Mac app about dialog (ignored in platforms)
94
- @about_observer = on_about {
94
+ on_about {
95
95
  show_about_dialog
96
96
  }
97
97
 
98
- @quit_observer = on_quit {
98
+ on_quit {
99
99
  exit(0)
100
100
  }
101
101
  }
@@ -0,0 +1,233 @@
1
+ require 'glimmer-dsl-swt'
2
+ require 'os'
3
+
4
+ class Timer
5
+ include Glimmer::UI::CustomShell
6
+
7
+ import 'javax.sound.sampled'
8
+
9
+ FILE_SOUND_ALARM = File.expand_path(File.join('timer', 'alarm1.wav'), __dir__)
10
+ COMMAND_KEY = OS.mac? ? :command : :ctrl
11
+
12
+ ## Add options like the following to configure CustomShell by outside consumers
13
+ #
14
+ # options :title, :background_color
15
+ # option :width, default: 320
16
+ # option :height, default: 240
17
+
18
+ attr_accessor :countdown, :min, :sec
19
+
20
+ ## Use before_body block to pre-initialize variables to use in body
21
+ #
22
+ #
23
+ before_body {
24
+ Display.setAppName('Glimmer Timer')
25
+
26
+ @display = display {
27
+ on_about {
28
+ display_about_dialog
29
+ }
30
+ on_preferences {
31
+ display_about_dialog
32
+ }
33
+ }
34
+
35
+ @min = 0
36
+ @sec = 0
37
+ }
38
+
39
+ ## Use after_body block to setup observers for widgets in body
40
+ #
41
+ after_body {
42
+ Thread.new {
43
+ loop {
44
+ sleep(1)
45
+ if @countdown
46
+ sync_exec {
47
+ @countdown_time = Time.new(1, 1, 1, 0, min, sec)
48
+ @countdown_time -= 1
49
+ self.min = @countdown_time.min
50
+ self.sec = @countdown_time.sec
51
+ if @countdown_time.min <= 0 && @countdown_time.sec <= 0
52
+ stop_countdown
53
+ play_countdown_done_sound
54
+ end
55
+ }
56
+ end
57
+ }
58
+ }
59
+ }
60
+
61
+ ## Add widget content inside custom shell body
62
+ ## Top-most widget must be a shell or another custom shell
63
+ #
64
+ body {
65
+ shell {
66
+ grid_layout # 1 column
67
+
68
+ # Replace example content below with custom shell content
69
+ minimum_size (OS.windows? ? 214 : 200), 114
70
+ image File.join(APP_ROOT, 'package', 'windows', "Timer.ico") if OS.windows?
71
+ text "Glimmer Timer"
72
+
73
+ timer_menu_bar
74
+
75
+ countdown_group
76
+ }
77
+ }
78
+
79
+ def timer_menu_bar
80
+ menu_bar {
81
+ menu {
82
+ text '&Action'
83
+
84
+ menu_item {
85
+ text '&Start'
86
+ accelerator COMMAND_KEY, 's'
87
+ enabled bind(self, :countdown, on_read: :!)
88
+
89
+ on_widget_selected {
90
+ start_countdown
91
+ }
92
+ }
93
+ menu_item {
94
+ text 'St&op'
95
+ enabled bind(self, :countdown)
96
+ accelerator COMMAND_KEY, 'o'
97
+
98
+ on_widget_selected {
99
+ stop_countdown
100
+ }
101
+ }
102
+ unless OS.mac?
103
+ menu_item(:separator)
104
+ menu_item {
105
+ text 'E&xit'
106
+ accelerator :alt, :f4
107
+
108
+ on_widget_selected {
109
+ exit(0)
110
+ }
111
+ }
112
+ end
113
+ }
114
+ menu {
115
+ text '&Help'
116
+
117
+ menu_item {
118
+ text '&About'
119
+ accelerator COMMAND_KEY, :shift, 'a'
120
+
121
+ on_widget_selected {
122
+ display_about_dialog
123
+ }
124
+ }
125
+ }
126
+ }
127
+ end
128
+
129
+ def countdown_group
130
+ group {
131
+ # has grid layout with 1 column by default
132
+ text 'Countdown'
133
+ font height: 20
134
+
135
+ countdown_group_field_composite
136
+
137
+ countdown_group_button_composite
138
+ }
139
+ end
140
+
141
+ def countdown_group_field_composite
142
+ composite {
143
+ row_layout {
144
+ margin_width 0
145
+ margin_height 0
146
+ }
147
+ @min_spinner = spinner {
148
+ text_limit 2
149
+ digits 0
150
+ maximum 59
151
+ selection bind(self, :min)
152
+ enabled bind(self, :countdown, on_read: :!)
153
+ on_widget_default_selected {
154
+ start_countdown
155
+ }
156
+ }
157
+ label {
158
+ text ':'
159
+ font(height: 18) if OS.mac?
160
+ }
161
+ @sec_spinner = spinner {
162
+ text_limit 2
163
+ digits 0
164
+ maximum 59
165
+ selection bind(self, :sec)
166
+ enabled bind(self, :countdown, on_read: :!)
167
+ on_widget_default_selected {
168
+ start_countdown
169
+ }
170
+ }
171
+ }
172
+ end
173
+
174
+ def countdown_group_button_composite
175
+ composite {
176
+ row_layout {
177
+ margin_width 0
178
+ margin_height 0
179
+ }
180
+ @start_button = button {
181
+ text '&Start'
182
+ enabled bind(self, :countdown, on_read: :!)
183
+ on_widget_selected {
184
+ start_countdown
185
+ }
186
+ on_key_pressed { |event|
187
+ start_countdown if event.keyCode == swt(:cr)
188
+ }
189
+ }
190
+ @stop_button = button {
191
+ text 'St&op'
192
+ enabled bind(self, :countdown)
193
+ on_widget_selected {
194
+ stop_countdown
195
+ }
196
+ on_key_pressed { |event|
197
+ stop_countdown if event.keyCode == swt(:cr)
198
+ }
199
+ }
200
+ }
201
+ end
202
+
203
+ def display_about_dialog
204
+ message_box(body_root) {
205
+ text 'About'
206
+ message "Glimmer Timer\n\nCopyright (c) 2007-2021 Andy Maleh"
207
+ }.open
208
+ end
209
+
210
+ def start_countdown
211
+ self.countdown = true
212
+ @stop_button.swt_widget.set_focus
213
+ end
214
+
215
+ def stop_countdown
216
+ self.countdown = false
217
+ @min_spinner.swt_widget.set_focus
218
+ end
219
+
220
+ def play_countdown_done_sound
221
+ begin
222
+ file_or_stream = java.io.File.new(FILE_SOUND_ALARM)
223
+ audio_stream = AudioSystem.get_audio_input_stream(file_or_stream)
224
+ clip = AudioSystem.clip
225
+ clip.open(audio_stream)
226
+ clip.start
227
+ rescue => e
228
+ Glimmer::Config.logger.error e.full_message
229
+ end
230
+ end
231
+ end
232
+
233
+ Timer.launch
Binary file
@@ -23,7 +23,9 @@ require 'glimmer-dsl-swt'
23
23
 
24
24
  include Glimmer
25
25
 
26
- shell {
26
+ # A version of this was featured in a dzone article as an intro to Glimmer syntax:
27
+ # https://dzone.com/articles/an-introduction-glimmer
28
+ shell { |shell_proxy|
27
29
  text "User Profile"
28
30
 
29
31
  composite {
@@ -72,7 +74,7 @@ shell {
72
74
  button {
73
75
  text "close"
74
76
  layout_data :left, :center, true, true
75
- on_widget_selected { exit(0) }
77
+ on_widget_selected { shell_proxy.close }
76
78
  }
77
79
  }
78
80
  }.open
@@ -19,10 +19,10 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ require 'glimmer-dsl-swt'
22
23
  require 'net/http'
23
24
  require 'json'
24
25
  require 'facets/string/titlecase'
25
- require 'glimmer-dsl-swt'
26
26
 
27
27
  class Weather
28
28
  include Glimmer::UI::CustomShell
@@ -31,7 +31,7 @@ class HelloCoolBar
31
31
  end
32
32
 
33
33
  before_body {
34
- self.font_size = '10'
34
+ self.font_size = '30'
35
35
  }
36
36
 
37
37
  body {
@@ -80,9 +80,9 @@ class HelloCoolBar
80
80
 
81
81
 
82
82
  label {
83
- font height: 30
84
- text bind(self, :operation)
85
- text bind(self, :font_size)
83
+ font <= [self, :font_size, on_read: ->(size) { {height: size.to_i} }]
84
+ text <= [self, :operation]
85
+ text <= [self, :font_size]
86
86
  }
87
87
  }
88
88
  }
@@ -0,0 +1,120 @@
1
+ require 'glimmer-dsl-swt'
2
+
3
+ class HelloText
4
+ include Glimmer::UI::CustomShell
5
+
6
+ attr_accessor :default, :center, :left, :right, :password, :telephone, :read_only, :wrap, :multi
7
+
8
+ before_body {
9
+ self.default = 'default is :border style'
10
+ self.center = 'centered'
11
+ self.left = 'left-aligned'
12
+ self.right = 'right-aligned'
13
+ self.password = 'password'
14
+ self.telephone = '555-555-5555'
15
+ self.read_only = 'Telephone area code is 555'
16
+ self.wrap = 'wraps if text content is too long like this example'
17
+ self.multi = "multi-line enables hitting enter,\nbut does not wrap by default"
18
+ }
19
+
20
+ body {
21
+ shell {
22
+ grid_layout 2, false
23
+
24
+ text 'Hello, Text!'
25
+ minimum_size 350, 100
26
+
27
+ label {
28
+ text 'text'
29
+ }
30
+ text { # includes :border style by default
31
+ layout_data :fill, :center, true, false
32
+ text <=> [self, :default]
33
+ }
34
+
35
+ label {
36
+ text 'text(:center, :border)'
37
+ }
38
+ text(:center, :border) {
39
+ layout_data :fill, :center, true, false
40
+ text <=> [self, :center]
41
+ }
42
+
43
+ label {
44
+ text 'text(:left, :border)'
45
+ }
46
+ text(:left, :border) {
47
+ layout_data :fill, :center, true, false
48
+ text <=> [self, :left]
49
+ }
50
+
51
+ label {
52
+ text 'text(:right, :border)'
53
+ }
54
+ text(:right, :border) {
55
+ layout_data :fill, :center, true, false
56
+ text <=> [self, :right]
57
+ }
58
+
59
+ label {
60
+ text 'text(:password, :border)'
61
+ }
62
+ text(:password, :border) {
63
+ layout_data :fill, :center, true, false
64
+ text <=> [self, :password]
65
+ }
66
+
67
+ label {
68
+ text 'text(:read_only, :border)'
69
+ }
70
+ text(:read_only, :border) {
71
+ layout_data :fill, :center, true, false
72
+ text <=> [self, :read_only]
73
+ }
74
+
75
+ label {
76
+ text 'text with event handlers'
77
+ }
78
+ text {
79
+ layout_data :fill, :center, true, false
80
+ text <=> [self, :telephone]
81
+
82
+ # this event kicks in just after the user typed and before modifying the text attribute value
83
+ on_verify_text do |verify_event|
84
+ new_text = verify_event.widget.text.clone
85
+ new_text[verify_event.start...verify_event.end] = verify_event.text
86
+ verify_event.doit = telephone?(new_text)
87
+ end
88
+
89
+ # this event kicks in just after the text widget is verified and modified
90
+ on_modify_text do |modify_event|
91
+ self.read_only = "Telephone area code is #{modify_event.widget.text.gsub(/[^0-9]/, '')[0...3]}"
92
+ end
93
+ }
94
+
95
+ label {
96
+ text 'text(:wrap, :border)'
97
+ }
98
+ text(:wrap, :border) {
99
+ layout_data(:fill, :center, true, false) {
100
+ width_hint 100
101
+ }
102
+ text <=> [self, :wrap]
103
+ }
104
+
105
+ label {
106
+ text 'text(:multi, :border)'
107
+ }
108
+ text(:multi, :border) {
109
+ layout_data :fill, :center, true, false
110
+ text <=> [self, :multi]
111
+ }
112
+ }
113
+ }
114
+
115
+ def telephone?(text)
116
+ !!text.match(/^\d{0,3}[-.\/]?\d{0,3}[-.\/]?\d{0,4}$/)
117
+ end
118
+ end
119
+
120
+ HelloText.launch
@@ -31,7 +31,7 @@ class HelloToolBar
31
31
  end
32
32
 
33
33
  before_body {
34
- self.font_size = '10'
34
+ self.font_size = '30'
35
35
  }
36
36
 
37
37
  body {
@@ -76,9 +76,9 @@ class HelloToolBar
76
76
  }
77
77
 
78
78
  label {
79
- font height: 30
80
- text bind(self, :operation)
81
- text bind(self, :font_size)
79
+ font <= [self, :font_size, on_read: ->(size) { {height: size.to_i} }]
80
+ text <= [self, :operation]
81
+ text <= [self, :font_size]
82
82
  }
83
83
  }
84
84
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-swt
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.20.0.4
4
+ version: 4.20.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
@@ -505,6 +505,19 @@ files:
505
505
  - lib/glimmer/ui/custom_shell.rb
506
506
  - lib/glimmer/ui/custom_widget.rb
507
507
  - lib/glimmer/util/proc_tracker.rb
508
+ - samples/elaborate/calculator.rb
509
+ - samples/elaborate/calculator/model/command.rb
510
+ - samples/elaborate/calculator/model/command/all_clear.rb
511
+ - samples/elaborate/calculator/model/command/command_history.rb
512
+ - samples/elaborate/calculator/model/command/equals.rb
513
+ - samples/elaborate/calculator/model/command/number.rb
514
+ - samples/elaborate/calculator/model/command/operation.rb
515
+ - samples/elaborate/calculator/model/command/operation/add.rb
516
+ - samples/elaborate/calculator/model/command/operation/divide.rb
517
+ - samples/elaborate/calculator/model/command/operation/multiply.rb
518
+ - samples/elaborate/calculator/model/command/operation/subtract.rb
519
+ - samples/elaborate/calculator/model/command/point.rb
520
+ - samples/elaborate/calculator/model/presenter.rb
508
521
  - samples/elaborate/contact_manager.rb
509
522
  - samples/elaborate/contact_manager/contact.rb
510
523
  - samples/elaborate/contact_manager/contact_manager_presenter.rb
@@ -528,6 +541,9 @@ files:
528
541
  - samples/elaborate/tic_tac_toe.rb
529
542
  - samples/elaborate/tic_tac_toe/board.rb
530
543
  - samples/elaborate/tic_tac_toe/cell.rb
544
+ - samples/elaborate/timer.rb
545
+ - samples/elaborate/timer/alarm1.wav
546
+ - samples/elaborate/timer/sounds/alarm1.wav
531
547
  - samples/elaborate/user_profile.rb
532
548
  - samples/elaborate/weather.rb
533
549
  - samples/hello/hello_browser.rb
@@ -588,6 +604,7 @@ files:
588
604
  - samples/hello/hello_tab.rb
589
605
  - samples/hello/hello_table.rb
590
606
  - samples/hello/hello_table/baseball_park.png
607
+ - samples/hello/hello_text.rb
591
608
  - samples/hello/hello_tool_bar.rb
592
609
  - samples/hello/hello_tree.rb
593
610
  - samples/hello/hello_world.rb