]> git.decadent.org.uk Git - ion3.git/blob - ioncore/ioncore_menudb.lua
[svn-upgrade] Integrating new upstream version, ion3 (20070318)
[ion3.git] / ioncore / ioncore_menudb.lua
1 --
2 -- ion/ioncore/ioncore_menudb.lua -- Routines for defining menus.
3 -- 
4 -- Copyright (c) Tuomo Valkonen 2004-2007.
5 --
6 -- Ion is free software; you can redistribute it and/or modify it under
7 -- the terms of the GNU Lesser General Public License as published by
8 -- the Free Software Foundation; either version 2.1 of the License, or
9 -- (at your option) any later version.
10 --
11
12 local ioncore=_G.ioncore
13
14
15 -- Table to hold defined menus.
16 local menus={}
17
18
19 -- Menu construction {{{
20
21 --DOC
22 -- Define a new menu with \var{name} being the menu's name and \var{tab} 
23 -- being a table of menu entries. If \var{tab.append} is set, the entries 
24 -- are appended to previously-defined ones, if possible.
25 function ioncore.defmenu(name, tab)
26     if menus[name] and type(tab)=="table" and tab.append then
27         if type(menus[name])~="table" then
28             ioncore.warn(TR("Unable to append to non-table menu"))
29             return
30         else
31             for k, v in ipairs(tab) do
32                 table.insert(menus[name], v)
33             end
34         end
35     else
36         menus[name]=tab
37     end
38 end
39
40 --DOC
41 -- Returns a menu defined with \fnref{ioncore.defmenu}.
42 function ioncore.getmenu(name)
43     return menus[name]
44 end
45
46 --DOC
47 -- Define context menu for context \var{ctx}, \var{tab} being a table 
48 -- of menu entries.
49 function ioncore.defctxmenu(ctx, ...)
50     local tab, add
51     if #arg>1 and type(arg[1])=="string" then
52         tab=arg[2]
53         tab.label=ioncore.gettext(arg[1])
54     else
55         tab=arg[1]
56     end
57     ioncore.defmenu("ctxmenu-"..ctx, tab)
58 end
59
60 --DOC
61 -- Returns a context menu defined with \fnref{ioncore.defctxmenu}.
62 function ioncore.getctxmenu(name)
63     return menus["ctxmenu-"..name]
64 end
65
66 function ioncore.evalmenu(menu, ...)
67     if type(menu)=="string" then
68         return ioncore.evalmenu(menus[menu], ...)
69     elseif type(menu)=="function" then
70         return menu(...)
71     elseif type(menu)=="table" then
72         return menu
73     end
74 end
75
76 --DOC
77 -- Use this function to define normal menu entries. The string \var{name} 
78 -- is the string shown in the visual representation of menu. The
79 -- parameter \var{cmd} and \var{guard_or_opts} (when string) are similar
80 -- to those of \fnref{ioncore.defbindings}.  If \var{guard_or_opts} is
81 -- a table, it may contains the \var{guard} field, and the \var{priority}
82 -- field, for controlling positioning of entries in context menus.
83 -- (The default priority is 1 for most entries, and -1 for auto-generated
84 -- submenus.)
85 function ioncore.menuentry(name, cmd, guard_or_opts)
86     local guard
87     local opts
88     
89     if type(guard_or_opts)=="string" then
90         guard=guard_or_opts
91     elseif type(guard_or_opts)=="table" then
92         opts=guard_or_opts
93         guard=opts.guard
94     end
95     
96     local fn, gfn=ioncore.compile_cmd(cmd, guard)
97     if fn then
98         return table.append({
99                    name=ioncore.gettext(name), 
100                    func=fn, 
101                    guard_func=gfn, 
102                }, opts or {})
103     end
104 end
105
106
107 --DOC
108 -- Use this function to define menu entries for submenus. The parameter
109 -- \fnref{sub_or_name} is either a table of menu entries or the name
110 -- of an already defined menu. The initial menu entry to highlight can be
111 -- specified by \var{options.initial} as either an integer starting from 1, 
112 -- or a  function that returns such a number. Another option supported is
113 -- \var{options.noautoexpand} that will cause \fnref{mod_query.query_menu}
114 -- to not automatically expand this submenu.
115 function ioncore.submenu(name, sub_or_name, options)
116     return table.append({
117                name=ioncore.gettext(name),
118                submenu_fn=function()
119                               return ioncore.evalmenu  (sub_or_name)
120                           end,
121            }, options or {})
122 end
123
124
125 -- }}}
126
127
128 -- Workspace and window lists {{{
129
130 local function addto(list)
131     return function(tgt, attr)
132         local e=menuentry(tgt:name(), function() tgt:goto() end)
133         e.attr=attr;
134         table.insert(list, e)
135         return true
136     end
137 end
138     
139 local function sort(entries)
140     table.sort(entries, function(a, b) return a.name < b.name end)
141     return entries
142 end
143
144 function menus.windowlist()
145     local entries={}
146     ioncore.clientwin_i(addto(entries))
147     return sort(entries)
148 end
149
150 function menus.workspacelist()
151     local entries={}
152     local iter_=addto(entries)
153     
154     local function iter(obj) 
155         return (not obj_is(obj, "WGroupWS") 
156                 or iter_(obj))
157     end
158     
159     ioncore.region_i(iter)
160     
161     return sort(entries)
162 end
163
164 local function focuslist(do_act)
165     local entries={}
166     local seen={}
167     local iter_=addto(entries)
168     
169     local function iter(obj, attr) 
170         if obj_is(obj, "WClientWin") then
171             iter_(obj, attr)
172             seen[obj]=true
173         end
174         return true
175     end
176     
177     local function iter_act(obj)
178         return iter(obj, "activity")
179     end
180     
181     local function iter_foc(obj)
182         return (seen[obj] or iter(obj))
183     end
184     
185     if do_act then
186         -- Windows with activity first
187         ioncore.activity_i(iter_act)
188     end
189     
190     -- The ones that have been focused in their lifetime
191     ioncore.focushistory_i(iter_foc)
192     
193     -- And then the rest
194     ioncore.clientwin_i(iter_foc)
195     
196     return entries
197 end
198
199 menus.focuslist=function() return focuslist(true) end
200 menus.focuslist_=function() return focuslist(false) end
201
202 -- }}}
203
204
205 -- Style menu {{{
206
207
208 local function mplex_of(reg)
209     while reg and not obj_is(reg, "WMPlex") do
210         reg=reg:parent()
211     end
212     return reg
213 end
214
215 local function selectstyle(look, where)
216     dopath(look)
217
218     local fname=ioncore.get_savefile('look')
219
220     local function writeit()
221         local f, err=io.open(fname, 'w')
222         if not f then
223             mod_query.message(where, err)
224         else
225             f:write(string.format('dopath("%s")\n', look))
226             f:close()
227         end
228     end
229
230     if not mod_query then
231         if fname then
232             writeit()
233         end
234         return
235     end
236     
237     where=mplex_of(where)
238     if not where then 
239         return 
240     end
241     
242     if not fname then
243         query_message(where, TR("Cannot save selection."))
244         return
245     end
246     
247     mod_query.query_yesno(where, TR("Save look selection in %s?", fname),
248                           writeit)
249 end
250
251 local function receive_styles(str)
252     local data=""
253     
254     while str do
255         data=data .. str
256         if string.len(data)>ioncore.RESULT_DATA_LIMIT then
257             error(TR("Too much result data"))
258         end
259         str=coroutine.yield()
260     end
261     
262     local found={}
263     local styles={}
264     local stylemenu={}
265     
266     for look in string.gfind(data, "(look[-_][^\n]*)%.lua\n") do
267         if not found[look] then
268             found[look]=true
269             table.insert(styles, look)
270         end
271     end
272     
273     table.sort(styles)
274     
275     for _, look in ipairs(styles) do
276         local look_=look
277         table.insert(stylemenu, menuentry(look,  
278                                           function(where)
279                                               selectstyle(look_, where)
280                                           end))
281     end
282     
283     table.insert(stylemenu, menuentry(TR("Refresh list"),
284                                       ioncore.refresh_stylelist))
285     
286     menus.stylemenu=stylemenu
287 end
288
289
290 --DOC
291 -- Refresh list of known style files.
292 function ioncore.refresh_stylelist()
293     local cmd=ioncore.lookup_script("ion-completefile")
294     if cmd then
295         local path=ioncore.get_paths().searchpath
296         local function mkarg(s)
297             if s=="" then
298                 return ""
299             else
300                 return (" "..string.shell_safe(s).."/look_"..
301                         " "..string.shell_safe(s).."/look-")
302             end
303             return ""
304         end
305
306         cmd=cmd..string.gsub(path..":", "([^:]*):", mkarg)
307         
308         ioncore.popen_bgread(cmd, coroutine.wrap(receive_styles))
309     end
310 end
311
312 -- }}}
313
314
315 -- Context menu {{{
316
317
318 local function classes(reg)
319     local function classes_(t)
320         if t.__parentclass then
321             classes_(t.__parentclass)
322         end
323         coroutine.yield(t.__typename)
324     end
325     return coroutine.wrap(function() classes_(reg) end)
326 end
327
328
329 local function modeparts(mode)
330     if not mode then
331         return function() return end
332     end
333     
334     local f, s, v=string.gmatch(mode, "(%-?[^-]+)");
335     
336     local function nxt(_, m)
337         v = f(s, v)
338         return (v and (m .. v))
339     end
340     
341     return nxt, nil, ""
342 end
343
344
345 local function get_ctxmenu(reg, sub)
346     local m={}
347     
348     local function cp(m2)
349         local m3={}
350         for k, v in ipairs(m2) do
351             local v2=table.copy(v)
352             
353             if v2.func then
354                 local ofunc=v2.func
355                 v2.func=function() return ofunc(reg, sub) end
356             end
357             
358             if v2.submenu_fn then
359                 local ofn=v2.submenu_fn
360                 v2.submenu_fn=function() return cp(ofn()) end
361             end
362             
363             m3[k]=v2
364         end
365         m3.label=m2.label
366         return m3
367     end
368     
369     local function add_ctxmenu(m2, use_label)
370         if m2 then
371             m=table.icat(m, cp(m2))
372             m.label=(use_label and m2.label) or m.label
373         end
374     end
375     
376     local mgr=reg:manager()
377     local mgrname=(mgr and mgr:name()) or nil
378     local mode=(reg.mode and reg:mode())
379
380     for s in classes(reg) do
381         local nm="ctxmenu-"..s
382         add_ctxmenu(ioncore.evalmenu(nm), true)
383         for m in modeparts(mode) do
384             add_ctxmenu(ioncore.evalmenu(nm.."."..m), false)
385         end
386         if mgrname then
387             add_ctxmenu(ioncore.evalmenu(nm.."@"..mgrname), false)
388         end
389     end
390     return m
391 end
392
393
394 local function sortmenu(m)
395     local v=1/2
396     
397     for _, e in ipairs(m) do
398         e.priority=(e.priority or 1)+v
399         v=v/2
400     end
401     
402     table.sort(m, function(e1, e2) return e1.priority > e2.priority end)
403     
404     return m
405 end
406
407
408 function menus.ctxmenu(reg, sub_or_chld)
409     local m, r, s
410     
411     -- First, stuff between reg (inclusive) and sub_or_chld (inclusive)
412     -- at the top level in the menu.
413     r=(sub_or_chld or reg)
414     while r and s~=reg do
415         local mm=get_ctxmenu(r, s)
416         m=((m and table.icat(mm, m)) or mm)
417         s=r
418         r=r:manager()
419     end
420     
421     m=(m or {})
422     
423     -- Then stuff below reg (exclusive) as submenus
424     while r do
425         local mm = get_ctxmenu(r, s)
426         if #mm>0 then
427             local nm=mm.label or obj_typename(reg)
428             local tmp=ioncore.submenu(nm, sortmenu(mm), {priority=-1})
429             table.insert(m, tmp)
430         end
431         s=r
432         r=r:manager()
433     end
434     
435     return sortmenu(m)
436 end
437
438 -- }}}
439
440 ioncore.refresh_stylelist()
441