glimmer-dsl-swt 4.20.0.0 → 4.20.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +18 -14
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +11 -7
  6. data/docs/reference/GLIMMER_SAMPLES.md +170 -17
  7. data/docs/reference/GLIMMER_STYLE_GUIDE.md +4 -3
  8. data/glimmer-dsl-swt.gemspec +0 -0
  9. data/lib/glimmer/dsl/swt/widget_expression.rb +2 -0
  10. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +3 -3
  11. data/lib/glimmer/rake_task/scaffold.rb +0 -2
  12. data/lib/glimmer/swt/combo_proxy.rb +48 -0
  13. data/lib/glimmer/swt/display_proxy.rb +11 -8
  14. data/lib/glimmer/swt/tool_bar_proxy.rb +51 -0
  15. data/lib/glimmer/swt/widget_proxy.rb +7 -1
  16. data/lib/glimmer/ui/custom_shell.rb +3 -3
  17. data/lib/glimmer/ui/custom_widget.rb +5 -2
  18. data/samples/elaborate/calculator.rb +116 -0
  19. data/samples/elaborate/calculator/model/command.rb +105 -0
  20. data/samples/elaborate/calculator/model/command/all_clear.rb +17 -0
  21. data/samples/elaborate/calculator/model/command/command_history.rb +0 -0
  22. data/samples/elaborate/calculator/model/command/equals.rb +18 -0
  23. data/samples/elaborate/calculator/model/command/number.rb +20 -0
  24. data/samples/elaborate/calculator/model/command/operation.rb +27 -0
  25. data/samples/elaborate/calculator/model/command/operation/add.rb +15 -0
  26. data/samples/elaborate/calculator/model/command/operation/divide.rb +15 -0
  27. data/samples/elaborate/calculator/model/command/operation/multiply.rb +15 -0
  28. data/samples/elaborate/calculator/model/command/operation/subtract.rb +15 -0
  29. data/samples/elaborate/calculator/model/command/point.rb +20 -0
  30. data/samples/elaborate/calculator/model/presenter.rb +30 -0
  31. data/samples/elaborate/login.rb +15 -13
  32. data/samples/elaborate/tetris.rb +4 -4
  33. data/samples/elaborate/tetris/model/game.rb +0 -3
  34. data/samples/elaborate/timer.rb +233 -0
  35. data/samples/elaborate/timer/alarm1.wav +0 -0
  36. data/samples/elaborate/timer/sounds/alarm1.wav +0 -0
  37. data/samples/elaborate/user_profile.rb +4 -2
  38. data/samples/elaborate/weather.rb +164 -0
  39. data/samples/hello/hello_composite.rb +71 -0
  40. data/samples/hello/hello_cool_bar.rb +147 -0
  41. data/samples/hello/hello_layout.rb +243 -0
  42. data/samples/hello/hello_shell.rb +205 -0
  43. data/samples/hello/hello_text.rb +120 -0
  44. data/samples/hello/hello_tool_bar.rb +143 -0
  45. metadata +28 -3
@@ -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
@@ -70,3 +70,5 @@ require 'glimmer/swt/sash_form_proxy'
70
70
  require 'glimmer/swt/styled_text_proxy'
71
71
  require 'glimmer/swt/date_time_proxy'
72
72
  require 'glimmer/swt/tab_folder_proxy'
73
+ require 'glimmer/swt/combo_proxy'
74
+ require 'glimmer/swt/tool_bar_proxy'
@@ -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
@@ -441,8 +441,6 @@ module Glimmer
441
441
  require '#{file_name(app_name)}/view/app_view'
442
442
 
443
443
  class #{class_name(app_name)}
444
- include Glimmer
445
-
446
444
  APP_ROOT = File.expand_path('../..', __FILE__)
447
445
  VERSION = File.read(File.join(APP_ROOT, 'VERSION'))
448
446
  LICENSE = File.read(File.join(APP_ROOT, 'LICENSE.txt'))
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/swt/widget_proxy'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ # Proxy for org.eclipse.swt.widgets.Combo
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class ComboProxy < WidgetProxy
30
+ attr_accessor :tool_item_proxy, :swt_tool_item
31
+
32
+ def initialize(*init_args, &block)
33
+ super
34
+ self.tool_item_proxy = WidgetProxy.new("tool_item", parent_proxy, [:separator]) if parent_proxy.swt_widget.is_a?(ToolBar)
35
+ self.swt_tool_item = tool_item_proxy&.swt_widget
36
+ end
37
+
38
+ def post_add_content
39
+ if self.tool_item_proxy
40
+ self.swt_widget.pack
41
+ self.tool_item_proxy.text = 'filler' # text seems needed (any text works)
42
+ self.tool_item_proxy.width = swt_widget.size.x
43
+ self.tool_item_proxy.control = swt_widget
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -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
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/swt/widget_proxy'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ # Proxy for org.eclipse.swt.widgets.ToolBarProxy
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class ToolBarProxy < WidgetProxy
30
+ attr_accessor :cool_item_proxy, :swt_cool_item
31
+
32
+ def initialize(*init_args, &block)
33
+ super
34
+ self.cool_item_proxy = WidgetProxy.new("cool_item", parent_proxy, []) if parent_proxy.swt_widget.is_a?(CoolBar)
35
+ self.swt_cool_item = cool_item_proxy&.swt_widget
36
+ end
37
+
38
+ def post_add_content
39
+ apply_preferred_size if cool_item_proxy
40
+ end
41
+
42
+ def apply_preferred_size
43
+ swt_widget.pack
44
+ size = swt_widget.size
45
+ cool_item_proxy.control = swt_widget
46
+ preferred = swt_cool_item.computeSize(size.x, size.y)
47
+ swt_cool_item.setPreferredSize(preferred)
48
+ end
49
+ end
50
+ end
51
+ 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
 
@@ -1006,6 +1006,12 @@ module Glimmer
1006
1006
  image: lambda do |*value|
1007
1007
  ImageProxy.create(*value).swt_image
1008
1008
  end,
1009
+ disabled_image: lambda do |*value|
1010
+ ImageProxy.create(*value).swt_image
1011
+ end,
1012
+ hot_image: lambda do |*value|
1013
+ ImageProxy.create(*value).swt_image
1014
+ end,
1009
1015
  images: lambda do |array|
1010
1016
  array.to_a.map do |value|
1011
1017
  ImageProxy.create(value).swt_image
@@ -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'