//-*-c++-*-
// -------------------------------------------
// RCS data:
// $Date: 2003/07/04 09:48:55 $
// $Revision: 1.4 $
// $Source: /cvsroot/miwm/miwm/miwm/wm.cc,v $
// $Id: wm.cc,v 1.4 2003/07/04 09:48:55 sgbeal Exp $
// $RCSfile: wm.cc,v $
// -------------------------------------------
// Copyright by Ben Paul Wise.
// -------------------------------------------
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------

#include "miwm.h"
#include "mimenu.h"
#include "mipix.h"
#include "ws.h"
#include "miwm_framework.h"

// -------------------------------------------


WindowManager::WindowManager() {

  // enough to get it off the screen
  offWorkSpace = display_width + display_height;
  whichMenu = noMenu;
  menu = NULL;
  mode = wmStartup;
  auto_x = 10; // starting position
  auto_y = 10; // starting position
  opaqueMoveSize = 5; // 5 percent is built-in default

  allClients = new NodeList();
  hiddenClients = new NodeList();
  setupWS();
  // --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  -- 
  // set some options
  raiseOnFocusP = miwm::args().getBool( "raise", miwm::config().getBool( "raise_client_on_focus", false ) );
  previewWSP = 0;
  previewCP = miwm::args().getBool( "preview", miwm::config().getBool( "preview_client_before_unhide", true ) );
  tileP = 1;
  usePixmapsP = miwm::args().getBool( "pixmaps", miwm::config().getBool( "enable_pixmaps", false ) );

  
  followClientToWS = 0;

  protectTopLeft = 1;
// #ifdef USE_PIXMAPS
//   usePixmapsP = 1;
// #endif
  // --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  -- 

  wmRNG = new RNG();
  activeBgd = None; // pure black
  inactiveBgd = None; // pure black
  popupBgd = None; // pure black

#ifdef USE_PIXMAPS
//    loadPixmap(wood_xpm, &popupBgd);
//  loadPixmap(greenstucco, &popupBgd);
  loadPixmap(Sunflower, &popupBgd);
  loadPixmap(wood_xpm, &inactiveBgd);
  loadPixmap(magick, &activeBgd);
#endif

  // this works under everything but Solaris
//   sleepval.tv_sec = 0;
//   sleepval.tv_usec = 250000;
// //   cout <<  "About to sleep "<<  (sleepval.tv_usec)/1000 << " millisec" << flush;
//   select(0, 0, 0, 0, &sleepval); 
// //   cout << " ... done sleeping" << endl << flush;

}


WindowManager::~WindowManager()
{

  miwm::config().set( "preview_client_before_unhide", this->previewCP );
  miwm::config().set( "raise_client_on_focus", this->raiseOnFocusP );


  Client* c = NULL;
  Node* cND = NULL;
  int i;
  WorkSpace* ws = NULL;
  // Clear the menu
  clearMenu();
  

  // Set all clients free.
  freeAllClients();

  for (cND = allClients->first; cND != NULL; cND = allClients->nextNode(cND)) {
    c = ((Client*) cND->data);

    // if this client is NULL, we are in deep doo doo
    assert (NULL != c);


    if (0 != c->name) {
      cout << "deleting c->name: |"<<c->name<<"| "<<endl<<flush;
      //      delete c->name;
      // this was the line of BUG 100
      c->name = NULL;
    }
    if (0 != c->fallback_name) {
      cout << "deleting c->fallback_name: |"<<c->fallback_name<<"| "<<endl<<flush;
      delete c->fallback_name;
      c->fallback_name = NULL;
    }

    delete c;
    c = NULL;
    cND->data = NULL;

  }



  delete allClients;
  allClients = NULL;

  delete hiddenClients;
  hiddenClients = NULL;

  // delete all workspaces
  for (i=0; i<numWorkSpaces; i++) {
    ws = allWorkSpaces[i];
    delete ws;
    allWorkSpaces[i] = NULL;
  }

  delete menu;
  menu = NULL;

  delete wmRNG;
  wmRNG = NULL;

}

void
WindowManager::scanWindowTree(void) {
  unsigned int	i;
  unsigned int	nwins;
  Client	*c;
  Window	dw1;
  Window	dw2;
  Window	*wins;
  XWindowAttributes	attr;

  // dw1 and dw2 are ignored
  XQueryTree(dpy, root, &dw1, &dw2, &wins, &nwins);
  for (i = 0; i < nwins; i++) {
    XGetWindowAttributes(dpy, wins[i], &attr);

    if (attr.override_redirect || isshaped(wins[i]) || wins[i] == popup)
      continue;

    // the client is actually created here
    cout << "Adding pre-existing client" << endl << flush;
    c = addClient(wins[i]);
    cout << "Added pre-existing client, now with ID "<<c->clientID << endl << flush;
    

    if (c != 0 && c->window == wins[i]) {
      c->size.x = attr.x - border;
      c->size.y = attr.y - border;
      c->size.width  = attr.width  + 2 * border;
      c->size.height = attr.height + 2 * border;
      c->border = attr.border_width;
      if (attr.map_state == IsViewable) {
	c->internal_state = IPendingReparenting;
	theWM->manageClient( c, true );
      }
    }
  }
  XFree(wins);
  return;
}


// -------------------------------------------


void 
WindowManager::autoPlaceCoords(Client *c) {
  int autoX = -1;
  int autoY = -1;
  if (1 == tileP)
    tilePlaceCoords(c, &autoX, &autoY);

  // if tiling is not desired or fails, do the diagonal placement
  if ((0 == tileP) || (autoX < 0) || (autoY<0))
    diagonalPlaceCoords(c, &autoX, &autoY);

  auto_x = autoX;
  auto_y = autoY;
  c->size.x = auto_x;
  c->size.y = auto_y;

  return;
}

void 
WindowManager::tilePlaceCoords(Client *c, int *autoX, int *autoY) {
  int i, j;
//   int dx = imax(border, c->size.width / 30);
//   int dy = imax(border, c->size.height / 30);
  int dx = border;
  int dy = border;
  int  clms = (display_width) / dx;
  int rows = (display_height) / dy;
  int found = 0;
  int xTmp, yTmp;
  int smallP = 0;
  int th = titleHeightc(c);
  if (c->size.width * c->size.height * 100 <= opaqueMoveSize * display_width * display_height)
    smallP = 1; 
  else
    smallP = 0; 


  if (1 == smallP) {// vertical
    for (j = 0; ((0 == found) && (j<clms)) ; j++) {
      xTmp =  border/2 + j * dx;
      for (i = 0; ((0 == found) && (i<rows)) ; i++) {
	yTmp = th+ border/2 + i * dy;
	if  ((0 == found) &&
	     (1 == okPlacement(c,
			       xTmp, yTmp , 
			       c->size.width, c->size.height))) {
	  *autoX = xTmp;
	  *autoY = yTmp ;
	  found = 1;
	}
      }
    }
  }

  if (0 == smallP) { // horizontal
    for (i = 0; ((0 == found) && (i<rows)) ; i++) {
      yTmp =   th+ border/2 + i * dy;
      for (j = 0; ((0 == found) && (j<clms)) ; j++) {
	xTmp =  border/2 + j * dx;
	if  ((0 == found) &&
	     (1 == okPlacement(c,
			       xTmp, yTmp , 
			       c->size.width, c->size.height))) {
	  *autoX = xTmp;
	  *autoY = yTmp ;
	  found = 1;
	}
      }
    }
  }
  return;
}

 void 
WindowManager::diagonalPlaceCoords(Client *c, int *autoX, int *autoY) {
  int xTmp, yTmp;

  xTmp = auto_x + (int)(wmRNG->uniform(30, 60));
  yTmp = auto_y + (int)(wmRNG->uniform(20, 40));

  if (xTmp + c->size.width > display_width && c->size.width <= display_width)
    xTmp = (display_width - c->size.width) / 2;

  if (yTmp + c->size.height > display_height && c->size.height <= display_height)
    yTmp = (display_height - c->size.height) / 2;

  if (xTmp > 500)
    xTmp = xTmp - 450;

  if (yTmp > 500)
    yTmp = yTmp - 450;

  *autoX = xTmp;
  *autoY = yTmp;
  return;
}


// -------------------------------------------

int
WindowManager::okPlacement(Client *c1, int x, int y, int w, int h) {
  int okP = 1;
  int doneP = 0;
  Node *cND;
  Client* c = NULL;
  int th1 = titleHeightc(c1);
  int sep = DEFAULT_SEPARATION ; // minimum pixel separation

  if ((x<0) || (y-th1<0) || (x+w >= display_width) || (y+h >= display_height)) {
    okP = 0;
    doneP = 1;
  }
  for (cND = allClients->first; 
       ((okP == 1) && (cND != NULL)); 
       cND = allClients->nextNode(cND)) {
    c = ((Client*) cND->data);

    // if this client is NULL, we are in deep doo doo
    assert (NULL != c);


    if ((False == c->hidden) && (1 == c->isMapped) && (workSpace == c->workSpace) &&  (c1 != c)) {
      if ((c->size.x + c->size.width + sep < x) // client to the left
	  || (x + w + sep < c->size.x) // client to the right
	  || (c->size.y + c->size.height + sep < y - th1) // client above
	  || (y + h + sep < c->size.y - titleHeightc(c))) // client below
	{
	  // c1 is ok with respect to c.
	  // just because it does not conflict with this c does not
	  // ensure it is OK overall
	}
      else {
	okP = 0;
	doneP = 1;
      }
    }
  }
  return okP;
}

// -------------------------------------------

void 
WindowManager::doFullPreview(Client *pc) {
  if (NULL != pc) {
    // this little dance of the windows is necessary
    // to ensure that previewed windows are on top
    // of all the unhidden ones, but do not mess up the
    // popup.

//     cout << "Previewing client"<< endl << flush;
    XUnmapWindow(dpy, popup);
    XMapRaised (dpy, pc->parent);
    XMapRaised (dpy, pc->window);
    pc->isMapped = 1;
    XMapRaised (dpy, popup);
  }
  return;
}

void 
WindowManager::undoFullPreview(Client *pc) {
  if (NULL != pc) {
    XUnmapWindow(dpy, pc->window);
    XUnmapWindow(dpy, pc->parent);
    pc->isMapped = 0;

    // raise the popup, in case there was an overlap
//     XMapRaised (dpy, popup);
  }
  return;
}


void
WindowManager::spawn(char *command) {
  char* sh = ((char*) getenv("SHELL"));
//   char *displayName = DisplayString(dpy);
  if (sh == 0)
    sh = "/bin/bash"; 

//   cout << "SHELL is " << sh << endl;

  switch (fork()) {
  case 0:  { // child
    close(ConnectionNumber(dpy));
    execl(sh, sh, "-c", command, 0);

    // should not 'fall through' to here!
    XBell(dpy, 70);
    fprintf(stderr, "miwm: exec %s failed (errno %d)\n",
	    command, errno);
    exit(1);
  }
  case -1: // error
    fprintf(stderr, "%s: couldn't fork\n", argv0);
    exit(0);
  }
//   wait((int *) 0);
  return;
}


int 
WindowManager::insideClientP(Client* c) {
  int rx, ry;
  if (NULL == c) 
    return 0;

  getMouse ( &rx , &ry );
  if (( c->size.x - border < rx) && (rx < c->size.x + c->size.width + border) &&
      ( c->size.y - border < ry) && (ry < c->size.y + c->size.height + border))
    return 1;

  return 0;
}

void
WindowManager::warpPointerToBorder(Client* c) {
  int rx, ry;
  getMouse ( &rx, &ry );
  Edge edge =  whichEdge(c, rx, ry);
//   int xNew = c->size.x + (c->size.width)/2;
//   int yNew = c->size.y + (c->size.height)/2;
  int xNew = rx;
  int yNew = ry;

  switch (edge) {

  case ETop:
//     xNew = c->size.x + (c->size.width)/2;
    yNew = c->size.y + (border/2);
    break;

  case ETopLeft:
    xNew = c->size.x + (border/2);
    yNew = c->size.y + (border/2);
    break;

  case ETopRight:
    xNew = c->size.x + c->size.width - (border/2);
    yNew = c->size.y+(border/2);
    break;

  case ELeft:
    xNew = c->size.x + (border/2);
//     yNew = c->size.y + (c->size.height)/2;
    break;

  case ERight:
    xNew = c->size.x + c->size.width - (border/2);
//     yNew = c->size.y + (c->size.height)/2;
    break;

  case EBottom:
//     xNew = c->size.x + (c->size.width)/2;
    yNew = c->size.y + c->size.height - (border/2);
    break;

  case EBottomLeft:
    xNew = c->size.x + (border/2);
    yNew = c->size.y + c->size.height - (border/2);
    break;

  case EBottomRight:
    xNew = c->size.x + c->size.width - (border/2);
    yNew = c->size.y + c->size.height - (border/2);
    break;

  default:
    // leave in the middle
    break;

  }

   XWarpPointer(dpy, // obvious
 	       None,  // unknown meaning and function
 	       root,  // window coordinate system to use
 	       0, 0, 0, 0, // unknown meaning and function
 	       xNew, yNew);

  return;
}

// -------------------------------------------
// end of wm.cc
// -------------------------------------------
