]> git.decadent.org.uk Git - ion3.git/blob - ioncore/ioncore_menudb.lua
Imported Upstream version 20090110
[ion3.git] / ioncore / ioncore_menudb.lua
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 --
8
9 local ioncore=_G.ioncore
10
11
12 -- Table to hold defined menus.
13 local menus={}
14
15
16 -- Menu construction {{{
17
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
36
37 --DOC
38 -- Returns a menu defined with \fnref{ioncore.defmenu}.
39 function ioncore.getmenu(name)
40     return menus[name]
41 end
42
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
57
58 --DOC
59 -- Returns a context menu defined with \fnref{ioncore.defctxmenu}.
60 function ioncore.getctxmenu(name)
61     return menus["ctxmenu-"..name]
62 end
63
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
73
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
86     
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
93     
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 {})
101     end
102 end
103
104
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 {})
120 end
121
122
123 -- }}}
124
125
126 -- Workspace and window lists {{{
127
128 local function addto(list)
129     return function(tgt, attr)
130         local e=menuentry(tgt:name(), function() tgt:goto() end)
131         e.attr=attr;
132         table.insert(list, e)
133         return true
134     end
135 end
136     
137 local function sort(entries)
138     table.sort(entries, function(a, b) return a.name < b.name end)
139     return entries
140 end
141
142 function menus.windowlist()
143     local entries={}
144     ioncore.clientwin_i(addto(entries))
145     return sort(entries)
146 end
147
148 function menus.workspacelist()
149     local entries={}
150     local iter_=addto(entries)
151     
152     local function iter(obj) 
153         return (not obj_is(obj, "WGroupWS") 
154                 or iter_(obj))
155     end
156     
157     ioncore.region_i(iter)
158     
159     return sort(entries)
160 end
161
162 local function focuslist(do_act)
163     local entries={}
164     local seen={}
165     local iter_=addto(entries)
166     
167     local function iter(obj, attr) 
168         if obj_is(obj, "WClientWin") then
169             iter_(obj, attr)
170             seen[obj]=true
171         end
172         return true
173     end
174     
175     local function iter_act(obj)
176         return iter(obj, "activity")
177     end
178     
179     local function iter_foc(obj)
180         return (seen[obj] or iter(obj))
181     end
182     
183     if do_act then
184         -- Windows with activity first
185         ioncore.activity_i(iter_act)
186     end
187     
188     -- The ones that have been focused in their lifetime
189     ioncore.focushistory_i(iter_foc)
190     
191     -- And then the rest
192     ioncore.clientwin_i(iter_foc)
193     
194     return entries
195 end
196
197 menus.focuslist=function() return focuslist(true) end
198 menus.focuslist_=function() return focuslist(false) end
199
200 -- }}}
201
202
203 -- Style menu {{{
204
205
206 local function mplex_of(reg)
207     while reg and not obj_is(reg, "WMPlex") do
208         reg=reg:parent()
209     end
210     return reg
211 end
212
213 local function selectstyle(look, where)
214     dopath(look)
215
216     local fname=ioncore.get_savefile('look')
217
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()
225         end
226     end
227
228     if not mod_query then
229         if fname then
230             writeit()
231         end
232         return
233     end
234     
235     where=mplex_of(where)
236     if not where then 
237         return 
238     end
239     
240     if not fname then
241         query_message(where, TR("Cannot save selection."))
242         return
243     end
244     
245     mod_query.query_yesno(where, TR("Save look selection in %s?", fname),
246                           writeit)
247 end
248
249 local function receive_styles(str)
250     local data=""
251     
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"))
256         end
257         str=coroutine.yield()
258     end
259     
260     local found={}
261     local styles={}
262     local stylemenu={}
263     
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)
268         end
269     end
270     
271     table.sort(styles)
272     
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))
279     end
280     
281     table.insert(stylemenu, menuentry(TR("Refresh list"),
282                                       ioncore.refresh_stylelist))
283     
284     menus.stylemenu=stylemenu
285 end
286
287
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-")
300             end
301             return ""
302         end
303
304         cmd=cmd..string.gsub(path..":", "([^:]*):", mkarg)
305         
306         ioncore.popen_bgread(cmd, coroutine.wrap(receive_styles))
307     end
308 end
309
310 -- }}}
311
312
313 -- Context menu {{{
314
315
316 local function classes(reg)
317     local function classes_(t)
318         if t.__parentclass then
319             classes_(t.__parentclass)
320         end
321         coroutine.yield(t.__typename)
322     end
323     return coroutine.wrap(function() classes_(reg) end)
324 end
325
326
327 local function modeparts(mode)
328     if not mode then
329         return function() return end
330     end
331     
332     local f, s, v=string.gmatch(mode, "(%-?[^-]+)");
333     
334     local function nxt(_, m)
335         v = f(s, v)
336         return (v and (m .. v))
337     end
338     
339     return nxt, nil, ""
340 end
341
342
343 local function get_ctxmenu(reg, sub)
344     local m={}
345     
346     local function cp(m2)
347         local m3={}
348         for k, v in ipairs(m2) do
349             local v2=table.copy(v)
350             
351             if v2.func then
352                 local ofunc=v2.func
353                 v2.func=function() return ofunc(reg, sub) end
354             end
355             
356             if v2.submenu_fn then
357                 local ofn=v2.submenu_fn
358                 v2.submenu_fn=function() return cp(ofn()) end
359             end
360             
361             m3[k]=v2
362         end
363         m3.label=m2.label
364         return m3
365     end
366     
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
371         end
372     end
373     
374     local mgr=reg:manager()
375     local mgrname=(mgr and mgr:name()) or nil
376     local mode=(reg.mode and reg:mode())
377
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)
383         end
384         if mgrname then
385             add_ctxmenu(ioncore.evalmenu(nm.."@"..mgrname), false)
386         end
387     end
388     return m
389 end
390
391
392 local function sortmenu(m)
393     local v=1/2
394     
395     for _, e in ipairs(m) do
396         e.priority=(e.priority or 1)+v
397         v=v/2
398     end
399     
400     table.sort(m, function(e1, e2) return e1.priority > e2.priority end)
401     
402     return m
403 end
404
405
406 function menus.ctxmenu(reg, sub)
407     local m, r, s
408     
409     if obj_is(sub, "WGroup") then
410         sub=(sub:bottom() or sub)
411     end
412     
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)
419         s=r
420         r=r:manager()
421     end
422     
423     m=(m or {})
424     
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)
432         end
433         s=r
434         r=r:manager()
435     end
436     
437     return sortmenu(m)
438 end
439
440 -- }}}
441
442 ioncore.refresh_stylelist()
443