/* 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
 */

/****************************************************************************
 * This module is all new
 * by Rob Nation
 *
 * This code does smart-placement initial window placement stuff
 *
 * Copyright 1994 Robert Nation. No restrictions are placed on this code,
 * as long as the copyright notice is preserved . No guarantees or
 * warrantees of any sort whatsoever are given or implied or anything.
 ****************************************************************************/

#include "config.h"

#include <stdio.h>

#include "libs/fvwmlib.h"
#include "libs/FScreen.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "bindings.h"
#include "misc.h"
#include "screen.h"
#include "placement.h"
#include "geometry.h"
#include "update.h"
#include "style.h"
#include "borders.h"
#include "move_resize.h"
#include "virtual.h"
#include "stack.h"
#include "add_window.h"

#ifndef MIN
#define MIN(A,B) ((A)<(B)? (A):(B))
#endif
#ifndef MAX
#define MAX(A,B) ((A)>(B)? (A):(B))
#endif

static int get_next_x(
  FvwmWindow *t, rectangle *screen_g,
  int x, int y, int pdeltax, int pdeltay, int use_percent);
static int get_next_y(
  FvwmWindow *t, rectangle *screen_g, int y, int pdeltay, int use_percent);
static float test_fit(
  FvwmWindow *t, rectangle *screen_g,
  int x11, int y11, float aoimin, int pdeltax, int pdeltay, int use_percent);
static void CleverPlacement(
  FvwmWindow *t, rectangle *screen_g,
  int *x, int *y, int pdeltax, int pdeltay, int use_percent);

static int SmartPlacement(
  FvwmWindow *t, rectangle *screen_g,
  int width, int height, int *x, int *y, int pdeltax, int pdeltay)
{
  int PageLeft   = screen_g->x - pdeltax;
  int PageTop    = screen_g->y - pdeltay;
  int PageRight  = PageLeft + screen_g->width;
  int PageBottom = PageTop + screen_g->height;
  int temp_h;
  int temp_w;
  int test_x = 0;
  int test_y = 0;
  int loc_ok = False;
  int tw = 0;
  int tx = 0;
  int ty = 0;
  int th = 0;
  FvwmWindow *test_window;
  int stickyx;
  int stickyy;
  Bool do_test;

  test_x = PageLeft;
  test_y = PageTop;
  temp_h = height;
  temp_w = width;

  for ( ; test_y + temp_h < PageBottom && !loc_ok; )
  {
    test_x = PageLeft;
    while (test_x + temp_w < PageRight && !loc_ok)
    {
      loc_ok = True;
      test_window = Scr.FvwmRoot.next;
      for ( ; test_window != NULL && loc_ok; test_window = test_window->next)
      {
	if (t == test_window)
	  continue;
        /*  RBW - account for sticky windows...  */
        if (test_window->Desk == t->Desk || IS_STICKY(test_window))
        {
          if (IS_STICKY(test_window))
	  {
	    stickyx = pdeltax;
	    stickyy = pdeltay;
	  }
          else
	  {
	    stickyx = 0;
	    stickyy = 0;
	  }
	  do_test = False;
          if(PLACEMENT_AVOID_ICON != 0 && IS_ICONIFIED(test_window) &&
	     !IS_ICON_UNMAPPED(test_window) && test_window->icon_w)
          {
            tw=test_window->icon_p_width;
            th=test_window->icon_p_height+
              test_window->icon_g.height;
            tx = test_window->icon_g.x - stickyx;
            ty = test_window->icon_g.y - stickyy;
	    do_test = True;
          }
          else if(!IS_ICONIFIED(test_window))
          {
            tw = test_window->frame_g.width;
            th = test_window->frame_g.height;
            tx = test_window->frame_g.x - stickyx;
            ty = test_window->frame_g.y - stickyy;
	    do_test = True;
          }
	  if (do_test)
	  {
            if (tx < test_x + width  && test_x < tx + tw &&
		ty < test_y + height && test_y < ty + th)
            {
	      /* window overlaps, look for a different place */
              loc_ok = False;
              test_x = tx + tw - 1;
            }
	  }
        } /* if (test_window->Desk == t->Desk || IS_STICKY(test_window)) */
      } /* for */
      if (!loc_ok)
	test_x +=1;
    } /* while */
    if (!loc_ok)
      test_y +=1;
  } /* while */
  if (loc_ok == False)
  {
    return False;
  }
  *x = test_x;
  *y = test_y;

  return True;
}


/* CleverPlacement by Anthony Martin <amartin@engr.csulb.edu>
 * This function will place a new window such that there is a minimum amount
 * of interference with other windows.  If it can place a window without any
 * interference, fine.  Otherwise, it places it so that the area of of
 * interference between the new window and the other windows is minimized */
static void CleverPlacement(
  FvwmWindow *t, rectangle *screen_g,
  int *x, int *y, int pdeltax, int pdeltay, int use_percent)
{
  int test_x;
  int test_y;
  int xbest;
  int ybest;
  /* area of interference */
  float aoi;
  float aoimin;
  int PageLeft = screen_g->x - pdeltax;
  int PageTop = screen_g->y - pdeltay;

  test_x = PageLeft;
  test_y = PageTop;
  aoi = test_fit(
    t, screen_g, test_x, test_y, -1, pdeltax, pdeltay, use_percent);
  aoimin = aoi;
  xbest = test_x;
  ybest = test_y;

  while (aoi != 0 && aoi != -1)
  {
    if (aoi > 0)
    {
      /* Windows interfere.  Try next x. */
      test_x = get_next_x(
	t, screen_g, test_x, test_y, pdeltax, pdeltay, use_percent);
    }
    else
    {
      /* Out of room in x direction. Try next y. Reset x.*/
      test_x = PageLeft;
      test_y = get_next_y(t, screen_g, test_y, pdeltay, use_percent);
    }
    aoi = test_fit(
      t, screen_g, test_x, test_y, aoimin, pdeltax, pdeltay, use_percent);
    /* I've added +0.00001 because whith my machine the < test fail with
     * certain *equal* float numbers! */
    if (aoi >= 0 && aoi + 0.00001 < aoimin)
    {
      xbest = test_x;
      ybest = test_y;
      aoimin = aoi;
    }
  }
  *x = xbest;
  *y = ybest;

  return;
}

static int get_next_x(
  FvwmWindow *t, rectangle *screen_g, int x, int y, int pdeltax, int pdeltay,
  int use_percent)
{
  FvwmWindow *testw;
  int xnew;
  int xtest;
  int PageLeft = screen_g->x - pdeltax;
  int PageRight = PageLeft + screen_g->width;
  int minx_inc = screen_g->width / 20;
  int stickyx;
  int stickyy;

  /* Test window at far right of screen */
  xnew = PageRight;
  xtest = PageRight - t->frame_g.width;
  if (xtest > x)
    xnew = MIN(xnew, xtest);
  /* Test the values of the right edges of every window */
  for (testw = Scr.FvwmRoot.next ; testw != NULL ; testw = testw->next)
  {
    if (testw == t || (testw->Desk != t->Desk && !IS_STICKY(testw)))
      continue;

    if (IS_STICKY(testw))
    {
      stickyx = pdeltax;
      stickyy = pdeltay;
    }
    else
    {
      stickyx = 0;
      stickyy = 0;
    }

    if (IS_ICONIFIED(testw))
    {
      if (y < testw->icon_p_height + testw->icon_g.height + testw->icon_g.y
	  - stickyy &&
	  testw->icon_g.y - stickyy < t->frame_g.height + y)
      {
        xtest = PageLeft + testw->icon_p_width + testw->icon_g.x - stickyx;
        if (xtest > x)
          xnew = MIN(xnew, xtest);
        xtest = PageLeft + testw->icon_g.x - stickyx - t->frame_g.width;
        if (xtest > x)
          xnew = MIN(xnew, xtest);
      }
    }
    else if (y < testw->frame_g.height + testw->frame_g.y - stickyy &&
	     testw->frame_g.y - stickyy < t->frame_g.height + y)
    {
      xtest = PageLeft + testw->frame_g.width + testw->frame_g.x - stickyx;
      if (xtest > x)
        xnew = MIN(xnew, xtest);
      xtest = PageLeft + testw->frame_g.x - stickyx - t->frame_g.width;
      if (xtest > x)
        xnew = MIN(xnew, xtest);
    }
  }
  if (use_percent)
    xnew = MIN(xnew, x + minx_inc);

  return xnew;
}
static int get_next_y(
  FvwmWindow *t, rectangle *screen_g, int y, int pdeltay, int use_percent)
{
  FvwmWindow *testw;
  int ynew;
  int ytest;
  int PageBottom = screen_g->y + screen_g->height - pdeltay;
  int miny_inc = (screen_g->height / 20);
  int stickyy;

  /* Test window at far bottom of screen */
  ynew = PageBottom;
  ytest = PageBottom - t->frame_g.height;
  if (ytest > y)
    ynew = MIN(ynew, ytest);
  /* Test the values of the bottom edge of every window */
  for (testw = Scr.FvwmRoot.next ; testw != NULL ; testw = testw->next)
  {
    if (testw == t || (testw->Desk != t->Desk && !IS_STICKY(testw)))
      continue;

    if (IS_STICKY(testw))
    {
      stickyy = pdeltay;
    }
    else
    {
      stickyy = 0;
    }

    if (IS_ICONIFIED(testw))
    {
      ytest =
	testw->icon_p_height + testw->icon_g.height + testw->icon_g.y - stickyy;
      if (ytest > y)
        ynew = MIN(ynew, ytest);
      ytest = testw->icon_g.y - stickyy - t->frame_g.height;
      if (ytest > y)
        ynew = MIN(ynew, ytest);
    }
    else
    {
      ytest = testw->frame_g.height + testw->frame_g.y - stickyy;
      if (ytest > y)
        ynew = MIN(ynew, ytest);
      ytest = testw->frame_g.y - stickyy - t->frame_g.height;
      if (ytest > y)
        ynew = MIN(ynew, ytest);
    }
  }
  if (use_percent)
    ynew = MIN(ynew,y+miny_inc);

  return ynew;
}

static float test_fit(
  FvwmWindow *t, rectangle *screen_g,
  int x11, int y11, float aoimin, int pdeltax, int pdeltay, int use_percent)
{
  FvwmWindow *testw;
  int x12;
  int x21;
  int x22;
  int y12;
  int y21;
  int y22;
  /* xleft, xright, ytop, ybottom */
  int xl;
  int xr;
  int yt;
  int yb;
  /* area of interference */
  float aoi = 0;
  float anew;
  float cover_factor = 1;
  float avoidance_factor;
  int PageRight  = screen_g->x + screen_g->width - pdeltax;
  int PageBottom = screen_g->y + screen_g->height - pdeltay;
  int stickyx, stickyy;

  x12 = x11 + t->frame_g.width;
  y12 = y11 + t->frame_g.height;

  if (y12 > PageBottom) /* No room in y direction */
    return -1;
  if (x12 > PageRight) /* No room in x direction */
    return -2;
  for (testw = Scr.FvwmRoot.next ; testw != NULL ; testw = testw->next)
  {
    if (testw == t || (testw->Desk != t->Desk && !IS_STICKY(testw)))
       continue;

    if (IS_STICKY(testw))
    {
      stickyx = pdeltax;
      stickyy = pdeltay;
    }
    else
    {
      stickyx = 0;
      stickyy = 0;
    }

    if (IS_ICONIFIED(testw))
    {
       if (testw->icon_w == None || IS_ICON_UNMAPPED(testw))
	  continue;
       x21 = testw->icon_g.x - stickyx;
       y21 = testw->icon_g.y - stickyy;
       x22 = x21 + testw->icon_p_width;
       y22 = y21 + testw->icon_p_height + testw->icon_g.height;
    }
    else
    {
       x21 = testw->frame_g.x - stickyx;
       y21 = testw->frame_g.y - stickyy;
       x22 = x21 + testw->frame_g.width;
       y22 = y21 + testw->frame_g.height;
    }
    if (x11 < x22 && x12 > x21 &&
	y11 < y22 && y12 > y21)
    {
      /* Windows interfere */
      xl = MAX(x11, x21);
      xr = MIN(x12, x22);
      yt = MAX(y11, y21);
      yb = MIN(y12, y22);
      anew = (xr - xl) * (yb - yt);
      if (IS_ICONIFIED(testw))
        avoidance_factor = PLACEMENT_AVOID_ICON;
      else if(compare_window_layers(testw, t) > 0)
        avoidance_factor = PLACEMENT_AVOID_ONTOP;
      else if(compare_window_layers(testw, t) < 0)
        avoidance_factor = PLACEMENT_AVOID_BELOW;
      else if(IS_STICKY(testw))
        avoidance_factor = PLACEMENT_AVOID_STICKY;
      else
        avoidance_factor = 1.0;

      if (use_percent)
      {
	/* normalisation */
	if ((x22 - x21) * (y22 - y21) != 0 && (x12 - x11) * (y12 - y11) != 0)
	{
	  anew = 100 * MAX(anew / ((x22 - x21) * (y22 - y21)),
			   anew / ((x12 - x11) * (y12 - y11)));
	  if (anew >= 99)
	    cover_factor = PLACEMENT_AVOID_COVER_99;
	  else if (anew >= 95)
	    cover_factor = PLACEMENT_AVOID_COVER_95;
	  else if (anew >= 85)
	    cover_factor = PLACEMENT_AVOID_COVER_85;
	  else if (anew >= 75)
	    cover_factor = PLACEMENT_AVOID_COVER_75;
	}
	if (avoidance_factor>1)
	  avoidance_factor =
	    avoidance_factor + (cover_factor > 1) ? cover_factor : 0;
	else
	  avoidance_factor = cover_factor;
      }
      anew *= avoidance_factor;
      aoi += anew;
      if (aoi > aoimin && aoimin != -1)
        return aoi;
    }
  }
  return aoi;
}


/**************************************************************************
 *
 * Handles initial placement and sizing of a new window
 *
 * Return value:
 *
 *   0 = window lost
 *   1 = OK
 *   2 = OK, window must be resized too
 *
 **************************************************************************/
Bool PlaceWindow(
  FvwmWindow *tmp_win, style_flags *sflags,
  int Desk, int PageX, int PageY, int XineramaScreen, int mode)
{
  FvwmWindow *t;
  int xl = -1;
  int yt;
  int DragWidth;
  int DragHeight;
  int px = 0;
  int py = 0;
  int pdeltax = 0;
  int pdeltay = 0;
  int PageLeft;
  int PageTop;
  int PageRight;
  int PageBottom;
  rectangle screen_g;
  Bool rc = False;
  struct
  {
    unsigned do_forbid_manual_placement : 1;
    unsigned do_honor_starts_on_page : 1;
    unsigned do_honor_starts_on_screen : 1;
    unsigned is_smartly_placed : 1;
    unsigned do_not_use_wm_placement : 1;
  } flags;
  extern Bool Restarting;
  extern Bool PPosOverride;

  memset(&flags, 0, sizeof(flags));

  /* Select a desk to put the window on (in list of priority):
   * 1. Sticky Windows stay on the current desk.
   * 2. Windows specified with StartsOnDesk go where specified
   * 3. Put it on the desk it was on before the restart.
   * 4. Transients go on the same desk as their parents.
   * 5. Window groups stay together (if the KeepWindowGroupsOnDesk style is
   *    used).
   */

  /*
   *  Let's get the StartsOnDesk/Page tests out of the way first.
   */
  if (SUSE_START_ON_DESK(sflags) || SUSE_START_ON_SCREEN(sflags))
  {
    flags.do_honor_starts_on_page = True;
    flags.do_honor_starts_on_screen = True;
    /*
     * Honor the flag unless...
     * it's a restart or recapture, and that option's disallowed...
     */
    if (PPosOverride && (Restarting || (Scr.flags.windows_captured)) &&
	!SRECAPTURE_HONORS_STARTS_ON_PAGE(sflags))
    {
      flags.do_honor_starts_on_page = False;
      flags.do_honor_starts_on_screen = False;
    }
    /*
     * it's a cold start window capture, and that's disallowed...
     */
    if (PPosOverride && (!Restarting && !(Scr.flags.windows_captured)) &&
	!SCAPTURE_HONORS_STARTS_ON_PAGE(sflags))
    {
      flags.do_honor_starts_on_page = False;
      flags.do_honor_starts_on_screen = False;
    }
    /*
     * it's ActivePlacement and SkipMapping, and that's disallowed.
     */
    if (!PPosOverride &&
	(DO_NOT_SHOW_ON_MAP(tmp_win) &&
	 (SPLACEMENT_MODE(sflags) == PLACE_MANUAL ||
	  SPLACEMENT_MODE(sflags) == PLACE_MANUAL_B ||
	  SPLACEMENT_MODE(sflags) == PLACE_TILEMANUAL) &&
         !SMANUAL_PLACEMENT_HONORS_STARTS_ON_PAGE(sflags)))
    {
      flags.do_honor_starts_on_page = False;
      fvwm_msg(WARN, "PlaceWindow",
	       "illegal style combination used: StartsOnPage/StartsOnDesk"
	       " and SkipMapping don't work with ManualPlacement and"
	       " TileManualPlacement. Putting window on current page,"
	       " please use an other placement style or"
	       " ActivePlacementHonorsStartsOnPage.");
    }
  } /* if (SUSE_START_ON_DESK(sflags) || SUSE_START_ON_SCREEN(sflags))) */
  /* get the screen coordinates to place window on */
  if (SUSE_START_ON_SCREEN(sflags))
  {
    if (flags.do_honor_starts_on_screen)
    {
      /* use screen from style */
      FScreenGetScrRect(
	NULL, XineramaScreen,
	&screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height);
    }
    else
    {
      /* use global screen */
      FScreenGetScrRect(
	NULL, FSCREEN_GLOBAL,
	&screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height);
    }
  }
  else
  {
    /* use current screen */
    FScreenGetScrRect(
      NULL, FSCREEN_CURRENT,
      &screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height);
  }
  PageLeft   = screen_g.x - pdeltax;
  PageTop    = screen_g.y - pdeltay;
  PageRight  = PageLeft + screen_g.width;
  PageBottom = PageTop + screen_g.height;
  yt = PageTop;

  /* Don't alter the existing desk location during Capture/Recapture.  */
  if (!PPosOverride)
  {
    tmp_win->Desk = Scr.CurrentDesk;
  }
  if (SIS_STICKY(*sflags))
    tmp_win->Desk = Scr.CurrentDesk;
  else if (SUSE_START_ON_DESK(sflags) && Desk && flags.do_honor_starts_on_page)
    tmp_win->Desk = (Desk > -1) ? Desk - 1 : Desk;
  else
  {
    if ((DO_USE_WINDOW_GROUP_HINT(tmp_win)) &&
	(tmp_win->wmhints) && (tmp_win->wmhints->flags & WindowGroupHint)&&
	(tmp_win->wmhints->window_group != None) &&
	(tmp_win->wmhints->window_group != Scr.Root))
    {
      /* Try to find the group leader or another window in the group */
      for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
      {
	if (t->w == tmp_win->wmhints->window_group)
	{
	  /* found the group leader, break out */
	  tmp_win->Desk = t->Desk;
	  break;
	}
	else if (t->wmhints && (t->wmhints->flags & WindowGroupHint) &&
		 (t->wmhints->window_group == tmp_win->wmhints->window_group))
	{
	  /* found a window from the same group, but keep looking for the group
	   * leader */
	  tmp_win->Desk = t->Desk;
	}
      }
    }
    if ((IS_TRANSIENT(tmp_win))&&(tmp_win->transientfor!=None)&&
	(tmp_win->transientfor != Scr.Root))
    {
      /* Try to find the parent's desktop */
      for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
      {
	if(t->w == tmp_win->transientfor)
	  tmp_win->Desk = t->Desk;
      }
    }

    {
      /* migo - I am not sure this block is ever needed */

      Atom atype;
      int aformat;
      unsigned long nitems, bytes_remain;
      unsigned char *prop;

      if (
	XGetWindowProperty(
	  dpy, tmp_win->w, _XA_WM_DESKTOP, 0L, 1L, True, _XA_WM_DESKTOP,
	  &atype, &aformat, &nitems, &bytes_remain, &prop) == Success)
      {
	if (prop != NULL)
	{
	  tmp_win->Desk = *(unsigned long *)prop;
	  XFree(prop);
	}
      }
    }
  }

  /* I think it would be good to switch to the selected desk
   * whenever a new window pops up, except during initialization */
  /*  RBW - 11/02/1998  --  I dont. */
  if ((!PPosOverride)&&(!DO_NOT_SHOW_ON_MAP(tmp_win)))
  {
    goto_desk(tmp_win->Desk);
  }

  /* Don't move viewport if SkipMapping, or if recapturing the window,
   * adjust the coordinates later. Otherwise, just switch to the target
   * page - it's ever so much simpler.
   */
  if (!SIS_STICKY(*sflags) && SUSE_START_ON_DESK(sflags))
  {
    if (PageX && PageY)
    {
      px = PageX - 1;
      py = PageY - 1;
      px *= Scr.MyDisplayWidth;
      py *= Scr.MyDisplayHeight;
      if ( (!PPosOverride) && (!DO_NOT_SHOW_ON_MAP(tmp_win)))
      {
	MoveViewport(px,py,True);
      }
      else if (flags.do_honor_starts_on_page)
      {
	/*  Save the delta from current page */
	pdeltax     = Scr.Vx - px;
	pdeltay     = Scr.Vy - py;
	PageLeft   -= pdeltax;
	PageRight  -= pdeltax;
	PageTop    -= pdeltay;
	PageBottom -= pdeltay;
      }
    }
  }

  /* Desk has been selected, now pick a location for the window.
   *
   * Windows use the position hint if these conditions are met:
   *
   *  The program specified a USPosition hint and it is not overridden with
   *  the No(Transient)USPosition style.
   *
   * OR
   *
   *  The program specified a PPosition hint and it is not overridden with
   *  the No(Transient)PPosition style.
   *
   * Windows without a position hint are placed using wm placement.
   *
   * If RandomPlacement was specified, then place the window in a
   * psuedo-random location
   */
  {
    Bool override_ppos;
    Bool override_uspos;
    Bool has_ppos = False;
    Bool has_uspos = False;

    if (IS_TRANSIENT(tmp_win))
    {
      override_ppos = SUSE_NO_TRANSIENT_PPOSITION(sflags);
      override_uspos = SUSE_NO_TRANSIENT_USPOSITION(sflags);
    }
    else
    {
      override_ppos = SUSE_NO_PPOSITION(sflags);
      override_uspos = SUSE_NO_USPOSITION(sflags);
    }
    if ((tmp_win->hints.flags & PPosition) && !override_ppos)
    {
      has_ppos = True;
    }
    if ((tmp_win->hints.flags & USPosition) && !override_uspos)
    {
      has_uspos = True;
    }

    if (mode == PLACE_AGAIN)
    {
      flags.do_not_use_wm_placement = False;
    }
    else if (has_ppos || has_uspos)
    {
      flags.do_not_use_wm_placement = True;
    }
    else if (PPosOverride)
    {
      flags.do_not_use_wm_placement = True;
    }
    else if (!flags.do_honor_starts_on_page &&
	     tmp_win->wmhints && (tmp_win->wmhints->flags & StateHint) &&
	     tmp_win->wmhints->initial_state == IconicState)
    {
      flags.do_forbid_manual_placement = True;
    }
  }

  if (!flags.do_not_use_wm_placement)
  {
    unsigned int placement_mode = SPLACEMENT_MODE(sflags);

    /* override if Manual placement happen */
    SET_PLACED_BY_FVWM(tmp_win,True);

    if (flags.do_forbid_manual_placement)
    {
      switch (placement_mode)
      {
      case PLACE_MANUAL:
      case PLACE_MANUAL_B:
	placement_mode = PLACE_CASCADE;
	break;
      case PLACE_TILEMANUAL:
	placement_mode = PLACE_TILECASCADE;
	break;
      default:
	break;
      }
    }

    /* first, try various "smart" placement */
    switch (placement_mode)
    {
    case PLACE_TILEMANUAL:
      flags.is_smartly_placed =
	SmartPlacement(
	  tmp_win, &screen_g, tmp_win->frame_g.width, tmp_win->frame_g.height,
	  &xl, &yt, pdeltax, pdeltay);
      if (flags.is_smartly_placed)
	break;
      /* fall through to manual placement */
    case PLACE_MANUAL:
    case PLACE_MANUAL_B:
      /*  either "smart" placement fail and the second choice is a manual
	  placement (TileManual) or we have a manual placement in any case
	  (Manual)
      */
      xl = -1;
      yt = -1;

      if (GrabEm(CRS_POSITION, GRAB_NORMAL))
      {
	int mx;
	int my;

	/* Grabbed the pointer - continue */
	MyXGrabServer(dpy);
	if (XGetGeometry(dpy, tmp_win->w, &JunkRoot, &JunkX, &JunkY,
			 (unsigned int *)&DragWidth,
			 (unsigned int *)&DragHeight,
			 &JunkBW,  &JunkDepth) == 0)
        {
	  MyXUngrabServer(dpy);
          UngrabEm(GRAB_NORMAL);
	  return False;
	}
	SET_PLACED_BY_FVWM(tmp_win,False);
	MyXGrabKeyboard(dpy);
	DragWidth = tmp_win->frame_g.width;
	DragHeight = tmp_win->frame_g.height;

	XMapRaised(dpy, Scr.SizeWindow);
	FScreenGetScrRect(NULL, FSCREEN_GLOBAL, &mx, &my, NULL, NULL);
	if (moveLoop(tmp_win, mx, my, DragWidth, DragHeight,
		     &xl, &yt, False))
	{
	  /* resize too */
	  rc = True;
	}
	else
	{
	  /* ok */
	  rc = False;
	}
	XUnmapWindow(dpy, Scr.SizeWindow);
	MyXUngrabKeyboard(dpy);
	MyXUngrabServer(dpy);
	UngrabEm(GRAB_NORMAL);
      }
      else
      {
	/* couldn't grab the pointer - better do something */
	XBell(dpy, 0);
	xl = 0;
	yt = 0;
      }
      if (flags.do_honor_starts_on_page)
      {
        xl -= pdeltax;
        yt -= pdeltay;
      }
      tmp_win->attr.y = yt;
      tmp_win->attr.x = xl;
      break;
    case PLACE_MINOVERLAPPERCENT:
      CleverPlacement(tmp_win, &screen_g, &xl, &yt, pdeltax, pdeltay, 1);
      flags.is_smartly_placed = True;
      break;
    case PLACE_TILECASCADE:
      flags.is_smartly_placed =
	SmartPlacement(
	  tmp_win, &screen_g, tmp_win->frame_g.width, tmp_win->frame_g.height,
	  &xl, &yt, pdeltax, pdeltay);
      if (flags.is_smartly_placed)
	break;
      /* fall through to cascade placement */
    case PLACE_CASCADE:
    case PLACE_CASCADE_B:
      /*  either "smart" placement fail and the second choice is "random"
	  placement (TileCascade) or we have a "random" placement in any case
	  (Cascade) or we have a crazy SPLACEMENT_MODE(sflags) value set with
	  the old Style Dumb/Smart, Random/Active, Smart/SmartOff (i.e.:
	  Dumb+Random+Smart or Dumb+Active+Smart)
      */
      if ((Scr.randomx += tmp_win->title_g.height) > screen_g.width / 2)
      {
	Scr.randomx = tmp_win->title_g.height;
      }
      if ((Scr.randomy += 2 * tmp_win->title_g.height) > screen_g.height / 2)
      {
	Scr.randomy = 2 * tmp_win->title_g.height;
      }
      tmp_win->attr.x = Scr.randomx + PageLeft - pdeltax;
      tmp_win->attr.y = Scr.randomy + PageTop - pdeltay;
      /* try to keep the window on the screen */
      tmp_win->frame_g.x = PageLeft + tmp_win->attr.x + tmp_win->old_bw + 10;
      tmp_win->frame_g.y = PageTop + tmp_win->attr.y + tmp_win->old_bw + 10;

      if (tmp_win->attr.x + tmp_win->frame_g.width >= PageRight)
      {
	tmp_win->attr.x = PageRight - tmp_win->attr.width
	  - tmp_win->old_bw - 2*tmp_win->boundary_width;
	Scr.randomx = 0;
      }
      if (tmp_win->attr.y + tmp_win->frame_g.height >= PageBottom)
      {
	tmp_win->attr.y = PageBottom - tmp_win->attr.height
	  - tmp_win->old_bw - tmp_win->title_g.height -
	  2*tmp_win->boundary_width;
	Scr.randomy = 0;
      }

      /* the left and top sides are more important in huge windows */
      if (tmp_win->attr.x < PageLeft)
      {
	tmp_win->attr.x = PageLeft;
      }
      if (tmp_win->attr.y < PageTop)
      {
	tmp_win->attr.y = PageTop;
      }
      break;
    case PLACE_MINOVERLAP:
      CleverPlacement(tmp_win, &screen_g, &xl, &yt, pdeltax, pdeltay, 0);
      flags.is_smartly_placed = True;
      break;
    default:
      /* can't happen */
      break;
    }

    if (flags.is_smartly_placed)
    {
      /* "smart" placement succed, we have done ... */
      tmp_win->attr.x = xl;
      tmp_win->attr.y = yt;
    }
  } /* !flags.do_not_use_wm_placement */
  else
  {
    if (!PPosOverride)
      SET_PLACED_BY_FVWM(tmp_win,False);
    /* the USPosition was specified, or the window is a transient,
     * or it starts iconic so place it automatically */

    if (SUSE_START_ON_SCREEN(sflags) && flags.do_honor_starts_on_screen)
    {
      /*
       * If StartsOnScreen has been given for a window, translate its
       * USPosition so that it is relative to that particular screen.
       * If we don't do this, then a geometry would completely cancel
       * the effect of the StartsOnScreen style.
       *
       * So there are two ways to get a window to pop up on a particular
       * Xinerama screen.  1: The intuitive way giving a geometry hint
       * relative to the desired screen's 0,0 along with the appropriate
       * StartsOnScreen style (or *wmscreen resource), or 2: Do NOT
       * specify a Xinerama screen (or specify it to be 'g') and give
       * the geometry hint in terms of the global screen.
       */

      FScreenTranslateCoordinates(
	NULL, XineramaScreen, NULL, FSCREEN_GLOBAL,
	&tmp_win->attr.x, &tmp_win->attr.y);
    }

    /*
     *  If SkipMapping, and other legalities are observed, adjust for
     * StartsOnPage.
     */
    if ( ( DO_NOT_SHOW_ON_MAP(tmp_win) && flags.do_honor_starts_on_page )  &&

	 ( (!(IS_TRANSIENT(tmp_win)) ||
	    SUSE_START_ON_PAGE_FOR_TRANSIENT(sflags))

#if 0
	   /* dv 08-Jul-2003:  Do not use this.  Instead, force the
	    * window on the requested page even if the application
	    * requested a different position. */
	   && ((SUSE_NO_PPOSITION(sflags)) ||
	       !(tmp_win->hints.flags & PPosition))

	   /* dv 08-Jul-2003:  This condition is always true because we
	    * already checked for flags.do_honor_starts_on_page above. */
           /*  RBW - allow StartsOnPage to go through, even if iconic.  */
           && ( ((!((tmp_win->wmhints)&&
		    (tmp_win->wmhints->flags & StateHint)&&
		    (tmp_win->wmhints->initial_state == IconicState)))
		 || (flags.do_honor_starts_on_page)) )

#endif
		 ) )
    {
      /*
       * We're placing a SkipMapping window - either capturing one that's
       * previously been mapped, or overriding USPosition - so what we
       * have here is its actual untouched coordinates. In case it was
       * a StartsOnPage window, we have to 1) convert the existing x,y
       * offsets relative to the requested page (i.e., as though there
       * were only one page, no virtual desktop), then 2) readjust
       * relative to the current page.
       */
      if (tmp_win->attr.x < 0)
      {
	tmp_win->attr.x =
	  ((Scr.MyDisplayWidth + tmp_win->attr.x) % Scr.MyDisplayWidth);
      }
      else
      {
	tmp_win->attr.x = tmp_win->attr.x % Scr.MyDisplayWidth;
      }
      /*
       * Noticed a quirk here. With some apps (e.g., xman), we find the
       * placement has moved 1 pixel away from where we originally put it when
       * we come through here. Why is this happening?
       * Probably old_bw, try xclock -borderwidth 100
       */
      if (tmp_win->attr.y < 0)
      {
	tmp_win->attr.y =
	  ((Scr.MyDisplayHeight + tmp_win->attr.y) % Scr.MyDisplayHeight);
      }
      else
      {
	tmp_win->attr.y = tmp_win->attr.y % Scr.MyDisplayHeight;
      }
      tmp_win->attr.x -= pdeltax;
      tmp_win->attr.y -= pdeltay;
    }

    /* put it where asked, mod title bar */
    /* if the gravity is towards the top, move it by the title height */
    {
      rectangle final_g;
      int gravx;
      int gravy;

      gravity_get_offsets(tmp_win->hints.win_gravity, &gravx, &gravy);
      final_g.x = tmp_win->attr.x + gravx * tmp_win->old_bw;
      final_g.y = tmp_win->attr.y + gravy * tmp_win->old_bw;
      /* Virtually all applications seem to share a common bug: they request
       * the top left pixel of their *border* as their origin instead of the
       * top left pixel of their client window, e.g. 'xterm -g +0+0' creates an
       * xterm that tries to map at (0 0) although its border (width 1) would
       * not be visible if it ran under plain X.  It should have tried to map
       * at (1 1) instead.  This clearly violates the ICCCM, but trying to
       * change this is like tilting at windmills.  So we have to add the
       * border width here. */
      final_g.x += tmp_win->old_bw;
      final_g.y += tmp_win->old_bw;
      final_g.width = 0;
      final_g.height = 0;
      if (mode == PLACE_INITIAL)
      {
        gravity_resize(
          tmp_win->hints.win_gravity, &final_g,
          2 * tmp_win->boundary_width,
          2 * tmp_win->boundary_width + tmp_win->title_g.height);
      }
      tmp_win->attr.x = final_g.x;
      tmp_win->attr.y = final_g.y;
    }
  }

  return rc;
}


void CMD_PlaceAgain(F_CMD_ARGS)
{
  int x;
  int y;
  char *token;
  float noMovement[1] = {1.0};
  float *ppctMovement = noMovement;
  window_style style;

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

  lookup_style(tmp_win, &style);
  PlaceWindow(
    tmp_win, &style.flags,
    SGET_START_DESK(style), SGET_START_PAGE_X(style), SGET_START_PAGE_Y(style),
    SGET_START_SCREEN(style), PLACE_AGAIN);
  x = tmp_win->attr.x;
  y = tmp_win->attr.y;

  /* Possibly animate the movement */
  token = PeekToken(action, NULL);
  if(token && StrEquals("ANIM", token))
    ppctMovement = NULL;

  AnimatedMoveFvwmWindow(tmp_win, tmp_win->frame, -1, -1, x, y, False, -1,
			 ppctMovement);

  return;
}


syntax highlighted by Code2HTML, v. 0.9.1