awful.menu: add custom theme and fix various bugs
[awesome.git] / lib / awful / menu.lua.in
bloba35c7c45cdbd92b8755afb9bc4abf564c9ba0e80
1 ---------------------------------------------------------------------------
2 -- @author Damien Leone <damien.leone@gmail.com>
3 -- @copyright 2008 Damien Leone
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local pairs = pairs
9 local table = table
10 local type = type
11 local wibox = wibox
12 local image = image
13 local string = string
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 -- Table containing all currently opened menus
25 local menus = {}
27 local function load_theme(custom)
28 local theme = {}
29 local beautiful
31 beautiful = awbeautiful.get()
33 theme.fg_focus = custom.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus
34 theme.bg_focus = custom.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus
35 theme.fg_normal = custom.fg_normal or beautiful.menu_fg_normal or beautiful.fg_normal
36 theme.bg_normal = custom.bg_normal or beautiful.menu_bg_normal or beautiful.bg_normal
38 theme.submenu_icon = custom.submenu_icon or beautiful.menu_submenu_icon or "@AWESOME_ICON_PATH@/submenu.png"
40 theme.menu_height = custom.height or beautiful.menu_height or 15
41 theme.menu_width = custom.width or beautiful.menu_width or 100
43 theme.border = custom.border_color or beautiful.menu_border_color or beautiful.border_normal
44 theme.border_width = custom.border_width or beautiful.menu_border_width or beautiful.border_width
46 return theme
47 end
49 local function mouse_enter(w, theme)
50 w.fg = theme.fg_focus
51 w.bg = theme.bg_focus
52 end
54 local function mouse_leave(w, theme)
55 w.fg = theme.fg_normal
56 w.bg = theme.bg_normal
57 end
59 local function destroy(data)
60 if data then
61 -- Remove items from screen
62 for i = 1, #data.items do
63 data.items[i].screen = nil
64 end
66 -- Clean memory, dirty but I'm lazy
67 for i = 1, #data.items do
68 table.remove(data.items)
69 end
71 destroy(data.child)
73 -- Remove menu from menus table
74 menus[data.id] = nil
75 end
76 end
78 local function get_parents(data)
79 local p_data = data
81 -- Get the elder parent so when you kill
82 -- it he will destroy the whole family
83 while p_data.parent do
84 p_data = p_data.parent
85 end
87 return p_data
88 end
90 local function exec(data, action, num)
91 if type(action[2]) == "table" then
92 destroy(data.child)
93 data.child = new({ id=action[1], items=action[2] }, data, num)
94 elseif type(action[2]) == "string" then
95 destroy(get_parents(data))
96 util.spawn(action[2])
97 elseif type(action[2]) == "function" then
98 destroy(get_parents(data))
99 action[2]()
103 local function add_item(data, num, item_info)
104 local item = wibox({
105 name = data.id .. "item" .. num,
106 position = "floating",
107 fg = data.theme.fg_normal,
108 bg = data.theme.bg_normal,
109 border_color = data.theme.border,
110 border_width = data.theme.border_width
113 -- Create bindings
114 local bindings = {
115 button({}, 1, function () exec(data, item_info, num) end),
116 button({}, 3, function () destroy(data) end)
119 -- Create the item icon widget
120 local icon = nil
121 if item_info[3] then
122 icon = widget({ type = "imagebox", name = "icon", align = "left" })
123 if type(item_info[3]) == "string" then
124 icon.image = image(item_info[3])
125 else
126 icon.image = item_info[3]
128 else
129 icon = widget({ type = "textbox", name = "icon", align = "left" })
130 icon.width = data.h
133 icon:buttons(bindings)
135 function icon.mouse_enter() mouse_enter(item, data.theme) end
136 function icon.mouse_leave() mouse_leave(item, data.theme) end
138 -- Create the item label widget
139 local label = widget({
140 type = "textbox",
141 name = data.id .. "label" .. num,
142 align = "flex"
144 label.text = string.format(" %s", item_info[1])
145 label:buttons(bindings)
147 function label.mouse_enter() mouse_enter(item, data.theme) end
148 function label.mouse_leave() mouse_leave(item, data.theme) end
150 -- Create the submenu icon widget
151 local submenu = nil
152 if type(item_info[2]) == "table" then
153 submenu = widget({ type = "imagebox", name = "submenu", align = "right" })
154 submenu.image = image(data.theme.submenu_icon)
155 submenu:buttons(bindings)
157 function submenu.mouse_enter() mouse_enter(item, data.theme) end
158 function submenu.mouse_leave() mouse_leave(item, data.theme) end
161 -- Add widgets to the wibox
162 item.widgets = { icon, label, submenu }
164 item:geometry({
165 width = data.w,
166 height = data.h,
167 x = data.x,
168 y = data.y + (num - 1)*(data.h + data.theme.border_width)
171 item.ontop = true
172 item.screen = data.screen
174 return item
177 --- Open a popup menu with running clients.
178 -- @param menu Menu table, see new() function for more informations
179 function clients(menu)
180 local cls = capi.client.get()
181 local cls_t = {}
182 for k, c in pairs(cls) do
183 cls_t[#cls_t + 1] = { c.name,
184 function ()
185 if not c:isvisible() then
186 tags.viewmore(c:tags(), c.screen)
188 capi.client.focus = c
189 end,
190 c.icon }
193 if not menu then
194 menu = {}
197 menu.id="Clients"
198 menu.items=cls_t
200 return new(menu)
203 local function set_coords(data)
204 local m_coords = capi.mouse.coords()
205 local s_geometry = capi.screen[data.screen].workarea
207 local screen_w = s_geometry.x + s_geometry.width
208 local screen_h = s_geometry.y + s_geometry.height
210 if data.parent then
211 data.w = data.parent.w
212 data.h = data.parent.h
214 local p_w = data.h*(data.num - 1)
215 local m_h = data.theme.border_width + (data.h + data.theme.border_width)*data.nb_items
216 local m_w = data.w + data.theme.border_width
217 data.y = data.parent.y + p_w + m_h > screen_h and screen_h - m_h or data.parent.y + p_w
218 data.x = data.parent.x + data.w*2 + data.theme.border_width > screen_w and data.parent.x - m_w or data.parent.x + m_w
219 else
220 data.y = m_coords.y < s_geometry.y and s_geometry.y or m_coords.y
221 data.x = m_coords.x < s_geometry.x and s_geometry.x or m_coords.x
223 local m_h = data.theme.border_width + (data.h + data.theme.border_width)*data.nb_items
224 local m_w = data.w + data.theme.border_width*2
225 data.y = data.y + m_h > screen_h and screen_h - m_h or data.y
226 data.x = data.x + m_w > screen_w and screen_w - m_w or data.x
230 --- Open a menu popup.
231 -- @param menu Table containing the menu informations. Element id: string naming your menu, only one menu with the same id can be displayed on screen at the same time. Element items: Table containing the displayed items, each element is a tab containing: item name, tiggered action, submenu table or function, item icon (optional). Elements [fg|bg]_[focus|normal], border, border_width, submenu_icon, height and width override the default display for your menu, each of them are optional
232 -- @param parent Specify the parent menu if we want to open a submenu, this value should never be set by the user.
233 -- @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.
234 function new(menu, parent, num)
235 -- Close the menu if it was already opened
236 if menus[menu.id] then
237 destroy(menus[menu.id])
240 -- Create a table to store our menu informations
241 local data = {}
242 data.id = menu.id
243 data.screen = capi.mouse.screen
244 data.items = {}
245 data.child = nil
246 data.nb_items = #menu.items
247 data.num = num and num or 1
248 data.theme = parent and parent.theme or load_theme(menu)
249 data.parent = parent and parent or nil
250 data.h = parent and parent.h or data.theme.menu_height
251 data.w = parent and parent.w or data.theme.menu_width
253 set_coords(data)
255 -- Create items
256 for k, v in pairs(menu.items) do
257 table.insert(data.items, add_item(data, k, v))
260 -- Add menu to menus table
261 menus[menu.id] = data
263 return data