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
12 local setmetatable
= setmetatable
16 local button
= require("awful.button")
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.
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.
41 menu_keys
= { up
= { "Up" },
43 exec
= { "Return", "Right" },
45 close
= { "Escape" } }
47 local function load_theme(custom
)
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
69 local function item_leave(menu
, num
)
71 menu
.items
[num
].wibox
.fg
= menu
.theme
.fg_normal
72 menu
.items
[num
].wibox
.bg
= menu
.theme
.bg_normal
76 --- Hide a menu popup.
77 -- @param menu The menu to hide.
79 -- Remove items from screen
80 for i
= 1, #menu
.items
do
82 menu
.items
[i
].wibox
.screen
= nil
84 if menu
.active_child
then
85 menu
.active_child
:hide()
86 menu
.active_child
= nil
90 if cur_menu
== menu
then
91 cur_menu
= cur_menu
.parent
93 if not cur_menu
and menu
.keygrabber
then
94 capi
.keygrabber
.stop()
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
)
102 return get_parents(menu
.parent
)
107 local function exec(menu
, num
, mouse_event
)
108 local cmd
= menu
.items
[num
].cmd
109 if type(cmd
) == "table" then
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()
126 elseif type(cmd
) == "function" then
127 get_parents(menu
):hide()
132 local function item_enter(menu
, num
, mouse_event
)
133 if menu
.sel
== num
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
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
156 local function grabber(mod, key
, event
)
157 if event
== "release" then
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
170 elseif util
.table.hasitem(menu_keys
.back
, key
) then
172 elseif util
.table.hasitem(menu_keys
.close
, key
) then
173 get_parents(cur_menu
):hide()
179 local function add_item(data
, num
, item_info
)
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
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
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
202 if ((data
.h
/icon
.height
) * icon
.width
) > data
.h
then
203 width
, height
= data
.h
, (data
.h
/ icon
.width
) * icon
.height
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" }
211 layout
.margins
[label
] = { left
= 2 }
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
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
234 { submenu
, layout
= layout
.horizontal
.rightleft
},
235 layout
= layout
.horizontal
.leftright
240 { submenu
, layout
= layout
.horizontal
.rightleft
},
241 layout
= layout
.horizontal
.leftright
245 item
.height
= label
:extents().height
+ 2
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
254 function clients(menu
)
255 local cls
= capi
.client
.get()
257 for k
, c
in pairs(cls
) do
258 cls_t
[#cls_t
+ 1] = { util
.escape(c
.name
) or "",
260 if not c
:isvisible() then
261 tags
.viewmore(c
:tags(), c
.screen
)
263 capi
.client
.focus
= c
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
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
297 local m_coords
= capi
.mouse
.coords()
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
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
317 wibox
.height
= menu
.h
319 wibox
.y
= menu
.y
+ (num
- 1) * (menu
.h
- menu
.theme
.border_width
)
320 wibox
.screen
= screen_index
324 menu
.keygrabber
= menu
.parent
.keygrabber
325 elseif keygrabber
~= nil then
326 menu
.keygrabber
= keygrabber
328 menu
.keygrabber
= false
331 if not cur_menu
and menu
.keygrabber
then
332 capi
.keygrabber
.run(grabber
)
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
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
358 data
.theme
= parent
and parent
.theme
or load_theme(menu
)
362 data
.auto_expand
= parent
.auto_expand
363 elseif menu
.auto_expand
~= nil then
364 data
.auto_expand
= menu
.auto_expand
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
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
390 setmetatable(_M
, { __call
= function(_
, ...) return new(...) end })
392 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80