---------------------------------------------------------------------------
-- @author Alexander Yakushev <yakushev.alex@gmail.com>
-- @copyright 2011-2012 Alexander Yakushev
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
-- Grab environment we need
local capi = {
client = client,
mouse = mouse,
screen = screen
}
local awful = require("awful")
local common = require("awful.widget.common")
local theme = require("beautiful")
local wibox = require("wibox")
-- menubar
local menubar = { mt = {} }
menubar.menu_gen = require("menubar.menu_gen")
menubar.utils = require("menubar.utils")
--- List of menubar keybindings:
--
-- - "Left" | "C-j" select an item on the left
-- - "Right" | "C-k" select an item on the right
-- - "Backspace" exit the current category if we are in any
-- - "Escape" exit the current directory or exit menubar
-- - "Home" select the first item
-- - "End" select the last
-- - "Return" execute the entry
-- - "C-Return" execute the command with awful.util.spawn
-- - "C-M-Return" execute the command in a terminal
--
--
-- @class table
-- @name Menubar keybindings
-- Options section
-- When true the .desktop files will be reparsed only when the
-- extension is initialized. Use this if menubar takes much time to
-- open.
menubar.cache_entries = true
-- When true the categories will be shown alongside application
-- entries.
menubar.show_categories = true
-- Specifies the geometry of the menubar. This is a table with the keys
-- x, y, width and height. Missing values are replaced via the screen's
-- geometry. However, missing height is replaced by the font size.
menubar.geometry = { width = nil,
height = nil,
x = nil,
y = nil }
-- Allows user to specify custom parameters for prompt.run function
-- (like colors).
menubar.prompt_args = {}
-- Private section
local current_item = 1
local previous_item = nil
local current_category = nil
local shownitems = nil
local instance = { prompt = nil,
widget = nil,
wibox = nil }
local common_args = { w = wibox.layout.fixed.horizontal(),
data = setmetatable({}, { __mode = 'kv' }) }
-- Wrap the text with the color span tag.
-- @param s The text.
-- @param c The desired text color.
-- @return the text wrapped in a span tag.
local function colortext(s, c)
return "" .. s .. ""
end
-- Generate a pattern matching expression that ignores case.
-- @param s Original pattern matching expresion.
local function nocase (s)
s = string.gsub(s, "%a",
function (c)
return string.format("[%s%s]", string.lower(c),
string.upper(c))
end)
return s
end
-- Get how the menu item should be displayed.
-- @param o The menu item.
-- @return item name, item background color, background image, item icon.
local function label(o)
if o.focused then
local color = awful.util.color_strip_alpha(theme.fg_focus)
return colortext(o.name, color), theme.bg_focus, nil, o.icon
else
return o.name, theme.bg_normal, nil, o.icon
end
end
-- Perform an action for the given menu item.
-- @param o The menu item.
-- @return if the function processed the callback, new awful.prompt command, new awful.prompt prompt text.
local function perform_action(o)
if not o then return end
if o.key then
current_category = o.key
local new_prompt = shownitems[current_item].name .. ": "
previous_item = current_item
current_item = 1
return true, "", new_prompt
elseif shownitems[current_item].cmdline then
awful.util.spawn(shownitems[current_item].cmdline)
-- Let awful.prompt execute dummy exec_callback and
-- done_callback to stop the keygrabber properly.
return false
end
end
-- Update the menubar according to the command entered by user.
-- @param query The text to filter entries by.
local function menulist_update(query)
local query = query or ""
shownitems = {}
local match_inside = {}
-- First we add entries which names match the command from the
-- beginning to the table shownitems, and the ones that contain
-- command in the middle to the table match_inside.
-- Add the categories
if menubar.show_categories then
for _, v in pairs(menubar.menu_gen.all_categories) do
v.focused = false
if not current_category and v.use then
if string.match(v.name, nocase(query)) then
if string.match(v.name, "^" .. nocase(query)) then
table.insert(shownitems, v)
else
table.insert(match_inside, v)
end
end
end
end
end
-- Add the applications according to their name and cmdline
for i, v in ipairs(menu_entries) do
v.focused = false
if not current_category or v.category == current_category then
if string.match(v.name, nocase(query))
or string.match(v.cmdline, nocase(query)) then
if string.match(v.name, "^" .. nocase(query))
or string.match(v.cmdline, "^" .. nocase(query)) then
table.insert(shownitems, v)
else
table.insert(match_inside, v)
end
end
end
end
-- Now add items from match_inside to shownitems
for i, v in ipairs(match_inside) do
table.insert(shownitems, v)
end
if #shownitems > 0 then
-- Insert a run item value as the last choice
table.insert(shownitems, { name = "Exec: " .. query, cmdline = query, icon = nil })
if current_item > #shownitems then
current_item = #shownitems
end
shownitems[current_item].focused = true
else
table.insert(shownitems, { name = "", cmdline = query, icon = nil })
end
common.list_update(common_args.w, nil, label,
common_args.data,
shownitems)
end
-- Create the menubar wibox and widgets.
local function initialize()
instance.wibox = wibox({})
instance.widget = menubar.get()
instance.wibox.ontop = true
instance.prompt = awful.widget.prompt()
local layout = wibox.layout.fixed.horizontal()
layout:add(instance.prompt)
layout:add(instance.widget)
instance.wibox:set_widget(layout)
end
--- Refresh menubar's cache by reloading .desktop files.
function menubar.refresh()
menu_entries = menubar.menu_gen.generate()
end
-- Awful.prompt keypressed callback to be used when the user presses a key.
-- @param mod Table of key combination modifiers (Control, Shift).
-- @param key The key that was pressed.
-- @param comm The current command in the prompt.
-- @return if the function processed the callback, new awful.prompt command, new awful.prompt prompt text.
local function prompt_keypressed_callback(mod, key, comm)
if key == "Left" or (mod.Control and key == "j") then
current_item = math.max(current_item - 1, 1)
return true
elseif key == "Right" or (mod.Control and key == "k") then
current_item = current_item + 1
return true
elseif key == "BackSpace" then
if comm == "" and current_category then
current_category = nil
current_item = previous_item
return true, nil, "Run: "
end
elseif key == "Escape" then
if current_category then
current_category = nil
current_item = previous_item
return true, nil, "Run: "
end
elseif key == "Home" then
current_item = 1
return true
elseif key == "End" then
current_item = #shownitems
return true
elseif key == "Return" or key == "KP_Enter" then
if mod.Control then
current_item = #shownitems
if mod.Mod1 then
-- add a terminal to the cmdline
shownitems[current_item].cmdline = menubar.utils.terminal
.. " -e " .. shownitems[current_item].cmdline
end
end
return perform_action(shownitems[current_item])
end
return false
end
--- Show the menubar on the given screen.
-- @param scr Screen number.
function menubar.show(scr)
if not instance.wibox then
initialize()
elseif instance.wibox.visible then -- Menu already shown, exit
return
elseif not menubar.cache_entries then
menubar.refresh()
end
-- Set position and size
scr = scr or capi.mouse.screen or 1
local scrgeom = capi.screen[scr].workarea
local geometry = menubar.geometry
instance.wibox:geometry({x = geometry.x or scrgeom.x,
y = geometry.y or scrgeom.y,
height = geometry.height or theme.get_font_height() * 1.5,
width = geometry.width or scrgeom.width})
current_item = 1
current_category = nil
menulist_update()
local prompt_args = menubar.prompt_args or {}
prompt_args.prompt = "Run: "
awful.prompt.run(prompt_args, instance.prompt.widget,
function(s) end, -- exe_callback function set to do nothing
awful.completion.shell, -- completion_callback
awful.util.getdir("cache") .. "/history_menu",
nil, menubar.hide, menulist_update, prompt_keypressed_callback
)
instance.wibox.visible = true
end
--- Hide the menubar.
function menubar.hide()
instance.wibox.visible = false
end
--- Get a menubar wibox.
-- @return menubar wibox.
function menubar.get()
if app_folders then
menubar.menu_gen.all_menu_dirs = app_folders
end
menubar.refresh()
-- Add to each category the name of its key in all_categories
for k, v in pairs(menubar.menu_gen.all_categories) do
v.key = k
end
return common_args.w
end
function menubar.mt:__call(...)
return menubar.get(...)
end
return setmetatable(menubar, menubar.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80