awful.menu: use actual wibox border width
[awesome.git] / lib / awful / menu.lua.in
bloba13bd5ee9847b8a40699a7ba2b68eed945eccc5a
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 type = type
12 local setmetatable = setmetatable
13 local wibox = wibox
14 local image = image
15 local widget = widget
16 local button = require("awful.button")
17 local capi =
19 screen = screen,
20 mouse = mouse,
21 client = client,
22 keygrabber = keygrabber
24 local util = require("awful.util")
25 local tags = require("awful.tag")
26 local layout = require("awful.widget.layout")
27 local awbeautiful = require("beautiful")
28 local tonumber = tonumber
30 --- Creation of menus.
31 module("awful.menu")
33 local cur_menu
35 --- Key bindings for menu navigation.
36 -- Keys are: up, down, exec, back, close. Value are table with a list of valid
37 -- keys for the action, i.e. menu_keys.up = { "j", "k" } will bind 'j' and 'k'
38 -- key to up action. This is common to all created menu.
39 -- @class table
40 -- @name menu_keys
41 menu_keys = { up = { "Up" },
42 down = { "Down" },
43 exec = { "Return", "Right" },
44 back = { "Left" },
45 close = { "Escape" } }
47 local function load_theme(custom)
48 local theme = {}
49 local beautiful
51 beautiful = awbeautiful.get()
53 theme.fg_focus = custom.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus
54 theme.bg_focus = custom.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus
55 theme.fg_normal = custom.fg_normal or beautiful.menu_fg_normal or beautiful.fg_normal
56 theme.bg_normal = custom.bg_normal or beautiful.menu_bg_normal or beautiful.bg_normal
58 theme.submenu_icon = custom.submenu_icon or beautiful.menu_submenu_icon
60 theme.menu_height = custom.height or beautiful.menu_height or 16
61 theme.menu_width = custom.width or beautiful.menu_width or 100
63 theme.border = custom.border_color or beautiful.menu_border_color or beautiful.border_normal
64 theme.border_width = custom.border_width or beautiful.menu_border_width or beautiful.border_width
66 return theme
67 end
69 local function item_leave(menu, num)
70 if num > 0 then
71 menu.items[num].wibox.fg = menu.theme.fg_normal
72 menu.items[num].wibox.bg = menu.theme.bg_normal
73 end
74 end
76 --- Hide a menu popup.
77 -- @param menu The menu to hide.
78 function hide(menu)
79 -- Remove items from screen
80 for i = 1, #menu.items do
81 item_leave(menu, i)
82 menu.items[i].wibox.screen = nil
83 end
84 if menu.active_child then
85 menu.active_child:hide()
86 menu.active_child = nil
87 end
88 menu.sel = nil
90 if cur_menu == menu then
91 cur_menu = cur_menu.parent
92 end
93 if not cur_menu and menu.keygrabber then
94 capi.keygrabber.stop()
95 end
96 end
98 -- Get the elder parent so for example when you kill
99 -- it, it will destroy the whole family.
100 local function get_parents(menu)
101 if menu.parent then
102 return get_parents(menu.parent)
104 return menu
107 local function exec(menu, num, mouse_event)
108 local cmd = menu.items[num].cmd
109 if type(cmd) == "table" then
110 if #cmd == 0 then
111 return
113 if not menu.child[num] then
114 menu.child[num] = new({ items = cmd }, menu, num)
117 if menu.active_child then
118 menu.active_child:hide()
119 menu.active_child = nil
121 menu.active_child = menu.child[num]
122 menu.active_child:show()
123 elseif type(cmd) == "string" then
124 get_parents(menu):hide()
125 util.spawn(cmd)
126 elseif type(cmd) == "function" then
127 get_parents(menu):hide()
128 cmd()
132 local function item_enter(menu, num, mouse_event)
133 if menu.sel == num then
134 return
135 elseif menu.sel then
136 item_leave(menu, menu.sel)
139 menu.items[num].wibox.fg = menu.theme.fg_focus
140 menu.items[num].wibox.bg = menu.theme.bg_focus
141 menu.sel = num
142 cur_menu = menu
144 if menu.auto_expand and mouse_event then
145 if menu.active_child then
146 menu.active_child:hide()
147 menu.active_child = nil
150 if type(menu.items[num].cmd) == "table" then
151 exec(menu, num)
156 local function grabber(mod, key, event)
157 if event == "release" then
158 return true
161 local sel = cur_menu.sel or 0
162 if util.table.hasitem(menu_keys.up, key) then
163 local sel_new = sel-1 < 1 and #cur_menu.items or sel-1
164 item_enter(cur_menu, sel_new)
165 elseif util.table.hasitem(menu_keys.down, key) then
166 local sel_new = sel+1 > #cur_menu.items and 1 or sel+1
167 item_enter(cur_menu, sel_new)
168 elseif sel > 0 and util.table.hasitem(menu_keys.exec, key) then
169 exec(cur_menu, sel)
170 elseif util.table.hasitem(menu_keys.back, key) then
171 cur_menu:hide()
172 elseif util.table.hasitem(menu_keys.close, key) then
173 get_parents(cur_menu):hide()
176 return true
179 local function add_item(data, num, item_info)
180 local item = wibox({
181 fg = data.theme.fg_normal,
182 bg = data.theme.bg_normal,
183 border_color = data.theme.border,
184 border_width = data.theme.border_width
187 -- Create bindings
188 local bindings = util.table.join(
189 button({}, 1, function () item_enter(data, num); exec(data, num) end),
190 button({}, 3, function () hide(data) end)
193 -- Create the item label widget
194 local label = widget({ type = "textbox" })
195 label.text = item_info[1]
196 -- Set icon if needed
197 local iconbox
198 if item_info[3] then
199 local icon = type(item_info[3]) == "string" and image(item_info[3]) or item_info[3]
200 if icon.width > data.h or icon.height > data.h then
201 local width, height
202 if ((data.h/icon.height) * icon.width) > data.h then
203 width, height = data.h, (data.h / icon.width) * icon.height
204 else
205 width, height = (data.h / icon.height) * icon.width, data.h
207 icon = icon:crop_and_scale(0, 0, icon.width, icon.height, width, height)
209 iconbox = widget { type = "imagebox" }
210 iconbox.image = icon
211 layout.margins[label] = { left = 2 }
212 else
213 layout.margins[label] = { left = data.h + 2 }
216 item:buttons(bindings)
218 local mouse_enter_func = function () item_enter(data, num, true) end
219 item:add_signal("mouse::enter", mouse_enter_func)
221 -- Create the submenu icon widget
222 local submenu
223 if type(item_info[2]) == "table" then
224 submenu = widget({ type = "imagebox" })
225 submenu.image = data.theme.submenu_icon and image(data.theme.submenu_icon)
226 submenu:buttons(bindings)
229 -- Add widgets to the wibox
230 if iconbox then
231 item.widgets = {
232 iconbox,
233 label,
234 { submenu, layout = layout.horizontal.rightleft },
235 layout = layout.horizontal.leftright
237 else
238 item.widgets = {
239 label,
240 { submenu, layout = layout.horizontal.rightleft },
241 layout = layout.horizontal.leftright
245 item.height = label:extents().height + 2
246 item.ontop = true
248 return { wibox = item, cmd = item_info[2] }
251 --- Build a popup menu with running clients and shows it.
252 -- @param menu Menu table, see new() function for more informations
253 -- @return The menu.
254 function clients(menu)
255 local cls = capi.client.get()
256 local cls_t = {}
257 for k, c in pairs(cls) do
258 cls_t[#cls_t + 1] = { util.escape(c.name) or "",
259 function ()
260 if not c:isvisible() then
261 tags.viewmore(c:tags(), c.screen)
263 capi.client.focus = c
264 end,
265 c.icon }
268 if not menu then
269 menu = {}
272 menu.items = cls_t
274 local m = new(menu)
275 m:show()
276 return m
279 local function set_coords(menu, screen_idx)
280 local s_geometry = capi.screen[screen_idx].workarea
281 local screen_w = s_geometry.x + s_geometry.width
282 local screen_h = s_geometry.y + s_geometry.height
284 local i_h = menu.h + menu.theme.border_width
285 local m_h = (i_h * #menu.items) + menu.theme.border_width
287 if menu.parent then
288 menu.w = menu.parent.w
289 menu.h = menu.parent.h
291 local p_w = i_h * (menu.num - 1)
292 local m_w = menu.w - menu.theme.border_width
294 menu.y = menu.parent.y + p_w + m_h > screen_h and screen_h - m_h or menu.parent.y + p_w
295 menu.x = menu.parent.x + m_w*2 > screen_w and menu.parent.x - m_w or menu.parent.x + m_w
296 else
297 local m_coords = capi.mouse.coords()
298 local m_w = menu.w
300 menu.y = m_coords.y+1 < s_geometry.y and s_geometry.y or m_coords.y+1
301 menu.x = m_coords.x+1 < s_geometry.x and s_geometry.x or m_coords.x+1
303 menu.y = menu.y + m_h > screen_h and screen_h - m_h or menu.y
304 menu.x = menu.x + m_w > screen_w and screen_w - m_w or menu.x
308 --- Show a menu.
309 -- @param menu The menu to show.
310 -- @param keygrabber A boolean enabling or not the keyboard navigation.
311 function show(menu, keygrabber)
312 local screen_index = capi.mouse.screen
313 set_coords(menu, screen_index)
314 for num, item in pairs(menu.items) do
315 local wibox = item.wibox
316 wibox.width = menu.w
317 wibox.height = menu.h
318 wibox.x = menu.x
319 wibox.y = menu.y + (num - 1) * (menu.h + wibox.border_width)
320 wibox.screen = screen_index
323 if menu.parent then
324 menu.keygrabber = menu.parent.keygrabber
325 elseif keygrabber ~= nil then
326 menu.keygrabber = keygrabber
327 else
328 menu.keygrabber = false
331 if not cur_menu and menu.keygrabber then
332 capi.keygrabber.run(grabber)
334 cur_menu = menu
337 --- Toggle menu visibility.
338 -- @param menu The menu to show if it's hidden, or to hide if it's shown.
339 -- @param keygrabber A boolean enabling or not the keyboard navigation.
340 function toggle(menu, keygrabber)
341 if menu.items[1] and menu.items[1].wibox.screen then
342 hide(menu)
343 else
344 show(menu, keygrabber)
348 --- Open a menu popup.
349 -- @param menu Table containing the menu informations. Key items: Table containing the displayed items, each element is a tab containing: item name, tiggered 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.
350 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
351 -- @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.
352 function new(menu, parent, num)
353 -- Create a table to store our menu informations
354 local data = {}
356 data.items = {}
357 data.num = num or 1
358 data.theme = parent and parent.theme or load_theme(menu)
359 data.parent = parent
360 data.child = {}
361 if parent then
362 data.auto_expand = parent.auto_expand
363 elseif menu.auto_expand ~= nil then
364 data.auto_expand = menu.auto_expand
365 else
366 data.auto_expand = true
368 data.h = parent and parent.h or data.theme.menu_height
369 if type(data.h) ~= 'number' then data.h = tonumber(data.h) end
370 data.w = parent and parent.w or data.theme.menu_width
371 if type(data.w) ~= 'number' then data.w = tonumber(data.w) end
373 -- Create items
374 for k, v in pairs(menu.items) do
375 table.insert(data.items, add_item(data, k, v))
378 if #data.items > 0 and data.h < data.items[1].wibox.height then
379 data.h = data.items[1].wibox.height
382 -- Set methods
383 data.hide = hide
384 data.show = show
385 data.toggle = toggle
387 return data
390 setmetatable(_M, { __call = function(_, ...) return new(...) end })
392 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80