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