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
13 local setmetatable
= setmetatable
17 local button
= require("awful.button")
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.
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.
42 menu_keys
= { up
= { "Up" },
44 exec
= { "Return", "Right" },
46 close
= { "Escape" } }
48 local function load_theme(custom
)
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
70 local function item_leave(menu
, num
)
72 menu
.items
[num
].wibox
.fg
= menu
.theme
.fg_normal
73 menu
.items
[num
].wibox
.bg
= menu
.theme
.bg_normal
77 --- Hide a menu popup.
78 -- @param menu The menu to hide.
80 -- Remove items from screen
81 for i
= 1, #menu
.items
do
83 menu
.items
[i
].wibox
.screen
= nil
85 if menu
.active_child
then
86 menu
.active_child
:hide()
87 menu
.active_child
= nil
91 if cur_menu
== menu
then
92 cur_menu
= cur_menu
.parent
94 if not cur_menu
and menu
.keygrabber
then
95 capi
.keygrabber
.stop()
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
)
103 return get_parents(menu
.parent
)
108 local function exec(menu
, num
, mouse_event
)
109 local cmd
= menu
.items
[num
].cmd
110 if type(cmd
) == "table" then
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()
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
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
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
157 local function check_access_key(menu
, key
)
158 for i
, item
in pairs(menu
.items
) do
159 if item
.akey
== key
then
166 check_access_key(menu
.parent
, key
)
170 local function grabber(mod, key
, event
)
171 if event
== "release" then
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
184 elseif util
.table.hasitem(menu_keys
.back
, key
) then
186 elseif util
.table.hasitem(menu_keys
.close
, key
) then
187 get_parents(cur_menu
):hide()
189 check_access_key(cur_menu
, key
)
195 local function add_item(data
, num
, item_info
)
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
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" })
212 label
.text
= string.gsub(util
.escape(item_info
[1]), "&(%w)",
214 key
= string.lower(l
)
215 return "<u>"..l
.."</u>"
217 -- Set icon if needed
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
223 if ((data
.h
/icon
.height
) * icon
.width
) > data
.h
then
224 width
, height
= data
.h
, (data
.h
/ icon
.width
) * icon
.height
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" }
232 layout
.margins
[label
] = { left
= 2 }
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
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
255 { submenu
, layout
= layout
.horizontal
.rightleft
},
256 layout
= layout
.horizontal
.leftright
261 { submenu
, layout
= layout
.horizontal
.rightleft
},
262 layout
= layout
.horizontal
.leftright
266 item
.height
= label
:extents().height
+ 2
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.
276 function clients(menu
, args
)
277 local cls
= capi
.client
.get()
279 for k
, c
in pairs(cls
) do
280 cls_t
[#cls_t
+ 1] = { util
.escape(c
.name
) or "",
282 if not c
:isvisible() then
283 tags
.viewmore(c
:tags(), c
.screen
)
285 capi
.client
.focus
= c
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
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
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
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
)
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
348 wibox
.height
= menu
.h
350 wibox
.y
= menu
.y
+ (num
- 1) * (menu
.h
+ wibox
.border_width
)
351 wibox
.screen
= screen_index
355 menu
.keygrabber
= menu
.parent
.keygrabber
356 elseif keygrabber
~= nil then
357 menu
.keygrabber
= keygrabber
359 menu
.keygrabber
= false
362 if not cur_menu
and menu
.keygrabber
then
363 capi
.keygrabber
.run(grabber
)
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
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
390 data
.theme
= parent
and parent
.theme
or load_theme(menu
)
394 data
.auto_expand
= parent
.auto_expand
395 elseif menu
.auto_expand
~= nil then
396 data
.auto_expand
= menu
.auto_expand
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
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
422 setmetatable(_M
, { __call
= function(_
, ...) return new(...) end })
424 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80