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
165 check_access_key(menu
.parent
, key
)
169 local function grabber(mod, key
, event
)
170 if event
== "release" then
174 local sel
= cur_menu
.sel
or 0
175 if util
.table.hasitem(menu_keys
.up
, key
) then
176 local sel_new
= sel
-1 < 1 and #cur_menu
.items
or sel
-1
177 item_enter(cur_menu
, sel_new
)
178 elseif util
.table.hasitem(menu_keys
.down
, key
) then
179 local sel_new
= sel
+1 > #cur_menu
.items
and 1 or sel
+1
180 item_enter(cur_menu
, sel_new
)
181 elseif sel
> 0 and util
.table.hasitem(menu_keys
.exec
, key
) then
183 elseif util
.table.hasitem(menu_keys
.back
, key
) then
185 elseif util
.table.hasitem(menu_keys
.close
, key
) then
186 get_parents(cur_menu
):hide()
188 check_access_key(cur_menu
, key
)
194 local function add_item(data
, num
, item_info
)
196 fg
= data
.theme
.fg_normal
,
197 bg
= data
.theme
.bg_normal
,
198 border_color
= data
.theme
.border
,
199 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
= widget({ type = "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
219 local icon
= type(item_info
[3]) == "string" and image(item_info
[3]) or item_info
[3]
220 if icon
.width
> data
.h
or icon
.height
> data
.h
then
222 if ((data
.h
/icon
.height
) * icon
.width
) > data
.h
then
223 width
, height
= data
.h
, (data
.h
/ icon
.width
) * icon
.height
225 width
, height
= (data
.h
/ icon
.height
) * icon
.width
, data
.h
227 icon
= icon
:crop_and_scale(0, 0, icon
.width
, icon
.height
, width
, height
)
229 iconbox
= widget
{ type = "imagebox" }
231 layout
.margins
[label
] = { left
= 2 }
233 layout
.margins
[label
] = { left
= data
.h
+ 2 }
236 item
:buttons(bindings
)
238 local mouse_enter_func
= function () item_enter(data
, num
, true) end
239 item
:add_signal("mouse::enter", mouse_enter_func
)
241 -- Create the submenu icon widget
243 if type(item_info
[2]) == "table" then
244 submenu
= widget({ type = "imagebox" })
245 submenu
.image
= data
.theme
.submenu_icon
and image(data
.theme
.submenu_icon
)
246 submenu
:buttons(bindings
)
249 -- Add widgets to the wibox
254 { submenu
, layout
= layout
.horizontal
.rightleft
},
255 layout
= layout
.horizontal
.leftright
260 { submenu
, layout
= layout
.horizontal
.rightleft
},
261 layout
= layout
.horizontal
.leftright
265 item
.height
= label
:extents().height
+ 2
268 return { wibox
= item
, akey
= key
, cmd
= item_info
[2], returned_value
=item_info
[1] }
271 --- Build a popup menu with running clients and shows it.
272 -- @param menu Menu table, see new() function for more informations
273 -- @param keygrabber A boolean enabling or not the keyboard navigation.
275 function clients(menu
, keygrabber
)
276 local cls
= capi
.client
.get()
278 for k
, c
in pairs(cls
) do
279 cls_t
[#cls_t
+ 1] = { util
.escape(c
.name
) or "",
281 if not c
:isvisible() then
282 tags
.viewmore(c
:tags(), c
.screen
)
284 capi
.client
.focus
= c
300 local function set_coords(menu
, screen_idx
)
301 local s_geometry
= capi
.screen
[screen_idx
].workarea
302 local screen_w
= s_geometry
.x
+ s_geometry
.width
303 local screen_h
= s_geometry
.y
+ s_geometry
.height
305 local i_h
= menu
.h
+ menu
.theme
.border_width
306 local m_h
= (i_h
* #menu
.items
) + menu
.theme
.border_width
309 menu
.w
= menu
.parent
.w
310 menu
.h
= menu
.parent
.h
312 local p_w
= i_h
* (menu
.num
- 1)
313 local m_w
= menu
.w
- menu
.theme
.border_width
315 menu
.y
= menu
.parent
.y
+ p_w
+ m_h
> screen_h
and screen_h
- m_h
or menu
.parent
.y
+ p_w
316 menu
.x
= menu
.parent
.x
+ m_w
*2 > screen_w
and menu
.parent
.x
- m_w
or menu
.parent
.x
+ m_w
318 local m_coords
= capi
.mouse
.coords()
321 menu
.y
= m_coords
.y
+1 < s_geometry
.y
and s_geometry
.y
or m_coords
.y
+1
322 menu
.x
= m_coords
.x
+1 < s_geometry
.x
and s_geometry
.x
or m_coords
.x
+1
324 menu
.y
= menu
.y
+ m_h
> screen_h
and screen_h
- m_h
or menu
.y
325 menu
.x
= menu
.x
+ m_w
> screen_w
and screen_w
- m_w
or menu
.x
330 -- @param menu The menu to show.
331 -- @param keygrabber A boolean enabling or not the keyboard navigation.
332 function show(menu
, keygrabber
)
333 local screen_index
= capi
.mouse
.screen
334 set_coords(menu
, screen_index
)
335 for num
, item
in pairs(menu
.items
) do
336 local wibox
= item
.wibox
338 wibox
.height
= menu
.h
340 wibox
.y
= menu
.y
+ (num
- 1) * (menu
.h
+ wibox
.border_width
)
341 wibox
.screen
= screen_index
345 menu
.keygrabber
= menu
.parent
.keygrabber
346 elseif keygrabber
~= nil then
347 menu
.keygrabber
= keygrabber
349 menu
.keygrabber
= false
352 if not cur_menu
and menu
.keygrabber
then
353 capi
.keygrabber
.run(grabber
)
358 --- Toggle menu visibility.
359 -- @param menu The menu to show if it's hidden, or to hide if it's shown.
360 -- @param keygrabber A boolean enabling or not the keyboard navigation.
361 function toggle(menu
, keygrabber
)
362 if menu
.items
[1] and menu
.items
[1].wibox
.screen
then
365 menu
:show(keygrabber
)
369 --- Open a menu popup.
370 -- @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.
371 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
372 -- @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.
373 function new(menu
, parent
, num
)
374 -- Create a table to store our menu informations
379 data
.theme
= parent
and parent
.theme
or load_theme(menu
)
383 data
.auto_expand
= parent
.auto_expand
384 elseif menu
.auto_expand
~= nil then
385 data
.auto_expand
= menu
.auto_expand
387 data
.auto_expand
= true
389 data
.h
= parent
and parent
.h
or data
.theme
.menu_height
390 if type(data
.h
) ~= 'number' then data
.h
= tonumber(data
.h
) end
391 data
.w
= parent
and parent
.w
or data
.theme
.menu_width
392 if type(data
.w
) ~= 'number' then data
.w
= tonumber(data
.w
) end
395 for k
, v
in pairs(menu
.items
) do
396 table.insert(data
.items
, add_item(data
, k
, v
))
399 if #data
.items
> 0 and data
.h
< data
.items
[1].wibox
.height
then
400 data
.h
= data
.items
[1].wibox
.height
411 setmetatable(_M
, { __call
= function(_
, ...) return new(...) end })
413 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80