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