massive update, probably broken
[dotfiles/.git] / .config / awesome / lain / util / menu_iterator.lua
1 --[[
2
3      Licensed under GNU General Public License v2
4       * (c) 2017, Simon Désaulniers <sim.desaulniers@gmail.com>
5       * (c) 2017, Uli Schlachter
6       * (c) 2017, Jeferson Siqueira <jefersonlsiq@gmail.com>
7
8 --]]
9
10 -- Menu iterator with Naughty notifications
11 -- lain.util.menu_iterator
12
13 local naughty = require("naughty")
14 local helpers = require("lain.helpers")
15 local util    = require("lain.util")
16 local atable  = require("awful.util").table
17 local assert  = assert
18 local pairs   = pairs
19 local tconcat = table.concat
20 local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
21
22 local state = { cid = nil }
23
24 local function naughty_destroy_callback(reason)
25     local closed = naughty.notificationClosedReason
26     if reason == closed.expired or reason == closed.dismissedByUser then
27         local actions = state.index and state.menu[state.index - 1][2]
28         if actions then
29             for _,action in pairs(actions) do
30                 -- don't try to call nil callbacks
31                 if action then action() end
32             end
33             state.index = nil
34         end
35     end
36 end
37
38 -- Iterates over a menu.
39 -- After the timeout, callbacks associated to the last visited choice are
40 -- executed. Inputs:
41 -- * menu:    a list of {label, {callbacks}} pairs
42 -- * timeout: time to wait before confirming the menu selection
43 -- * icon:    icon to display in the notification of the chosen label
44 local function iterate(menu, timeout, icon)
45     local timeout = timeout or 4 -- default timeout for each menu entry
46     local icon    = icon or nil  -- icon to display on the menu
47
48     -- Build the list of choices
49     if not state.index then
50         state.menu = menu
51         state.index = 1
52     end
53
54     -- Select one and display the appropriate notification
55     local label
56     local next = state.menu[state.index]
57     state.index = state.index + 1
58
59     if not next then
60         label = "Cancel"
61         state.index = nil
62     else
63         label, _ = unpack(next)
64     end
65
66     state.cid = naughty.notify({
67         text        = label,
68         icon        = icon,
69         timeout     = timeout,
70         screen      = mouse.screen,
71         replaces_id = state.cid,
72         destroy     = naughty_destroy_callback
73     }).id
74 end
75
76 -- Generates a menu compatible with the first argument of `iterate` function and
77 -- suitable for the following cases:
78 -- * all possible choices individually (partition of singletons);
79 -- * all possible subsets of the set of choices (powerset).
80 --
81 -- Inputs:
82 -- * args: an array containing the following members:
83 --   * choices:       Array of choices (string) on which the menu will be
84 --                    generated.
85 --   * name:          Displayed name of the menu (in the form "name: choices").
86 --   * selected_cb:   Callback to execute for each selected choice. Takes
87 --                    the choice as a string argument. Can be `nil` (no action
88 --                    to execute).
89 --   * rejected_cb:   Callback to execute for each rejected choice (possible
90 --                    choices which are not selected). Takes the choice as a
91 --                    string argument. Can be `nil` (no action to execute).
92 --   * extra_choices: An array of extra { choice_str, callback_fun } pairs to be
93 --                    added to the menu. Each callback_fun can be `nil`.
94 --   * combination:   The combination of choices to generate. Possible values:
95 --                    "powerset" and "single" (default).
96 -- Output:
97 -- * m: menu to be iterated over.
98 local function menu(args)
99     local choices       = assert(args.choices or args[1])
100     local name          = assert(args.name or args[2])
101     local selected_cb   = args.selected_cb
102     local rejected_cb   = args.rejected_cb
103     local extra_choices = args.extra_choices or {}
104
105     local ch_combinations = args.combination == "powerset" and helpers.powerset(choices) or helpers.trivial_partition_set(choices)
106
107     for _,c in pairs(extra_choices) do
108         ch_combinations = atable.join(ch_combinations, {{c[1]}})
109     end
110
111     local m = {} -- the menu
112
113     for _,c in pairs(ch_combinations) do
114         if #c > 0 then
115             local cbs = {}
116
117             -- selected choices
118             for _,ch in pairs(c) do
119                 if atable.hasitem(choices, ch) then
120                     cbs[#cbs + 1] = selected_cb and function() selected_cb(ch) end or nil
121                 end
122             end
123
124             -- rejected choices
125             for _,ch in pairs(choices) do
126                 if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then
127                     cbs[#cbs + 1] = rejected_cb and function() rejected_cb(ch) end or nil
128                 end
129             end
130
131             -- add user extra choices (like the choice "None" for example)
132             for _,x in pairs(extra_choices) do
133                 if x[1] == c[1] then
134                     cbs[#cbs + 1] = x[2]
135                 end
136             end
137
138             m[#m + 1] = { name .. ": " .. tconcat(c, " + "), cbs }
139         end
140     end
141
142     return m
143 end
144
145 return { iterate = iterate, menu = menu }