+2008-04-11 15:02 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  tagged ion-3-20080411
+
+2008-04-11 15:02 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Release notes
+
+2008-04-03 14:06 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Focus behaviour improvement
+  (in relation to scratchpad close w/ sticky windows).
+
+2008-04-02 09:45 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Workspace switch focus policy fix.
+  (Keep focus in a "sticky" window, if one has it.)
+
+2008-03-31 20:36 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Fixed frame load handler to support string-based mode
+  (as WFrame.set/get_mode do support).
+
+2008-03-31 20:03 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * "unconstrained" size policy wasn't available to lua code
+
+2008-03-18 15:54 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * string.gfind -> string.gmatch
+
+2008-02-24 19:46 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Attach wrapper fixes.
+  The changes related to session management code improvements had bypassed
+  the method for passing an existing object to load.
+
+2008-02-22 19:30 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Man page generation script fix
+
+2008-02-17 17:31 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * unlinked placeholder unlink attempt segfault fix
+
+2008-02-17 15:02 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * Fixed groupws dispose chain
+
+2008-02-06 06:52 UTC  Tuomo Valkonen <tuomov@iki.fi>
+  * doc. fix
+
 2008-02-05 17:40 UTC  Tuomo Valkonen <tuomov@iki.fi>
   tagged ion-3-20080207
 
 
 
+ion-3-20080411
+--------------
+
+This is a maintenance release that fixes a few issues in the
+previous release.
+
+
 ion-3-20080207
 --------------
 
 
     function p.submap(kcb_, list)
         if not list then
             return function(lst)
-                       return submap(kcb_, lst)
+                       return p.submap(kcb_, lst)
                    end
         end
         return {action = "kpress", kcb = kcb_, submap = list}
     local outi=0
     
     local function parsetable(t, prefix)
-        for _, v in ipairs(t) do
-            if not v.invalid then
+        --for _, v in ipairs(t) do
+        -- ipairs doesn't like nil values, that e.g. submap_wait dummy might generate
+        for i=1,#t do
+            local v=t[i]
+            if v and not v.invalid then
                 if v.kcb then
                     v.kcb=string.gsub(v.kcb, "AnyModifier%+", "")
                 end
 
 
 Context:
 
-[TAG ion-3-20080207
-Tuomo Valkonen <tuomov@iki.fi>**20080205174053] 
+[TAG ion-3-20080411
+Tuomo Valkonen <tuomov@iki.fi>**20080411150249] 
 
 static WRegion *doit_load(WRegion *mgr,
                           WWindow *par, const WFitParams *fp,
                           WRegionDoAttachFn *cont, void *cont_param,
-                          const WRegionAttachData *data)
+                          ExtlTab tab, WPHolder **sm_ph)
 {
     WRegion *reg=NULL;
     
-    if(extl_table_gets_o(data->u.tab, "reg", (Obj**)®)){
+    if(extl_table_gets_o(tab, "reg", (Obj**)®)){
         if(!OBJ_IS(reg, WRegion))
             return FALSE;
     }/*else if(extl_table_is_bool_set(tab, "reg_use_new")){
         return doit_reparent(mgr, par, fp, cont, cont_param, reg);
     }else{
         WLP p;
-        p.tab=data->u.tab;
-        p.sm_ph_p=NULL;
+        p.tab=tab;
+        p.sm_ph_p=sm_ph;
         
         return doit_new(mgr, par, fp, cont, cont_param,
                         (WRegionCreateFn*)wrap_load, &p);
                                    WRegionDoAttachFn *fn, void *fn_param,
                                    ExtlTab tab, WPHolder **sm_ph)
 {
-    WLP p;
-    p.tab=tab;
-    p.sm_ph_p=sm_ph;
-    
-    return doit_new(mgr, par, fp, fn, fn_param,
-                    (WRegionCreateFn*)wrap_load, &p);
-}                                   
+    return doit_load(mgr, par, fp, fn, fn_param, tab, sm_ph);
+}
 
 
 WRegion *region_attach_helper(WRegion *mgr,
         return doit_new(mgr, par, fp, fn, fn_param, 
                         data->u.n.fn, data->u.n.param);
     }else if(data->type==REGION_ATTACH_LOAD){
-        return doit_load(mgr, par, fp, fn, fn_param, data);
+        return doit_load(mgr, par, fp, fn, fn_param, data->u.tab, NULL);
     }else if(data->type==REGION_ATTACH_REPARENT){
         return doit_reparent(mgr, par, fp, fn, fn_param, data->u.reg);
     }else{
 
     int mode=FRAME_MODE_UNKNOWN;
     WFrame *frame;
     
-    extl_table_gets_i(tab, "mode", &mode);
+    if(!extl_table_gets_i(tab, "mode", &mode)){
+        char *tmp;
+        if(extl_table_gets_s(tab, "mode", &tmp)){
+            mode=stringintmap_value(frame_modes, tmp, mode);
+            free(tmp);
+        }
+    }
     
     frame=create_frame(par, fp, mode);
     
 
 static WRegion *groupws_managed_disposeroot(WGroupWS *ws, WRegion *reg)
 {
     if(group_bottom(&ws->grp)==reg){
-        if(group_empty_for_bottom_stdisp(&ws->grp)){
-            WRegion *tmpr=region_disposeroot((WRegion*)ws);
-            return (tmpr!=NULL ? tmpr : reg);
-        }
+        if(group_empty_for_bottom_stdisp(&ws->grp))
+            return region_disposeroot((WRegion*)ws);
     }
     
     return reg;
 
     
     if(group!=NULL){
         UNLINK_ITEM(group->phs, ph, next, prev);
-    }else{
+    }else if(ph->prev!=NULL){
         WGroupPHolder *next=ph->next;
         
-        if(ph->prev!=NULL)
-            ph->prev->next=next;
+        ph->prev->next=next;
 
         if(next==NULL){
             next=get_head(ph);
             assert(next->prev==ph);
         }
         next->prev=ph->prev;
+    }else{
+        /* ph should not be on a list, if prev pointer is NULL (whereas
+         * next alone can be NULL in our semi-doubly-linked lists).
+         */
+        assert(ph->next==NULL);
     }
     
     ph->group=NULL;
 
     local styles={}
     local stylemenu={}
     
-    for look in string.gfind(data, "(look[-_][^\n]*)%.lua\n") do
+    for look in string.gmatch(data, "(look[-_][^\n]*)%.lua\n") do
         if not found[look] then
             found[look]=true
             table.insert(styles, look)
 
 }
 
 
-static WStacking *mplex_do_to_focus(WMPlex *mplex, WStacking *to_try,
-                                    PtrList **hidelist)
-{
-    return mplex_find_to_focus(mplex, to_try, NULL, hidelist);
-}
-
-
 static WStacking *mplex_do_to_focus_on(WMPlex *mplex, WStacking *node,
                                        WStacking *to_try, 
                                        PtrList **hidelist, bool *within)
         }
     }
     
-    st=mplex_do_to_focus(mplex, node, hidelist);
+    st=mplex_find_to_focus(mplex, node, NULL, hidelist);
     
     if(st==node && within!=NULL)
         *within=TRUE;
 }
 
 
-static WStacking *mplex_to_focus(WMPlex *mplex)
+/* 1. Try keep focus in REGION_ACTIVE_SUB.
+ * 2. Try given `node`.
+ * 3. Choose something else, attempting previous in focus history
+ *    (unless `node` was set).
+ */
+static WStacking *mplex_to_focus(WMPlex *mplex, WStacking *node)
 {
-    WStacking *to_try=NULL;
+    WStacking *foc=NULL, *fallback=NULL;
     WRegion *reg=NULL;
+    bool within=FALSE;
     WStacking *st;
     
-    to_try=maybe_focusable(REGION_ACTIVE_SUB(mplex));
+    foc=maybe_focusable(REGION_ACTIVE_SUB(mplex));
     
-    if(to_try==NULL){
-        /* Search focus history */
+    if(foc==NULL && node==NULL){
+        /* Search focus history if no specific attempt set.*/
         for(reg=ioncore_g.focus_current; reg!=NULL; reg=reg->active_next){
-            to_try=has_stacking_within(mplex, reg);
-            if(to_try!=NULL)
+            foc=has_stacking_within(mplex, reg);
+            if(foc!=NULL)
                 break;
         }
     }
     
-    st=mplex_do_to_focus(mplex, to_try, NULL);
+    if(foc!=NULL){
+        /*fallback=mplex_find_to_focus(mplex, foc, NULL, NULL);*/
+        /* In the history search case, 'foc' might point to a group,
+         * since we don't properly try to find a stacking within it...
+         */
+        fallback=mplex_do_to_focus_on(mplex, foc, NULL, NULL, NULL);
+        if(fallback!=foc)
+            foc=NULL;
+    }
+    
+    if(foc==NULL && node!=NULL)
+        foc=mplex_do_to_focus_on(mplex, node, NULL, NULL, &within);
+        
+    if(foc==NULL || !within)
+        foc=fallback;
     
-    return (st!=NULL 
-            ? st 
-            : (mplex->mx_current!=NULL
-               ? mplex->mx_current->st
-               : NULL));
+    return foc;
 }
 
 
 void mplex_do_set_focus(WMPlex *mplex, bool warp)
 {
     if(!MPLEX_MGD_UNVIEWABLE(mplex)){
-        WStacking *st=mplex_to_focus(mplex);
+        WStacking *st=mplex_to_focus(mplex, NULL);
+        
+        if(st==NULL){
+            st=(mplex->mx_current!=NULL
+                ? mplex->mx_current->st
+                : NULL);
+        }
         
         if(st!=NULL){
             region_do_set_focus(st->reg, warp);
 }
 
 
+static void mplex_refocus(WMPlex *mplex, WStacking *node, bool warp)
+{
+    WStacking *foc=mplex_to_focus(mplex, node);
+    
+    if(foc!=NULL)
+        region_maybewarp(foc->reg, warp);
+}
+
+
 /*}}}*/
 
 
 }
 
 
-static bool mplex_refocus(WMPlex *mplex, WStacking *node, bool warp)
-{
-    WStacking *foc=NULL;
-    bool within=FALSE;
-    
-    if(node!=NULL)
-        foc=mplex_do_to_focus_on(mplex, node, NULL, NULL, &within);
-        
-    if(foc==NULL || !within)
-        foc=mplex_to_focus(mplex);
-    
-    if(foc!=NULL)
-        region_maybewarp(foc->reg, warp);
-    
-    return within;
-}
-
-
 bool mplex_do_prepare_focus(WMPlex *mplex, WStacking *node,
                             WStacking *sub, int flags, 
                             WPrepareFocusResult *res)
 
         UNLINK_ITEM(ph->after->phs, ph, next, prev);
     }else if(mplex!=NULL && on_ph_list(mplex->misc_phs, ph)){
         UNLINK_ITEM(mplex->misc_phs, ph, next, prev);
-    }else{
+    }else if(ph->prev!=NULL){
         WMPlexPHolder *next=ph->next;
-        
-        if(ph->prev!=NULL)
-            ph->prev->next=next;
+
+        ph->prev->next=next;
 
         if(next==NULL){
             next=get_head(ph);
             assert(next->prev==ph);
         }
         next->prev=ph->prev;
+    }else{
+        /* ph should not be on a list, if prev pointer is NULL (whereas
+         * next alone can be NULL in our semi-doubly-linked lists).
+         */
+        assert(ph->next==NULL);
     }
     
     ph->after=NULL;
 
     {"free_glue_southwest",  SIZEPOLICY_FREE_GLUE__SOUTHWEST},
     {"free_glue_south",      SIZEPOLICY_FREE_GLUE__SOUTH},
     {"free_glue_southeast",  SIZEPOLICY_FREE_GLUE__SOUTHEAST},
+    {"unconstrained",   SIZEPOLICY_UNCONSTRAINED},
     { NULL,             SIZEPOLICY_DEFAULT}   /* end marker */
 };
 
 
 
 Context:
 
+[lua 5.1 updates to mkexports
+Tuomo Valkonen <tuomov@iki.fi>**20080318210900] 
+
 [Do not remove proxy from cache in object destroy watch handler.
 Tuomo Valkonen <tuomov@iki.fi>**20071215143858
  The GC can remove it. Just have the pointer to the actual object be 
 
 
 -- Helper functions {{{
 
-function errorf(fmt, ...)
-    error(string.format(fmt, unpack(arg)), 2)
+function errorf(...)
+    error(string.format(...), 2)
 end
 
 function matcherr(s)
     error(string.format("Parse error in \"%s...\"", string.sub(s, 1, 50)), 2)
 end
 
-function fprintf(h, fmt, ...)
-    h:write(string.format(fmt, unpack(arg)))
+function fprintf(h, ...)
+    h:write(string.format(...))
 end
 
 function trim(str)
         end
         param=trim(param)
         if string.len(param)>0 then
-            for p in string.gfind(param .. ",", "([^,]*),") do
+            for p in string.gmatch(param .. ",", "([^,]*),") do
                 local spec, objtype, varname=parse_type(p)
                 idesc=idesc .. spec
                 table.insert(itypes, objtype)
 
  * 
  * \begin{tabularx}{\linewidth}{lX}
  *  \tabhead{Field & Description}
- *  \var{scroll_amount} & Number of pixels to scroll at a time 
+ *  \var{scroll_amount} & Number of pixels to scroll at a time in
  *                        pointer-controlled menus when one extends
  *                        beyond a border of the screen and the pointer
  *                        touches that border. \\
 
                 patterns=pat
             elseif string.find(substr, "^[nN][aA][mM][eE]")
                 and patterns then
-                for s in string.gfind(patterns, "%S+") do
+                for s in string.gmatch(patterns, "%S+") do
                     if not string.find(s, "[*?]") then
                         table.insert(mod_query.hostnicks, s)
                     end
     
     -- Descend into tables
     if tocomp and string.len(tocomp)>=1 then
-        for t in string.gfind(tocomp, "([^.:]*)[.:]") do
+        for t in string.gmatch(tocomp, "([^.:]*)[.:]") do
             metas=false
             if string.len(t)==0 then
                 comptab=env;
 
-#define ION_RELEASE "3-20080207"
+#define ION_RELEASE "3-20080411"
 #define ION_VERSION ION_RELEASE
 #define ION_API_VERSION "3"