awful.prompt: "Fix" for multi-byte characters
[awesome.git] / lib / awful / menu.lua.in
blob772f214c934785224632b0f83d26eb55022a9a9c
1 ---------------------------------------------------------------------------
2 -- @author Damien Leone <damien.leone@gmail.com>
3 -- @author Julien Danjou <julien@danjou.info>
4 -- @copyright 2008 Damien Leone, Julien Danjou
5 -- @release @AWESOME_VERSION@
6 ---------------------------------------------------------------------------
8 -- Grab environment we need
9 local pairs = pairs
10 local table = table
11 local string = string
12 local type = type
13 local setmetatable = setmetatable
14 local wibox = wibox
15 local image = image
16 local widget = widget
17 local button = require("awful.button")
18 local capi =
20 screen = screen,
21 mouse = mouse,
22 client = client,
23 keygrabber = keygrabber
25 local util = require("awful.util")
26 local tags = require("awful.tag")
27 local layout = require("awful.widget.layout")
28 local awbeautiful = require("beautiful")
29 local tonumber = tonumber
31 --- Creation of menus.
32 module("awful.menu")
34 local cur_menu
36 --- Key bindings for menu navigation.
37 -- Keys are: up, down, exec, back, close. Value are table with a list of valid
38 -- keys for the action, i.e. menu_keys.up = { "j", "k" } will bind 'j' and 'k'
39 -- key to up action. This is common to all created menu.
40 -- @class table
41 -- @name menu_keys
42 menu_keys = { up = { "Up" },
43 down = { "Down" },
44 exec = { "Return", "Right" },
45 back = { "Left" },
46 close = { "Escape" } }
48 local function load_theme(custom)
49 local theme = {}
50 local beautiful
52 beautiful = awbeautiful.get()
54 theme.fg_focus = custom.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus
55 theme.bg_focus = custom.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus
56 theme.fg_normal = custom.fg_normal or beautiful.menu_fg_normal or beautiful.fg_normal
57 theme.bg_normal = custom.bg_normal or beautiful.menu_bg_normal or beautiful.bg_normal
59 theme.submenu_icon = custom.submenu_icon or beautiful.menu_submenu_icon
61 theme.menu_height = custom.height or beautiful.menu_height or 16
62 theme.menu_width = custom.width or beautiful.menu_width or 100
64 theme.border = custom.border_color or beautiful.menu_border_color or beautiful.border_normal
65 theme.border_width = custom.border_width or beautiful.menu_border_width or beautiful.border_width
67 return theme
68 end
70 local function item_leave(menu, num)
71 if num > 0 then
72 menu.items[num].wibox.fg = menu.theme.fg_normal
73 menu.items[num].wibox.bg = menu.theme.bg_normal
74 end
75 end
77 --- Hide a menu popup.
78 -- @param menu The menu to hide.
79 function hide(menu)
80 -- Remove items from screen
81 for i = 1, #menu.items do
82 item_leave(menu, i)
83 menu.items[i].wibox.screen = nil
84 end
85 if menu.active_child then
86 menu.active_child:hide()
87 menu.active_child = nil
88 end
89 menu.sel = nil
91 if cur_menu == menu then
92 cur_menu = cur_menu.parent
93 end
94 if not cur_menu and menu.keygrabber then
95 capi.keygrabber.stop()
96 end
97 end
99 -- Get the elder parent so for example when you kill
100 -- it, it will destroy the whole family.
101 local function get_parents(menu)
102 if menu.parent then
103 return get_parents(menu.parent)
105 return menu
108 local function exec(menu, num, mouse_event)
109 local cmd = menu.items[num].cmd
110 if type(cmd) == "table" then
111 if #cmd == 0 then
112 return
114 if not menu.child[num] then
115 menu.child[num] = new({ items = cmd }, menu, num)
118 if menu.active_child then
119 menu.active_child:hide()
120 menu.active_child = nil
122 menu.active_child = menu.child[num]
123 menu.active_child:show()
124 elseif type(cmd) == "string" then
125 get_parents(menu):hide()
126 util.spawn(cmd)
127 elseif type(cmd) == "function" then
128 get_parents(menu):hide()
129 cmd(menu.items[num].returned_value)
133 local function item_enter(menu, num, mouse_event)
134 if menu.sel == num then
135 return
136 elseif menu.sel then
137 item_leave(menu, menu.sel)
140 menu.items[num].wibox.fg = menu.theme.fg_focus
141 menu.items[num].wibox.bg = menu.theme.bg_focus
142 menu.sel = num
143 cur_menu = menu
145 if menu.auto_expand and mouse_event then
146 if menu.active_child then
147 menu.active_child:hide()
148 menu.active_child = nil
151 if type(menu.items[num].cmd) == "table" then
152 exec(menu, num)
157 local function check_access_key(menu, key)
158 for i, item in pairs(menu.items) do
159 if item.akey == key then
160 item_enter(menu, i)
161 exec(menu, i)
162 return
165 if menu.parent then
166 check_access_key(menu.parent, key)
170 local function grabber(mod, key, event)
171 if event == "release" then
172 return true
175 local sel = cur_menu.sel or 0
176 if util.table.hasitem(menu_keys.up, key) then
177 local sel_new = sel-1 < 1 and #cur_menu.items or sel-1
178 item_enter(cur_menu, sel_new)
179 elseif util.table.hasitem(menu_keys.down, key) then
180 local sel_new = sel+1 > #cur_menu.items and 1 or sel+1
181 item_enter(cur_menu, sel_new)
182 elseif sel > 0 and util.table.hasitem(menu_keys.exec, key) then
183 exec(cur_menu, sel)
184 elseif util.table.hasitem(menu_keys.back, key) then
185 cur_menu:hide()
186 elseif util.table.hasitem(menu_keys.close, key) then
187 get_parents(cur_menu):hide()
188 else
189 check_access_key(cur_menu, key)
192 return true
195 local function add_item(data, num, item_info)
196 local item = wibox({
197 fg = data.theme.fg_normal,
198 bg = data.theme.bg_normal,
199 border_color = data.theme.border,
200 border_width = data.theme.border_width
203 -- Create bindings
204 local bindings = util.table.join(
205 button({}, 1, function () item_enter(data, num); exec(data, num) end),
206 button({}, 3, function () data:hide() end)
209 -- Create the item label widget
210 local label = widget({ type = "textbox" })
211 local key = ''
212 label.text = string.gsub(util.escape(item_info[1]), "&amp;(%w)",
213 function (l)
214 key = string.lower(l)
215 return "<u>"..l.."</u>"
216 end, 1)
217 -- Set icon if needed
218 local iconbox
219 if item_info[3] then
220 local icon = type(item_info[3]) == "string" and image(item_info[3]) or item_info[3]
221 if icon.width > data.h or icon.height > data.h then
222 local width, height
223 if ((data.h/icon.height) * icon.width) > data.h then
224 width, height = data.h, (data.h / icon.width) * icon.height
225 else
226 width, height = (data.h / icon.height) * icon.width, data.h
228 icon = icon:crop_and_scale(0, 0, icon.width, icon.height, width, height)
230 iconbox = widget { type = "imagebox" }
231 iconbox.image = icon
232 layout.margins[label] = { left = 2 }
233 else
234 layout.margins[label] = { left = data.h + 2 }
237 item:buttons(bindings)
239 local mouse_enter_func = function () item_enter(data, num, true) end
240 item:add_signal("mouse::enter", mouse_enter_func)
242 -- Create the submenu icon widget
243 local submenu
244 if type(item_info[2]) == "table" then
245 submenu = widget({ type = "imagebox" })
246 submenu.image = data.theme.submenu_icon and image(data.theme.submenu_icon)
247 submenu:buttons(bindings)
250 -- Add widgets to the wibox
251 if iconbox then
252 item.widgets = {
253 iconbox,
254 label,
255 { submenu, layout = layout.horizontal.rightleft },
256 layout = layout.horizontal.leftright
258 else
259 item.widgets = {
260 label,
261 { submenu, layout = layout.horizontal.rightleft },
262 layout = layout.horizontal.leftright
266 item.height = label:extents().height + 2
267 item.ontop = true
269 return { wibox = item, akey= key, cmd = item_info[2], returned_value=item_info[1] }
272 --- Build a popup menu with running clients and shows it.
273 -- @param menu Menu table, see new() function for more informations
274 -- @param args.keygrabber A boolean enabling or not the keyboard navigation.
275 -- @return The menu.
276 function clients(menu, args)
277 local cls = capi.client.get()
278 local cls_t = {}
279 for k, c in pairs(cls) do
280 cls_t[#cls_t + 1] = { util.escape(c.name) or "",
281 function ()
282 if not c:isvisible() then
283 tags.viewmore(c:tags(), c.screen)
285 capi.client.focus = c
286 c:raise()
287 end,
288 c.icon }
291 if not menu then
292 menu = {}
295 menu.items = cls_t
297 local m = new(menu)
298 m:show(args)
299 return m
302 local function set_coords(menu, screen_idx, m_coords)
303 local s_geometry = capi.screen[screen_idx].workarea
304 local screen_w = s_geometry.x + s_geometry.width
305 local screen_h = s_geometry.y + s_geometry.height
307 local i_h = menu.h + menu.theme.border_width
308 local m_h = (i_h * #menu.items) + menu.theme.border_width
310 if menu.parent then
311 menu.w = menu.parent.w
312 menu.h = menu.parent.h
314 local p_w = i_h * (menu.num - 1)
315 local m_w = menu.w - menu.theme.border_width
317 menu.y = menu.parent.y + p_w + m_h > screen_h and screen_h - m_h or menu.parent.y + p_w
318 menu.x = menu.parent.x + m_w*2 > screen_w and menu.parent.x - m_w or menu.parent.x + m_w
319 else
320 local m_w = menu.w
321 if m_coords == nil then
322 m_coords = capi.mouse.coords()
323 m_coords.x = m_coords.x + 1
324 m_coords.y = m_coords.y + 1
327 menu.y = m_coords.y < s_geometry.y and s_geometry.y or m_coords.y
328 menu.x = m_coords.x < s_geometry.x and s_geometry.x or m_coords.x
330 menu.y = menu.y + m_h > screen_h and screen_h - m_h or menu.y
331 menu.x = menu.x + m_w > screen_w and screen_w - m_w or menu.x
335 --- Show a menu.
336 -- @param menu The menu to show.
337 -- @param args.keygrabber A boolean enabling or not the keyboard navigation.
338 -- @param args.coords Menu position defaulting to mouse.coords()
339 function show(menu, args)
340 args = args or {}
341 local screen_index = capi.mouse.screen
342 local keygrabber = args.keygrabber or false
343 local coords = args.coords or nil
344 set_coords(menu, screen_index, coords)
345 for num, item in pairs(menu.items) do
346 local wibox = item.wibox
347 wibox.width = menu.w
348 wibox.height = menu.h
349 wibox.x = menu.x
350 wibox.y = menu.y + (num - 1) * (menu.h + wibox.border_width)
351 wibox.screen = screen_index
354 if menu.parent then
355 menu.keygrabber = menu.parent.keygrabber
356 elseif keygrabber ~= nil then
357 menu.keygrabber = keygrabber
358 else
359 menu.keygrabber = false
362 if not cur_menu and menu.keygrabber then
363 capi.keygrabber.run(grabber)
365 cur_menu = menu
368 --- Toggle menu visibility.
369 -- @param menu The menu to show if it's hidden, or to hide if it's shown.
370 -- @param args.keygrabber A boolean enabling or not the keyboard navigation.
371 -- @param args.coords Menu position {x,y}
372 function toggle(menu, args)
373 if menu.items[1] and menu.items[1].wibox.screen then
374 menu:hide()
375 else
376 menu:show(args)
380 --- Open a menu popup.
381 -- @param menu Table containing the menu informations. Key items: Table containing the displayed items, each element is a tab containing: item name, triggered action, submenu table or function, item icon (optional). Keys [fg|bg]_[focus|normal], border, border_width, submenu_icon, height and width override the default display for your menu, each of them are optional. Key auto_expand controls the submenu auto expand behaviour by setting it to true (default) or false.
382 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
383 -- @param num Specify the parent's clicked item number if we want to open a submenu, this value should never be set by the user.
384 function new(menu, parent, num)
385 -- Create a table to store our menu informations
386 local data = {}
388 data.items = {}
389 data.num = num or 1
390 data.theme = parent and parent.theme or load_theme(menu)
391 data.parent = parent
392 data.child = {}
393 if parent then
394 data.auto_expand = parent.auto_expand
395 elseif menu.auto_expand ~= nil then
396 data.auto_expand = menu.auto_expand
397 else
398 data.auto_expand = true
400 data.h = parent and parent.h or data.theme.menu_height
401 if type(data.h) ~= 'number' then data.h = tonumber(data.h) end
402 data.w = parent and parent.w or data.theme.menu_width
403 if type(data.w) ~= 'number' then data.w = tonumber(data.w) end
405 -- Create items
406 for k, v in pairs(menu.items) do
407 table.insert(data.items, add_item(data, k, v))
410 if #data.items > 0 and data.h < data.items[1].wibox.height then
411 data.h = data.items[1].wibox.height
414 -- Set methods
415 data.hide = hide
416 data.show = show
417 data.toggle = toggle
419 return data
422 setmetatable(_M, { __call = function(_, ...) return new(...) end })
424 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80