//-*-c++-*-
// -------------------------------------------
// RCS data:
// $Date: 2003/06/23 14:47:22 $
// $Revision: 1.1.1.1 $
// $Source: /cvsroot/miwm/miwm/miwm/disp.cc,v $
// $Id: disp.cc,v 1.1.1.1 2003/06/23 14:47:22 bwise837 Exp $
// $RCSfile: disp.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	<X11/X.h>
#include	<X11/Xlib.h>
#include	<X11/Xutil.h>
#include	<X11/Xos.h>
#include	<X11/Xresource.h>
#include	<X11/Xatom.h>

#include	<stdio.h>
#include	<stdlib.h>

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

//    Dispatcher for main event loop.

typedef struct Disp Disp;
struct Disp {
  int	type;
  void	(*handler)(XEvent *);
};

void logXEvents (XEvent *ev);

// comment these out to limit functionality
void buttonpress(XEvent *);
void buttonrelease(XEvent *);
void motionnotify(XEvent *);

void expose(XEvent *);
void maprequest(XEvent *);
void configurereq(XEvent *);
void unmap(XEvent *);
void destroy(XEvent *);
void clientmessage(XEvent *);
void colormap(XEvent *);
void property(XEvent *);
void reparent(XEvent *);
void enter(XEvent *);
void expose(XEvent *);

void leave(XEvent *);
void keypress(XEvent *);
void keyrelease(XEvent *);

// void motionnotifyMoving(XEvent*);
// void motionnotifyResizing(XEvent*);
void reshaping_motionnotify(XEvent*);

static Disp disps[] =
{
  {Expose, expose},
  {MapRequest, maprequest},
  {ConfigureRequest, configurereq},
  {UnmapNotify, unmap},
  {DestroyNotify, destroy},
  {ClientMessage, clientmessage},
  {ColormapNotify, colormap},
  {PropertyNotify, property},
  {ReparentNotify, reparent},
  {EnterNotify, enter},

// comment these out to limit functionality
  {MotionNotify, motionnotify},
  {ButtonPress, buttonpress},
  {ButtonRelease, buttonrelease},


  {LeaveNotify, 0},
  {CirculateRequest, 0},
  {ConfigureNotify, 0},
  {CreateNotify, 0},
  {GravityNotify, 0},
  {MapNotify, 0},
  {MappingNotify, 0},
  {SelectionClear, 0},
  {SelectionNotify, 0},
  {SelectionRequest, 0},
  {NoExpose, 0},
};

 void
dispatch(XEvent *ev)
{
  Disp *p;

#ifdef MIWM_TRACE_ALL_X_EVENTS
  logXEvents(ev);
#endif


  for (p = disps; p < disps + sizeof disps / sizeof disps[0]; p++) {
    if (p->type == ev->type) {
      if (p->handler != 0)
	p->handler(ev); // purify says this is a leak?!
      return;
    }
  }

  if (!shapeevent(ev))
    fprintf(stderr, "%s: unknown event %d\n", argv0, ev->type);
}

void
logXEvents (XEvent *ev) {

  cout <<  "<";
  switch (ev->type) {
  case ButtonPress:
    cout <<  "ButtonPress"; break;
  case ButtonRelease:
    cout <<  "ButtonRelease"; break;
  case MapRequest:
    cout <<  "MapRequest"; break;
  case ConfigureRequest:
    cout <<  "ConfigureRequest"; break;
  case UnmapNotify:
    cout <<  "UnmapNotify"; break;
  case DestroyNotify:
    cout <<  "DestroyNotify"; break;
  case ClientMessage:
    cout <<  "ClientMessage"; break;
  case ColormapNotify:
    cout <<  "ColormapNotify"; break;
  case PropertyNotify:
    cout <<  "PropertyNotify"; break;
  case ReparentNotify:
    cout <<  "ReparentNotify"; break;
  case EnterNotify:
    cout <<  "EnterNotify"; break;
  case LeaveNotify:
    cout <<  "LeaveNotify"; break;
  case Expose:
    // too frequent to post, usually!
    cout <<  "Expose"; break;
  case MotionNotify:
    // too frequent to post, usually!
    cout <<  "MotionNotify"; break;
    break;
  case CirculateRequest:
    cout <<  "CirculateRequest"; break;
  case ConfigureNotify:
    cout <<  "ConfigureNotify"; break;
  case CreateNotify:
    cout <<  "CreateNotify"; break;
  case GravityNotify:
    cout <<  "GravityNotify"; break;
  case MapNotify:
    cout <<  "MapNotify"; break;
  case MappingNotify:
    cout <<  "MappingNotify"; break;
  case SelectionClear:
    cout <<  "SelectionClear"; break;
  case SelectionNotify:
    cout <<  "SelectionNotify"; break;
  case SelectionRequest:
    cout <<  "SelectionRequest"; break;
  default:
    cout <<  "unknown event type " << ev->type;
}
cout <<  ">\n"  << flush;
return;
}

void
expose(XEvent *ev) {
  Client *c;
  Window w;	// Window the expose event is for.

  if (ev->xexpose.count != 0) return;

  w = ev->xexpose.window;

  // We don't draw on the root window (yet)
  if (w == root) return;

  // Decide what needs redrawing: window frame or menu?
  if (w == popup) {
    switch (theWM->mode) {

    case wmMenuing:
      theWM->menu->expose();
      break;

    case wmMoving:
      theWM->positionExpose();
      break;

    case wmResizing:
      theWM->sizeExpose();
      break;

    default:
      break;
    }
  } 
  else {
    c = getClient(w);
//     cout << "Exposing Client " << c->clientID << endl << flush;
//     XSync(dpy, False);
    if (c != 0) {
      theWM->drawClientBorder(c, c == current);
    }
  }
  return;
}

int
WindowManager::titleHeightc(Client *c)
{
 if (c->accepts_focus && c->name) 
   return titleHeight();
 else
   return 0;
}

void
keypress(XEvent *) {
  // do nothing, yet
}

void
keyrelease(XEvent *) {
  // do nothing, yet
}

// -------------------------------------------
// these are the big three: press button, move, release
// -------------------------------------------

// this should be a dispatch table on (location, button, modifiers)
// pointing at fn(Client*, XEvent*)
void
buttonpress(XEvent *ev) {
  Client *c;
  XButtonEvent *e = &ev->xbutton;
//   int quarter;

  if (theWM->mode != wmReady) return;

  c = getClient(e->window);

  // =================================================
  // FRAME button presses
  // =================================================

  if (c && (e->window == c->parent))   {    // click on frame around a client.
//     cout << "Frame button press"<<endl;
    theWM->focusOn(c);
    switch (e->button) {
  
    case Button3:   // hide client
      if (e->state & ControlMask) {
	if (theWM->mouseInDeadlyBox(current)) {
	  theWM->mode = wmKilling;
	  theWM->startKillingClient(e, c);
	}
	else { // raise client ops menu
	  theWM->mode = wmMenuing;
	  theWM->setClientOpsMenu();
	  theWM->menu->startMenuing(e);
	}
      }
      else {
	hide(c);
      }
      break;

    case Button2: // start moving client on screen or between WS
      if (e->state & ControlMask) {
	// raise WS menu 
	theWM->mode = wmMenuing;
	theWM->setWSMenu();
	if (e->state & ShiftMask) {
	  // follow client to new WS
	  theWM->followClientToWS = 1;
	}
	theWM->whichMenu = sendCtoWSMenu;
	theWM->menu->startMenuing(e);
      }
      else {
	theWM->mode = wmMoving;
	theWM->startMovingClient(ev, c);
      }
       break;

    case Button1:
      if (e->state & ControlMask)
	theWM->backClient(c);  
      else {
	theWM->mode = wmResizing;
	theWM->startResizingClient(e, c);
      }
      break;

    default:
      break;
    }
  } // end of clicks on border

  // =================================================
  // ROOT button presses
  // =================================================
  if (e->window == root) {
//      cout << "Root button press"<<endl;
//       XChangeActivePointerGrab(dpy, 
// 			       theWM->moving_event_mask, 
// 			       root_cursor, CurrentTime);
    switch  (e->button) {

    case Button1:
//       cout << "Trying to raise topclient = " << theWM->workSpace->topClient << endl << flush;
//       if (theWM->workSpace->topClient != NULL) {
// 	cout << "  named " << choose_label(theWM->workSpace->topClient) << endl;
// 	theWM->raiseClient(theWM->workSpace->topClient );
// 	theWM->focusOn(theWM->workSpace->topClient);
//       }
      // raise command menu
      theWM->mode = wmMenuing;
      theWM->setShellCmdMenu();
      theWM->menu->startMenuing(e);
      break;

    case Button2:
      // if ctrl-Button2, skip 
      if (e->state & ControlMask) {
	if (e->state & ShiftMask) 
	  // skip back
	  stepWS(-1);
	else
	  // skip forward
	  stepWS(+1);
      }
      else { // raise WS menu 
      theWM->mode = wmMenuing;
      theWM->setWSMenu();
      theWM->whichMenu = changeWSMenu;
      theWM->menu->startMenuing(e);
      }
      break;

    case Button3:
      if (e->state & ControlMask) {
      theWM->mode = wmMenuing;
      theWM->setWMOpsMenu();
      theWM->menu->startMenuing(e);
      }
      else {
      // raise hidden client menu
      theWM->mode = wmMenuing;
      theWM->setHiddenClientMenu();
      theWM->menu->startMenuing(e);
      }
      break;

    default:
      break;
    }
  }  // end of root button clicks
  return;
}

void
motionnotify(XEvent *ev) {
  switch (theWM->mode) {

  case wmResizing: 
    theWM->motionNotifyResizing();
    break;

  case wmKilling: 
    theWM->motionNotifyKilling();
    break;

  case wmMoving: 
//     cout << "Moving" << endl << flush;
    theWM->motionNotifyMoving();
    break;

  case wmMenuing: 
    theWM->menu->motionNotify(ev);
    break;

  default:
    // do nothing!
    break;
  }
  return;
}




void
buttonrelease(XEvent *ev) {
//     cout << "Button release"<<endl;
   switch (theWM->mode) {

   case wmMenuing:
//       cout << "Stop menuing" << endl << flush;

     theWM->menu->buttonRelease();
     break;

   case wmResizing:
     theWM->buttonReleaseResizing();
     break;

   case wmMoving:
     theWM->buttonReleaseMoving();
     break;

   case wmKilling:
     theWM->buttonReleaseKilling();
     break;

   default:
     break;
   }

   theWM->mode = wmReady;
   return;
 }

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


void maprequest(XEvent *ev) {
  Client *c;
  XMapRequestEvent	*e = &ev->xmaprequest;

  c = getClient(e->window);

  if (c == 0 || c->window != e->window) {
    cout << "Adding client in maprequest" << endl << flush;
    c = addClient(e->window);
    if (c == 0)
      return;
  }

  unhidec(c, 1);

  switch (c->state) {
  case WithdrawnState:
    if (c->parent == root) {
      theWM->manageClient(c);
      break;
    }
    XReparentWindow(dpy, c->window, c->parent, border, border);
    XAddToSaveSet(dpy, c->window);
    // fall through
  case NormalState:
    XMapRaised(dpy, c->parent);
    XMapWindow(dpy, c->window);
    c->isMapped = 1;
    setClientState(c, NormalState);
    break;
  }
}

void
unmap(XEvent *ev)
{
  Client *c;
  XUnmapEvent *e = &ev->xunmap;

  c = getClient(e->window);
  if (c == 0) return;

//  In the description of the ReparentWindow request we read: "If the window
//  is mapped, an UnmapWindow request is performed automatically first". This
//  might seem stupid, but it's the way it is. While a reparenting is pending
//  we ignore UnmapWindow requests.

  if (c->internal_state == IPendingReparenting) {
    c->internal_state = INormal;
    return;
  }

  if (c->state == IconicState) {

    //Is this a hidden window disappearing? If not, then we
    //are not interested because it's an unmap request caused
    //by our hiding a window.

    if (e->send_event)
      unhidec(c, 0); // It's a hidden window disappearing.
  } else {
  // This is a plain unmap, so withdraw the window.
    withdraw(c);
  }

  c->internal_state = INormal;
}

void
configurereq(XEvent *ev)
{
  XWindowChanges wc;
  Client *c;
  XConfigureRequestEvent *e = &ev->xconfigurerequest;

  c = getClient(e->window);
  if (c) {
    // ICCCM section 4.1.5 says that the x and y coordinates here
    //will have been "adjusted for the border width". 
    // NOTE: this may not be the only place to bear this in mind.
    if (e->value_mask & CWBorderWidth) {
      e->x -= e->border_width;
      e->y -= e->border_width;
    } else {
      // The ICCCM also says that clients should always set the
      //border width in a configure request. As usual, many don't.
      e->x--;
      e->y--;
    }

    if (e->value_mask & CWX)
      c->size.x = e->x;
    if (e->value_mask & CWY)
      c->size.y = e->y;
    if (e->value_mask & CWWidth)
      c->size.width = e->width + 2 * border;
    if (e->value_mask & CWHeight)
      c->size.height = e->height + 2 * border;
    if (e->value_mask & CWBorderWidth)
      c->border = e->border_width;

    if (c->parent != root && c->window == e->window) {
      wc.x = c->size.x;
      wc.y = c->size.y;
      wc.width = c->size.width;
      wc.height = c->size.height;
      if (c == current) {
	wc.y -= titleHeight();
	wc.height += titleHeight();
      }
      wc.border_width = 1;
      wc.sibling = e->above;
      wc.stack_mode = e->detail;
      XConfigureWindow(dpy, e->parent, e->value_mask, &wc);
      sendConfigureNotify(c);
    }
  }
  if (c && (c->internal_state == INormal)) {
    wc.x = border;
    wc.y = border;
  } else {
    wc.x = e->x;
    wc.y = e->y;
  }
  wc.width = e->width;
  wc.height = e->height;
  wc.border_width = 0;
  wc.sibling = e->above;
  wc.stack_mode = e->detail;
  e->value_mask |= CWBorderWidth;

  if (c == current)
    wc.y += titleHeight();

  XConfigureWindow(dpy, e->window, e->value_mask, &wc);
}

void
destroy(XEvent *ev)
{
  Client	*c;
  Window	w = ev->xdestroywindow.window;

  c = getClient(w);
  if (c == 0)
    return;

  removeClient(c);
}

void
clientmessage(XEvent *ev)
{
  Client	*c;
  XClientMessageEvent	*e = &ev->xclient;

  if (e->message_type == wm_change_state) {
    c = getClient(e->window);
    if (c == 0) return;
    if (e->format == 32 && e->data.l[0] == IconicState && normal(c))
      hide(c);
    return;
  }
}

void
colormap(XEvent *ev)
{
//   Client	*c;
//   XColormapEvent	*e = &ev->xcolormap;

  // I commented this out as the 'new' keyword appeared to be confusing g++
  //   if (e->new) {
  //     c = getClient(e->window);
  //     if (c) {
  //       c->cmap = e->colormap;
  //       if (c == current)
  // 	cmapfocus(c);
  //     } else
  //       Client_ColorMap(ev);
  //   }

}

void
property(XEvent * ev) {
  Client * c;
  XPropertyEvent * e = &ev->xproperty;

  c = getClient(e->window);
  if (c == 0)
    return;

  switch (e->atom) {
  case XA_WM_NAME:
    getWindowName(c);
    setactive(c, c == current, 0L);
    return;
  case XA_WM_TRANSIENT_FOR:
    getTransientFor(c);
    return;
  case XA_WM_NORMAL_HINTS:
    getNormalHints(c);
    return;
  default:
    if (e->atom == wm_colormaps) {
      getColormaps(c);
      if (c == current)
	cmapfocus(c);
    }
  }
}

void
reparent(XEvent *ev)
{
  Client	*c;
  XReparentEvent	*e = &ev->xreparent;

  if (e->event != root || e->override_redirect || e->parent == root)
    return;

  c = getClient(e->window);
  if (c != 0 && (c->parent == root || withdrawn(c)))
    removeClient(c);
}

void
leave(XEvent *) {
  // do nothing for now
  return;
}


void
enter(XEvent *ev) {
  if (wmReady ==theWM->mode) {
    Client *c;
    // it is possible for a client to die when you are
    // doing a menu operation (send to WS, toggle sticky, etc.)
    // in that case, we do NOT want to apply the operation
    // to the next client, so I allow change of focus
    // only if we are in wmReady mode.
    c = getClient(ev->xcrossing.window);
    if (c == 0)
      return;

    //   if (c != current && !c->hidden) {
    //     // Take focus away from current holder. 
    //     if (current)
    //       setactive(current, 0, 0L);

    //     // Give focus to new holder.
    //     current = c;
    //     setactive(current, 1, ev->xcrossing.time);
    //   }
    XSync(dpy, False);
    //   cout << "Entering Client " << c->clientID << endl << flush;
    if (1 == theWM->insideClientP(c))
      theWM->focusOn(c);
  }
  return;
}

void
WindowManager::focusOn(Client* c) {
  if (c == NULL)     { // de focus
    if (current)
      setactive(current,0,0L);
    current = NULL;
  }

  if (c != NULL)
    if (c != current && !c->hidden) {
      // Take focus away from current holder. 
      if (current)
	setactive(current, 0, 0L);

      if (1 == raiseOnFocusP)
	raiseClient(c);

      // Give focus to new holder.
      current = c;
      setactive(current, 1, CurrentTime);
    }
  return;
}
// void 
// motionnotifyMoving(XEvent*ev) {
// //   reshaping_motionnotify(ev);

// //     XChangeActivePointerGrab(dpy, theWM->moving_event_mask, 
// // 			   root_cursor, CurrentTime);

//   cout << "motionnotifyMoving"<<endl << flush;
//   switch (mode) {

//   case wmResizing: 
//     break;

//   case wmMoving: 
//     theWM->drawClientOutline(current); // erase old one



//     break;

//   case wmMenuing: 
//     break;
//   }
//   return;
// } 


// void
// reshaping_motionnotify(XEvent*)  // parameter ev was unused
// {
//   int	nx;	/* New x. */
//   int	ny;	/* New y. */
//   int	ox;	/* Original x. */
//   int	oy;	/* Original y. */
//   int	ndx;	/* New width. */
//   int	ndy;	/* New height. */
//   int	odx;	/* Original width. */
//   int	ody;	/* Original height. */
//   int	pointer_x;
//   int	pointer_y;
//   int	yTitleAdjust;

//   if ((theWM->mode != wmResizing) || (mode != wmMoving))
//     return;

//   yTitleAdjust = (current->accepts_focus && current->name) ? titleHeight() : 0;

//   getMouse(&pointer_x, &pointer_y);

//   if (interacting_edge != ENone) {
//     nx = ox = current->size.x;
//     ny = oy = current->size.y;
//     ndx = odx = current->size.width;
//     ndy = ody = current->size.height;
		
//     Client_SizeFeedback();

//     /* Vertical. */
//     switch (interacting_edge) {
//     case ETop:
//     case ETopLeft:
//     case ETopRight:
//       ndy += (current->size.y - pointer_y);
//       ny = pointer_y;
//       break;
//     case EBottom:
//     case EBottomLeft:
//     case EBottomRight:
//       ndy = pointer_y - current->size.y;
//       break;
//     default:	break;
//     }

//     /* Horizontal. */
//     switch (interacting_edge) {
//     case ERight:
//     case ETopRight:
//     case EBottomRight:
//       ndx = pointer_x - current->size.x;
//       break;
//     case ELeft:
//     case ETopLeft:
//     case EBottomLeft:
//       ndx += (current->size.x - pointer_x);
//       nx = pointer_x;
//       break;
//     default: break;
//     }

//     adjustClientBorder(current, interacting_edge, &nx, &ny, &ndx, &ndy);
//     XMoveResizeWindow(dpy, current->parent,
// 		      current->size.x, current->size.y - yTitleAdjust,
// 		      current->size.width, current->size.height + yTitleAdjust);
//     if (current->size.width == odx && current->size.height == ody) {
//       if (current->size.x != ox || current->size.y != oy)
// 	sendConfigureNotify(current);
//     } else
//       XMoveResizeWindow(dpy, current->window, border, border + yTitleAdjust,
// 			current->size.width - 2 * border, current->size.height - 2 * border);
//   } // edge of reshaping section, edge != ENone
//   else 
//     { // bodily  move the client
//       nx = pointer_x + start_x;
//       ny = pointer_y + start_y;

//       adjustClientBorder(current, interacting_edge, &nx, &ny, 0, 0);
//       XMoveWindow(dpy, current->parent,
// 		  current->size.x, current->size.y - yTitleAdjust);
//       sendConfigureNotify(current);
//     }
// }


// -------------------------------------------
// end of disp.cc
// -------------------------------------------

