Remove useless loop in hide()
[awesome.git] / lib / awful / menu.lua.in
blob24f98bb57aaa24a7b70ea4cfe5b173936ea14129
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 wibox = wibox
13 local image = image
14 local widget = widget
15 local button = require("awful.button")
16 local capi =
18 screen = screen,
19 mouse = mouse,
20 client = client,
21 keygrabber = keygrabber
23 local util = require("awful.util")
24 local tags = require("awful.tag")
25 local awbeautiful = require("beautiful")
26 local tonumber = tonumber
28 --- Menu module for awful
29 module("awful.menu")
31 local cur_menu
32 local menu_keys = {}
34 local function load_theme(custom)
35 local theme = {}
36 local beautiful
38 beautiful = awbeautiful.get()
40 theme.fg_focus = custom.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus
41 theme.bg_focus = custom.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus
42 theme.fg_normal = custom.fg_normal or beautiful.menu_fg_normal or beautiful.fg_normal
43 theme.bg_normal = custom.bg_normal or beautiful.menu_bg_normal or beautiful.bg_normal
45 theme.submenu_icon = custom.submenu_icon or beautiful.menu_submenu_icon
47 theme.menu_height = custom.height or beautiful.menu_height or 16
48 theme.menu_width = custom.width or beautiful.menu_width or 100
50 theme.border = custom.border_color or beautiful.menu_border_color or beautiful.border_normal
51 theme.border_width = custom.border_width or beautiful.menu_border_width or beautiful.border_width
53 return theme
54 end
56 local function item_leave(menu, num)
57 if num > 0 then
58 menu.items[num].wibox.fg = menu.theme.fg_normal
59 menu.items[num].wibox.bg = menu.theme.bg_normal
60 end
61 end
63 --- Hide a menu popup.
64 -- @param menu The menu to hide.
65 function hide(menu)
66 -- Remove items from screen
67 for i = 1, #menu.items do
68 item_leave(menu, i)
69 menu.items[i].wibox.screen = nil
70 end
71 if menu.active_child then
72 menu.active_child:hide()
73 menu.active_child = nil
74 end
75 menu.sel = nil
77 if cur_menu == menu then
78 cur_menu = cur_menu.parent
79 end
80 if not cur_menu and menu.keygrabber then
81 capi.keygrabber.stop()
82 end
83 end
85 -- Get the elder parent so for example when you kill
86 -- it, it will destroy the whole family.
87 local function get_parents(menu)
88 if menu.parent then
89 return get_parents(menu.parent)
90 end
91 return menu
92 end
94 local function exec(menu, num, mouse_event)
95 local cmd = menu.items[num].cmd
96 if type(cmd) == "table" then
97 if #cmd == 0 then
98 return
99 end
100 if not menu.child[num] then
101 menu.child[num] = new({ items = cmd }, menu, num)
104 if menu.active_child then
105 menu.active_child:hide()
106 menu.active_child = nil
108 menu.active_child = menu.child[num]
109 menu.active_child:show()
110 elseif type(cmd) == "string" then
111 get_parents(menu):hide()
112 util.spawn(cmd)
113 elseif type(cmd) == "function" then
114 get_parents(menu):hide()
115 cmd()
119 local function item_enter(menu, num, mouse_event)
120 if menu.sel == num then
121 return
122 elseif menu.sel then
123 item_leave(menu, menu.sel)
126 menu.items[num].wibox.fg = menu.theme.fg_focus
127 menu.items[num].wibox.bg = menu.theme.bg_focus
128 menu.sel = num
129 cur_menu = menu
131 if menu.auto_expand and mouse_event then
132 if menu.active_child then
133 menu.active_child:hide()
134 menu.active_child = nil
137 if type(menu.items[num].cmd) == "table" then
138 exec(menu, num)
143 local function grabber(mod, key, event)
144 if event == "release" then
145 return true
148 local sel = cur_menu.sel or 0
149 if key == menu_keys.up then
150 local sel_new = sel-1 < 1 and #cur_menu.items or sel-1
151 item_enter(cur_menu, sel_new)
152 elseif key == menu_keys.down then
153 local sel_new = sel+1 > #cur_menu.items and 1 or sel+1
154 item_enter(cur_menu, sel_new)
155 elseif sel > 0 and key == menu_keys.exec then
156 exec(cur_menu, sel)
157 elseif key == menu_keys.back then
158 cur_menu:hide()
159 elseif key == menu_keys.close then
160 get_parents(cur_menu):hide()
163 return true
166 local function add_item(data, num, item_info)
167 local item = wibox({
168 position = "floating",
169 fg = data.theme.fg_normal,
170 bg = data.theme.bg_normal,
171 border_color = data.theme.border,
172 border_width = data.theme.border_width
175 -- Create bindings
176 local bindings = util.table.join(
177 button({}, 1, function () item_enter(data, num); exec(data, num) end),
178 button({}, 3, function () hide(data) end)
181 -- Create the item label widget
182 local label = widget({ type = "textbox", align = "left" })
183 label.text = item_info[1]
184 label:margin({ left = data.h + 2 })
185 -- Set icon if needed
186 if item_info[3] then
187 local icon = type(item_info[3]) == "string" and image(item_info[3]) or item_info[3]
188 if icon.width > tonumber(data.h) or icon.height > tonumber(data.h) then
189 local width, height
190 if ((data.h/icon.height) * icon.width) > tonumber(data.h) then
191 width, height = data.h, (tonumber(data.h) / icon.width) * icon.height
192 else
193 width, height = (tonumber(data.h) / icon.height) * icon.width, data.h
195 icon = icon:crop_and_scale(0, 0, icon.width, icon.height, width, height)
197 label.bg_image = icon
200 item:buttons(bindings)
202 function label.mouse_enter() item_enter(data, num, true) end
203 function item.mouse_enter() item_enter(data, num, true) end
205 -- Create the submenu icon widget
206 local submenu
207 if type(item_info[2]) == "table" then
208 submenu = widget({ type = "imagebox", align = "right" })
209 submenu.image = data.theme.submenu_icon and image(data.theme.submenu_icon)
210 submenu:buttons(bindings)
212 function submenu.mouse_enter() item_enter(data, num, true) end
215 -- Add widgets to the wibox
216 item.widgets = { label, submenu }
218 item.ontop = true
220 return { wibox = item, cmd = item_info[2] }
223 --- Build a popup menu with running clients and shows it.
224 -- @param menu Menu table, see new() function for more informations
225 -- @return The menu.
226 function clients(menu)
227 local cls = capi.client.get()
228 local cls_t = {}
229 for k, c in pairs(cls) do
230 cls_t[#cls_t + 1] = { util.escape(c.name) or "",
231 function ()
232 if not c:isvisible() then
233 tags.viewmore(c:tags(), c.screen)
235 capi.client.focus = c
236 end,
237 c.icon }
240 if not menu then
241 menu = {}
244 menu.items = cls_t
246 local m = new(menu)
247 m:show()
248 return m
251 local function set_coords(menu, screen_idx)
252 local s_geometry = capi.screen[screen_idx].workarea
253 local screen_w = s_geometry.x + s_geometry.width
254 local screen_h = s_geometry.y + s_geometry.height
256 local i_h = menu.h - menu.theme.border_width
257 local m_h = (i_h * #menu.items) + menu.theme.border_width
259 if menu.parent then
260 menu.w = menu.parent.w
261 menu.h = menu.parent.h
263 local p_w = i_h * (menu.num - 1)
264 local m_w = menu.w - menu.theme.border_width
266 menu.y = menu.parent.y + p_w + m_h > screen_h and screen_h - m_h or menu.parent.y + p_w
267 menu.x = menu.parent.x + m_w*2 > screen_w and menu.parent.x - m_w or menu.parent.x + m_w
268 else
269 local m_coords = capi.mouse.coords()
270 local m_w = menu.w
272 menu.y = m_coords.y+1 < s_geometry.y and s_geometry.y or m_coords.y+1
273 menu.x = m_coords.x+1 < s_geometry.x and s_geometry.x or m_coords.x+1
275 menu.y = menu.y + m_h > screen_h and screen_h - m_h or menu.y
276 menu.x = menu.x + m_w > screen_w and screen_w - m_w or menu.x
280 --- Show a menu.
281 -- @param menu The menu to show.
282 -- @param keygrabber A boolean enabling or not the keyboard navigation.
283 function show(menu, keygrabber)
284 local screen_index = capi.mouse.screen
285 set_coords(menu, screen_index)
286 for num, item in pairs(menu.items) do
287 local wibox = item.wibox
288 wibox:geometry({
289 width = menu.w,
290 height = menu.h,
291 x = menu.x,
292 y = menu.y + (num - 1) * (menu.h - menu.theme.border_width)
294 wibox.screen = screen_index
297 if menu.parent then
298 menu.keygrabber = menu.parent.keygrabber
299 elseif keygrabber ~= nil then
300 menu.keygrabber = keygrabber
301 else
302 menu.keygrabber = false
305 if not cur_menu and menu.keygrabber then
306 capi.keygrabber.run(grabber)
308 cur_menu = menu
311 --- Toggle menu visibility.
312 -- @param menu The menu to show if it's hidden, or to hide if it's shown.
313 -- @param keygrabber A boolean enabling or not the keyboard navigation.
314 function toggle(menu, keygrabber)
315 if menu.items[1] and menu.items[1].wibox.screen then
316 hide(menu)
317 else
318 show(menu, keygrabber)
322 --- Set key bindings for menu navigation.
323 -- @param keys Table containing the following keys: up, down, exec, back, close. If a key is missing the default key binding will be used, defaults are respectively: "Up", "Down", "Return", "Left", "Escape".
324 function setkeys(keys)
325 menu_keys.up = keys and keys.up or "Up"
326 menu_keys.down = keys and keys.down or "Down"
327 menu_keys.exec = keys and keys.exec or "Return"
328 menu_keys.back = keys and keys.back or "Left"
329 menu_keys.close = keys and keys.close or "Escape"
332 --- Open a menu popup.
333 -- @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.
334 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
335 -- @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.
336 function new(menu, parent, num)
337 -- Create a table to store our menu informations
338 local data = {}
340 if not menu_keys.up then
341 setkeys(menu.keys)
344 data.items = {}
345 data.num = num or 1
346 data.theme = parent and parent.theme or load_theme(menu)
347 data.parent = parent
348 data.child = {}
349 if parent then
350 data.auto_expand = parent.auto_expand
351 elseif menu.auto_expand ~= nil then
352 data.auto_expand = menu.auto_expand
353 else
354 data.auto_expand = true
356 data.h = parent and parent.h or data.theme.menu_height
357 data.w = parent and parent.w or data.theme.menu_width
359 -- Create items
360 for k, v in pairs(menu.items) do
361 table.insert(data.items, add_item(data, k, v))
364 -- Set methods
365 data.hide = hide
366 data.show = show
367 data.toggle = toggle
369 return data