glimmer-dsl-tk 0.0.34 → 0.0.38

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.
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2020-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/tk/widget_proxy'
23
+
24
+ module Glimmer
25
+ module Tk
26
+ class MenuItemProxy < WidgetProxy
27
+ ACCELERATOR_MODIFIER_EVENT_MAP = {
28
+ 'Command' => 'Command',
29
+ 'Cmd' => 'Command',
30
+ 'Meta' => 'Command',
31
+ 'Option' => 'Option',
32
+ 'Opt' => 'Option',
33
+ 'Alt' => 'Option',
34
+ 'Shift' => 'Shift',
35
+ 'Ctrl' => 'Control',
36
+ 'Control' => 'Control',
37
+ }
38
+
39
+ attr_reader :options
40
+
41
+ def initialize(underscored_widget_name, parent_proxy, args, &block)
42
+ @options = args.last.is_a?(Hash) ? args.last : {}
43
+ super
44
+ end
45
+
46
+ def accelerator=(value)
47
+ @accelerator = value
48
+ @parent_proxy.tk.entryconfigure @options[:label], accelerator: value
49
+ root_parent_proxy.bind(accelerator_event) do |event|
50
+ @command_block&.call(event)
51
+ end
52
+ end
53
+
54
+ def accelerator
55
+ @accelerator
56
+ end
57
+
58
+ def accelerator_event
59
+ accelerator_parts = @accelerator.split('+')
60
+ accelerator_parts.map do |accelerator_part|
61
+ ACCELERATOR_MODIFIER_EVENT_MAP[accelerator_part.capitalize] || accelerator_part.downcase
62
+ end.join('-')
63
+ end
64
+
65
+ def state=(value)
66
+ @state = value
67
+ @parent_proxy.tk.entryconfigure @options[:label], :state => value
68
+ end
69
+
70
+ def state
71
+ @state
72
+ end
73
+
74
+ def command_block=(proc)
75
+ @command_block = proc
76
+ @parent_proxy.tk.entryconfigure @options[:label], command: @command_block
77
+ end
78
+
79
+ def handle_listener(listener_name, &listener)
80
+ case listener_name.to_s.downcase
81
+ when 'command'
82
+ self.command_block = listener
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def command?
89
+ @args.first.nil? || @args.first == :command || @args.first.is_a?(Hash)
90
+ end
91
+
92
+ def radiobutton?
93
+ @args.first == :radiobutton
94
+ end
95
+
96
+ def checkbutton?
97
+ @args.first == :radiobutton
98
+ end
99
+
100
+ def separator?
101
+ @args.first == :separator
102
+ end
103
+
104
+ def variable(auto_create: true)
105
+ if @variable.nil? && auto_create
106
+ sibling_variable = sibling_radio_menu_items.map {|mi| mi.variable(auto_create: false)}.compact.first
107
+ @variable = sibling_variable.nil? ? ::TkVariable.new : sibling_variable
108
+ else
109
+ @variable
110
+ end
111
+ @variable
112
+ end
113
+
114
+ def selection=(value)
115
+ if value
116
+ variable.value = @options[:label]
117
+ # TODO handle image case where there is no label
118
+ elsif checkbutton?
119
+ variable.value = '__unchecked__'
120
+ end
121
+ end
122
+
123
+ def selection
124
+ variable.value == @options[:label]
125
+ end
126
+
127
+ private
128
+
129
+ def sibling_radio_menu_items
130
+ @parent_proxy.children.select {|child| child.is_a?(MenuItemProxy) && radiobutton? && child != self}
131
+ end
132
+
133
+ def build_widget
134
+ @args.prepend(:command) if @args.first.is_a?(Hash)
135
+ @args.append({}) if !@args.last.is_a?(Hash)
136
+ @args.last.merge!(variable: variable, value: @options[:label]) if radiobutton? || checkbutton?
137
+ case @parent_proxy
138
+ when MenuProxy
139
+ @parent_proxy.tk.add(*@args)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2020-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/tk/widget_proxy'
23
+
24
+ module Glimmer
25
+ module Tk
26
+ class MenuProxy < WidgetProxy
27
+ attr_reader :options
28
+
29
+ def initialize(underscored_widget_name, parent_proxy, args, &block)
30
+ @options = args.last.is_a?(Hash) ? args.last : {}
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ def build_widget
37
+ tk_widget_class = self.class.tk_widget_class_for(@keyword)
38
+ @tk = tk_widget_class.new(@parent_proxy.tk)
39
+ case @parent_proxy
40
+ when ToplevelProxy
41
+ @parent_proxy.tk['menu'] = @tk
42
+ when MenuProxy
43
+ @parent_proxy.tk.add(:cascade, {menu: @tk}.merge(@options))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -36,6 +36,7 @@ module Glimmer
36
36
 
37
37
  def post_add_content
38
38
  set_attribute('iconphoto', File.expand_path('../../../icons/glimmer.png', __dir__)) if @tk.iconphoto.nil?
39
+ super
39
40
  end
40
41
 
41
42
  def open
@@ -46,22 +47,6 @@ module Glimmer
46
47
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Tk::RootExpression.new, keyword, *args, &block)
47
48
  end
48
49
 
49
- def set_attribute(attribute, *args)
50
- case attribute.to_s
51
- when 'iconphoto'
52
- args[0..-1] = [image_argument(args)]
53
- super
54
- when 'resizable'
55
- if args.size == 1 && !args.first.is_a?(Array)
56
- self.resizable = [args.first]*2
57
- else
58
- super
59
- end
60
- else
61
- super
62
- end
63
- end
64
-
65
50
  def handle_listener(listener_name, &listener)
66
51
  case listener_name.to_s.upcase
67
52
  when 'WM_OPEN_WINDOW', 'OPEN_WINDOW'
@@ -39,7 +39,6 @@ module Glimmer
39
39
  @modified_count ||= 0
40
40
  @modified_count += 1
41
41
  listener.call(*args)
42
- apply_all_tag
43
42
  @insert_mark_moved_proc&.call
44
43
  @tk.modified = false
45
44
  end
@@ -72,28 +71,17 @@ module Glimmer
72
71
  end
73
72
  end
74
73
  else
75
- apply_all_tag
76
- # TODO make listener pass an event that has a modifiers attribute for easy representation of :shift, :meta, :control, etc... while a letter button is pressed
77
- @listeners ||= {}
78
- begin
79
- @listeners[listener_name] ||= []
80
- @tk.tag_bind(ALL_TAG, listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
81
- @listeners[listener_name] << listener
82
- rescue => e
83
- @listeners.delete(listener_name)
84
- Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
85
- Glimmer::Config.logger.debug {e.full_message}
86
- listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
87
- listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
88
- @listeners[listener_name] ||= []
89
- @tk.tag_bind(ALL_TAG, listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
90
- @listeners[listener_name] << listener
91
- end
74
+ super
92
75
  end
93
76
  end
94
77
 
95
78
  def edit_undo
96
- @tk.edit_undo if @modified_count.to_i > 2 # <Modified> fires twice the first time, which is equivalent to one change.
79
+ # <Modified> fires twice the first time, which is equivalent to one change.
80
+ if @modified_count.to_i > 2
81
+ # must count the extra 2 modified count that will occur upon undo too
82
+ @modified_count -= 4
83
+ @tk.edit_undo
84
+ end
97
85
  end
98
86
 
99
87
  def edit_redo
@@ -104,31 +92,31 @@ module Glimmer
104
92
  end
105
93
  end
106
94
 
107
- def add_selection_format(option, value, no_selection_default: :insert_word)
108
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| add_format(range_start, range_end, option, value) }
95
+ def add_selection_format(option, value, no_selection_default: :insert_word, focus: true)
96
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| add_format(range_start, range_end, option, value) }
109
97
  end
110
98
 
111
- def remove_selection_format(option, value, no_selection_default: :insert_word)
112
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| remove_format(range_start, range_end, option, value) }
99
+ def remove_selection_format(option, value, no_selection_default: :insert_word, focus: true)
100
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| remove_format(range_start, range_end, option, value) }
113
101
  end
114
102
 
115
- def toggle_selection_format(option, value, no_selection_default: :insert_word)
116
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
103
+ def toggle_selection_format(option, value, no_selection_default: :insert_word, focus: true)
104
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
117
105
  end
118
106
 
119
- def add_selection_font_format(option, value, no_selection_default: :insert_word)
120
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
107
+ def add_selection_font_format(option, value, no_selection_default: :insert_word, focus: true)
108
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
121
109
  end
122
110
 
123
- def remove_selection_font_format(option, value, no_selection_default: :insert_word)
124
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
111
+ def remove_selection_font_format(option, value, no_selection_default: :insert_word, focus: true)
112
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
125
113
  end
126
114
 
127
- def toggle_selection_font_format(option, value, no_selection_default: :insert_word)
128
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
115
+ def toggle_selection_font_format(option, value, no_selection_default: :insert_word, focus: true)
116
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
129
117
  end
130
118
 
131
- def process_selection_ranges(no_selection_default: :insert_word, &processor)
119
+ def process_selection_ranges(no_selection_default: :insert_word, focus: true, &processor)
132
120
  regions = @tk.tag_ranges('sel')
133
121
  if regions.empty?
134
122
  case no_selection_default
@@ -143,6 +131,11 @@ module Glimmer
143
131
  range_end = region.last
144
132
  processor.call(range_start, range_end)
145
133
  end
134
+ if focus == true
135
+ @tk.focus
136
+ elsif focus.is_a?(Integer)
137
+ ::Tk.after(focus) { @tk.focus }
138
+ end
146
139
  end
147
140
 
148
141
  def applied_format?(region_start, region_end, option, value)
@@ -444,16 +437,11 @@ module Glimmer
444
437
  self.wrap = 'none'
445
438
  self.padx = 5
446
439
  self.pady = 5
447
- on('Modified') { apply_all_tag }
448
440
  end
449
441
 
450
442
  def clone_font(font)
451
443
  ::TkFont.new(Hash[font.actual])
452
444
  end
453
-
454
- def apply_all_tag
455
- @tk.tag_add(ALL_TAG, '1.0', 'end')
456
- end
457
445
  end
458
446
  end
459
447
  end
@@ -32,11 +32,40 @@ module Glimmer
32
32
  DEFAULT_HEIGHT = 95
33
33
 
34
34
  attr_reader :tk
35
+ attr_accessor :escapable
36
+ alias escapable? escapable
37
+
38
+ def post_add_content
39
+ if escapable?
40
+ on('KeyPress') do |event|
41
+ if event.keysym == 'Escape'
42
+ grab_release
43
+ destroy
44
+ end
45
+ end
46
+ end
47
+ end
35
48
 
36
49
  def has_attribute?(attribute, *args)
37
50
  %w[width height x y].include?(attribute.to_s) || super
38
51
  end
39
52
 
53
+ def set_attribute(attribute, *args)
54
+ case attribute.to_s
55
+ when 'iconphoto'
56
+ args[0..-1] = [image_argument(args)]
57
+ super
58
+ when 'resizable'
59
+ if args.size == 1 && !args.first.is_a?(Array)
60
+ self.resizable = [args.first]*2
61
+ else
62
+ super
63
+ end
64
+ else
65
+ super
66
+ end
67
+ end
68
+
40
69
  def width
41
70
  geometry.split(REGEX_GEOMETRY)[0].to_i
42
71
  end
@@ -114,6 +143,17 @@ module Glimmer
114
143
  destroy
115
144
  end
116
145
  end
146
+
147
+ def mac_style=(mac_class, mac_attribute_list = nil)
148
+ if OS.mac?
149
+ @mac_style = [mac_class, mac_attribute_list]
150
+ ::Tk.tk_call("::tk::unsupported::MacWindowStyle", "style", @tk, *@mac_style.compact)
151
+ end
152
+ end
153
+
154
+ def mac_style
155
+ @mac_style
156
+ end
117
157
  end
118
158
  end
119
159
  end
@@ -77,8 +77,7 @@ module Glimmer
77
77
  @args = args
78
78
  @keyword = underscored_widget_name
79
79
  @block = block
80
- tk_widget_class = self.class.tk_widget_class_for(underscored_widget_name)
81
- @tk = tk_widget_class.new(@parent_proxy.tk, *args)
80
+ build_widget
82
81
  # a common widget initializer
83
82
  @parent_proxy.post_initialize_child(self)
84
83
  initialize_defaults
@@ -121,7 +120,7 @@ module Glimmer
121
120
  end
122
121
 
123
122
  def self.widget_exists?(underscored_widget_name)
124
- !!tk_widget_class_for(underscored_widget_name)
123
+ !!tk_widget_class_for(underscored_widget_name) || (Glimmer::Tk.constants.include?("#{underscored_widget_name.camelcase(:upper)}Proxy".to_sym) && Glimmer::Tk.const_get("#{underscored_widget_name.camelcase(:upper)}Proxy".to_sym).respond_to?(:new))
125
124
  end
126
125
 
127
126
  def tk_widget_has_attribute_setter?(attribute)
@@ -213,8 +212,8 @@ module Glimmer
213
212
  raise "#{self} cannot handle attribute #{attribute} with args #{args.inspect}"
214
213
  end
215
214
  rescue => e
216
- Glimmer::Config.logger.debug {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
217
- Glimmer::Config.logger.debug {e.full_message}
215
+ Glimmer::Config.logger.error {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
216
+ Glimmer::Config.logger.error {e.full_message}
218
217
  apply_style(attribute => args.first)
219
218
  end
220
219
  end
@@ -460,14 +459,20 @@ module Glimmer
460
459
  parent_proxy.handle_listener(listener_name, &listener) if parent_proxy
461
460
  # TODO return a listener registration object that has a deregister method
462
461
  else
462
+ @listeners ||= {}
463
463
  begin
464
- @tk.bind(listener_name, &listener)
464
+ @listeners[listener_name] ||= []
465
+ @tk.bind(listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
466
+ @listeners[listener_name] << listener
465
467
  rescue => e
468
+ @listeners.delete(listener_name)
466
469
  Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
467
470
  Glimmer::Config.logger.debug {e.full_message}
468
471
  listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
469
472
  listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
470
- @tk.bind(listener_name, &listener)
473
+ @listeners[listener_name] ||= []
474
+ @tk.bind(listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
475
+ @listeners[listener_name] << listener
471
476
  end
472
477
  end
473
478
  end
@@ -501,11 +506,16 @@ module Glimmer
501
506
 
502
507
  private
503
508
 
509
+ def build_widget
510
+ tk_widget_class = self.class.tk_widget_class_for(@keyword)
511
+ @tk = tk_widget_class.new(@parent_proxy.tk, *args)
512
+ end
513
+
504
514
  def initialize_defaults
505
515
  options = {}
506
516
  options[:sticky] = 'nsew'
507
517
  options[:column_weight] = 1 if @parent_proxy.children.count == 1
508
- grid(options) unless @tk.is_a?(::Tk::Toplevel)
518
+ grid(options) unless @tk.is_a?(::Tk::Toplevel) || @tk.is_a?(::Tk::Menu) || @tk.nil? # TODO refactor by adding a griddable? method that could be overriden by subclasses to consult for this call
509
519
  end
510
520
  end
511
521
  end
@@ -29,6 +29,9 @@ require 'puts_debuggerer' if ENV['pd'].to_s.downcase == 'true'
29
29
  require 'tk'
30
30
  require 'os'
31
31
  require 'facets/hash/symbolize_keys'
32
+ require 'facets/string/underscore'
33
+ require 'facets/string/camelcase'
34
+ require 'delegate'
32
35
 
33
36
  # Internal requires
34
37
  # require 'ext/glimmer/config'
@@ -40,3 +43,4 @@ Glimmer::Config.excluded_keyword_checkers << lambda do |method_symbol, *args|
40
43
  result = false
41
44
  result ||= method == 'load_iseq'
42
45
  end
46
+ ::TkOption.add '*tearOff', 0
@@ -56,7 +56,7 @@ class MetaSample
56
56
 
57
57
  def run_sample(sample)
58
58
  Thread.new do
59
- command = "ruby -r #{glimmer_dsl_tk_file} #{sample} 2>&1"
59
+ command = "#{RbConfig.ruby} -r #{glimmer_dsl_tk_file} #{sample} 2>&1"
60
60
  result = ''
61
61
  IO.popen(command) do |f|
62
62
  f.each_line do |line|
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2021 Andy Maleh
1
+ `# Copyright (c) 2020-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
@@ -0,0 +1,183 @@
1
+ # Copyright (c) 2020-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-dsl-tk'
23
+
24
+ include Glimmer
25
+
26
+ COLORS = [:white, :red, :yellow, :green, :blue, :magenta, :gray, :black]
27
+
28
+ Tk::Tile::Style.theme_use "classic" # this enables setting background on label just for demo purposes
29
+
30
+ root { |r|
31
+ title 'Hello, Menu Bar!'
32
+
33
+ @label = label {
34
+ grid row_weight: 1, column_weight: 1
35
+ text 'Check Out The Menu Bar Above!'
36
+ font size: 50
37
+ anchor 'center'
38
+ }
39
+
40
+ menu {
41
+ menu(label: 'File', underline: 0) {
42
+ menu_item(label: 'New', underline: 0) {
43
+ accelerator 'Command+N'
44
+
45
+ on('command') do
46
+ message_box(parent: r, title: 'New', message: 'New file created.')
47
+ end
48
+ }
49
+ menu_item(label: 'Open...', underline: 0) {
50
+ accelerator 'Command+O'
51
+
52
+ on('command') do
53
+ message_box(parent: r, title: 'Open', message: 'Opening File...')
54
+ end
55
+ }
56
+ menu(label: 'Open Recent', underline: 5) {
57
+ menu_item(label: 'File 1') {
58
+ on('command') do
59
+ message_box(parent: r, title: 'File 1', message: 'File 1 Contents')
60
+ end
61
+ }
62
+ menu_item(label: 'File 2') {
63
+ on('command') do
64
+ message_box(parent: r, title: 'File 2', message: 'File 2 Contents')
65
+ end
66
+ }
67
+ }
68
+ menu_item(:separator)
69
+ menu_item(label: 'Exit', underline: 1) {
70
+ on('command') do
71
+ exit(0)
72
+ end
73
+ }
74
+ }
75
+ menu(label: 'Edit', underline: 0) {
76
+ menu_item(label: 'Cut', underline: 2) {
77
+ accelerator 'Command+X'
78
+ }
79
+ menu_item(label: 'Copy', underline: 0) {
80
+ accelerator 'Command+C'
81
+ }
82
+ menu_item(label: 'Paste', underline: 0) {
83
+ accelerator 'Command+V'
84
+ }
85
+ }
86
+ menu(label: 'Options', underline: 0) {
87
+ menu_item(:checkbutton, label: 'Enabled', underline: 0) {
88
+ on('command') do
89
+ @select_one_menu.children.each { |menu_item| menu_item.state = menu_item.state == 'disabled' ? 'normal' : 'disabled' }
90
+ @select_multiple_menu.children.each { |menu_item| menu_item.state = menu_item.state == 'disabled' ? 'normal' : 'disabled' }
91
+ end
92
+ }
93
+ @select_one_menu = menu(label: 'Select One', underline: 0) {
94
+ menu_item(:radiobutton, label: 'Option 1') {
95
+ state 'disabled'
96
+ }
97
+ menu_item(:radiobutton, label: 'Option 2') {
98
+ state 'disabled'
99
+ }
100
+ menu_item(:radiobutton, label: 'Option 3') {
101
+ state 'disabled'
102
+ }
103
+ }
104
+ @select_multiple_menu = menu(label: 'Select Multiple', underline: 0) {
105
+ menu_item(:checkbutton, label: 'Option 4') {
106
+ state 'disabled'
107
+ }
108
+ menu_item(:checkbutton, label: 'Option 5') {
109
+ state 'disabled'
110
+ }
111
+ menu_item(:checkbutton, label: 'Option 6') {
112
+ state 'disabled'
113
+ }
114
+ }
115
+ }
116
+ menu(label: 'Format', underline: 0) {
117
+ menu(label: 'Background Color', underline: 0) {
118
+ COLORS.each { |color_style|
119
+ menu_item(:radiobutton, label: color_style.to_s.split('_').map(&:capitalize).join(' ')) {
120
+ on('command') do
121
+ @label.background = color_style
122
+ end
123
+ }
124
+ }
125
+ }
126
+ menu(label: 'Foreground Color', underline: 11) {
127
+ COLORS.each { |color_style|
128
+ menu_item(:radiobutton, label: color_style.to_s.split('_').map(&:capitalize).join(' ')) {
129
+ on('command') do
130
+ @label.foreground = color_style
131
+ end
132
+ }
133
+ }
134
+ }
135
+ }
136
+ menu(label: 'View', underline: 0) {
137
+ menu_item(:radiobutton, label: 'Small', underline: 0) {
138
+ accelerator 'Command+S'
139
+
140
+ on('command') do
141
+ @label.font = {size: 25}
142
+ end
143
+ }
144
+ menu_item(:radiobutton, label: 'Medium', underline: 0) {
145
+ accelerator 'Command+M'
146
+ selection true
147
+
148
+ on('command') do
149
+ @label.font = {size: 50}
150
+ end
151
+ }
152
+ menu_item(:radiobutton, label: 'Large', underline: 0) {
153
+ accelerator 'Command+L'
154
+
155
+ on('command') do
156
+ @label.font = {size: 75}
157
+ end
158
+ }
159
+ }
160
+ menu(label: 'Help', underline: 0) {
161
+ menu_item(label: 'Manual', underline: 0) {
162
+ accelerator 'Command+Shift+M'
163
+
164
+ on('command') do
165
+ message_box(parent: r, title: 'Manual', message: 'Manual Contents')
166
+ end
167
+ }
168
+ menu_item(label: 'Tutorial', underline: 0) {
169
+ accelerator 'Command+Shift+T'
170
+
171
+ on('command') do
172
+ message_box(parent: r, title: 'Tutorial', message: 'Tutorial Contents')
173
+ end
174
+ }
175
+ menu_item(:separator)
176
+ menu_item(label: 'Report an Issue...', underline: 0) {
177
+ on('command') do
178
+ message_box(parent: r, title: 'Report an Issue', message: 'Reporting an issue...')
179
+ end
180
+ }
181
+ }
182
+ }
183
+ }.open