glimmer-dsl-swt 4.20.0.0 → 4.20.0.5

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