awful.menu: rewrite to be more object compliant
[awesome.git] / lib / awful / menu.lua.in
blobd562034fcb4b365ca50b3bcad2c53dcb1427cdc6
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
9 local pairs = pairs
10 local table = table
11 local type = type
12 local wibox = wibox
13 local image = image
14 local widget = widget
15 local button = button
16 local capi = { screen = screen, mouse = mouse, client = client }
17 local util = require("awful.util")
18 local tags = require("awful.tag")
19 local awbeautiful = require("awful.beautiful")
21 --- Menu module for awful
22 module("awful.menu")
24 local function load_theme(custom)
25 local theme = {}
26 local beautiful
28 beautiful = awbeautiful.get()
30 theme.fg_focus = custom.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus
31 theme.bg_focus = custom.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus
32 theme.fg_normal = custom.fg_normal or beautiful.menu_fg_normal or beautiful.fg_normal
33 theme.bg_normal = custom.bg_normal or beautiful.menu_bg_normal or beautiful.bg_normal
35 theme.submenu_icon = custom.submenu_icon or beautiful.menu_submenu_icon or "@AWESOME_ICON_PATH@/submenu.png"
37 theme.menu_height = custom.height or beautiful.menu_height or 15
38 theme.menu_width = custom.width or beautiful.menu_width or 100
40 theme.border = custom.border_color or beautiful.menu_border_color or beautiful.border_normal
41 theme.border_width = custom.border_width or beautiful.menu_border_width or beautiful.border_width
43 return theme
44 end
46 local function mouse_enter(w, theme)
47 w.fg = theme.fg_focus
48 w.bg = theme.bg_focus
49 end
51 local function mouse_leave(w, theme)
52 w.fg = theme.fg_normal
53 w.bg = theme.bg_normal
54 end
56 --- Hide a menu popup.
57 -- @param menu The menu to hide.
58 function hide(menu)
59 if menu then
60 -- Remove items from screen
61 for i = 1, #menu.items do
62 -- Call mouse_leave to clear menu entry
63 for k, w in pairs(menu.items[i].widgets) do
64 w.mouse_leave()
65 end
66 menu.items[i].screen = nil
67 end
68 hide(menu.child)
69 end
70 end
72 --- Get the elder parent so for example when you kill
73 -- it, it will destroy the whole family.
74 local function get_parents(data)
75 if data.parent then
76 return get_parents(data.parent)
77 end
78 return data
79 end
81 local function exec(data, action, num)
82 if type(action[2]) == "table" then
83 if not data.child then
84 data.child = new({ items=action[2] }, data, num)
85 end
86 data.child:show()
87 elseif type(action[2]) == "string" then
88 hide(get_parents(data))
89 util.spawn(action[2])
90 elseif type(action[2]) == "function" then
91 hide(get_parents(data))
92 action[2]()
93 end
94 end
96 local function add_item(data, num, item_info)
97 local item = wibox({
98 position = "floating",
99 fg = data.theme.fg_normal,
100 bg = data.theme.bg_normal,
101 border_color = data.theme.border,
102 border_width = data.theme.border_width
105 -- Create bindings
106 local bindings = {
107 button({}, 1, function () exec(data, item_info, num) end),
108 button({}, 3, function () hide(data) end)
111 -- Create the item icon widget
112 local icon = widget({ type = "imagebox", align = "left" })
113 if item_info[3] then
114 if type(item_info[3]) == "string" then
115 icon.image = image(item_info[3])
116 else
117 icon.image = item_info[3]
121 icon:buttons(bindings)
123 function icon.mouse_enter() mouse_enter(item, data.theme) end
124 function icon.mouse_leave() mouse_leave(item, data.theme) end
126 -- Create the item label widget
127 local label = widget({
128 type = "textbox",
129 align = "flex"
131 label.text = " " .. item_info[1]
132 label:buttons(bindings)
134 function label.mouse_enter() mouse_enter(item, data.theme) end
135 function label.mouse_leave() mouse_leave(item, data.theme) end
137 -- Create the submenu icon widget
138 local submenu
139 if type(item_info[2]) == "table" then
140 submenu = widget({ type = "imagebox", align = "right" })
141 submenu.image = image(data.theme.submenu_icon)
142 submenu:buttons(bindings)
144 function submenu.mouse_enter() mouse_enter(item, data.theme) end
145 function submenu.mouse_leave() mouse_leave(item, data.theme) end
148 -- Add widgets to the wibox
149 item.widgets = { icon, label, submenu }
151 item.ontop = true
153 return item
156 --- Build a popup menu with running clients and shows it.
157 -- @param menu Menu table, see new() function for more informations
158 -- @return The menu.
159 function clients(menu)
160 local cls = capi.client.get()
161 local cls_t = {}
162 for k, c in pairs(cls) do
163 cls_t[#cls_t + 1] = { util.escape(c.name) or "",
164 function ()
165 if not c:isvisible() then
166 tags.viewmore(c:tags(), c.screen)
168 capi.client.focus = c
169 end,
170 c.icon }
173 if not menu then
174 menu = {}
177 menu.items = cls_t
179 m = new(menu)
180 m:show()
181 return m
184 local function set_coords(menu, screen_idx)
185 local s_geometry = capi.screen[screen_idx].workarea
186 local screen_w = s_geometry.x + s_geometry.width
187 local screen_h = s_geometry.y + s_geometry.height
189 if menu.parent then
190 menu.w = menu.parent.w
191 menu.h = menu.parent.h
193 local p_w = (menu.h + menu.theme.border_width) * (menu.num - 1)
194 local m_h = menu.theme.border_width + (menu.h + menu.theme.border_width) * #menu.items
195 local m_w = menu.w + menu.theme.border_width
196 menu.y = menu.parent.y + p_w + m_h > screen_h and screen_h - m_h or menu.parent.y + p_w
197 menu.x = menu.parent.x + menu.w*2 + menu.theme.border_width > screen_w and menu.parent.x - m_w or menu.parent.x + m_w
198 else
199 local m_coords = capi.mouse.coords()
201 menu.y = m_coords.y < s_geometry.y and s_geometry.y or m_coords.y
202 menu.x = m_coords.x < s_geometry.x and s_geometry.x or m_coords.x
204 local m_h = menu.theme.border_width + (menu.h + menu.theme.border_width) * #menu.items
205 local m_w = menu.w + menu.theme.border_width*2
206 menu.y = menu.y + m_h > screen_h and screen_h - m_h or menu.y
207 menu.x = menu.x + m_w > screen_w and screen_w - m_w or menu.x
211 --- Show a menu.
212 -- @param menu The menu to show.
213 function show(menu)
214 local screen_index = capi.mouse.screen
215 set_coords(menu, screen_index)
216 for num, item in pairs(menu.items) do
217 item:geometry({
218 width = menu.w,
219 height = menu.h,
220 x = menu.x,
221 y = menu.y + (num - 1) * (menu.h + menu.theme.border_width)
223 item.screen = screen_index
227 --- Toggle menu visibility.
228 -- @param menu The menu to show if it's hidden, or to hide if it's shown.
229 function toggle(menu)
230 if menu.items[1].screen then
231 hide(menu)
232 else
233 show(menu)
237 --- Open a menu popup.
238 -- @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.
239 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
240 -- @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.
241 function new(menu, parent, num)
242 -- Create a table to store our menu informations
243 local data = {}
245 data.items = {}
246 data.num = num or 1
247 data.theme = parent and parent.theme or load_theme(menu)
248 data.parent = parent
249 data.h = parent and parent.h or data.theme.menu_height
250 data.w = parent and parent.w or data.theme.menu_width
252 -- Create items
253 for k, v in pairs(menu.items) do
254 table.insert(data.items, add_item(data, k, v))
257 -- Set methods
258 data.hide = hide
259 data.show = show
260 data.toggle = toggle
262 return data