diff options
Diffstat (limited to 'awesome/lib/awful/hotkeys_popup/widget.lua')
-rw-r--r-- | awesome/lib/awful/hotkeys_popup/widget.lua | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/awesome/lib/awful/hotkeys_popup/widget.lua b/awesome/lib/awful/hotkeys_popup/widget.lua new file mode 100644 index 0000000..62a2231 --- /dev/null +++ b/awesome/lib/awful/hotkeys_popup/widget.lua @@ -0,0 +1,482 @@ +--------------------------------------------------------------------------- +--- Popup widget which shows current hotkeys and their descriptions. +-- +-- @author Yauheni Kirylau <yawghen@gmail.com> +-- @copyright 2014-2015 Yauheni Kirylau +-- @module awful.hotkeys_popup.widget +--------------------------------------------------------------------------- + +local capi = { + screen = screen, + client = client, + keygrabber = keygrabber, +} +local awful = require("awful") +local wibox = require("wibox") +local beautiful = require("beautiful") +local dpi = beautiful.xresources.apply_dpi +local compute_textbox_width = require("menubar").utils.compute_textbox_width + + +-- Stripped copy of this module https://github.com/copycat-killer/lain/blob/master/util/markup.lua: +local markup = {} +-- Set the font. +function markup.font(font, text) + return '<span font="' .. tostring(font) .. '">' .. tostring(text) ..'</span>' +end +-- Set the foreground. +function markup.fg(color, text) + return '<span foreground="' .. tostring(color) .. '">' .. tostring(text) .. '</span>' +end +-- Set the background. +function markup.bg(color, text) + return '<span background="' .. tostring(color) .. '">' .. tostring(text) .. '</span>' +end + +local widget_module = { + group_rules = {}, +} + +--- Don't show hotkeys without descriptions. +widget_module.hide_without_description = true + +--- Merge hotkey records into one if they have the same modifiers and +-- description. +widget_module.merge_duplicates = true + + +function widget_module.new() +local widget = { + hide_without_description = widget_module.hide_without_description, + merge_duplicates = widget_module.merge_duplicates, + group_rules = awful.util.table.clone(widget_module.group_rules), + title_font = "Monospace Bold 9", + description_font = "Monospace 8", + width = dpi(1200), + height = dpi(800), + border_width = beautiful.border_width or dpi(2), + modifiers_color = beautiful.bg_minimize or "#555555", + group_margin = dpi(6), + additional_hotkeys = {}, + labels = { + Mod4="Super", + Mod1="Alt", + Escape="Esc", + Insert="Ins", + Delete="Del", + Backspace="BackSpc", + Return="Enter", + Next="PgDn", + Prior="PgUp", + ['#108']="Alt Gr", + Left='←', + Up='↑', + Right='→', + Down='↓', + ['#67']="F1", + ['#68']="F2", + ['#69']="F3", + ['#70']="F4", + ['#71']="F5", + ['#72']="F6", + ['#73']="F7", + ['#74']="F8", + ['#75']="F9", + ['#76']="F10", + ['#95']="F11", + ['#96']="F12", + ['#10']="1", + ['#11']="2", + ['#12']="3", + ['#13']="4", + ['#14']="5", + ['#15']="6", + ['#16']="7", + ['#17']="8", + ['#18']="9", + ['#19']="0", + ['#20']="-", + ['#21']="=", + Control="Ctrl" + }, +} + +local cached_wiboxes = {} +local cached_awful_keys = nil +local colors_counter = {} +local colors = beautiful.xresources.get_current_theme() +local group_list = {} + + +local function get_next_color(id) + id = id or "default" + if colors_counter[id] then + colors_counter[id] = math.fmod(colors_counter[id] + 1, 15) + 1 + else + colors_counter[id] = 1 + end + return colors["color"..tostring(colors_counter[id], 15)] +end + + +local function join_plus_sort(modifiers) + if #modifiers<1 then return "none" end + table.sort(modifiers) + return table.concat(modifiers, '+') +end + + +local function add_hotkey(key, data, target) + if widget.hide_without_description and not data.description then return end + + local readable_mods = {} + for _, mod in ipairs(data.mod) do + table.insert(readable_mods, widget.labels[mod] or mod) + end + local joined_mods = join_plus_sort(readable_mods) + + local group = data.group or "none" + group_list[group] = true + if not target[group] then target[group] = {} end + local new_key = { + key = (widget.labels[key] or key), + mod = joined_mods, + description = data.description + } + local index = data.description or "none" -- or use its hash? + if not target[group][index] then + target[group][index] = new_key + else + if widget.merge_duplicates and joined_mods == target[group][index].mod then + target[group][index].key = target[group][index].key .. "/" .. new_key.key + else + while target[group][index] do + index = index .. " " + end + target[group][index] = new_key + end + end +end + + +local function sort_hotkeys(target) + -- @TODO: add sort by 12345qwertyasdf etc + for group, _ in pairs(group_list) do + if target[group] then + local sorted_table = {} + for _, key in pairs(target[group]) do + table.insert(sorted_table, key) + end + table.sort( + sorted_table, + function(a,b) return (a.mod or '')..a.key<(b.mod or '')..b.key end + ) + target[group] = sorted_table + end + end +end + + +local function import_awful_keys() + if cached_awful_keys then + return + end + cached_awful_keys = {} + for _, data in pairs(awful.key.hotkeys) do + add_hotkey(data.key, data, cached_awful_keys) + end + sort_hotkeys(cached_awful_keys) +end + + +local function group_label(group, color) + local textbox = wibox.widget.textbox( + markup.font(widget.title_font, + markup.bg( + color or (widget.group_rules[group] and + widget.group_rules[group].color or get_next_color("group_title") + ), + markup.fg(beautiful.bg_normal or "#000000", " "..group.." ") + ) + ) + ) + local margin = wibox.container.margin() + margin:set_widget(textbox) + margin:set_top(widget.group_margin) + return margin +end + +local function get_screen(s) + return s and capi.screen[s] +end + +local function create_wibox(s, available_groups) + s = get_screen(s) + + local wa = s.workarea + local height = (widget.height < wa.height) and widget.height or + (wa.height - widget.border_width * 2) + local width = (widget.width < wa.width) and widget.width or + (wa.width - widget.border_width * 2) + + -- arrange hotkey groups into columns + local line_height = beautiful.get_font_height(widget.title_font) + local group_label_height = line_height + widget.group_margin + -- -1 for possible pagination: + local max_height_px = height - group_label_height + local column_layouts = {} + for _, group in ipairs(available_groups) do + local keys = cached_awful_keys[group] or widget.additional_hotkeys[group] + local joined_descriptions = "" + for i, key in ipairs(keys) do + joined_descriptions = joined_descriptions .. key.description .. (i~=#keys and "\n" or "") + end + -- +1 for group label: + local items_height = awful.util.linecount(joined_descriptions) * line_height + group_label_height + local current_column + local available_height_px = max_height_px + local add_new_column = true + for i, column in ipairs(column_layouts) do + if ((column.height_px + items_height) < max_height_px) or + (i == #column_layouts and column.height_px < max_height_px / 2) + then + current_column = column + add_new_column = false + available_height_px = max_height_px - current_column.height_px + break + end + end + local overlap_leftovers + if items_height > available_height_px then + local new_keys = {} + overlap_leftovers = {} + -- +1 for group title and +1 for possible hyphen (v): + local available_height_items = (available_height_px - group_label_height*2) / line_height + for i=1,#keys do + table.insert(((i<available_height_items) and new_keys or overlap_leftovers), keys[i]) + end + keys = new_keys + table.insert(keys, {key=markup.fg(widget.modifiers_color, "▽"), description=""}) + end + if not current_column then + current_column = {layout=wibox.layout.fixed.vertical()} + end + current_column.layout:add(group_label(group)) + + local function insert_keys(_keys, _add_new_column) + local max_label_width = 0 + local max_label_content = "" + local joined_labels = "" + for i, key in ipairs(_keys) do + local length = string.len(key.key or '') + string.len(key.description or '') + local modifiers = key.mod + if not modifiers or modifiers == "none" then + modifiers = "" + else + length = length + string.len(modifiers) + 1 -- +1 for "+" character + modifiers = markup.fg(widget.modifiers_color, modifiers.."+") + end + local rendered_hotkey = markup.font(widget.title_font, + modifiers .. (key.key or "") .. " " + ) .. markup.font(widget.description_font, + key.description or "" + ) + if length > max_label_width then + max_label_width = length + max_label_content = rendered_hotkey + end + joined_labels = joined_labels .. rendered_hotkey .. (i~=#_keys and "\n" or "") + end + current_column.layout:add(wibox.widget.textbox(joined_labels)) + local max_width = compute_textbox_width(wibox.widget.textbox(max_label_content), s) + + widget.group_margin + if not current_column.max_width or max_width > current_column.max_width then + current_column.max_width = max_width + end + -- +1 for group label: + current_column.height_px = (current_column.height_px or 0) + + awful.util.linecount(joined_labels)*line_height + group_label_height + if _add_new_column then + table.insert(column_layouts, current_column) + end + end + + insert_keys(keys, add_new_column) + if overlap_leftovers then + current_column = {layout=wibox.layout.fixed.vertical()} + insert_keys(overlap_leftovers, true) + end + end + + -- arrange columns into pages + local available_width_px = width + local pages = {} + local columns = wibox.layout.fixed.horizontal() + for _, item in ipairs(column_layouts) do + if item.max_width > available_width_px then + columns.widgets[#columns.widgets]['widget']:add( + group_label("PgDn - Next Page", beautiful.fg_normal) + ) + table.insert(pages, columns) + columns = wibox.layout.fixed.horizontal() + available_width_px = width - item.max_width + local old_widgets = item.layout.widgets + item.layout.widgets = {group_label("PgUp - Prev Page", beautiful.fg_normal)} + awful.util.table.merge(item.layout.widgets, old_widgets) + else + available_width_px = available_width_px - item.max_width + end + local column_margin = wibox.container.margin() + column_margin:set_widget(item.layout) + column_margin:set_left(widget.group_margin) + columns:add(column_margin) + end + table.insert(pages, columns) + + local mywibox = wibox({ + ontop = true, + opacity = beautiful.notification_opacity or 1, + border_width = widget.border_width, + border_color = beautiful.fg_normal, + }) + mywibox:geometry({ + x = wa.x + math.floor((wa.width - width - widget.border_width*2) / 2), + y = wa.y + math.floor((wa.height - height - widget.border_width*2) / 2), + width = width, + height = height, + }) + mywibox:set_widget(pages[1]) + mywibox:buttons(awful.util.table.join( + awful.button({ }, 1, function () mywibox.visible=false end), + awful.button({ }, 3, function () mywibox.visible=false end) + )) + + local widget_obj = {} + widget_obj.current_page = 1 + widget_obj.wibox = mywibox + function widget_obj:page_next() + if self.current_page == #pages then return end + self.current_page = self.current_page + 1 + self.wibox:set_widget(pages[self.current_page]) + end + function widget_obj:page_prev() + if self.current_page == 1 then return end + self.current_page = self.current_page - 1 + self.wibox:set_widget(pages[self.current_page]) + end + function widget_obj:show() + self.wibox.visible = true + end + function widget_obj:hide() + self.wibox.visible = false + end + + return widget_obj +end + + +--- Show popup with hotkeys help. +-- @tparam[opt] client c Client. +-- @tparam[opt] screen s Screen. +function widget.show_help(c, s) + import_awful_keys() + c = c or capi.client.focus + s = s or (c and c.screen or awful.screen.focused()) + + local available_groups = {} + for group, _ in pairs(group_list) do + local need_match + for group_name, data in pairs(widget.group_rules) do + if group_name==group and ( + data.rule or data.rule_any or data.except or data.except_any + ) then + if not c or not awful.rules.matches(c, { + rule=data.rule, + rule_any=data.rule_any, + except=data.except, + except_any=data.except_any + }) then + need_match = true + break + end + end + end + if not need_match then table.insert(available_groups, group) end + end + + local joined_groups = join_plus_sort(available_groups) + if not cached_wiboxes[s] then + cached_wiboxes[s] = {} + end + if not cached_wiboxes[s][joined_groups] then + cached_wiboxes[s][joined_groups] = create_wibox(s, available_groups) + end + local help_wibox = cached_wiboxes[s][joined_groups] + help_wibox:show() + + return capi.keygrabber.run(function(_, key, event) + if event == "release" then return end + if key then + if key == "Next" then + help_wibox:page_next() + elseif key == "Prior" then + help_wibox:page_prev() + else + capi.keygrabber.stop() + help_wibox:hide() + end + end + end) +end + + +--- Add hotkey descriptions for third-party applications. +-- @tparam table hotkeys Table with bindings, +-- see `awful.hotkeys_popup.key.vim` as an example. +function widget.add_hotkeys(hotkeys) + for group, bindings in pairs(hotkeys) do + for _, binding in ipairs(bindings) do + local modifiers = binding.modifiers + local keys = binding.keys + for key, description in pairs(keys) do + add_hotkey(key, { + mod=modifiers, + description=description, + group=group}, + widget.additional_hotkeys + ) + end + end + end + sort_hotkeys(widget.additional_hotkeys) +end + + +return widget +end + +local function get_default_widget() + if not widget_module.default_widget then + widget_module.default_widget = widget_module.new() + end + return widget_module.default_widget +end + +--- Show popup with hotkeys help (default widget instance will be used). +-- @tparam[opt] client c Client. +-- @tparam[opt] screen s Screen. +function widget_module.show_help(...) + return get_default_widget().show_help(...) +end + +--- Add hotkey descriptions for third-party applications +-- (default widget instance will be used). +-- @tparam table hotkeys Table with bindings, +-- see `awful.hotkeys_popup.key.vim` as an example. +function widget_module.add_hotkeys(...) + return get_default_widget().add_hotkeys(...) +end + +return widget_module + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 |