glimmer-dsl-swt 4.20.0.4 → 4.20.3.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +13 -7
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +5 -2
  6. data/docs/reference/GLIMMER_SAMPLES.md +13 -0
  7. data/docs/reference/GLIMMER_STYLE_GUIDE.md +4 -3
  8. data/glimmer-dsl-swt.gemspec +0 -0
  9. data/lib/glimmer/data_binding/shine.rb +2 -1
  10. data/lib/glimmer/data_binding/table_items_binding.rb +2 -2
  11. data/lib/glimmer/data_binding/tree_items_binding.rb +2 -2
  12. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +1 -1
  13. data/lib/glimmer/dsl/swt/radio_group_selection_data_binding_expression.rb +1 -1
  14. data/lib/glimmer/dsl/swt/shine_data_binding_expression.rb +6 -5
  15. data/lib/glimmer/dsl/swt/table_items_data_binding_expression.rb +2 -2
  16. data/lib/glimmer/dsl/swt/tree_items_data_binding_expression.rb +17 -12
  17. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +3 -3
  18. data/lib/glimmer/rake_task/scaffold.rb +2 -2
  19. data/lib/glimmer/swt/custom/animation.rb +6 -0
  20. data/lib/glimmer/swt/custom/checkbox_group.rb +1 -1
  21. data/lib/glimmer/swt/custom/radio_group.rb +2 -1
  22. data/lib/glimmer/swt/display_proxy.rb +11 -8
  23. data/lib/glimmer/swt/proxy_properties.rb +7 -6
  24. data/lib/glimmer/swt/table_proxy.rb +15 -8
  25. data/lib/glimmer/swt/widget_proxy.rb +5 -2
  26. data/lib/glimmer/ui/custom_shape.rb +1 -1
  27. data/lib/glimmer/ui/custom_shell.rb +3 -3
  28. data/lib/glimmer/ui/custom_widget.rb +6 -3
  29. data/samples/elaborate/calculator.rb +116 -0
  30. data/samples/elaborate/calculator/model/command.rb +105 -0
  31. data/samples/elaborate/calculator/model/command/all_clear.rb +17 -0
  32. data/samples/elaborate/calculator/model/command/command_history.rb +0 -0
  33. data/samples/elaborate/calculator/model/command/equals.rb +18 -0
  34. data/samples/elaborate/calculator/model/command/number.rb +20 -0
  35. data/samples/elaborate/calculator/model/command/operation.rb +27 -0
  36. data/samples/elaborate/calculator/model/command/operation/add.rb +15 -0
  37. data/samples/elaborate/calculator/model/command/operation/divide.rb +15 -0
  38. data/samples/elaborate/calculator/model/command/operation/multiply.rb +15 -0
  39. data/samples/elaborate/calculator/model/command/operation/subtract.rb +15 -0
  40. data/samples/elaborate/calculator/model/command/point.rb +20 -0
  41. data/samples/elaborate/calculator/model/presenter.rb +30 -0
  42. data/samples/elaborate/contact_manager.rb +2 -2
  43. data/samples/elaborate/meta_sample.rb +5 -5
  44. data/samples/elaborate/tetris.rb +6 -6
  45. data/samples/elaborate/tetris/view/bevel.rb +11 -11
  46. data/samples/elaborate/tetris/view/block.rb +1 -1
  47. data/samples/elaborate/tetris/view/high_score_dialog.rb +4 -4
  48. data/samples/elaborate/tetris/view/score_lane.rb +3 -3
  49. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +9 -9
  50. data/samples/elaborate/timer.rb +233 -0
  51. data/samples/elaborate/timer/alarm1.wav +0 -0
  52. data/samples/elaborate/timer/sounds/alarm1.wav +0 -0
  53. data/samples/elaborate/user_profile.rb +4 -2
  54. data/samples/elaborate/weather.rb +1 -1
  55. data/samples/hello/hello_canvas_animation_data_binding.rb +1 -1
  56. data/samples/hello/hello_checkbox_group.rb +1 -1
  57. data/samples/hello/hello_code_text.rb +3 -3
  58. data/samples/hello/hello_cool_bar.rb +5 -5
  59. data/samples/hello/hello_cursor.rb +1 -1
  60. data/samples/hello/hello_custom_shell.rb +1 -1
  61. data/samples/hello/hello_directory_dialog.rb +1 -1
  62. data/samples/hello/hello_radio_group.rb +2 -2
  63. data/samples/hello/hello_table.rb +4 -4
  64. data/samples/hello/hello_text.rb +120 -0
  65. data/samples/hello/hello_tool_bar.rb +5 -5
  66. data/samples/hello/hello_tree.rb +11 -11
  67. metadata +19 -2
@@ -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
@@ -110,7 +110,7 @@ class ContactManager
110
110
  }
111
111
  }
112
112
 
113
- table(:multi) { |table_proxy|
113
+ table(:editable, :multi) { |table_proxy|
114
114
  layout_data {
115
115
  horizontal_alignment :fill
116
116
  vertical_alignment :fill
@@ -132,7 +132,7 @@ class ContactManager
132
132
  width 200
133
133
  }
134
134
 
135
- items bind(@contact_manager_presenter, :results), column_properties(:first_name, :last_name, :email)
135
+ items <=> [@contact_manager_presenter, :results, column_properties: [:first_name, :last_name, :email]]
136
136
 
137
137
  on_mouse_up { |event|
138
138
  table_proxy.edit_table_item(event.table_item, event.column_index)
@@ -226,7 +226,7 @@ class MetaSampleApplication
226
226
  row_layout(:vertical) {
227
227
  fill true
228
228
  }
229
- selection bind(sample_directory, :selected_sample_name)
229
+ selection <=> [sample_directory, :selected_sample_name]
230
230
  font height: 20
231
231
  }
232
232
  }
@@ -242,7 +242,7 @@ class MetaSampleApplication
242
242
  button {
243
243
  text 'Launch'
244
244
  font height: 25
245
- enabled bind(SampleDirectory, 'selected_sample.launchable')
245
+ enabled <= [SampleDirectory, 'selected_sample.launchable']
246
246
 
247
247
  on_widget_selected {
248
248
  begin
@@ -255,7 +255,7 @@ class MetaSampleApplication
255
255
  button {
256
256
  text 'Reset'
257
257
  font height: 25
258
- enabled bind(SampleDirectory, 'selected_sample.editable')
258
+ enabled <= [SampleDirectory, 'selected_sample.editable']
259
259
 
260
260
  on_widget_selected {
261
261
  SampleDirectory.selected_sample.reset_code!
@@ -277,8 +277,8 @@ class MetaSampleApplication
277
277
  line_numbers {
278
278
  background :white
279
279
  }
280
- text bind(SampleDirectory, 'selected_sample.code', read_only: true)
281
- editable bind(SampleDirectory, 'selected_sample.editable')
280
+ text <=> [SampleDirectory, 'selected_sample.code']
281
+ editable <= [SampleDirectory, 'selected_sample.editable']
282
282
  left_margin 7
283
283
  right_margin 7
284
284
  }
@@ -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,25 +91,25 @@ 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
  }
102
102
  }
103
103
 
104
104
  after_body {
105
- @game_over_observer = observe(@game, :game_over) do |game_over|
105
+ observe(@game, :game_over) do |game_over|
106
106
  if game_over
107
107
  show_high_score_dialog
108
108
  else
109
109
  start_moving_tetrominos_down
110
110
  end
111
111
  end
112
- @show_high_scores_observer = observe(@game, :show_high_scores) do |show_high_scores|
112
+ observe(@game, :show_high_scores) do |show_high_scores|
113
113
  if show_high_scores
114
114
  show_high_score_dialog
115
115
  else
@@ -35,44 +35,44 @@ class Tetris
35
35
 
36
36
  body {
37
37
  rectangle(x, y, size, size) {
38
- background bind(self, :base_color)
38
+ background <= [self, :base_color]
39
39
  polygon(0, 0, size, 0, size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
40
- background bind(self, :base_color) { |color_value|
40
+ background <= [self, :base_color, on_read: ->(color_value) {
41
41
  unless color_value.nil?
42
42
  color = color(color_value)
43
43
  rgb(color.red + 4*BEVEL_CONSTANT, color.green + 4*BEVEL_CONSTANT, color.blue + 4*BEVEL_CONSTANT)
44
44
  end
45
- }
45
+ }]
46
46
  }
47
47
  polygon(size, 0, size - bevel_pixel_size, bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size, size, size) {
48
- background bind(self, :base_color) { |color_value|
48
+ background <= [self, :base_color, on_read: ->(color_value) {
49
49
  unless color_value.nil?
50
50
  color = color(color_value)
51
51
  rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
52
52
  end
53
- }
53
+ }]
54
54
  }
55
55
  polygon(size, size, 0, size, bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size) {
56
- background bind(self, :base_color) { |color_value|
56
+ background <= [self, :base_color, on_read: ->(color_value) {
57
57
  unless color_value.nil?
58
58
  color = color(color_value)
59
59
  rgb(color.red - 2*BEVEL_CONSTANT, color.green - 2*BEVEL_CONSTANT, color.blue - 2*BEVEL_CONSTANT)
60
60
  end
61
- }
61
+ }]
62
62
  }
63
63
  polygon(0, 0, 0, size, bevel_pixel_size, size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
64
- background bind(self, :base_color) { |color_value|
64
+ background <= [self, :base_color, on_read: ->(color_value) {
65
65
  unless color_value.nil?
66
66
  color = color(color_value)
67
67
  rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
68
68
  end
69
- }
69
+ }]
70
70
  }
71
71
  rectangle(0, 0, size, size) {
72
- foreground bind(self, :base_color) { |color_value|
72
+ foreground <= [self, :base_color, on_read: ->(color_value) {
73
73
  # use gray instead of white for the border
74
74
  color_value == Model::Block::COLOR_CLEAR ? :gray : color_value
75
- }
75
+ }]
76
76
  }
77
77
  }
78
78
  }
@@ -31,7 +31,7 @@ class Tetris
31
31
  body {
32
32
  canvas { |canvas_proxy|
33
33
  bevel(size: block_size) {
34
- base_color bind(game_playfield[row][column], :color)
34
+ base_color <= [game_playfield[row][column], :color]
35
35
  }
36
36
  }
37
37
  }
@@ -40,12 +40,12 @@ class Tetris
40
40
  type :vertical
41
41
  center true
42
42
  }
43
- text 'Tetris'
43
+ text 'Glimmer Tetris'
44
44
 
45
45
  tetris_menu_bar(game: game)
46
46
 
47
47
  label(:center) {
48
- text bind(game, :game_over) {|game_over| game_over ? 'Game Over!' : 'High Scores'}
48
+ text <= [game, :game_over, on_read: ->(game_over) { game_over ? 'Game Over!' : 'High Scores' }]
49
49
  font name: FONT_NAME, height: FONT_TITLE_HEIGHT, style: FONT_TITLE_STYLE
50
50
  }
51
51
  @high_score_table = table {
@@ -66,13 +66,13 @@ class Tetris
66
66
  text 'Level'
67
67
  }
68
68
 
69
- items bind(game, :high_scores, read_only_sort: true), column_properties(:name, :score, :lines, :level)
69
+ items <=> [game, :high_scores, read_only_sort: true, column_properties: [:name, :score, :lines, :level]]
70
70
  }
71
71
  composite {
72
72
  row_layout :horizontal
73
73
 
74
74
  @play_close_button = button {
75
- text bind(game, :game_over) {|game_over| game_over ? 'Play Again?' : 'Close'}
75
+ text <= [game, :game_over, on_read: ->(game_over) { game_over ? 'Play Again?' : 'Close'}]
76
76
  focus true # initial focus
77
77
 
78
78
  on_widget_selected {
@@ -55,7 +55,7 @@ class Tetris
55
55
  font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
56
56
  }
57
57
  label(:center) {
58
- text bind(game, :score)
58
+ text <= [game, :score]
59
59
  font height: @font_height
60
60
  }
61
61
 
@@ -66,7 +66,7 @@ class Tetris
66
66
  font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
67
67
  }
68
68
  label(:center) {
69
- text bind(game, :lines)
69
+ text <= [game, :lines]
70
70
  font height: @font_height
71
71
  }
72
72
 
@@ -77,7 +77,7 @@ class Tetris
77
77
  font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
78
78
  }
79
79
  label(:center) {
80
- text bind(game, :level)
80
+ text <= [game, :level]
81
81
  font height: @font_height
82
82
  }
83
83
  }
@@ -35,7 +35,7 @@ class Tetris
35
35
 
36
36
  menu_item {
37
37
  text '&Start'
38
- enabled bind(game, :game_over)
38
+ enabled <= [game, :game_over]
39
39
  accelerator COMMAND_KEY, :s
40
40
 
41
41
  on_widget_selected {
@@ -45,9 +45,9 @@ class Tetris
45
45
  menu_item(:check) {
46
46
  text '&Pause'
47
47
  accelerator COMMAND_KEY, :p
48
- enabled bind(game, :game_over, on_read: :!) {|value| value && !game.show_high_scores}
49
- enabled bind(game, :show_high_scores, on_read: :!) {|value| value && !game.game_over}
50
- selection bind(game, :paused)
48
+ enabled <= [game, :game_over, on_read: ->(value) { value && !game.show_high_scores }]
49
+ enabled <= [game, :show_high_scores, on_read: ->(value) { value && !game.game_over }]
50
+ selection <=> [game, :paused]
51
51
  }
52
52
  menu_item {
53
53
  text '&Restart'
@@ -76,7 +76,7 @@ class Tetris
76
76
  menu_item(:check) {
77
77
  text '&Show'
78
78
  accelerator COMMAND_KEY, :shift, :h
79
- selection bind(game, :show_high_scores)
79
+ selection <=> [game, :show_high_scores]
80
80
  }
81
81
  menu_item {
82
82
  text '&Clear'
@@ -94,24 +94,24 @@ class Tetris
94
94
  menu_item(:check) {
95
95
  text '&Beeping'
96
96
  accelerator COMMAND_KEY, :b
97
- selection bind(game, :beeping)
97
+ selection <=> [game, :beeping]
98
98
  }
99
99
  menu {
100
100
  text 'Up Arrow'
101
101
  menu_item(:radio) {
102
102
  text '&Instant Down'
103
103
  accelerator COMMAND_KEY, :shift, :i
104
- selection bind(game, :instant_down_on_up, computed_by: :up_arrow_action)
104
+ selection <=> [game, :instant_down_on_up, computed_by: :up_arrow_action]
105
105
  }
106
106
  menu_item(:radio) {
107
107
  text 'Rotate &Right'
108
108
  accelerator COMMAND_KEY, :shift, :r
109
- selection bind(game, :rotate_right_on_up, computed_by: :up_arrow_action)
109
+ selection <=> [game, :rotate_right_on_up, computed_by: :up_arrow_action]
110
110
  }
111
111
  menu_item(:radio) {
112
112
  text 'Rotate &Left'
113
113
  accelerator COMMAND_KEY, :shift, :l
114
- selection bind(game, :rotate_left_on_up, computed_by: :up_arrow_action)
114
+ selection <=> [game, :rotate_left_on_up, computed_by: :up_arrow_action]
115
115
  }
116
116
  }
117
117
  } # end of menu
@@ -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 <= [self, :countdown, on_read: :!]
88
+
89
+ on_widget_selected {
90
+ start_countdown
91
+ }
92
+ }
93
+ menu_item {
94
+ text 'St&op'
95
+ enabled <= [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 <=> [self, :min]
152
+ enabled <= [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 <=> [self, :sec]
166
+ enabled <= [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 <= [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 <= [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