2008-10-22 14:22:48 +02:00
---------------------------------------------------------------------------
-- @author Damien Leone <damien.leone@gmail.com>
2008-11-07 15:27:35 +01:00
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Damien Leone, Julien Danjou
2008-10-22 14:22:48 +02:00
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
-- Grab environment we need
2008-10-23 11:19:46 +02:00
local pairs = pairs
2008-10-22 14:22:48 +02:00
local table = table
2009-11-26 12:47:39 +01:00
local string = string
2008-10-22 14:22:48 +02:00
local type = type
2009-07-15 15:57:31 +02:00
local setmetatable = setmetatable
2010-10-06 14:21:19 +02:00
local tonumber = tonumber
2009-03-04 19:42:25 +01:00
local capi =
{
screen = screen ,
mouse = mouse ,
2009-03-09 14:14:01 +01:00
client = client ,
2010-09-29 16:06:44 +02:00
keygrabber = keygrabber ,
oocairo = oocairo
2009-03-04 19:42:25 +01:00
}
2010-10-06 14:21:19 +02:00
local wibox = require ( " wibox " )
local button = require ( " awful.button " )
2008-10-22 14:22:48 +02:00
local util = require ( " awful.util " )
2008-10-23 11:19:46 +02:00
local tags = require ( " awful.tag " )
2008-11-13 11:53:41 +01:00
local awbeautiful = require ( " beautiful " )
2008-10-22 14:22:48 +02:00
2009-08-28 15:34:52 +02:00
--- Creation of menus.
2008-10-22 14:22:48 +02:00
module ( " awful.menu " )
2009-03-09 14:14:01 +01:00
local cur_menu
2009-08-21 16:34:41 +02:00
--- Key bindings for menu navigation.
-- Keys are: up, down, exec, back, close. Value are table with a list of valid
-- keys for the action, i.e. menu_keys.up = { "j", "k" } will bind 'j' and 'k'
-- key to up action. This is common to all created menu.
-- @class table
-- @name menu_keys
menu_keys = { up = { " Up " } ,
down = { " Down " } ,
exec = { " Return " , " Right " } ,
back = { " Left " } ,
close = { " Escape " } }
2009-03-09 14:14:01 +01:00
2008-10-24 22:31:14 +02:00
local function load_theme ( custom )
local theme = { }
2008-10-22 14:22:48 +02:00
local beautiful
2008-10-24 22:31:14 +02:00
2008-10-22 14:22:48 +02:00
beautiful = awbeautiful.get ( )
2008-10-24 22:31:14 +02:00
theme.fg_focus = custom.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus
theme.bg_focus = custom.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus
theme.fg_normal = custom.fg_normal or beautiful.menu_fg_normal or beautiful.fg_normal
theme.bg_normal = custom.bg_normal or beautiful.menu_bg_normal or beautiful.bg_normal
2008-11-10 11:57:20 +01:00
theme.submenu_icon = custom.submenu_icon or beautiful.menu_submenu_icon
2008-10-22 14:22:48 +02:00
2008-12-18 18:10:52 +01:00
theme.menu_height = custom.height or beautiful.menu_height or 16
2008-10-24 22:31:14 +02:00
theme.menu_width = custom.width or beautiful.menu_width or 100
2008-10-22 14:22:48 +02:00
2008-10-24 22:31:14 +02:00
theme.border = custom.border_color or beautiful.menu_border_color or beautiful.border_normal
theme.border_width = custom.border_width or beautiful.menu_border_width or beautiful.border_width
2008-10-22 14:22:48 +02:00
2008-10-24 22:31:14 +02:00
return theme
2008-10-22 14:22:48 +02:00
end
2009-03-04 19:42:25 +01:00
local function item_leave ( menu , num )
if num > 0 then
2010-10-06 14:21:19 +02:00
menu.items [ num ] . wibox : set_fg ( menu.theme . fg_normal )
menu.items [ num ] . wibox : set_bg ( menu.theme . bg_normal )
2009-03-04 19:42:25 +01:00
end
end
2008-11-07 15:27:35 +01:00
--- Hide a menu popup.
-- @param menu The menu to hide.
function hide ( menu )
2008-11-10 19:09:20 +01:00
-- Remove items from screen
for i = 1 , # menu.items do
2009-05-06 23:46:39 +02:00
item_leave ( menu , i )
2009-02-23 12:04:27 +01:00
menu.items [ i ] . wibox.screen = nil
2008-11-10 19:09:20 +01:00
end
if menu.active_child then
menu.active_child : hide ( )
2009-03-04 19:42:25 +01:00
menu.active_child = nil
2008-10-22 14:22:48 +02:00
end
2009-03-04 19:42:25 +01:00
menu.sel = nil
2009-03-09 14:14:01 +01:00
if cur_menu == menu then
cur_menu = cur_menu.parent
end
if not cur_menu and menu.keygrabber then
capi.keygrabber . stop ( )
end
2008-10-22 14:22:48 +02:00
end
2009-01-08 12:25:27 +01:00
-- Get the elder parent so for example when you kill
2008-11-07 15:27:35 +01:00
-- it, it will destroy the whole family.
2009-03-04 19:42:25 +01:00
local function get_parents ( menu )
if menu.parent then
return get_parents ( menu.parent )
2008-10-22 14:22:48 +02:00
end
2009-03-04 19:42:25 +01:00
return menu
2008-10-22 14:22:48 +02:00
end
2009-03-04 19:42:25 +01:00
local function exec ( menu , num , mouse_event )
2009-04-30 02:12:23 +02:00
local cmd = menu.items [ num ] . cmd
2009-02-23 12:04:27 +01:00
if type ( cmd ) == " table " then
2009-03-30 23:45:29 +02:00
if # cmd == 0 then
return
end
2009-03-04 19:42:25 +01:00
if not menu.child [ num ] then
menu.child [ num ] = new ( { items = cmd } , menu , num )
2008-11-10 19:09:20 +01:00
end
2009-03-04 19:42:25 +01:00
if menu.active_child then
menu.active_child : hide ( )
menu.active_child = nil
2008-10-30 12:46:49 +01:00
end
2009-03-04 19:42:25 +01:00
menu.active_child = menu.child [ num ]
menu.active_child : show ( )
2009-02-23 12:04:27 +01:00
elseif type ( cmd ) == " string " then
2009-03-04 19:42:25 +01:00
get_parents ( menu ) : hide ( )
2009-02-23 12:04:27 +01:00
util.spawn ( cmd )
elseif type ( cmd ) == " function " then
2009-03-04 19:42:25 +01:00
get_parents ( menu ) : hide ( )
2010-03-17 13:22:45 +01:00
cmd ( menu.items [ num ] . returned_value )
2008-10-22 14:22:48 +02:00
end
end
2009-03-04 19:42:25 +01:00
local function item_enter ( menu , num , mouse_event )
if menu.sel == num then
return
elseif menu.sel then
item_leave ( menu , menu.sel )
end
2009-02-22 16:01:39 +01:00
2010-10-06 14:21:19 +02:00
menu.items [ num ] . wibox : set_fg ( menu.theme . fg_focus )
menu.items [ num ] . wibox : set_bg ( menu.theme . bg_focus )
2009-03-04 19:42:25 +01:00
menu.sel = num
2009-03-09 14:14:01 +01:00
cur_menu = menu
2009-03-04 19:42:25 +01:00
if menu.auto_expand and mouse_event then
if menu.active_child then
menu.active_child : hide ( )
menu.active_child = nil
2009-02-22 16:01:39 +01:00
end
2009-03-04 19:42:25 +01:00
if type ( menu.items [ num ] . cmd ) == " table " then
exec ( menu , num )
2009-02-22 16:01:39 +01:00
end
end
2009-02-23 12:04:27 +01:00
end
2009-11-26 12:47:39 +01:00
local function check_access_key ( menu , key )
for i , item in pairs ( menu.items ) do
if item.akey == key then
2010-04-11 12:37:14 +02:00
item_enter ( menu , i )
exec ( menu , i )
return
2009-11-26 12:47:39 +01:00
end
end
if menu.parent then
check_access_key ( menu.parent , key )
end
end
2009-03-09 14:14:01 +01:00
local function grabber ( mod , key , event )
if event == " release " then
return true
end
local sel = cur_menu.sel or 0
2009-08-21 16:28:55 +02:00
if util.table . hasitem ( menu_keys.up , key ) then
2009-03-09 14:14:01 +01:00
local sel_new = sel - 1 < 1 and # cur_menu.items or sel - 1
item_enter ( cur_menu , sel_new )
2009-08-21 16:28:55 +02:00
elseif util.table . hasitem ( menu_keys.down , key ) then
2009-03-09 14:14:01 +01:00
local sel_new = sel + 1 > # cur_menu.items and 1 or sel + 1
item_enter ( cur_menu , sel_new )
2009-08-21 16:28:55 +02:00
elseif sel > 0 and util.table . hasitem ( menu_keys.exec , key ) then
2009-03-09 14:14:01 +01:00
exec ( cur_menu , sel )
2009-08-21 16:28:55 +02:00
elseif util.table . hasitem ( menu_keys.back , key ) then
2009-03-09 14:14:01 +01:00
cur_menu : hide ( )
2009-08-21 16:28:55 +02:00
elseif util.table . hasitem ( menu_keys.close , key ) then
2009-03-09 14:14:01 +01:00
get_parents ( cur_menu ) : hide ( )
2009-11-26 12:47:39 +01:00
else
check_access_key ( cur_menu , key )
2009-03-09 14:14:01 +01:00
end
return true
end
2008-10-22 14:22:48 +02:00
local function add_item ( data , num , item_info )
local item = wibox ( {
2008-10-24 22:31:14 +02:00
fg = data.theme . fg_normal ,
bg = data.theme . bg_normal ,
border_color = data.theme . border ,
2010-09-28 13:13:14 +02:00
border_width = data.theme . border_width ,
type = " popup_menu "
2008-10-22 14:22:48 +02:00
} )
-- Create bindings
2009-04-17 18:08:52 +02:00
local bindings = util.table . join (
2009-03-04 19:42:25 +01:00
button ( { } , 1 , function ( ) item_enter ( data , num ) ; exec ( data , num ) end ) ,
2010-04-11 10:55:56 +02:00
button ( { } , 3 , function ( ) data : hide ( ) end )
2009-04-17 18:08:52 +02:00
)
2008-10-22 14:22:48 +02:00
2008-12-18 18:02:54 +01:00
-- Create the item label widget
2010-10-06 14:21:19 +02:00
local label = wibox.widget . textbox ( )
2009-11-26 12:47:39 +01:00
local key = ' '
2010-10-07 14:24:16 +02:00
label : set_markup ( string.gsub ( util.escape ( item_info [ 1 ] ) , " &(%w) " ,
2010-02-21 00:58:07 +01:00
function ( l )
key = string.lower ( l )
return " <u> " .. l .. " </u> "
2010-10-07 14:24:16 +02:00
end , 1 ) )
2008-12-18 18:02:54 +01:00
-- Set icon if needed
2009-08-25 18:13:19 +02:00
local iconbox
2010-10-06 14:21:19 +02:00
local margin = wibox.layout . margin ( )
margin : set_widget ( label )
2008-11-07 15:27:35 +01:00
if item_info [ 3 ] then
2010-09-29 16:06:44 +02:00
local icon = type ( item_info [ 3 ] ) == " string " and capi.oocairo . image_surface_create_from_png ( item_info [ 3 ] ) or item_info [ 3 ]
local width = icon : get_width ( )
local height = icon : get_height ( )
if width > data.h or height > data.h then
local w , h
if ( ( data.h / height ) * width ) > data.h then
w , h = data.h , ( data.h / width ) * height
2008-12-18 18:02:54 +01:00
else
2010-09-29 16:06:44 +02:00
w , h = ( data.h / height ) * width , data.h
2008-12-18 18:02:54 +01:00
end
2010-09-29 16:06:44 +02:00
-- We need to scale the image to size w x h
local img = capi.oocairo . image_surface_create ( " argb32 " , w , h )
local cr = capi.oocairo . context_create ( img )
cr : scale ( w / width , h / height )
cr : set_source ( icon , 0 , 0 )
cr : paint ( )
icon = img
2008-10-23 11:19:46 +02:00
end
2010-10-06 14:21:19 +02:00
iconbox = wibox.widget . imagebox ( )
iconbox : set_image ( icon )
margin : set_left ( 2 )
2009-08-25 18:13:19 +02:00
else
2010-10-06 14:21:19 +02:00
margin : set_left ( data.h + 2 )
2008-10-22 14:22:48 +02:00
end
2009-08-14 16:46:35 +02:00
item : buttons ( bindings )
2008-10-22 14:22:48 +02:00
2009-08-17 15:43:32 +02:00
local mouse_enter_func = function ( ) item_enter ( data , num , true ) end
2009-10-09 20:39:55 +02:00
item : connect_signal ( " mouse::enter " , mouse_enter_func )
2009-02-13 21:20:23 +01:00
2008-10-22 14:22:48 +02:00
-- Create the submenu icon widget
2008-11-07 15:27:35 +01:00
local submenu
2008-10-22 14:22:48 +02:00
if type ( item_info [ 2 ] ) == " table " then
2010-10-06 14:21:19 +02:00
submenu = wibox.widget . imagebox ( )
if data.theme . submenu_icon then
submenu : set_image ( capi.oocairo . image_surface_create_from_png ( data.theme . submenu_icon ) )
end
2009-08-14 16:46:35 +02:00
submenu : buttons ( bindings )
2008-10-22 14:22:48 +02:00
end
-- Add widgets to the wibox
2010-10-06 14:21:19 +02:00
local left = wibox.layout . fixed.horizontal ( )
2009-08-25 18:13:19 +02:00
if iconbox then
2010-10-06 14:21:19 +02:00
left : add ( iconbox )
end
-- This contains the label
left : add ( margin )
local layout = wibox.layout . align.horizontal ( )
2010-10-07 10:15:29 +02:00
layout : set_middle ( left )
2010-10-06 14:21:19 +02:00
if submenu then
layout : set_right ( submenu )
2009-08-25 18:13:19 +02:00
end
2010-10-06 14:21:19 +02:00
item : set_widget ( layout )
local w , h = label : fit ( 0 , 0 )
item.height = h + 2
2008-10-22 14:22:48 +02:00
item.ontop = true
2010-03-17 13:22:45 +01:00
return { wibox = item , akey = key , cmd = item_info [ 2 ] , returned_value = item_info [ 1 ] }
2008-10-22 14:22:48 +02:00
end
2008-11-07 15:27:35 +01:00
--- Build a popup menu with running clients and shows it.
2008-10-24 22:31:14 +02:00
-- @param menu Menu table, see new() function for more informations
2010-04-13 19:11:53 +02:00
-- @param args.keygrabber A boolean enabling or not the keyboard navigation.
2008-11-07 15:27:35 +01:00
-- @return The menu.
2010-04-13 19:11:53 +02:00
function clients ( menu , args )
2008-10-23 11:19:46 +02:00
local cls = capi.client . get ( )
local cls_t = { }
for k , c in pairs ( cls ) do
2008-10-27 12:05:34 +01:00
cls_t [ # cls_t + 1 ] = { util.escape ( c.name ) or " " ,
2008-10-23 11:19:46 +02:00
function ( )
if not c : isvisible ( ) then
tags.viewmore ( c : tags ( ) , c.screen )
end
capi.client . focus = c
2010-04-27 16:28:02 +02:00
c : raise ( )
2008-10-23 11:19:46 +02:00
end ,
c.icon }
end
2008-10-24 22:31:14 +02:00
if not menu then
menu = { }
end
2008-11-07 15:27:35 +01:00
menu.items = cls_t
2008-10-24 22:31:14 +02:00
2009-04-30 02:12:23 +02:00
local m = new ( menu )
2010-04-13 19:11:53 +02:00
m : show ( args )
2008-11-07 15:27:35 +01:00
return m
2008-10-23 11:19:46 +02:00
end
2010-04-13 19:11:53 +02:00
local function set_coords ( menu , screen_idx , m_coords )
2009-08-18 15:45:17 +02:00
local s_geometry = capi.screen [ screen_idx ] . workarea
2008-10-24 11:04:02 +02:00
local screen_w = s_geometry.x + s_geometry.width
2008-10-24 22:31:14 +02:00
local screen_h = s_geometry.y + s_geometry.height
2008-10-24 11:04:02 +02:00
2009-03-10 20:31:41 +01:00
local i_h = menu.h + menu.theme . border_width
2009-02-22 15:48:23 +01:00
local m_h = ( i_h * # menu.items ) + menu.theme . border_width
2008-11-07 15:27:35 +01:00
if menu.parent then
menu.w = menu.parent . w
menu.h = menu.parent . h
2009-02-22 15:48:23 +01:00
local p_w = i_h * ( menu.num - 1 )
local m_w = menu.w - menu.theme . border_width
2008-11-07 15:27:35 +01:00
menu.y = menu.parent . y + p_w + m_h > screen_h and screen_h - m_h or menu.parent . y + p_w
2009-02-22 15:48:23 +01:00
menu.x = menu.parent . x + m_w * 2 > screen_w and menu.parent . x - m_w or menu.parent . x + m_w
2008-10-24 09:10:42 +02:00
else
2009-02-22 15:48:23 +01:00
local m_w = menu.w
2010-04-13 19:11:53 +02:00
if m_coords == nil then
m_coords = capi.mouse . coords ( )
m_coords.x = m_coords.x + 1
m_coords.y = m_coords.y + 1
end
2008-11-07 15:27:35 +01:00
2010-04-13 19:11:53 +02:00
menu.y = m_coords.y < s_geometry.y and s_geometry.y or m_coords.y
menu.x = m_coords.x < s_geometry.x and s_geometry.x or m_coords.x
2008-11-07 15:27:35 +01:00
menu.y = menu.y + m_h > screen_h and screen_h - m_h or menu.y
menu.x = menu.x + m_w > screen_w and screen_w - m_w or menu.x
end
end
--- Show a menu.
-- @param menu The menu to show.
2010-04-13 19:11:53 +02:00
-- @param args.keygrabber A boolean enabling or not the keyboard navigation.
-- @param args.coords Menu position defaulting to mouse.coords()
function show ( menu , args )
args = args or { }
2008-11-07 15:27:35 +01:00
local screen_index = capi.mouse . screen
2010-04-13 19:11:53 +02:00
local keygrabber = args.keygrabber or false
local coords = args.coords or nil
set_coords ( menu , screen_index , coords )
2008-11-07 15:27:35 +01:00
for num , item in pairs ( menu.items ) do
2009-02-23 12:04:27 +01:00
local wibox = item.wibox
2009-08-17 16:56:03 +02:00
wibox.width = menu.w
wibox.height = menu.h
wibox.x = menu.x
2009-11-18 14:22:50 +01:00
wibox.y = menu.y + ( num - 1 ) * ( menu.h + wibox.border_width )
2009-02-23 12:04:27 +01:00
wibox.screen = screen_index
2008-11-07 15:27:35 +01:00
end
2009-03-09 14:14:01 +01:00
if menu.parent then
2009-03-09 14:22:13 +01:00
menu.keygrabber = menu.parent . keygrabber
2009-03-09 14:14:01 +01:00
elseif keygrabber ~= nil then
menu.keygrabber = keygrabber
else
menu.keygrabber = false
end
if not cur_menu and menu.keygrabber then
capi.keygrabber . run ( grabber )
end
cur_menu = menu
2008-11-07 15:27:35 +01:00
end
2008-10-24 09:10:42 +02:00
2008-11-07 15:27:35 +01:00
--- Toggle menu visibility.
-- @param menu The menu to show if it's hidden, or to hide if it's shown.
2010-04-13 19:11:53 +02:00
-- @param args.keygrabber A boolean enabling or not the keyboard navigation.
-- @param args.coords Menu position {x,y}
function toggle ( menu , args )
2009-02-23 12:04:27 +01:00
if menu.items [ 1 ] and menu.items [ 1 ] . wibox.screen then
2010-04-11 10:55:56 +02:00
menu : hide ( )
2008-11-07 15:27:35 +01:00
else
2010-04-13 19:11:53 +02:00
menu : show ( args )
2008-10-24 09:10:42 +02:00
end
end
2008-10-23 11:19:46 +02:00
--- Open a menu popup.
2011-01-26 21:21:53 +01:00
-- @param menu Table containing the menu informations.<br/>
-- <ul>
2011-01-26 21:28:43 +01:00
-- <li> Key items: Table containing the displayed items. Each element is a table containing: item name, triggered action, submenu table or function, item icon (optional). </li>
2011-01-26 21:21:53 +01:00
-- <li> 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. </li>
-- <li> Key auto_expand controls the submenu auto expand behaviour by setting it to true (default) or false. </li>
-- </ul>
2008-10-23 11:19:46 +02:00
-- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
-- @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.
2011-01-26 21:42:58 +01:00
-- @usage The following function builds, and shows a menu of clients that match
-- a particular rule. Bound to a key, it can for example be used to select from
-- dozens of terminals open on several tags. With the use of
-- <code>match_any</code> instead of <code>match</code>, menu of clients with
-- different classes can also be build.
--
-- <p><code>
-- function terminal_menu () <br/>
-- terms = {} <br/>
-- for i, c in pairs(client.get()) do <br/>
-- if awful.rules.match(c, {class = "URxvt"}) then <br/>
-- terms[i] = <br/>
-- {c.name, <br/>
-- function() <br/>
-- awful.tag.viewonly(c:tags()[1]) <br/>
-- client.focus = c <br/>
-- end, <br/>
-- c.icon <br/>
-- } <br/>
-- end <br/>
-- end <br/>
-- m = awful.menu({items = terms}) <br/>
-- m:show({keygrabber=true}) <br/>
-- return m <br/>
-- end <br/>
--</code></p>
2008-10-23 15:11:13 +02:00
function new ( menu , parent , num )
2008-10-22 14:22:48 +02:00
-- Create a table to store our menu informations
local data = { }
2008-10-30 12:46:49 +01:00
2008-10-22 14:22:48 +02:00
data.items = { }
2008-11-07 15:27:35 +01:00
data.num = num or 1
2008-10-24 22:31:14 +02:00
data.theme = parent and parent.theme or load_theme ( menu )
2008-11-07 15:27:35 +01:00
data.parent = parent
2008-11-10 19:09:20 +01:00
data.child = { }
2009-02-23 14:19:49 +01:00
if parent then
data.auto_expand = parent.auto_expand
2009-02-23 14:12:28 +01:00
elseif menu.auto_expand ~= nil then
data.auto_expand = menu.auto_expand
else
data.auto_expand = true
end
2008-10-24 22:31:14 +02:00
data.h = parent and parent.h or data.theme . menu_height
2009-09-08 10:24:32 +02:00
if type ( data.h ) ~= ' number ' then data.h = tonumber ( data.h ) end
2008-10-24 22:31:14 +02:00
data.w = parent and parent.w or data.theme . menu_width
2009-09-08 10:24:32 +02:00
if type ( data.w ) ~= ' number ' then data.w = tonumber ( data.w ) end
2008-10-22 14:22:48 +02:00
-- Create items
2008-10-23 15:11:13 +02:00
for k , v in pairs ( menu.items ) do
2008-10-23 11:19:46 +02:00
table.insert ( data.items , add_item ( data , k , v ) )
2008-10-22 14:22:48 +02:00
end
2009-09-08 10:24:32 +02:00
if # data.items > 0 and data.h < data.items [ 1 ] . wibox.height then
data.h = data.items [ 1 ] . wibox.height
end
2008-11-07 15:27:35 +01:00
-- Set methods
data.hide = hide
data.show = show
data.toggle = toggle
2008-10-22 14:22:48 +02:00
return data
2009-04-17 18:08:52 +02:00
end
2009-07-15 15:57:31 +02:00
setmetatable ( _M , { __call = function ( _ , ... ) return new ( ... ) end } )
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80