2 -- ion/mod_panews/mod_panews.lua
4 -- Copyright (c) Tuomo Valkonen 2004-2006.
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.
13 -- This is a slight abuse of the package.loaded variable perhaps, but
14 -- library-like packages should handle checking if they're loaded instead of
15 -- confusing the user with require/include differences.
16 if package.loaded["templates"] then return end
18 if not ioncore.load_module("mod_panews") then
22 assert(not _G["mod_panews"]);
24 _G["mod_panews"]=mod_panews
35 settings.valid_classifications={["V"]=true, ["T"]=true, ["M"]=true,}
37 -- Xterm, rxvt, aterm, etc. all have "XTerm" as class part of WM_CLASS
38 settings.terminal_emulators={["XTerm"]=true,}
40 -- Pixel scale factor from 1280x1024/75dpi
43 --settings.b_ratio=(1+math.sqrt(5))/2
53 settings.templates["default"]={
58 tls_brs_incl_handles=true,
64 tls=settings.b_ratio2,
65 brs=settings.s_ratio2,
66 tls_brs_incl_handles=true,
84 settings.templates["alternative1"]={
87 tls=settings.s_ratio2,
88 brs=settings.b_ratio2,
89 tls_brs_incl_handles=true,
99 tls_brs_incl_handles=true,
112 settings.templates["alternative2"]={
115 tls=settings.b_ratio,
116 brs=settings.s_ratio,
117 tls_brs_incl_handles=true,
121 tls=settings.s_ratio2,
122 brs=settings.b_ratio2,
123 tls_brs_incl_handles=true,
140 settings.template=settings.templates["default"]
142 settings.shrink_minimum=32
145 -- Set some module parameters. Currently \var{s} may contain the following
147 -- \begin{tabularx}{\linewidth}{lX}
148 -- \tabhead{Field & Description}
149 -- \var{template} & layout template for newly created \type{WPaneWS}
150 -- workspaces. This can be either a table or one of the
151 -- predefined layouts 'default', 'alternative1', and
152 -- 'alternative2'. \\
153 -- \var{scalef} & Scale factor for classification heuristics to work
154 -- with different screen resolutions. The default is 1.0
155 -- and is designed for 1280x1024 at 75dpi. \\
156 -- \var{valid_classifications} & A table with valid window classifications
159 function mod_panews.set(s)
162 if type(s.template)=="string" then
163 if settings.templates[s.template] then
164 settings.template=settings.templates[s.template]
167 elseif type(s.template)=="table" then
168 settings.template=s.template
172 ioncore.warn_traced("Invalid template.")
176 if type(s.scalef)~="number" or s.scalef<=0 then
177 ioncore.warn_traced('Invalid scale factor')
179 settings.scalef=s.scalef
183 if type(s.valid_classifications)=="table" then
184 settings.valid_classifications=s.valid_classifications
190 -- Get some module settings. See \fnref{mod_panews.set} for documentation
191 -- on the contents of the returned table.
192 function mod_panews.get()
193 return table.copy(settings, true)
202 local function sfind(s, p)
203 local function drop2(a, b, ...)
206 return drop2(string.find(s, p))
210 function private.div_length(w, r1, r2)
211 local a=math.ceil(w*r1/(r1+r2))
215 function private.split3(d, ls, cs, rs, lo, co, ro)
231 function private.center3(d, ts, cs, lo, co, ro)
232 local sc=math.min(ts, cs)
233 local sl=math.floor((ts-sc)/2)
235 local r=private.split3(d, sl, sc, sr, lo, co, ro)
239 function private.split2(d, ts, ls, rs, lo, ro)
247 assert(rs>=0 and ls>=0)
261 -- Classification {{{
263 function private.classify(ws, reg)
264 if obj_is(reg, "WClientWin") then
265 -- Check if there's a winprop override
266 local wp=ioncore.getwinprop(reg)
267 if wp and wp.panews_classification then
268 if settings.valid_classifications[wp.panews_classification] then
269 return wp.panews_classification
273 -- Handle known terminal emulators.
274 local id=reg:get_ident()
275 if settings.terminal_emulators[id.class] then
280 -- Try size heuristics.
283 if cg.w<3/8*(1280*settings.scalef) then
287 if cg.h>4/8*(960*settings.scalef) then
297 -- Placement code {{{
300 local just_some_pixel_count=16
302 function private.fitlevel_(min1, s1, us1, min2, s2, us2)
309 if us1>=math.max(s1/2, min1) then
311 elseif us1>=min1+just_some_pixel_count then
313 elseif us1>=min1 then
322 function private.fitlevel(frame, node, horiz)
323 local fg=frame:geom()
324 local fsh=frame:size_hints()
327 return private.fitlevel_(fsh.min_h, fg.h, sg.h, fsh.min_w, fg.w, sg.w)
329 return private.fitlevel_(fsh.min_w, fg.w, sg.w, fsh.min_h, fg.h, sg.h)
334 function private.use_unused(p, n, d, forcelevel)
335 local f=private.fitlevel(p.frame, n, (d=="left" or d=="right"))
342 p.res_config={reg=p.frame}
348 local fg=p.frame:geom()
350 if d=="up" or d=="down" then
352 local fh=math.min(fg.h, sg.h)
354 p.res_config=private.split2("vertical", sg.h, nil, fh,
357 p.res_config=private.split2("vertical", sg.h, fh, nil,
361 elseif d=="left" or d=="right" then
363 local fw=math.min(fg.w, sg.w)
365 p.res_config=private.split2("horizontal", sg.w, nil, fw,
368 p.res_config=private.split2("horizontal", sg.w, fw, nil,
378 function private.scan_pane(p, node, d)
379 local function do_scan_active(n, uf)
380 local t=obj_typename(n)
381 if t=="WSplitRegion" then
382 local f=private.fitlevel(p.frame, n, (d=="left" or d=="right"))
387 elseif t=="WSplitSplit" or t=="WSplitFloat" then
388 local a, b=n:tl(), n:br()
389 if b==n:current() then
392 return (do_scan_active(a, uf) or do_scan_active(b, uf))
397 local function do_scan_unused(n, forcelevel)
398 local t=obj_typename(n)
399 if t=="WSplitSplit" or t=="WSplitFloat" then
402 if (d=="up" and sd=="vertical") or (d=="left" and sd=="horizontal") then
404 elseif (d=="down" and sd=="vertical") or (d=="right" and sd=="horizontal") then
407 a, b=n:current(), (n:current()==n:tl() and n:br() or n:tl())
411 ok, f=do_scan_unused(a, forcelevel)
413 ok, f2=do_scan_unused(b, forcelevel)
415 return ok, math.min(f, f2 or max_penalty)
416 elseif t=="WSplitUnused" then
418 return private.use_unused(p, n, d, forcelevel)
420 return false, max_penalty
423 local ok, fitlevel=do_scan_unused(node, 0)
425 ok=do_scan_active(node, fitlevel)
427 ok=do_scan_unused(node, 3)
434 function private.make_placement(p)
439 while n and not obj_is(n, "WSplitPane") do
444 pcls, pdir=sfind((n:marker() or ""), "(.*):(.*)")
447 return private.use_unused(p, p.specifier, (pdir or "single"), 2)
450 local cls=private.classify(p.ws, p.reg)
452 local function do_scan_cls(n)
453 local t=obj_typename(n)
454 if t=="WSplitPane" then
457 local pcls, pdir=sfind(m, "(.*):(.*)")
458 if pcls and pcls==cls then
459 return private.scan_pane(p, n:contents(), pdir)
462 return do_scan_cls(n:contents())
464 elseif t=="WSplitSplit" or t=="WSplitFloat" then
465 return (do_scan_cls(n:tl()) or do_scan_cls(n:br()))
469 local function do_scan_fallback(n)
470 local t=obj_typename(n)
471 if t=="WSplitUnused" then
473 p.res_config={reg=p.frame}
475 elseif t=="WSplitRegion" then
478 elseif t=="WSplitPane" then
479 return do_scan_fallback(n:contents())
480 elseif t=="WSplitSplit" or t=="WSplitFloat" then
481 return (do_scan_fallback(n:tl()) or do_scan_fallback(n:br()))
485 local node=p.ws:split_tree()
486 return (do_scan_cls(node) or do_scan_fallback(node))
492 -- Layout initialisation {{{
495 function private.calc_sizes(tmpl, sw, sh)
496 tmpl._w, tmpl._h=sw, sh
498 if tmpl.type=="WSplitSplit" or tmpl.type=="WSplitFloat" then
499 local tmps, tlw, tlh, brw, brh
500 -- Calculate pixel sizes of things
501 if tmpl.dir=="vertical" then
507 tmpl.tls, tmpl.brs=private.div_length(tmps, tmpl.tls, tmpl.brs)
509 if tmpl.dir=="vertical" then
511 tlh, brh=tmpl.tls, tmpl.brs
513 tlw, brw=tmpl.tls, tmpl.brs
517 private.calc_sizes(tmpl.tl, tlw, tlh)
518 private.calc_sizes(tmpl.br, brw, brh)
523 function private.init_layout(p)
524 p.layout=table.copy(settings.template, true) -- deep copy template
526 private.calc_sizes(p.layout, wg.w, wg.h)
533 -- Initialisation {{{
535 function private.setup_hooks()
536 local function hookto(hkname, fn)
537 local hk=ioncore.get_hook(hkname)
539 error("No hook "..hkname)
541 if not hk:add(fn) then
542 error("Unable to hook to "..hkname)
546 hookto("panews_init_layout_alt", private.init_layout)
547 hookto("panews_make_placement_alt", private.make_placement)
551 private.setup_hooks()
556 -- Mark ourselves loaded.
557 package.loaded["templates"]=true
560 -- Load configuration file
561 dopath('cfg_panews', false)