/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <stdarg.h>

#include "libs/fvwmlib.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "bindings.h"
#include "misc.h"
#include "screen.h"
#include "defaults.h"
#include "module_interface.h"
#include "focus.h"
#include "stack.h"
#include "events.h"
#include "borders.h"
#include "virtual.h"
#include "icons.h"
#include "gnome.h"

/* ----------------------------- stack ring code --------------------------- */

/* If more than this many transients are in a single branch of a transient
 * tree, they will end up in more or less random stacking order. */
#define MAX_TRANSIENTS_IN_BRANCH 200000
/* Same for total levels of transients. */
#define MAX_TRANSIENT_LEVELS     10000
/* This number must fit in a signed int! */
#define LOWER_PENALTY (MAX_TRANSIENTS_IN_BRANCH * MAX_TRANSIENT_LEVELS)

static void __RaiseOrLowerWindow(
  FvwmWindow *t, Bool do_lower, Bool allow_recursion, Bool is_new_window);
static void RaiseOrLowerWindow(
  FvwmWindow *t, Bool do_lower, Bool allow_recursion, Bool is_new_window);
#if 0
static void ResyncFvwmStackRing(void);
#endif
static void ResyncXStackingOrder(void);
static void BroadcastRestack(FvwmWindow *s1, FvwmWindow *s2);
static int collect_transients_recursive(
  FvwmWindow *t, FvwmWindow *list_head, int layer, Bool do_lower);

#define DEBUG_STACK_RING 1
#ifdef DEBUG_STACK_RING
/* debugging function */
static void dump_stack_ring(void)
{
  FvwmWindow *t1;

  if (!debugging_stack_ring)
    return;
  XBell(dpy, 0);
  fprintf(stderr,"dumping stack ring:\n");
  for (t1 = Scr.FvwmRoot.stack_next; t1 != &Scr.FvwmRoot; t1 = t1->stack_next)
  {
    fprintf(stderr,"    l=%d fw=0x%08x f=0x%08x '%s'\n", t1->layer,
	    (int)t1, (int)t1->frame, t1->name);
  }

  return;
}

/* debugging function */
void verify_stack_ring_consistency(void)
{
  Window root, parent, *children;
  unsigned int nchildren, i;
  FvwmWindow *t1, *t2;
  int last_layer;
  int last_index;

  if (!debugging_stack_ring)
    return;
  XSync(dpy, 0);
  t2 = Scr.FvwmRoot.stack_next;
  if (t2 == &Scr.FvwmRoot)
    return;
  last_layer = t2->layer;

  for (t1 = t2->stack_next; t1 != &Scr.FvwmRoot; t2 = t1, t1 = t1->stack_next)
  {
    if (t1->layer > last_layer)
    {
      fprintf(
	stderr,
	"vsrc: stack ring is corrupt! "
	"'%s' (layer %d) is above '%s' (layer %d/%d)\n",
	t1->name, t1->layer, t2->name, t2->layer, last_layer);
      dump_stack_ring();
      return;
    }
    last_layer = t1->layer;
  }
  t2 = &Scr.FvwmRoot;
  for (t1 = t2->stack_next; t1 != &Scr.FvwmRoot; t2 = t1, t1 = t1->stack_next)
  {
    if (t1->stack_prev != t2)
      break;
  }
  if (t1 != &Scr.FvwmRoot || t1->stack_prev != t2)
  {
    fprintf(
      stderr,
      "vsrc: stack ring is corrupt - fvwm will probably crash! "
      "0x%08x -> 0x%08x but 0x%08x <- 0x%08x\n",
      (int)t2, (int)t1, (int)(t1->stack_prev), (int)t1);
    dump_stack_ring();
    return;
  }
  MyXGrabServer(dpy);
  if (!XQueryTree(dpy, Scr.Root, &root, &parent, &children, &nchildren))
  {
    MyXUngrabServer(dpy);
    return;
  }

  last_index = nchildren;
  for (t1 = Scr.FvwmRoot.stack_next; t1 != &Scr.FvwmRoot; t1 = t1->stack_next)
  {
    /* find window in window list */
    for (i = 0; i < nchildren && t1->frame != children[i]; i++)
      ;
    if (i == nchildren)
    {
      fprintf(stderr,"vsrc: window already died: fw=0x%08x w=0x%08x '%s'\n",
	      (int)t1, (int)t1->frame, t1->name);
    }
    else if (i >= last_index)
    {
      fprintf(
	stderr, "vsrc: window is at wrong position in stack ring: "
	"fw=0x%08x f=0x%08x '%s'\n", (int)t1, (int)t1->frame, t1->name);
      dump_stack_ring();
      fprintf(stderr,"dumping X stacking order:\n");
      for (i = nchildren; i-- > 0; )
      {
	for (t1 = Scr.FvwmRoot.stack_next; t1 != &Scr.FvwmRoot;
	     t1 = t1->stack_next)
	{
	  /* only dump frame windows */
	  if (t1->frame == children[i])
	  {
	    fprintf(stderr,"  f=0x%08x\n", (int)children[i]);
	    break;
	  }
	}
      }
      MyXUngrabServer(dpy);
      XFree(children);
      return;
    }
    last_index = i;
  }
  MyXUngrabServer(dpy);
  XFree(children);

  return;
}
#endif

/* Remove a window from the stack ring */
void remove_window_from_stack_ring(FvwmWindow *t)
{
  if (IS_SCHEDULED_FOR_DESTROY(t))
  {
    return;
  }
  t->stack_prev->stack_next = t->stack_next;
  t->stack_next->stack_prev = t->stack_prev;
  /* not really necessary, but gives a little more saftey */
  t->stack_prev = NULL;
  t->stack_next = NULL;
  return;
}

/* Add window t to the stack ring after window t */
void add_window_to_stack_ring_after(FvwmWindow *t, FvwmWindow *add_after_win)
{
  if (IS_SCHEDULED_FOR_DESTROY(t))
  {
    return;
  }
  if (t == add_after_win || t == add_after_win->stack_next)
  {
    /* tried to add the window before or after itself */
    fvwm_msg(ERR, "add_window_to_stack_ring_after",
	     "BUG: tried to add window '%s' %s itself in stack ring\n", t->name,
	     (t == add_after_win) ? "after" : "before");
    return;
  }
  t->stack_next = add_after_win->stack_next;
  add_after_win->stack_next->stack_prev = t;
  t->stack_prev = add_after_win;
  add_after_win->stack_next = t;
  return;
}

/* Add a whole ring of windows. The list_head itself will not be added. */
static void add_windowlist_to_stack_ring_after(
  FvwmWindow *list_head, FvwmWindow *add_after_win)
{
  add_after_win->stack_next->stack_prev = list_head->stack_prev;
  list_head->stack_prev->stack_next = add_after_win->stack_next;
  add_after_win->stack_next = list_head->stack_next;
  list_head->stack_next->stack_prev = add_after_win;
  return;
}

FvwmWindow *get_next_window_in_stack_ring(FvwmWindow *t)
{
  return t->stack_next;
}

FvwmWindow *get_prev_window_in_stack_ring(FvwmWindow *t)
{
  return t->stack_prev;
}

FvwmWindow *get_transientfor_fvwmwindow(FvwmWindow *t)
{
  FvwmWindow *s;

  if (!t || !IS_TRANSIENT(t) || t->transientfor == Scr.Root ||
      t->transientfor == None)
    return NULL;
  for (s = Scr.FvwmRoot.next; s != NULL; s = s->next)
  {
    if (s->w == t->transientfor)
    {
      return (s == t) ? NULL : s;
    }
  }

  return NULL;
}

static FvwmWindow *get_transientfor_top_fvwmwindow(FvwmWindow *t)
{
  FvwmWindow *s;

  s = t;
  while (s && IS_TRANSIENT(s) && DO_STACK_TRANSIENT_PARENT(s))
  {
    s = get_transientfor_fvwmwindow(s);
    if (s)
      t = s;
  }

  return t;
}

/* Takes a window from the top of the stack ring and puts it at the appropriate
 * place. Called when new windows are created. */
Bool position_new_window_in_stack_ring(FvwmWindow *t, Bool do_lower)
{
  if (t->stack_prev != &Scr.FvwmRoot)
    /* Not at top of stack ring, so it is already in place. add_window.c relies
     * on this. */
    return False;
  /* RaiseWindow/LowerWindow will put the window in its layer */
  RaiseOrLowerWindow(t, do_lower, False, True);
  return True;
}


/********************************************************************
 * Raise a target and all higher FVWM-managed windows above any
 *  override_redirects:
 *  - locate the highest override_redirect above our target
 *  - put all the FvwmWindows from the target to the highest FvwmWindow
 *    below the highest override_redirect in the restack list
 *  - configure our target window above the override_redirect sibling,
 *    and restack.
 ********************************************************************/
static void raise_over_unmanaged(FvwmWindow *t)
{
  Window junk;
  Window *tops;
  int i;
  unsigned int num;
  Window OR_Above = None;
  Window *wins;
  int count = 0;
  FvwmWindow *t2 = NULL;
  unsigned int flags;
  XWindowChanges changes;
  XWindowAttributes wa;

  if (!XQueryTree(dpy, Scr.Root, &junk, &junk, &tops, &num))
    return;

  /********************************************************************
   * Locate the highest override_redirect window above our target, and
   * the highest of our windows below it.
   ********************************************************************/
  for (i = 0; i < num && tops[i] != t->frame; i++)
  {
    /* look for target window in list */
  }
  for (; i < num; i++)
  {
    /* It might be just as well (and quicker) just to check for the absence of
     * an FvwmContext instead of for override_redirect... */
    if (!XGetWindowAttributes(dpy, tops[i], &wa))
    {
      continue;
    }
    if (wa.override_redirect == True && wa.class != InputOnly &&
	tops[i] != Scr.NoFocusWin)
    {
      OR_Above = tops[i];
    }
  } /* end for */

  /********************************************************************
   * Count the windows we need to restack, then build the stack list.
   ********************************************************************/
  if (OR_Above)
  {
    for (count = 0, t2 = Scr.FvwmRoot.stack_next; t2 != &Scr.FvwmRoot;
	 t2 = t2->stack_next)
    {
      count++;
      count += get_visible_icon_window_count(t2);
      if (t2 == t)
      {
	break;
      }
    }

    if (count > 0)
    {
      wins = (Window*) safemalloc (count * sizeof (Window));
      for (i = 0, t2 = Scr.FvwmRoot.stack_next; t2 != &Scr.FvwmRoot;
	   t2 = t2->stack_next)
      {
	wins[i++] = t2->frame;
	if (IS_ICONIFIED(t2) && ! IS_ICON_SUPPRESSED(t2))
	{
	  if (t2->icon_w != None)
	  {
	    wins[i++] = t2->icon_w;
          }
	  if (t2->icon_pixmap_w != None)
	  {
	    wins[i++] = t2->icon_pixmap_w;
          }
        }
	if (t2 == t)
	{
	  break;
	}
      }

      memset(&changes, '\0', sizeof(changes));
      changes.sibling = OR_Above;
      changes.stack_mode = Above;
      flags = CWSibling|CWStackMode;

      XConfigureWindow (dpy, t->frame/*topwin*/, flags, &changes);
      XRestackWindows (dpy, wins, i);
      free (wins);
    }
  }/*  end - we found an OR above our target  */

  XFree (tops);
  return;
}


static Bool must_move_transients(
  FvwmWindow *t, Bool do_lower)
{
  if (IS_ICONIFIED(t))
  {
    return False;
  }
  /* raise */
  if ((!do_lower && DO_RAISE_TRANSIENT(t)) ||
      (do_lower && DO_LOWER_TRANSIENT(t)))
  {
    Bool scanning_above_window = True;
    FvwmWindow *q;

    for (q = Scr.FvwmRoot.stack_next;
	 q != &Scr.FvwmRoot && t->layer <= q->layer; q = q->stack_next)
    {
      if (t->layer < q->layer)
      {
	/* We're not interested in higher layers. */
	continue;
      }
      else if (t == q)
      {
	/* We found our window. All further transients are below it. */
	scanning_above_window = False;
      }
      else if (IS_TRANSIENT(q) && (q->transientfor == t->w))
      {
	return True;
      }
      else if (scanning_above_window && !do_lower)
      {
	/* raise: The window is not raised, so itself and all transients will
	 * be raised. */
	return True;
      }
    }
  }
  return False;
}

static Window __get_stacking_sibling(FvwmWindow *fw, Bool do_stack_below)
{
  Window w;

  /* default to frame window */
  w = fw->frame;
  if (IS_ICONIFIED(fw) && do_stack_below == True)
  {
    /* override with icon windows when stacking below */
    if (fw->icon_pixmap_w != None)
    {
      w = fw->icon_pixmap_w;
    }
    else if (fw->icon_w != None)
    {
      w = fw->icon_w;
    }
  }

  return w;
}

static void restack_windows(
  FvwmWindow *r, FvwmWindow *s, int count, Bool do_broadcast_all, Bool do_lower)
{
  FvwmWindow *t;
  unsigned int flags;
  int i;
  XWindowChanges changes;
  Window *wins;
  int do_stack_above;
  int is_reversed;

  if (count <= 0)
  {
    for (count = 0, t = r->stack_next; t != s; t = t->stack_next)
    {
      count++;
      count += get_visible_icon_window_count(t);
    }
  }
  /* restack the windows between r and s */
  wins = (Window*) safemalloc ((count + 3) * sizeof (Window));
  i = 0;
  for (t = r->stack_next; t != s; t = t->stack_next)
  {
    if (i > count)
    {
      fvwm_msg (ERR, "restack_windows", "more transients than expected");
      break;
    }
    wins[i++] = t->frame;
    if (IS_ICONIFIED(t) && !IS_ICON_SUPPRESSED(t))
    {
      if(t->icon_w != None)
	wins[i++] = t->icon_w;
      if(t->icon_pixmap_w != None)
	wins[i++] = t->icon_pixmap_w;
    }
  }

  changes.sibling = __get_stacking_sibling(r, True);
  if (changes.sibling == None)
  {
    changes.sibling = __get_stacking_sibling(s, False);
    is_reversed = 1;
  }
  else
  {
    is_reversed = 0;
  }
  if (changes.sibling == None)
  {
    do_stack_above = !do_lower;
    flags = CWStackMode;
  }
  else
  {
    do_stack_above = 0;
    flags = CWStackMode | CWSibling;
  }
  changes.stack_mode = (do_stack_above ^ is_reversed) ? Above : Below;
  XConfigureWindow (dpy, r->stack_next->frame, flags, &changes);
  if (count > 1)
  {
    XRestackWindows (dpy, wins, count);
  }
  free(wins);
  if (do_broadcast_all)
  {
    /* send out M_RESTACK for all windows, to make sure we don't forget
     * anything. */
    BroadcastRestackAllWindows();
  }
  else
  {
    /* send out (one or more) M_RESTACK packets for windows between r and s */
    BroadcastRestack(r, s);
  }

  return;
}

static void __sort_transient_ring(FvwmWindow *ring)
{
  FvwmWindow *s;
  FvwmWindow *t;
  FvwmWindow *u;
  FvwmWindow *prev;

  if (ring->stack_next->stack_next == ring)
  {
    /* only one or zero windows */
    return;
  }
  /* Implementation note:  this sorting algorithm is about the most inefficient
   * possible.  It just swaps the position of two adjacent windows in the ring
   * if they are in the wrong order.  Since transient windows are rare, this
   * should not cause any notable performance hit.  Because it is important that
   * the order of windows with the same key is not changed, we can not just use
   * qsort() here. */
  for (t = ring->stack_next, prev = ring; t->stack_next != ring;
       prev = t->stack_prev)
  {
    s = t->stack_next;
    if (t->scratch.i < s->scratch.i)
    {
      /* swap windows */
      u = s->stack_next;
      s->stack_next = t;
      t->stack_next = u;
      u = t->stack_prev;
      t->stack_prev = s;
      s->stack_prev = u;
      s->stack_prev->stack_next = s;
      t->stack_next->stack_prev = t;
      if (prev != ring)
      {
	/* move further up the ring? */
	t = prev;
      }
      else
      {
	/* hit start of ring */
      }
    }
    else
    {
      /* correct order, advance one window */
      t = t->stack_next;
    }
  }

  return;
}

static Bool __restack_window(
  FvwmWindow *t, Bool do_lower, Bool do_restack_transients, Bool is_new_window)
{
  FvwmWindow *s, *r, tmp_r;
  int count;
  int test_layer;

  /* detach t, so it doesn't make trouble in the loops */
  remove_window_from_stack_ring(t);

  count = 0;
  if (do_restack_transients)
  {
    /* collect the transients in a temp list */
    tmp_r.stack_prev = &tmp_r;
    tmp_r.stack_next = &tmp_r;
    count = collect_transients_recursive(t, &tmp_r, t->layer, do_lower);
    if (count == 0)
    {
      do_restack_transients = False;
    }
  }
  count += 1 + get_visible_icon_window_count(t);

  test_layer = t->layer;
  if (do_lower)
  {
    test_layer--;
  }
  /* now find the place to reinsert t and friends */
  for (s = Scr.FvwmRoot.stack_next; s != &Scr.FvwmRoot; s = s->stack_next)
  {
    if (test_layer >= s->layer)
    {
      break;
    }
  }
  r = s->stack_prev;

  if (do_restack_transients)
  {
    /* re-sort the transient windows according to their scratch.i register */
    __sort_transient_ring(&tmp_r);
    /* insert all transients between r and s. */
    add_windowlist_to_stack_ring_after(&tmp_r, r);
  }

  /*
  ** Re-insert t - below transients
  */
  add_window_to_stack_ring_after(t, s->stack_prev);

  if (is_new_window && IS_TRANSIENT(t) && DO_STACK_TRANSIENT_PARENT(t) &&
      !IS_ICONIFIED(t))
  {
    /* now that the new transient is properly positioned in the stack ring,
     * raise/lower it again so that its parent is raised/lowered too */
    RaiseOrLowerWindow(t, do_lower, True, False);
    /* make sure the stacking order is correct - may be the sledge-hammer
     * method, but the recursion ist too hard to understand. */
    ResyncXStackingOrder();
    return True;
  }
  else
  {
    /* restack the windows between r and s */
    restack_windows(r, s, count, do_restack_transients, do_lower);
  }

  return False;
}

/* Important note:  __RaiseOrLowerWindow must *only* be called by
 * RaiseOrLowerWindow()! */
static void __RaiseOrLowerWindow(
  FvwmWindow *t, Bool do_lower, Bool allow_recursion, Bool is_new_window)
{
  FvwmWindow *t2;
  Bool do_move_transients;

  /* Do not raise this window after command execution (see HandleButtonPress()).
   */
  SET_SCHEDULED_FOR_RAISE(t, 0);

  /* New windows are simply raised/lowered without touching the transientfor
   * at first.  Then, further down in the code, __RaiseOrLowerWindow() is called
   * again to raise/lower the transientfor if necessary.  We can not do the
   * recursion stuff for new windows because the must_move_transients() call
   * needs a properly ordered stack ring - but the new window is still at the
   * front of the stack ring. */
  if (allow_recursion && !is_new_window && !IS_ICONIFIED(t))
  {
    /*
     * This part makes Raise/Lower on a Transient act on its Main and sibling
     * Transients.
     *
     * The recursion is limited to one level - which caters for most cases.
     * This code does not handle the case where there are trees of Main +
     * Transient (ie where a Main_window_with_Transients is itself Transient
     * for another window).
     *
     * Another strategy is required to handle trees of Main+Transients
     */
    if (IS_TRANSIENT(t) && DO_STACK_TRANSIENT_PARENT(t))
    {
      for (t2 = Scr.FvwmRoot.stack_next; t2 != &Scr.FvwmRoot;
	   t2 = t2->stack_next)
      {
        if (t2->w == t->transientfor)
        {
	  if (t2 == t)
	  {
	    break;
	  }
	  if (IS_ICONIFIED(t2) || t->layer != t2->layer)
	  {
	    break;
	  }
	  if (do_lower && (!IS_TRANSIENT(t2) || !DO_STACK_TRANSIENT_PARENT(t2)))
	  {
	    /* hit the highest level transient; lower this subtree below all
	     * other subtrees of the same window */
	    t->scratch.i = -LOWER_PENALTY;
	  }
	  else
	  {
	    /* Add a bonus to the stack ring position for this branch of the
	     * transient tree over all other branches. */
	    t->scratch.i = MAX_TRANSIENTS_IN_BRANCH;
	  }
	  __RaiseOrLowerWindow(t2, do_lower, True, False);
          if ((!do_lower && DO_RAISE_TRANSIENT(t2)) ||
              (do_lower && DO_LOWER_TRANSIENT(t2))  )
          {
	    /* moving the parent moves our window already */
	    return;
          }
        }
      }
    }
  }

  if (is_new_window)
  {
    do_move_transients = False;
  }
  else
  {
    do_move_transients = must_move_transients(t, do_lower);
  }
  if (__restack_window(
	t, do_lower, do_move_transients, is_new_window) == True)
  {
    return;
  }

  if (!do_lower)
  {
    /* This hack raises the target and all higher FVWM windows over any style
     * grabfocusoff override_redirect windows that may be above it. This is
     * used to cope with ill-behaved applications that insist on using
     * long-lived override_redirects. */
    if (Scr.bo.RaiseOverUnmanaged)
    {
      raise_over_unmanaged(t);
    }

    /*
     * The following is a hack to raise X windows over native windows
     * which is needed for some (all ?) X servers running under Windows
     * or Windows NT.
     */
    if (Scr.bo.RaiseHackNeeded)
    {
      /*
          RBW - 09/20/1999. I find that trying to raise unmanaged windows
          causes problems with some apps. If this seems to work well for
          everyone, I'll remove the #if 0.
      */
#if 0
      /* get *all* toplevels (even including override_redirects) */
      XQueryTree(dpy, Scr.Root, &junk, &junk, &tops, &num);

      /* raise from tmp_win upwards to get them above NT windows */
      for (i = 0; i < num; i++)
      {
        if (tops[i] == t->frame)
	  found = True;
        if (found)
	  XRaiseWindow (dpy, tops[i]);
      }
      XFree (tops);
#endif
      for (t2 = t; t2 != &Scr.FvwmRoot; t2 = t2->stack_prev)
      {
        XRaiseWindow (dpy, t2->frame);
      }
    }
    /*  This needs to be done after all the raise hacks.  */
    raisePanFrames();
    /* If the window has been raised, make sure the decorations are updated
     * immediately in case we are in a complex function (e.g. raise, unshade).
     */
    XSync(dpy, 0);
    handle_all_expose();
  }

  return;
}

static void RaiseOrLowerWindow(
  FvwmWindow *t, Bool do_lower, Bool allow_recursion, Bool is_new_window)
{
  FvwmWindow *fw;

  /* clean the auxiliary registers used in stacking transients */
  for (fw = Scr.FvwmRoot.next; fw != NULL; fw = fw->next)
  {
    fw->scratch.i = 0;
  }
  __RaiseOrLowerWindow(t, do_lower, allow_recursion, is_new_window);

  return;
}

/*
   Raise t and its transients to the top of its layer.
   For the pager to work properly it is necessary that
   RaiseWindow *always* sends a proper M_RESTACK packet,
   even if the stacking order didn't change.
*/
void RaiseWindow(FvwmWindow *t)
{
  BroadcastPacket(M_RAISE_WINDOW, 3, t->w, t->frame, (unsigned long)t);
  RaiseOrLowerWindow(t, False, True, False);
  focus_grab_buttons_on_pointer_window();
#ifdef DEBUG_STACK_RING
  verify_stack_ring_consistency();
#endif
  return;
}

void LowerWindow(FvwmWindow *t)
{
  BroadcastPacket(M_LOWER_WINDOW, 3, t->w, t->frame, (unsigned long)t);
  RaiseOrLowerWindow(t, True, True, False);
  focus_grab_buttons_on_pointer_window();
#ifdef DEBUG_STACK_RING
  verify_stack_ring_consistency();
#endif
  return;
}


static Bool
intersect (int x0, int y0, int w0, int h0,
           int x1, int y1, int w1, int h1)
{
  return !((x0 >= x1 + w1) || (x0 + w0 <= x1) ||
           (y0 >= y1 + h1) || (y0 + h0 <= y1));
}

static Bool
overlap_box (FvwmWindow *r, int x, int y, int w, int h)
{
  if (IS_ICONIFIED(r))
  {
    return ((r->icon_pixmap_w) &&
             intersect (x, y, w, h, r->icon_g.x, r->icon_g.y,
			r->icon_p_width, r->icon_p_height)) ||
           ((r->icon_w) &&
             intersect (x, y, w, h, r->icon_xl_loc,
			r->icon_g.y + r->icon_p_height,
			r->icon_g.width, r->icon_g.height));
  }
  else
  {
    return intersect (x, y, w, h, r->frame_g.x, r->frame_g.y,
                           r->frame_g.width, r->frame_g.height);
  }
}

static Bool
overlap (FvwmWindow *r, FvwmWindow *s)
{
  if (r->Desk != s->Desk)
  {
    return 0;
  }

  if (IS_ICONIFIED(r))
  {
    return ((r->icon_pixmap_w) &&
             overlap_box (s, r->icon_g.x, r->icon_g.y,
                             r->icon_p_width, r->icon_p_height)) ||
           ((r->icon_w) &&
             overlap_box (s, r->icon_xl_loc, r->icon_g.y + r->icon_p_height,
                             r->icon_g.width, r->icon_g.height));
  }
  else
  {
    return overlap_box (s, r->frame_g.x, r->frame_g.y,
                           r->frame_g.width, r->frame_g.height);
  }
}

/* return true if stacking order changed */
Bool
HandleUnusualStackmodes(unsigned int stack_mode, FvwmWindow *r, Window rw,
                                                 FvwmWindow *s, Window sw)
{
  Bool restack = 0;
  FvwmWindow *t;

/*  DBUG("HandleUnusualStackmodes", "called with %d, %lx\n", stack_mode, s);*/

  if (((rw != r->w) ^ IS_ICONIFIED(r)) ||
      (s && (((sw != s->w) ^ IS_ICONIFIED(s)) || (r->Desk != s->Desk))))
  {
    /* one of the relevant windows is unmapped */
    return 0;
  }

  switch (stack_mode)
  {
   case TopIf:
    for (t = r->stack_prev; (t != &Scr.FvwmRoot) && !restack;
	 t = t->stack_prev)
    {
      restack = (((s == NULL) || (s == t)) && overlap (t, r));
    }
    if (restack)
    {
      RaiseWindow (r);
    }
    break;
   case BottomIf:
    for (t = r->stack_next; (t != &Scr.FvwmRoot) && !restack;
	 t = t->stack_next)
    {
      restack = (((s == NULL) || (s == t)) && overlap (t, r));
    }
    if (restack)
    {
      LowerWindow (r);
    }
    break;
   case Opposite:
    restack = (HandleUnusualStackmodes (TopIf, r, rw, s, sw) ||
               HandleUnusualStackmodes (BottomIf, r, rw, s, sw));
    break;
  }
/*  DBUG("HandleUnusualStackmodes", "\t---> %d\n", restack);*/
#ifdef DEBUG_STACK_RING
  verify_stack_ring_consistency();
#endif
  return restack;
}




/*
    RBW - 01/07/1998  -  this is here temporarily - I mean to move it to
    libfvwm eventually, along with some other chain manipulation functions.
*/

#if 0
/*
    ResyncFvwmStackRing -
    Rebuilds the stacking order ring of FVWM-managed windows. For use in cases
    where apps raise/lower their own windows in a way that makes it difficult
    to determine exactly where they ended up in the stacking order.
    - Based on code from Matthias Clasen.
*/
static void ResyncFvwmStackRing (void)
{
  Window root, parent, *children;
  unsigned int nchildren, i;
  FvwmWindow *t1, *t2;

  MyXGrabServer (dpy);

  if (!XQueryTree (dpy, Scr.Root, &root, &parent, &children, &nchildren))
  {
    MyXUngrabServer (dpy);
    return;
  }

  t2 = &Scr.FvwmRoot;
  for (i = 0; i < nchildren; i++)
  {
    for (t1 = Scr.FvwmRoot.next; t1 != NULL; t1 = t1->next)
    {
      if (IS_ICONIFIED(t1) && !IS_ICON_SUPPRESSED(t1))
      {
	if (t1->icon_w == children[i] || t1->icon_pixmap_w == children[i])
	{
	  break;
	}
      }
      else
      {
	if (t1->frame == children[i])
	{
	  break;
	}
      }
    }

    if (t1 != NULL && t1 != t2)
    {
      /*
       * Move the window to its new position, working from the bottom up
       * (that's the way XQueryTree presents the list).
       */
      /* Pluck from chain. */
      remove_window_from_stack_ring(t1);
      add_window_to_stack_ring_after(t1, t2->stack_prev);
      if (t2 != &Scr.FvwmRoot && t2->layer > t1->layer)
      {
	/* oops, now our stack ring is out of order! */
	/* emergency fix */
	t1->layer = t2->layer;
      }
      t2 = t1;
    }
  }

  MyXUngrabServer (dpy);

  XFree (children);
}
#endif

/* same as above but synchronizes the stacking order in X from the stack ring.
 */
static void ResyncXStackingOrder(void)
{
  Window *wins;
  FvwmWindow *t;
  int count;
  int i;

  for (count = 0, t = Scr.FvwmRoot.next; t != NULL; count++, t = t->next)
    ;
  if (count > 0)
  {
    wins = (Window *)safemalloc(3 * count * sizeof (Window));

    for (i = 0, t = Scr.FvwmRoot.stack_next; count--; t = t->stack_next)
    {
      wins[i++] = t->frame;
      if (IS_ICONIFIED(t) && !IS_ICON_SUPPRESSED(t))
      {
	if (t->icon_w != None)
	  wins[i++] = t->icon_w;
	if (t->icon_pixmap_w != None)
	  wins[i++] = t->icon_pixmap_w;
      }
    }
    XRestackWindows(dpy, wins, i);
    free(wins);
    /* send out M_RESTACK for all windows, to make sure we don't forget
     * anything. */
    BroadcastRestackAllWindows();
  }
}


/* send RESTACK packets for all windows between s1 and s2 */
static void BroadcastRestack(FvwmWindow *s1, FvwmWindow *s2)
{
  FvwmWindow *fw;
  int num;
  int i;
  int n;
  unsigned long *body, *bp, length;
  unsigned long max_wins_per_packet;
  extern Time lastTimestamp;

  if (s2 == &Scr.FvwmRoot)
  {
    s2 = s2->stack_prev;
    if (s2 == &Scr.FvwmRoot)
      return;
  }
  if (s1 == &Scr.FvwmRoot)
  {
    s1 = s1->stack_next;
    if (s1 == &Scr.FvwmRoot)
      return;
    /* s1 has been moved to the top of stack */
    BroadcastPacket (M_RAISE_WINDOW, 3, s1->w, s1->frame, (unsigned long)s1);
    if (s1->stack_next == s2)
    {
      /* avoid sending empty RESTACK packet */
      return;
    }
  }
  if (s1 == s2)
  {
    /* A useful M_RESTACK packet must contain at least two windows. */
    return;
  }
  for (fw = s1, num = 1; fw != s2 && fw != &Scr.FvwmRoot;
       fw = fw->stack_next, num++)
  {
	  /* nothing */
  }
  max_wins_per_packet = (FvwmPacketMaxSize - FvwmPacketHeaderSize) / 3;
  /* split packet if it is too long */
  for ( ; num > 1; s1 = fw, num -= n)
  {
    n = min(num, max_wins_per_packet) - 1;
    length = FvwmPacketHeaderSize + 3 * (n + 1);
    body = (unsigned long *)safemalloc(length * sizeof(unsigned long));
    bp = body;
    *(bp++) = START_FLAG;
    *(bp++) = M_RESTACK;
    *(bp++) = length;
    *(bp++) = lastTimestamp;
    for (fw = s1, i = 0; i <= n; i++, fw = fw->stack_next)
    {
      *(bp++) = fw->w;
      *(bp++) = fw->frame;
      *(bp++) = (unsigned long)fw;
    }
    for (i = 0; i < npipes; i++)
    {
      PositiveWrite(i, body, length*sizeof(unsigned long));
    }
    free(body);
  }
#ifdef DEBUG_STACK_RING
  verify_stack_ring_consistency();
#endif

  return;
}

void BroadcastRestackAllWindows(void)
{
  BroadcastRestack(Scr.FvwmRoot.stack_next, Scr.FvwmRoot.stack_prev);
  return;
}

/* send RESTACK packets for t, t->stack_prev and t->stack_next */
void BroadcastRestackThisWindow(FvwmWindow *t)
{
  BroadcastRestack(t->stack_prev, t->stack_next);
  return;
}

/* ----------------------------- layer code -------------------------------- */

/* returns 0 if s and t are on the same layer, <1 if t is on a lower layer and
 * >1 if t is on a higher layer. */
int compare_window_layers(FvwmWindow *t, FvwmWindow *s)
{
  return t->layer - s->layer;
}

void set_default_layer(FvwmWindow *t, int layer)
{
  t->default_layer = layer;
  return;
}

void set_layer(FvwmWindow *t, int layer)
{
  t->layer = layer;
  return;
}

int get_layer(FvwmWindow *t)
{
  return t->layer;
}

/* This function recursively finds the transients of the window t and sets their
 * is_in_transient_subtree flag.  If a layer is given, only windows in this
 * layer are checked.  If the layer is < 0, all windows are considered.
 */
void mark_transient_subtree(
  FvwmWindow *t, int layer, int mark_mode, Bool do_ignore_icons,
  Bool use_window_group_hint)
{
  FvwmWindow *s;
  FvwmWindow *start;
  FvwmWindow *end;
  Bool is_finished;

  if (layer >= 0 && t->layer != layer)
    return;
  /* find out on which windows to operate */
  if (layer >= 0)
  {
    /* only work on the given layer */
    start = &Scr.FvwmRoot;
    end = &Scr.FvwmRoot;
    for (s = Scr.FvwmRoot.stack_next; s != &Scr.FvwmRoot && s->layer >= layer;
	 s = s->stack_next)
    {
      if (s->layer == layer)
      {
	if (start == &Scr.FvwmRoot)
	  start = s;
	end = s->stack_next;
      }
    }
  }
  else
  {
    /* work on complete window list */
    start = Scr.FvwmRoot.stack_next;
    end = &Scr.FvwmRoot;
  }
  /* clean the temporary flag in all windows and precalculate the transient
   * frame windows */
  for (s = Scr.FvwmRoot.stack_next; s != &Scr.FvwmRoot; s = s->stack_next)
  {
    SET_IN_TRANSIENT_SUBTREE(s, 0);
    if (IS_TRANSIENT(s) && (layer < 0 || layer == s->layer))
    {
      s->scratch.p = get_transientfor_fvwmwindow(s);
    }
    else
    {
      s->scratch.p = NULL;
    }
  }
  /* now loop over the windows and mark the ones we need to move */
  SET_IN_TRANSIENT_SUBTREE(t, 1);
  is_finished = False;
  while (!is_finished)
  {
    FvwmWindow *r;
    FvwmWindow *u;

    /* recursively search for all transient windows */
    is_finished = True;
    for (s = start; s != end; s = s->stack_next)
    {
      Bool use_group_hint = False;

      if (IS_IN_TRANSIENT_SUBTREE(s))
	continue;
      if (use_window_group_hint && DO_ICONIFY_WINDOW_GROUPS(s) && s->wmhints &&
	  (s->wmhints->flags & WindowGroupHint) &&
	  (s->wmhints->window_group != None) &&
	  (s->wmhints->window_group != s->w) &&
	  (s->wmhints->window_group != Scr.Root))
      {
	use_group_hint = True;
      }
      if (!IS_TRANSIENT(s) && !use_group_hint)
	continue;
      if (do_ignore_icons && IS_ICONIFIED(s))
	continue;
      r = (FvwmWindow *)s->scratch.p;
      if (IS_TRANSIENT(s))
      {
	if (r && IS_IN_TRANSIENT_SUBTREE(r) &&
	    ((mark_mode == MARK_ALL) ||
	     (mark_mode == MARK_LOWER && DO_LOWER_TRANSIENT(r)) ||
	     (mark_mode == MARK_RAISE && DO_RAISE_TRANSIENT(r))))
	{
	  /* have to move this one too */
	  SET_IN_TRANSIENT_SUBTREE(s, 1);
	  /* used for stacking transients */
	  s->scratch.i += r->scratch.i + 1;
	  /* need another scan through the list */
	  is_finished = False;
	  continue;
	}
      }
      if (use_group_hint)
      {
	for (u = start; u != end; u = u->stack_next)
	{
	  if (u->w == s->wmhints->window_group ||
	      (u->wmhints && (u->wmhints->flags & WindowGroupHint) &&
	       u->wmhints->window_group == s->wmhints->window_group))
	  {
	    if (IS_IN_TRANSIENT_SUBTREE(u))
	    {
	      /* have to move this one too */
	      SET_IN_TRANSIENT_SUBTREE(s, 1);
	      /* need another scan through the list */
	      is_finished = False;
	    }
	  }
	}
      }
    } /* for */
  } /* while */

  return;
}

static int collect_transients_recursive(
  FvwmWindow *t, FvwmWindow *list_head, int layer, Bool do_lower)
{
  FvwmWindow *s;
  int count = 0;

  mark_transient_subtree(
    t, layer, (do_lower) ? MARK_LOWER : MARK_RAISE, True, False);
  /* now collect the marked windows in a separate list */
  for (s = Scr.FvwmRoot.stack_next; s != &Scr.FvwmRoot; )
  {
    FvwmWindow *tmp;

    tmp = s->stack_next;
    if (IS_IN_TRANSIENT_SUBTREE(s))
    {
      remove_window_from_stack_ring(s);
      add_window_to_stack_ring_after(s, list_head->stack_prev);
      count++;
      count += get_visible_icon_window_count(t);
    }
    s = tmp;
  }

  return count;
}

void new_layer(FvwmWindow *tmp_win, int layer)
{
  FvwmWindow *s;
  FvwmWindow *target;
  FvwmWindow *prev;
  FvwmWindow list_head;
  int add_after_layer;
  int count;
  Bool do_lower;

  if (layer < 0)
  {
    layer = 0;
  }
  tmp_win = get_transientfor_top_fvwmwindow(tmp_win);
  if (layer == tmp_win->layer)
    return;
  list_head.stack_next = &list_head;
  list_head.stack_prev = &list_head;
  count = collect_transients_recursive(
    tmp_win, &list_head, tmp_win->layer, (layer < tmp_win->layer));
  if (count == 0)
  {
    /* no windows to move */
    return;
  }

  add_after_layer = layer;
  if (layer < tmp_win->layer)
  {
    /* lower below the windows in the new (lower) layer */
    add_after_layer = layer;
    do_lower = True;
  }
  else
  {
    /* raise above the windows in the new (higher) layer */
    add_after_layer = layer + 1;
    do_lower = False;
  }
  /* find the place to insert the windows */
  for (target = Scr.FvwmRoot.stack_next; target != &Scr.FvwmRoot;
       target = target->stack_next)
  {
    if (target->layer < add_after_layer)
    {
      /* add all windows before the current window */
      break;
    }
  }
  /* insert windows at new position */
  add_windowlist_to_stack_ring_after(&list_head, target->stack_prev);
  prev = NULL;
  for (s = list_head.stack_next; prev != list_head.stack_prev;
       prev = s, s = s->stack_next)
  {
    s->layer = layer;
    GNOME_SetLayer(tmp_win);
  }
  /* move the windows without modifying their stacking order */
  restack_windows(
    list_head.stack_next->stack_prev, target, count, (count > 1), do_lower);

  return;
}

/* ----------------------------- common functions -------------------------- */


  /*  RBW - 11/13/1998 - 2 new fields to init - stacking order chain.  */
void init_stack_and_layers(void)
{
  Scr.BottomLayer  = DEFAULT_BOTTOM_LAYER;
  Scr.DefaultLayer = DEFAULT_DEFAULT_LAYER;
  Scr.TopLayer     = DEFAULT_TOP_LAYER;
  Scr.FvwmRoot.stack_next = &Scr.FvwmRoot;
  Scr.FvwmRoot.stack_prev = &Scr.FvwmRoot;
  set_layer(&Scr.FvwmRoot, DEFAULT_ROOT_WINDOW_LAYER);
  return;
}


static Bool is_on_top_of_layer_ignore_rom(FvwmWindow *fw)
{
  FvwmWindow *t;
  Bool ontop = True;

  if (IS_SCHEDULED_FOR_DESTROY(fw))
  {
    /* stack ring members are no longer valid */
    return False;
  }
  if (DO_RAISE_TRANSIENT(fw))
  {
    mark_transient_subtree(fw, fw->layer, MARK_RAISE, True, False);
  }
  for (t = fw->stack_prev; t != &Scr.FvwmRoot; t = t->stack_prev)
  {
    if (t->layer > fw->layer)
    {
      break;
    }
    if (t->Desk != fw->Desk)
    {
      continue;
    }
    /* For RaiseOverUnmanaged we can not determine if the window is on top by
     * checking if the window overlaps another one.  If it was below unmanaged
     * windows, but on top of its layer, it would be considered on top. */
    if (Scr.bo.RaiseOverUnmanaged || overlap(fw, t))
    {
      if (!DO_RAISE_TRANSIENT(fw) || (!IS_IN_TRANSIENT_SUBTREE(t) && t != fw))
      {
	ontop = False;
	break;
      }
    }
  }

  return ontop;
}

Bool is_on_top_of_layer(FvwmWindow *fw)
{
  if (Scr.bo.RaiseOverUnmanaged)
  {
    return False;
  }
  return is_on_top_of_layer_ignore_rom(fw);
}

/* ----------------------------- built in functions ------------------------ */

void CMD_Raise(F_CMD_ARGS)
{
  if (DeferExecution(eventp,&w,&tmp_win,&context, CRS_SELECT,ButtonRelease))
    return;

  RaiseWindow(tmp_win);
}

void CMD_Lower(F_CMD_ARGS)
{
  if (DeferExecution(eventp,&w,&tmp_win,&context, CRS_SELECT, ButtonRelease))
    return;

  LowerWindow(tmp_win);
}

void CMD_RaiseLower(F_CMD_ARGS)
{
  Bool ontop;

  if (DeferExecution(eventp,&w,&tmp_win,&context, CRS_SELECT,ButtonRelease))
  {
#ifdef DEBUG_STACK_RING
    dump_stack_ring();
#endif
    return;
  }
  ontop = is_on_top_of_layer_ignore_rom(tmp_win);
  if (ontop)
    LowerWindow(tmp_win);
  else
    RaiseWindow(tmp_win);

  return;
}

void CMD_Layer(F_CMD_ARGS)
{
  int n, layer, val[2];
  char *token;

  if (DeferExecution(eventp,&w,&tmp_win,&context, CRS_SELECT,ButtonRelease))
    return;

  if(tmp_win == NULL)
    return;

  token = PeekToken(action, NULL);
  if (StrEquals("default", token))
  {
    layer = tmp_win->default_layer;
  }
  else
  {
    n = GetIntegerArguments(action, NULL, val, 2);

    layer = tmp_win->layer;
    if ((n == 1) ||
	((n == 2) && (val[0] != 0)))
    {
      layer += val[0];
    }
    else if ((n == 2) && (val[1] >= 0))
    {
      layer = val[1];
    }
    else
    {
      layer = tmp_win->default_layer;
    }
  }
  if (layer < 0)
  {
    layer = 0;
  }
  new_layer(tmp_win, layer);

#ifdef DEBUG_STACK_RING
  verify_stack_ring_consistency();
#endif
}

void CMD_DefaultLayers(F_CMD_ARGS)
{
  char *bot = NULL;
  char *def = NULL;
  char *top = NULL;
  int i;

  bot = PeekToken(action, &action);
  if (bot)
  {
    i = atoi (bot);
    if (i < 0)
    {
      fvwm_msg(ERR,"DefaultLayers", "Layer must be non-negative." );
    }
    else
    {
      Scr.BottomLayer = i;
    }
  }

  def = PeekToken(action, &action);
  if (def)
  {
    i = atoi (def);
    if (i < 0)
    {
      fvwm_msg(ERR,"DefaultLayers", "Layer must be non-negative." );
    }
    else
    {
      Scr.DefaultLayer = i;
    }
  }

  top = PeekToken(action, &action);
  if (top)
  {
    i = atoi (top);
    if (i < 0)
    {
      fvwm_msg(ERR,"DefaultLayers", "Layer must be non-negative." );
    }
    else
    {
      Scr.TopLayer = i;
    }
  }

#ifdef DEBUG_STACK_RING
  verify_stack_ring_consistency();
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1