/*
  9box, a windows repacker
  Copyright (C) 2001-2002  Benjamin Drieu <bdrieu@april.org>

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

/* $Id: 9box.c,v 1.14 2002/07/29 13:02:59 benj Exp $ */

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/X.h>
#include <X11/cursorfont.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Xatom.h>

#include "dat.h"
#include "fns.h"

#include <X11/bitmaps/cross_weave>


int
main (int argc, char ** argv)
{
  XEvent ev;
  Window tmp, *tmp2;
  XWindowAttributes xswa;
  int nb, i;
  long int mask;
  Box box;
  Window win, win2;
  XGCValues gv;
  Pixmap pixmap;

/*    bzero(box.boxed_windows, sizeof(BoxedWindow *) * MAX_BOXED_WINDOWS); */
  for (i=0; i < MAX_BOXED_WINDOWS; i++)
    box.boxed_windows[i] = NULL;

  box.dpy = XOpenDisplay(NULL);
  box.sn = DefaultScreen(box.dpy);
  box.active_window = NO_ACTIVE_WINDOW;

  box.win = XCreateSimpleWindow(box.dpy, DefaultRootWindow(box.dpy),
			       0, 0, 300, 300, 0,
			       BlackPixel(box.dpy,box.sn), 
			       WhitePixel(box.dpy,box.sn));
  XMapWindow(box.dpy, box.win);
  XSelectInput(box.dpy, box.win, 
	       ExposureMask|ButtonMask|LeaveWindowMask|EnterWindowMask|
	       StructureNotifyMask|SubstructureNotifyMask|
	       FocusChangeMask);
  
  box.box_height = 280;
  box.box_width = 300;
  box.box = XCreateSimpleWindow(box.dpy, box.win,
				0, 0, box.box_width, box.box_height, 1,
				BlackPixel(box.dpy,box.sn), 
				WhitePixel(box.dpy,box.sn));
  pixmap = XCreatePixmapFromBitmapData(box.dpy, box.box, cross_weave_bits,
				       cross_weave_width, cross_weave_height, 
				       BlackPixel(box.dpy, box.sn), 
				       WhitePixel(box.dpy, box.sn),
				       DefaultDepth(box.dpy, box.sn));
  XSetWindowBackgroundPixmap (box.dpy, box.box, pixmap);
  XMapWindow(box.dpy, box.box);
  XSelectInput(box.dpy, box.box, ExposureMask|ButtonMask|SubstructureNotifyMask|
	       LeaveWindowMask|EnterWindowMask);

  box.font = XLoadQueryFont(box.dpy, "9x15bold"); /* TODO: what if
						     this fails ? */

  gv.font = box.font->fid;
  gv.foreground = BlackPixel(box.dpy,box.sn)^WhitePixel(box.dpy,box.sn);
  gv.background = WhitePixel(box.dpy,box.sn);
  //  gv.font = font->fid;
  gv.function = GXxor;
  gv.line_width = 0;
/*    gv.subwindow_mode = IncludeInferiors; */
  mask = GCForeground | GCBackground | GCFunction | GCFont | GCLineWidth
    /*  | GCSubwindowMode */;
  box.gc = XCreateGC(box.dpy, DefaultRootWindow(box.dpy), mask, &gv);

  XNextEvent(box.dpy, &ev);

  set_title(&box, "<none>");

  selecting_window = 0;

  for (;;)
    {
      BoxedWindow * tmp_bw;

      XNextEvent(box.dpy, &ev);

      switch (ev.type)
	{
	case PropertyNotify:
	  switch (ev.xproperty.atom) {
	  case XA_WM_NAME:
	    tmp_bw = search_boxedwindow_client(&box, ev.xproperty.window);
	    if (tmp_bw)
	      {
		tmp_bw->name = getprop(&box, ev.xproperty.window, XA_WM_NAME);
		draw_button(tmp_bw);
	      }
	    break;
	  default:
/*  	    printf("Property changed\n"); */
	    break;
	  }
	  break;
	case DestroyNotify:
	  tmp_bw = search_boxedwindow_client(&box, ev.xdestroywindow.window);
	  if (tmp_bw)
	    remove_window(tmp_bw);
	  break;
	case ButtonPress:
	  tmp_bw = search_boxedwindow_button(&box, ev.xbutton.window);
	  if (tmp_bw)
	    {
	      switch (ev.xbutton.button)
		{
		case Button1:
		  if (box.active_window != NO_ACTIVE_WINDOW &&
		      tmp_bw != box.boxed_windows[box.active_window])
		    {
		      select_window(tmp_bw);
		    }
		  break;
		case Button2:
		  if (box.active_window != NO_ACTIVE_WINDOW &&
		      tmp_bw != box.boxed_windows[box.active_window])
		    select_window(tmp_bw);
		  b2menu(&box);
		  break;
		case Button3:
		  release_window(tmp_bw);
		  break;
		default:
		  /* TODO: use specific action (i.e. this would be
		     useful with mouse wheels)) */
		  break;
		}
	      break;
	    }
	  break;
	case ButtonRelease:
	  if (ev.xbutton.window == box.win)
	    reparent_window(&box);
	  break;
	case ConfigureRequest:
	case ConfigureNotify:
	  fprintf(stderr, "Configure %p, %p, %p!\n", box.win,
		  box.box, ev.xconfigure.event);
	  if (ev.xconfigure.window == box.win)
	    {
	      printf("resize %p %d, %d\n", win2, ev.xconfigure.width, ev.xconfigure.height);
	      XResizeWindow(box.dpy, box.box, ev.xconfigure.width,
			    ev.xconfigure.height - 20);
	      box.box_width = ev.xconfigure.width;
	      box.box_height = ev.xconfigure.height;
	      printf("done !\n");
	    }
	  else if (ev.xconfigure.window == box.box)
	    {
	      printf("Resize box\n");
	      for (i=0; i<MAX_BOXED_WINDOWS; i++)
		{
		  if (box.boxed_windows[i] &&
		      box.boxed_windows[i]->client)
		    {
		      Window root_return;
		      int x_return, y_return, width_return, height_return, border_width_return, depth_return;
		      XGetGeometry(box.dpy, box.win, &root_return, &x_return,
				   &y_return, &width_return, &height_return,
				   &border_width_return, &depth_return);
		      XMoveWindow(box.dpy,
				  box.boxed_windows[i]->button, i*BUTTON_DEFAULT_WIDTH, height_return-20);
		      XResizeWindow(box.dpy, box.boxed_windows[i]->client, 
				    ev.xconfigure.width,
				    ev.xconfigure.height);
		    }
		}
	    }
	  /* Attention au cas où c'est le client qui se resize lui même */
	  break;
	case ResizeRequest:
	  printf("Resize !\n");
	  break;
	case Expose:
	  printf("Expose\n");
	  for (i=0; i<MAX_BOXED_WINDOWS; i++)
	    {
	      if (box.boxed_windows[i] &&
		  box.boxed_windows[i]->button == ev.xexpose.window)
		{
		  draw_button(box.boxed_windows[i]);
		  break;
		}
	    }
	  break;
	case ReparentNotify:
	case CreateNotify:
	  /* Don't mind */
	  break;
	case UnmapNotify:
	  tmp_bw = search_boxedwindow_client(&box, ev.xunmap.window);
	  if (tmp_bw)
	    printf("UnMap notify pour %s\n", tmp_bw->name);
	  else
	    printf("UnMap notify\n");
	  /* Sélectionner le client de droite (ou de gauche si à la fin)  */
	  break;
	case MapNotify:
	  if (! selecting_window)
	    {
	      tmp_bw = search_boxedwindow_client(&box, ev.xmap.window);
	      if (tmp_bw)
		{
		  printf("Plop\n");
		  select_window(tmp_bw);
		}
	      else
		printf("Map notify (%p)\n", ev.xmap.window);
	    }
	  else
	    {
	      selecting_window = 0;
	    }
	  break;
	case FocusIn:
	  printf("Focus in %p, %p (%d)\n", ev.xfocus.window, box.win, ev.xfocus.detail);
	  if (box.active_window != NO_ACTIVE_WINDOW &&
	      ev.xfocus.window == box.win &&
	      ev.xfocus.detail == NotifyNonlinear)
	    XSetInputFocus(box.dpy, box.boxed_windows[box.active_window]->client,
			   RevertToParent, CurrentTime);
	  break;
	case FocusOut:
	  printf("Focus out %p, %p (%d)\n", ev.xfocus.window, box.win, ev.xfocus.detail);
	  if (box.active_window != NO_ACTIVE_WINDOW &&
	      ev.xfocus.window == box.win &&
	      ev.xfocus.detail == NotifyVirtual)
	    {
	      printf("Plop\n");
	      /* [FIXME] Find HOW to leave focus without destroying
		 everything else */
	      XSetInputFocus(box.dpy, None, RevertToParent, CurrentTime);
	    }
	  break;
	case LeaveNotify:
	  printf("Leave %p, %p (%d)\n", ev.xcrossing.window, box.win, ev.xcrossing.detail);
	  if (ev.xcrossing.window == box.win)
	    {
/* 	      XSetInputFocus(box.dpy, box.boxed_windows[box.active_window]->client, */
/* 			     RevertToParent, CurrentTime); */
	    }
	  break;
	case EnterNotify:
	  printf("Enter %p, %p (%d)\n", ev.xcrossing.window, box.win, ev.xcrossing.detail);
	  if (ev.xcrossing.window)
	    {
	    }
	  break;
	default:
	  printf("Unknown event %d !\n", ev.type);
	  break;
	}
      ev.type = 0;
    }
  

  return 0;
}


Window
selectwin(Box * box)
{
    XEvent ev;
    XButtonEvent *e;
    int status;
    Window w;

    status = grab(box, DefaultRootWindow(box->dpy), ButtonMask, XCreateFontCursor(box->dpy,XC_tcross), 0);
    if (status != GrabSuccess) {
      fprintf(stderr, "error\n");
      return 0;
    }
    w = None;
    for (;;) {
        XMaskEvent(box->dpy, ButtonMask, &ev);
        e = &ev.xbutton;
        switch (ev.type) {
        case ButtonPress:
	  ungrab(box, e);
	  if (ev.xbutton.button == Button1)
	    {
	      w = e->subwindow;
	      return w;
	    }
	  else
	    return (Window) NULL;
        }
    }
}


int curtime;

Time
timestamp(Box * box)
{
    XEvent ev;

    if (curtime == CurrentTime) {
        XMaskEvent(box->dpy, PropertyChangeMask, &ev);
        curtime = ev.xproperty.time;
    }
    return curtime;
}



Window
dump_tree(Box * box, Window window, int level)
{
  unsigned int number_of_subkids=0, number_of_subkids2=0, number_of_kids=0, number_of_kids2=0,
    number_of_subsubkids=0, number_of_subsubkids2=0;
  Window root, parent, *children = NULL, *subchildren = NULL, *subchildren2 = NULL,
    *subsubchildren = NULL, *children2 = NULL, *subsubchildren2 = NULL;
  
  Atom type_ret;
  int fmt_ret;
  unsigned long nitems_ret;
  unsigned long bytes_after_ret;
  Window *win = NULL;
  Window win2 = (Window) NULL;
  Atom _XA_WM_CLIENT_LEADER;
  XWMHints *leader_change;
  int i, k, l, j, m, ready = FALSE;

  _XA_WM_CLIENT_LEADER = XInternAtom (box->dpy, "WM_CLIENT_NAME", 0);

  if (XQueryTree (box->dpy, window, &root, &parent, &children2, &number_of_kids2))

    for (l = 0; l < number_of_kids2; l++)
      {
	char * name = "";
	int leader = 0;
	win2 = (Window) NULL;
	if (XGetWindowProperty (box->dpy, children2[l], _XA_WM_CLIENT_LEADER, 0,
				sizeof (Window), False, AnyPropertyType, 
				&type_ret, &fmt_ret, &nitems_ret, &bytes_after_ret,
				(unsigned char **) &win2) == Success)
	  {
	    leader = 1;
	  }
	if (getprop(box, children2[l], XA_WM_NAME))
	  name = getprop(box, children2[l], XA_WM_NAME);
	for (i=0; i<level; i++)
	  printf("  ");
	printf ("%s(%p) %s\n", name, children2[l], leader ? " *" : "");
	if (strlen(name) > 0)
	  {
	    printf("cool pour %s ! \n", name);
	    return children2[l];
	  }
	win2 = dump_tree(box, children2[l], level+1);
	if (win2) return win2;
      }
  return (Window) NULL;
}

int
_getprop(box, w, a, type, len, p)
Box * box;
Window w;
Atom a;
Atom type;
long len;       /* in 32-bit multiples... */
unsigned char **p;
{
    Atom real_type;
    int format;
    unsigned long n, extra;
    int status;

    status = XGetWindowProperty(box->dpy, w, a, 0L, len, False, type, &real_type, &format, &n, &extra, p);
    if (status != Success || *p == 0)
        return -1;
    if (n == 0)
        XFree((void*) *p);
    /* could check real_type, format, extra here... */
    return n;
}

char *
getprop(box, w, a)
     Box * box;
Window w;
Atom a;
{
    unsigned char *p;

    if (_getprop(box, w, a, XA_STRING, 100L, &p) <= 0)
        return 0;
    return (char *)p;
}


int
nobuttons(e)    /* Einstuerzende */
XButtonEvent *e;
{
    int state;

    state = (e->state & ButtonMask);
    return (e->type == ButtonRelease) && (state & (state - 1)) == 0;
}



Window
reparent_window(Box * box)
{
  Window win, win2;
  int i;

  printf("Going to reparent window\n");

  for (i=0; i < MAX_BOXED_WINDOWS; i++)
    {
      if (box->boxed_windows[i] == NULL)
	{
	  Window win, client;
	  int tmp;
	  
	  win = selectwin(box);
	  if (!win)
	    return;

	  client = dump_tree(box, win, 0);
	  /* Return immediatly if we were to reparent a box itself */
	  if (!client ||
	      client == box->win || client == box->box || client == box->bottom)
	    return;

	  printf("reparenting %p -> %p (%p)\n", client, box->box, box->win);
  
	  box->boxed_windows[i] = (BoxedWindow *) malloc (sizeof(BoxedWindow));

	  XSync (box->dpy, 0);
	  XWithdrawWindow (box->dpy, client, box->sn);
	  XReparentWindow(box->dpy, client, box->box, 0, 0);
	  XSync (box->dpy, 0);
  
	  for (tmp = 0; tmp < 1000; tmp++) /* Gruiiiiik! */
	    {
	      XReparentWindow(box->dpy, client, box->box, 0, 0);
	      XSync (box->dpy, 0);
	    }

	  XAddToSaveSet(box->dpy, client);

/* 	     XGrabButton(box.dpy, AnyButton, AnyModifier, win2, False, */
/* 	   	      ButtonMask, GrabModeAsync, GrabModeSync, None, None); */
	  
	  box->boxed_windows[i]->box = box;
	  box->boxed_windows[i]->client = client;
	  box->boxed_windows[i]->name = getprop(box, client, XA_WM_NAME);
	  box->boxed_windows[i]->number = i;

	  box->boxed_windows[i]->button =
	    XCreateSimpleWindow(box->dpy, box->win, 
				0, 0, BUTTON_DEFAULT_WIDTH, 20, 2, 
				BlackPixel(box->dpy,box->sn), 
				WhitePixel(box->dpy,box->sn));

	  if (box->active_window != NO_ACTIVE_WINDOW)
	    {
	      resize_window(box->boxed_windows[i]);
	    }
	  else
	    {
	      resize_box_for_client(box, i);
	    }

	  select_window(box->boxed_windows[i]);
	  XMapWindow(box->dpy, box->boxed_windows[i]->button);

	  XSelectInput(box->dpy, box->boxed_windows[i]->button, LeaveWindowMask|EnterWindowMask|FocusChangeMask|ButtonPressMask|ExposureMask);
	  XSelectInput(box->dpy, box->boxed_windows[i]->client, LeaveWindowMask|EnterWindowMask|FocusChangeMask|PropertyChangeMask);
	  draw_button(box->boxed_windows[i]);

	  return;
	}
    }

  fprintf(stderr, "Unable to pack more than %d windows\n", MAX_BOXED_WINDOWS); 
  
}




