1 #
2 # tk/menuspec.rb
3 # Hidethoshi NAGAI (nagai@ai.kyutech.ac.jp)
4 #
5 # based on tkmenubar.rb :
6 # Copyright (C) 1998 maeda shugo. All rights reserved.
7 # This file can be distributed under the terms of the Ruby.
8 #
9 # The format of the menu_spec is:
10 # [ menu_info, menu_info, ... ]
11 #
12 # And the format of the menu_info is:
13 # [
14 # [text, underline, configs], # menu button/entry (*1)
15 # [label, command, underline, accelerator, configs], # command entry
16 # [label, TkVar_obj, underline, accelerator, configs], # checkbutton entry
17 # [label, [TkVar_obj, value],
18 # underline, accelerator, configs], # radiobutton entry
19 # [label, [[...menu_info...], [...menu_info...], ...],
20 # underline, accelerator, configs], # cascade entry (*2)
21 # '---', # separator
22 # ...
23 # ]
24 #
25 # underline, accelerator, and configs are optional pearameters.
26 # Hashes are OK instead of Arrays. Then the entry type ('command',
27 # 'checkbutton', 'radiobutton' or 'cascade') is given by 'type' key
28 # (e.g. :type=>'cascade'). When type is 'cascade', an array of menu_info
29 # is acceptable for 'menu' key (then, create sub-menu).
30 #
31 # NOTE: (*1)
32 # If you want to make special menus (*.help for UNIX, *.system for Win,
33 # and *.apple for Mac), append 'menu_name'=>name (name is 'help' for UNIX,
34 # 'system' for Win, and 'apple' for Mac) option to the configs hash of
35 # menu button/entry information.
36 #
37 # NOTE: (*2)
38 # If you want to configure a cascade menu, add :menu_config=>{...configs..}
39 # to the configs of the cascade entry.
40
41 module TkMenuSpec
42 def _create_menu(parent, menu_info, menu_name = nil,
43 tearoff = false, default_opts = nil)
44 if tearoff.kind_of?(Hash)
45 default_opts = tearoff
46 tearoff = false
47 end
48
49 if menu_name.kind_of?(Hash)
50 default_opts = menu_name
51 menu_name = nil
52 tearoff = false
53 end
54
55 if default_opts.kind_of?(Hash)
56 orig_opts = _symbolkey2str(default_opts)
57 else
58 orig_opts = {}
59 end
60
61 tearoff = orig_opts.delete('tearoff') if orig_opts.key?('tearoff')
62
63 if menu_name
64 #menu = Tk::Menu.new(parent, :widgetname=>menu_name, :tearoff=>tearoff)
65 # --> use current TkMenu class
66 menu = TkMenu.new(parent, :widgetname=>menu_name, :tearoff=>tearoff)
67 else
68 #menu = Tk::Menu.new(parent, :tearoff=>tearoff)
69 # --> use current TkMenu class
70 menu = TkMenu.new(parent, :tearoff=>tearoff)
71 end
72
73 for item_info in menu_info
74 if item_info.kind_of?(Hash)
75 options = orig_opts.dup
76 options.update(_symbolkey2str(item_info))
77 item_type = (options.delete('type') || 'command').to_s
78 menu_name = options.delete('menu_name')
79 menu_opts = orig_opts.dup
80 menu_opts.update(_symbolkey2str(options.delete('menu_config') || {}))
81 if item_type == 'cascade' && options['menu'].kind_of?(Array)
82 # create cascade menu
83 submenu = _create_menu(menu, options['menu'], menu_name,
84 tearoff, menu_opts)
85 options['menu'] = submenu
86 end
87 menu.add(item_type, options)
88
89 elsif item_info.kind_of?(Array)
90 options = orig_opts.dup
91
92 options['label'] = item_info[0] if item_info[0]
93
94 case item_info[1]
95 when TkVariable
96 # checkbutton
97 item_type = 'checkbutton'
98 options['variable'] = item_info[1]
99 options['onvalue'] = true
100 options['offvalue'] = false
101
102 when Array
103 # radiobutton or cascade
104 if item_info[1][0].kind_of?(TkVariable)
105 # radiobutton
106 item_type = 'radiobutton'
107 options['variable'] = item_info[1][0]
108 options['value'] = item_info[1][1] if item_info[1][1]
109
110 else
111 # cascade
112 item_type = 'cascade'
113 menu_opts = orig_opts.dup
114 if item_info[4] && item_info[4].kind_of?(Hash)
115 opts = _symbolkey2str(item_info[4])
116 menu_name = opts.delete('menu_name')
117 menu_config = opts.delete('menu_config') || {}
118 menu_opts.update(_symbolkey2str(menu_config))
119 end
120 submenu = _create_menu(menu, item_info[1], menu_name,
121 tearoff, menu_opts)
122 options['menu'] = submenu
123 end
124
125 else
126 # command
127 item_type = 'command'
128 options['command'] = item_info[1] if item_info[1]
129 end
130
131 options['underline'] = item_info[2] if item_info[2]
132 options['accelerator'] = item_info[3] if item_info[3]
133 if item_info[4] && item_info[4].kind_of?(Hash)
134 opts = _symbolkey2str(item_info[4])
135 if item_type == 'cascade'
136 opts.delete('menu_name')
137 opts.delete('menu_config')
138 end
139 options.update(opts)
140 end
141 menu.add(item_type, options)
142
143 elsif /^-+$/ =~ item_info
144 menu.add('separator')
145
146 else
147 menu.add('command', 'label' => item_info)
148 end
149 end
150
151 menu
152 end
153 private :_create_menu
154
155 def _use_menubar?(parent)
156 use_menubar = false
157 if parent.kind_of?(Tk::Root) || parent.kind_of?(Tk::Toplevel)
158 true
159 elsif parent.current_configinfo.has_key?('menu')
160 true
161 else
162 false
163 end
164 end
165 private :_use_menubar?
166
167 def _create_menu_for_menubar(parent)
168 #unless (mbar = parent.menu).kind_of?(TkMenu)
169 # --> use current TkMenu class
170 mbar = parent.menu
171 unless mbar.kind_of?(Tk::Menu) || mbar.kind_of?(TkMenu)
172 #mbar = Tk::Menu.new(parent, :tearoff=>false)
173 mbar = TkMenu.new(parent, :tearoff=>false)
174 parent.menu(mbar)
175 end
176 mbar
177 end
178 private :_create_menu_for_menubar
179
180 def _create_menubutton(parent, menu_info, tearoff=false, default_opts = nil)
181 btn_info = menu_info[0]
182
183 if tearoff.kind_of?(Hash)
184 default_opts = tearoff
185 tearoff = false
186 end
187
188 if default_opts.kind_of?(Hash)
189 keys = _symbolkey2str(default_opts)
190 else
191 keys = {}
192 end
193
194 tearoff = keys.delete('tearoff') if keys.key?('tearoff')
195
196 if _use_menubar?(parent)
197 # menubar by menu entries
198 mbar = _create_menu_for_menubar(parent)
199
200 menu_name = nil
201
202 if btn_info.kind_of?(Hash)
203 keys.update(_symbolkey2str(btn_info))
204 menu_name = keys.delete('menu_name')
205 keys['label'] = keys.delete('text') if keys.key?('text')
206 elsif btn_info.kind_of?(Array)
207 keys['label'] = btn_info[0] if btn_info[0]
208 keys['underline'] = btn_info[1] if btn_info[1]
209 if btn_info[2]&&btn_info[2].kind_of?(Hash)
210 keys.update(_symbolkey2str(btn_info[2]))
211 menu_name = keys.delete('menu_name')
212 end
213 else
214 keys = {:label=>btn_info}
215 end
216
217 menu = _create_menu(mbar, menu_info[1..-1], menu_name,
218 tearoff, default_opts)
219 menu.tearoff(tearoff)
220
221 keys['menu'] = menu
222 mbar.add('cascade', keys)
223
224 [mbar, menu]
225
226 else
227 # menubar by menubuttons
228 #mbtn = Tk::Menubutton.new(parent)
229 # --> use current TkMenubutton class
230 mbtn = TkMenubutton.new(parent)
231
232 menu_name = nil
233
234 if btn_info.kind_of?(Hash)
235 keys.update(_symbolkey2str(btn_info))
236 menu_name = keys.delete('menu_name')
237 keys['text'] = keys.delete('label') if keys.key?('label')
238 mbtn.configure(keys)
239 elsif btn_info.kind_of?(Array)
240 mbtn.configure('text', btn_info[0]) if btn_info[0]
241 mbtn.configure('underline', btn_info[1]) if btn_info[1]
242 # mbtn.configure('accelerator', btn_info[2]) if btn_info[2]
243 if btn_info[2]&&btn_info[2].kind_of?(Hash)
244 keys.update(_symbolkey2str(btn_info[2]))
245 menu_name = keys.delete('menu_name')
246 mbtn.configure(keys)
247 end
248 else
249 mbtn.configure('text', btn_info)
250 end
251
252 mbtn.pack('side' => 'left')
253
254 menu = _create_menu(mbtn, menu_info[1..-1], menu_name,
255 tearoff, default_opts)
256
257 mbtn.menu(menu)
258
259 [mbtn, menu]
260 end
261 end
262 private :_create_menubutton
263
264 def _get_cascade_menus(menu)
265 menus = []
266 (0..(menu.index('last'))).each{|idx|
267 if menu.menutype(idx) == 'cascade'
268 submenu = menu.entrycget(idx, 'menu')
269 menus << [submenu, _get_cascade_menus(submenu)]
270 end
271 }
272 menus
273 end
274 private :_get_cascade_menus
275 end