glimmer-dsl-tk 0.0.34 → 0.0.38

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