Rename 'goto_' to 'goto_focus', keep backwards compatibility
[notion/jeffpc.git] / ioncore / ioncore_menudb.lua
blob0b4239db9abb78a4939f09e8357d3e0f28df8e7a
1 --
2 -- ion/ioncore/ioncore_menudb.lua -- Routines for defining menus.
3 --
4 -- Copyright (c) Tuomo Valkonen 2004-2009.
5 --
6 -- See the included file LICENSE for details.
7 --
9 local ioncore=_G.ioncore
12 -- Table to hold defined menus.
13 local menus={}
16 -- Menu construction {{{
18 --DOC
19 -- Define a new menu with \var{name} being the menu's name and \var{tab}
20 -- being a table of menu entries. If \var{tab.append} is set, the entries
21 -- are appended to previously-defined ones, if possible.
22 function ioncore.defmenu(name, tab)
23 if menus[name] and type(tab)=="table" and tab.append then
24 if type(menus[name])~="table" then
25 ioncore.warn(TR("Unable to append to non-table menu"))
26 return
27 else
28 for k, v in ipairs(tab) do
29 table.insert(menus[name], v)
30 end
31 end
32 else
33 menus[name]=tab
34 end
35 end
37 --DOC
38 -- Returns a menu defined with \fnref{ioncore.defmenu}.
39 function ioncore.getmenu(name)
40 return menus[name]
41 end
43 --DOC
44 -- Define context menu for context \var{ctx}, \var{tab} being a table
45 -- of menu entries.
46 function ioncore.defctxmenu(ctx, ...)
47 local tab, add
48 local a1, a2 = ...
49 if a2 and type(a1)=="string" then
50 tab=a2
51 tab.label=ioncore.gettext(a1)
52 else
53 tab=a1
54 end
55 ioncore.defmenu("ctxmenu-"..ctx, tab)
56 end
58 --DOC
59 -- Returns a context menu defined with \fnref{ioncore.defctxmenu}.
60 function ioncore.getctxmenu(name)
61 return menus["ctxmenu-"..name]
62 end
64 function ioncore.evalmenu(menu, ...)
65 if type(menu)=="string" then
66 return ioncore.evalmenu(menus[menu], ...)
67 elseif type(menu)=="function" then
68 return menu(...)
69 elseif type(menu)=="table" then
70 return menu
71 end
72 end
74 --DOC
75 -- Use this function to define normal menu entries. The string \var{name}
76 -- is the string shown in the visual representation of menu. The
77 -- parameter \var{cmd} and \var{guard_or_opts} (when string) are similar
78 -- to those of \fnref{ioncore.defbindings}. If \var{guard_or_opts} is
79 -- a table, it may contains the \var{guard} field, and the \var{priority}
80 -- field, for controlling positioning of entries in context menus.
81 -- (The default priority is 1 for most entries, and -1 for auto-generated
82 -- submenus.)
83 function ioncore.menuentry(name, cmd, guard_or_opts)
84 local guard
85 local opts
87 if type(guard_or_opts)=="string" then
88 guard=guard_or_opts
89 elseif type(guard_or_opts)=="table" then
90 opts=guard_or_opts
91 guard=opts.guard
92 end
94 local fn, gfn=ioncore.compile_cmd(cmd, guard)
95 if fn then
96 return table.append({
97 name=ioncore.gettext(name),
98 func=fn,
99 guard_func=gfn,
100 }, opts or {})
105 --DOC
106 -- Use this function to define menu entries for submenus. The parameter
107 -- \fnref{sub_or_name} is either a table of menu entries or the name
108 -- of an already defined menu. The initial menu entry to highlight can be
109 -- specified by \var{options.initial} as either an integer starting from 1,
110 -- or a function that returns such a number. Another option supported is
111 -- \var{options.noautoexpand} that will cause \fnref{mod_query.query_menu}
112 -- to not automatically expand this submenu.
113 function ioncore.submenu(name, sub_or_name, options)
114 return table.append({
115 name=ioncore.gettext(name),
116 submenu_fn=function()
117 return ioncore.evalmenu (sub_or_name)
118 end,
119 }, options or {})
123 -- }}}
126 -- Workspace and window lists {{{
128 local function addto(list)
129 return function(tgt, attr)
130 local e=menuentry(tgt:name(), function() tgt:goto_focus() end)
131 e.attr=attr;
132 table.insert(list, e)
133 return true
137 local function sort(entries)
138 table.sort(entries, function(a, b) return a.name < b.name end)
139 return entries
142 function menus.windowlist()
143 local entries={}
144 ioncore.clientwin_i(addto(entries))
145 return sort(entries)
148 function menus.workspacelist()
149 local entries={}
150 local iter_=addto(entries)
152 local function iter(obj)
153 return (not obj_is(obj, "WGroupWS")
154 or iter_(obj))
157 ioncore.region_i(iter)
159 return sort(entries)
162 local function focuslist(do_act)
163 local entries={}
164 local seen={}
165 local iter_=addto(entries)
167 local function iter(obj, attr)
168 if obj_is(obj, "WClientWin") then
169 iter_(obj, attr)
170 seen[obj]=true
172 return true
175 local function iter_act(obj)
176 return iter(obj, "activity")
179 local function iter_foc(obj)
180 return (seen[obj] or iter(obj))
183 if do_act then
184 -- Windows with activity first
185 ioncore.activity_i(iter_act)
188 -- The ones that have been focused in their lifetime
189 ioncore.focushistory_i(iter_foc)
191 -- And then the rest
192 ioncore.clientwin_i(iter_foc)
194 return entries
197 menus.focuslist=function() return focuslist(true) end
198 menus.focuslist_=function() return focuslist(false) end
200 -- }}}
203 -- Style menu {{{
206 local function mplex_of(reg)
207 while reg and not obj_is(reg, "WMPlex") do
208 reg=reg:parent()
210 return reg
213 local function selectstyle(look, where)
214 dopath(look)
216 local fname=ioncore.get_savefile('look')
218 local function writeit()
219 local f, err=io.open(fname, 'w')
220 if not f then
221 mod_query.message(where, err)
222 else
223 f:write(string.format('dopath("%s")\n', look))
224 f:close()
228 if not mod_query then
229 if fname then
230 writeit()
232 return
235 where=mplex_of(where)
236 if not where then
237 return
240 if not fname then
241 query_message(where, TR("Cannot save selection."))
242 return
245 mod_query.query_yesno(where, TR("Save look selection in %s?", fname),
246 writeit)
249 local function receive_styles(str)
250 local data=""
252 while str do
253 data=data .. str
254 if string.len(data)>ioncore.RESULT_DATA_LIMIT then
255 error(TR("Too much result data"))
257 str=coroutine.yield()
260 local found={}
261 local styles={}
262 local stylemenu={}
264 for look in string.gmatch(data, "(look[-_][^\n]*)%.lua\n") do
265 if not found[look] then
266 found[look]=true
267 table.insert(styles, look)
271 table.sort(styles)
273 for _, look in ipairs(styles) do
274 local look_=look
275 table.insert(stylemenu, menuentry(look,
276 function(where)
277 selectstyle(look_, where)
278 end))
281 table.insert(stylemenu, menuentry(TR("Refresh list"),
282 ioncore.refresh_stylelist))
284 menus.stylemenu=stylemenu
288 --DOC
289 -- Refresh list of known style files.
290 function ioncore.refresh_stylelist()
291 local cmd=ioncore.lookup_script("ion-completefile")
292 if cmd then
293 local path=ioncore.get_paths().searchpath
294 local function mkarg(s)
295 if s=="" then
296 return ""
297 else
298 return (" "..string.shell_safe(s).."/look_"..
299 " "..string.shell_safe(s).."/look-")
301 return ""
304 cmd=cmd..string.gsub(path..":", "([^:]*):", mkarg)
306 ioncore.popen_bgread(cmd, coroutine.wrap(receive_styles))
310 -- }}}
313 -- Context menu {{{
316 local function classes(reg)
317 local function classes_(t)
318 if t.__parentclass then
319 classes_(t.__parentclass)
321 coroutine.yield(t.__typename)
323 return coroutine.wrap(function() classes_(reg) end)
327 local function modeparts(mode)
328 if not mode then
329 return function() return end
332 local f, s, v=string.gmatch(mode, "(%-?[^-]+)");
334 local function nxt(_, m)
335 v = f(s, v)
336 return (v and (m .. v))
339 return nxt, nil, ""
343 local function get_ctxmenu(reg, sub)
344 local m={}
346 local function cp(m2)
347 local m3={}
348 for k, v in ipairs(m2) do
349 local v2=table.copy(v)
351 if v2.func then
352 local ofunc=v2.func
353 v2.func=function() return ofunc(reg, sub) end
356 if v2.submenu_fn then
357 local ofn=v2.submenu_fn
358 v2.submenu_fn=function() return cp(ofn()) end
361 m3[k]=v2
363 m3.label=m2.label
364 return m3
367 local function add_ctxmenu(m2, use_label)
368 if m2 then
369 m=table.icat(m, cp(m2))
370 m.label=(use_label and m2.label) or m.label
374 local mgr=reg:manager()
375 local mgrname=(mgr and mgr:name()) or nil
376 local mode=(reg.mode and reg:mode())
378 for s in classes(reg) do
379 local nm="ctxmenu-"..s
380 add_ctxmenu(ioncore.evalmenu(nm), true)
381 for m in modeparts(mode) do
382 add_ctxmenu(ioncore.evalmenu(nm.."."..m), false)
384 if mgrname then
385 add_ctxmenu(ioncore.evalmenu(nm.."@"..mgrname), false)
388 return m
392 local function sortmenu(m)
393 local v=1/2
395 for _, e in ipairs(m) do
396 e.priority=(e.priority or 1)+v
397 v=v/2
400 table.sort(m, function(e1, e2) return e1.priority > e2.priority end)
402 return m
406 function menus.ctxmenu(reg, sub)
407 local m, r, s
409 if obj_is(sub, "WGroup") then
410 sub=(sub:bottom() or sub)
413 -- First, stuff between reg (inclusive) and sub_or_chld (inclusive)
414 -- at the top level in the menu.
415 r=(sub or reg)
416 while r and s~=reg do
417 local mm=get_ctxmenu(r, s)
418 m=((m and table.icat(mm, m)) or mm)
420 r=r:manager()
423 m=(m or {})
425 -- Then stuff below reg (exclusive) as submenus
426 while r do
427 local mm = get_ctxmenu(r, s)
428 if #mm>0 then
429 local nm=mm.label or obj_typename(reg)
430 local tmp=ioncore.submenu(nm, sortmenu(mm), {priority=-1})
431 table.insert(m, tmp)
434 r=r:manager()
437 return sortmenu(m)
440 -- }}}
442 ioncore.refresh_stylelist()