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
14 local tonumber = tonumber
20 keygrabber
= keygrabber
,
23 local wibox
= require("wibox")
24 local button
= require("awful.button")
25 local util
= require("awful.util")
26 local tags
= require("awful.tag")
27 local awbeautiful
= require("beautiful")
29 --- Creation of menus.
34 --- Key bindings for menu navigation.
35 -- Keys are: up, down, exec, back, close. Value are table with a list of valid
36 -- keys for the action, i.e. menu_keys.up = { "j", "k" } will bind 'j' and 'k'
37 -- key to up action. This is common to all created menu.
40 menu_keys
= { up
= { "Up" },
42 exec
= { "Return", "Right" },
44 close
= { "Escape" } }
46 local function load_theme(custom
)
50 beautiful
= awbeautiful
.get()
52 theme
.fg_focus
= custom
.fg_focus
or beautiful
.menu_fg_focus
or beautiful
.fg_focus
53 theme
.bg_focus
= custom
.bg_focus
or beautiful
.menu_bg_focus
or beautiful
.bg_focus
54 theme
.fg_normal
= custom
.fg_normal
or beautiful
.menu_fg_normal
or beautiful
.fg_normal
55 theme
.bg_normal
= custom
.bg_normal
or beautiful
.menu_bg_normal
or beautiful
.bg_normal
57 theme
.submenu_icon
= custom
.submenu_icon
or beautiful
.menu_submenu_icon
59 theme
.menu_height
= custom
.height
or beautiful
.menu_height
or 16
60 theme
.menu_width
= custom
.width
or beautiful
.menu_width
or 100
62 theme
.border
= custom
.border_color
or beautiful
.menu_border_color
or beautiful
.border_normal
63 theme
.border_width
= custom
.border_width
or beautiful
.menu_border_width
or beautiful
.border_width
68 local function item_leave(menu
, num
)
70 menu
.items
[num
].wibox
:set_fg(menu
.theme
.fg_normal
)
71 menu
.items
[num
].wibox
:set_bg(menu
.theme
.bg_normal
)
75 --- Hide a menu popup.
76 -- @param menu The menu to hide.
78 -- Remove items from screen
79 for i
= 1, #menu
.items
do
81 menu
.items
[i
].wibox
.screen
= nil
83 if menu
.active_child
then
84 menu
.active_child
:hide()
85 menu
.active_child
= nil
89 if cur_menu
== menu
then
90 cur_menu
= cur_menu
.parent
92 if not cur_menu
and menu
.keygrabber
then
93 capi
.keygrabber
.stop()
97 -- Get the elder parent so for example when you kill
98 -- it, it will destroy the whole family.
99 local function get_parents(menu
)
101 return get_parents(menu
.parent
)
106 local function exec(menu
, num
, mouse_event
)
107 local cmd
= menu
.items
[num
].cmd
108 if type(cmd
) == "table" then
112 if not menu
.child
[num
] then
113 menu
.child
[num
] = new({ items
= cmd
}, menu
, num
)
116 if menu
.active_child
then
117 menu
.active_child
:hide()
118 menu
.active_child
= nil
120 menu
.active_child
= menu
.child
[num
]
121 menu
.active_child
:show()
122 elseif type(cmd
) == "string" then
123 get_parents(menu
):hide()
125 elseif type(cmd
) == "function" then
126 get_parents(menu
):hide()
127 cmd(menu
.items
[num
].returned_value
)
131 local function item_enter(menu
, num
, mouse_event
)
132 if menu
.sel
== num
then
135 item_leave(menu
, menu
.sel
)
138 menu
.items
[num
].wibox
:set_fg(menu
.theme
.fg_focus
)
139 menu
.items
[num
].wibox
:set_bg(menu
.theme
.bg_focus
)
143 if menu
.auto_expand
and mouse_event
then
144 if menu
.active_child
then
145 menu
.active_child
:hide()
146 menu
.active_child
= nil
149 if type(menu
.items
[num
].cmd
) == "table" then
155 local function check_access_key(menu
, key
)
156 for i
, item
in pairs(menu
.items
) do
157 if item
.akey
== key
then
164 check_access_key(menu
.parent
, key
)
168 local function grabber(mod, key
, event
)
169 if event
== "release" then
173 local sel
= cur_menu
.sel
or 0
174 if util
.table.hasitem(menu_keys
.up
, key
) then
175 local sel_new
= sel
-1 < 1 and #cur_menu
.items
or sel
-1
176 item_enter(cur_menu
, sel_new
)
177 elseif util
.table.hasitem(menu_keys
.down
, key
) then
178 local sel_new
= sel
+1 > #cur_menu
.items
and 1 or sel
+1
179 item_enter(cur_menu
, sel_new
)
180 elseif sel
> 0 and util
.table.hasitem(menu_keys
.exec
, key
) then
182 elseif util
.table.hasitem(menu_keys
.back
, key
) then
184 elseif util
.table.hasitem(menu_keys
.close
, key
) then
185 get_parents(cur_menu
):hide()
187 check_access_key(cur_menu
, key
)
193 local function add_item(data
, num
, item_info
)
195 fg
= data
.theme
.fg_normal
,
196 bg
= data
.theme
.bg_normal
,
197 border_color
= data
.theme
.border
,
198 border_width
= data
.theme
.border_width
,
203 local bindings
= util
.table.join(
204 button({}, 1, function () item_enter(data
, num
); exec(data
, num
) end),
205 button({}, 3, function () data
:hide() end)
208 -- Create the item label widget
209 local label
= wibox
.widget
.textbox()
211 label
.text
= string.gsub(util
.escape(item_info
[1]), "&(%w)",
213 key
= string.lower(l
)
214 return "<u>"..l
.."</u>"
216 -- Set icon if needed
218 local margin
= wibox
.layout
.margin()
219 margin
:set_widget(label
)
221 local icon
= type(item_info
[3]) == "string" and capi
.oocairo
.image_surface_create_from_png(item_info
[3]) or item_info
[3]
222 local width
= icon
:get_width()
223 local height
= icon
:get_height()
224 if width
> data
.h
or height
> data
.h
then
226 if ((data
.h
/height
) * width
) > data
.h
then
227 w
, h
= data
.h
, (data
.h
/ width
) * height
229 w
, h
= (data
.h
/ height
) * width
, data
.h
231 -- We need to scale the image to size w x h
232 local img
= capi
.oocairo
.image_surface_create("argb32", w
, h
)
233 local cr
= capi
.oocairo
.context_create(img
)
234 cr
:scale(w
/ width
, h
/ height
)
235 cr
:set_source(icon
, 0, 0)
239 iconbox
= wibox
.widget
.imagebox()
240 iconbox
:set_image(icon
)
243 margin
:set_left(data
.h
+ 2)
246 item
:buttons(bindings
)
248 local mouse_enter_func
= function () item_enter(data
, num
, true) end
249 item
:connect_signal("mouse::enter", mouse_enter_func
)
251 -- Create the submenu icon widget
253 if type(item_info
[2]) == "table" then
254 submenu
= wibox
.widget
.imagebox()
255 if data
.theme
.submenu_icon
then
256 submenu
:set_image(capi
.oocairo
.image_surface_create_from_png(data
.theme
.submenu_icon
))
258 submenu
:buttons(bindings
)
261 -- Add widgets to the wibox
262 local left
= wibox
.layout
.fixed
.horizontal()
266 -- This contains the label
269 local layout
= wibox
.layout
.align
.horizontal()
270 layout
:set_left(left
)
272 layout
:set_right(submenu
)
275 item
:set_widget(layout
)
277 local w
, h
= label
:fit(0, 0)
281 return { wibox
= item
, akey
= key
, cmd
= item_info
[2], returned_value
=item_info
[1] }
284 --- Build a popup menu with running clients and shows it.
285 -- @param menu Menu table, see new() function for more informations
286 -- @param args.keygrabber A boolean enabling or not the keyboard navigation.
288 function clients(menu
, args
)
289 local cls
= capi
.client
.get()
291 for k
, c
in pairs(cls
) do
292 cls_t
[#cls_t
+ 1] = { util
.escape(c
.name
) or "",
294 if not c
:isvisible() then
295 tags
.viewmore(c
:tags(), c
.screen
)
297 capi
.client
.focus
= c
314 local function set_coords(menu
, screen_idx
, m_coords
)
315 local s_geometry
= capi
.screen
[screen_idx
].workarea
316 local screen_w
= s_geometry
.x
+ s_geometry
.width
317 local screen_h
= s_geometry
.y
+ s_geometry
.height
319 local i_h
= menu
.h
+ menu
.theme
.border_width
320 local m_h
= (i_h
* #menu
.items
) + menu
.theme
.border_width
323 menu
.w
= menu
.parent
.w
324 menu
.h
= menu
.parent
.h
326 local p_w
= i_h
* (menu
.num
- 1)
327 local m_w
= menu
.w
- menu
.theme
.border_width
329 menu
.y
= menu
.parent
.y
+ p_w
+ m_h
> screen_h
and screen_h
- m_h
or menu
.parent
.y
+ p_w
330 menu
.x
= menu
.parent
.x
+ m_w
*2 > screen_w
and menu
.parent
.x
- m_w
or menu
.parent
.x
+ m_w
333 if m_coords
== nil then
334 m_coords
= capi
.mouse
.coords()
335 m_coords
.x
= m_coords
.x
+ 1
336 m_coords
.y
= m_coords
.y
+ 1
339 menu
.y
= m_coords
.y
< s_geometry
.y
and s_geometry
.y
or m_coords
.y
340 menu
.x
= m_coords
.x
< s_geometry
.x
and s_geometry
.x
or m_coords
.x
342 menu
.y
= menu
.y
+ m_h
> screen_h
and screen_h
- m_h
or menu
.y
343 menu
.x
= menu
.x
+ m_w
> screen_w
and screen_w
- m_w
or menu
.x
348 -- @param menu The menu to show.
349 -- @param args.keygrabber A boolean enabling or not the keyboard navigation.
350 -- @param args.coords Menu position defaulting to mouse.coords()
351 function show(menu
, args
)
353 local screen_index
= capi
.mouse
.screen
354 local keygrabber
= args
.keygrabber
or false
355 local coords
= args
.coords
or nil
356 set_coords(menu
, screen_index
, coords
)
357 for num
, item
in pairs(menu
.items
) do
358 local wibox
= item
.wibox
360 wibox
.height
= menu
.h
362 wibox
.y
= menu
.y
+ (num
- 1) * (menu
.h
+ wibox
.border_width
)
363 wibox
.screen
= screen_index
367 menu
.keygrabber
= menu
.parent
.keygrabber
368 elseif keygrabber
~= nil then
369 menu
.keygrabber
= keygrabber
371 menu
.keygrabber
= false
374 if not cur_menu
and menu
.keygrabber
then
375 capi
.keygrabber
.run(grabber
)
380 --- Toggle menu visibility.
381 -- @param menu The menu to show if it's hidden, or to hide if it's shown.
382 -- @param args.keygrabber A boolean enabling or not the keyboard navigation.
383 -- @param args.coords Menu position {x,y}
384 function toggle(menu
, args
)
385 if menu
.items
[1] and menu
.items
[1].wibox
.screen
then
392 --- Open a menu popup.
393 -- @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.
394 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
395 -- @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.
396 function new(menu
, parent
, num
)
397 -- Create a table to store our menu informations
402 data
.theme
= parent
and parent
.theme
or load_theme(menu
)
406 data
.auto_expand
= parent
.auto_expand
407 elseif menu
.auto_expand
~= nil then
408 data
.auto_expand
= menu
.auto_expand
410 data
.auto_expand
= true
412 data
.h
= parent
and parent
.h
or data
.theme
.menu_height
413 if type(data
.h
) ~= 'number' then data
.h
= tonumber(data
.h
) end
414 data
.w
= parent
and parent
.w
or data
.theme
.menu_width
415 if type(data
.w
) ~= 'number' then data
.w
= tonumber(data
.w
) end
418 for k
, v
in pairs(menu
.items
) do
419 table.insert(data
.items
, add_item(data
, k
, v
))
422 if #data
.items
> 0 and data
.h
< data
.items
[1].wibox
.height
then
423 data
.h
= data
.items
[1].wibox
.height
434 setmetatable(_M
, { __call
= function(_
, ...) return new(...) end })
436 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80