/*
 * Simple X11 interfacing routines
 * Implementation file
 *
 * $Id: xutil.cc,v 1.13 2003/03/13 02:25:17 hsteoh Exp hsteoh $
 */

#include <X11/xpm.h>
#include "exception.h"
#include "xutil.h"



/*
 *
 * Class xconnection
 *
 */

void xconnection::eventcallback::read_ready(eventloop *src, int fd) {
  // Make sure no pending events from the X server are queued; otherwise
  // when we return, we might block before completing all necessary updates
  while (XPending(conn->display())) {
    conn->process_pending_event();
    XFlush(conn->display());
  }
}

void xconnection::eventcallback::write_ready(eventloop *src, int fd) {}

void xconnection::dispatch_event(XEvent ev) {
  elistiter<xwindow*> it;

  if (ev.type==KeymapNotify) {
    // TBD...
  } else {
    // Not a keymap event; forward it to the appropriate window
    for (it=winlist.headp(); it; it++) {
      if ((*it)->win == ev.xany.window) {
        (*it)->handle_event(ev);
        return;				// done
      }
    }
    // No window found: ignore for now (ideally, we should trigger a warning
    // because this means a window forgot to register with us)
  }
}

void xconnection::process_pending_event() {
  XEvent ev;

  XNextEvent(disp, &ev);
  dispatch_event(ev);
}

xconnection::xconnection(char *xserver, eventloop *eloop) :
	loop(eloop), event_cb(this) {
  disp = XOpenDisplay(xserver);
  if (!disp) throw exception("@Unable to open X display %s",
                             xserver ? xserver : "(default display)");

  scrn=XDefaultScreen(disp);
  depth=DefaultDepth(disp, scrn);
  cmap=DefaultColormap(disp, scrn);

  // Register with event handler
  loop->register_handler(eventloop::READER, ConnectionNumber(disp),
                         &event_cb);
}

xconnection::~xconnection() {
  loop->unregister_handler(eventloop::READER, ConnectionNumber(disp));
  XCloseDisplay(disp);
}

void xconnection::register_window(xwindow *win) {
  winlist.append(win);
}

void xconnection::unregister_window(xwindow *win) {
  elistiter<xwindow*> it, prev;

  prev.invalidate();
  for (it=winlist.headp(); it; it++) {
    if ((*it) == win) {
      winlist.remove(prev);
    }
    prev=it;
  }
}



/*
 *
 * Class xwindow
 *
 */

void xwindow::update_attrs() {
  XWindowAttributes attr;

  if (!XGetWindowAttributes(conn->display(), win, &attr))
    throw exception("Error while updating window attributes\n");

  wd = attr.width;
  ht = attr.height;
  border_wd = attr.border_width;
}

xwindow::xwindow(xconnection *connection, xwindow *parent,
                 unsigned int x, unsigned int y,
                 unsigned int width, unsigned int height,
                 unsigned int border_width, unsigned long border_color,
                 unsigned long bckgnd_color) {
  Window parentwin;

  conn = connection;
  wd = width;
  ht = height;

  if (parent) {
    parentwin = parent->win;
  } else {
    parentwin = RootWindow(conn->display(), conn->screen());
  }

  win=XCreateSimpleWindow(conn->display(), parentwin, x,y, width, height,
                          border_width, border_color, bckgnd_color);

  // Register with event dispatcher so that we will receive X events
  conn->register_window(this);

  update_attrs();			// freshen window attributes
}

xwindow::~xwindow() {
  XDestroyWindow(conn->display(), win);
}

void xwindow::refresh() {}

void xwindow::handle_event(XEvent ev) {
  if (ev.xany.window != win) return;	// safeguard

  switch (ev.type) {
  case Expose:		expose(ev.xexpose);		break;
  case FocusIn:		focus_in(ev.xfocus);		break;
  case FocusOut:	focus_out(ev.xfocus);		break;
  case KeyPress:	key_press(ev.xkey);		break;
  case KeyRelease:	key_release(ev.xkey);		break;
  case ButtonPress:	mouse_buttondown(ev.xbutton);	break;
  case ButtonRelease:	mouse_buttonup(ev.xbutton);	break;
  case EnterNotify:	mouse_enter(ev.xcrossing);	break;
  case LeaveNotify:	mouse_leave(ev.xcrossing);	break;
  case MotionNotify:	mouse_move(ev.xmotion);		break;
  default:
    // For now, ignore unknown events -- ideally, this should call a virtual
    // function that can be extended by a derived class
    break;
  }
}

void xwindow::expose(XExposeEvent ev) {}
void xwindow::focus_in(XFocusInEvent ev) {}
void xwindow::focus_out(XFocusOutEvent ev) {}
void xwindow::key_press(XKeyPressedEvent ev) {}
void xwindow::key_release(XKeyReleasedEvent ev) {}
void xwindow::mouse_buttondown(XButtonPressedEvent ev) {}
void xwindow::mouse_buttonup(XButtonReleasedEvent ev) {}
void xwindow::mouse_enter(XEnterWindowEvent ev) {}
void xwindow::mouse_leave(XLeaveWindowEvent ev) {}
void xwindow::mouse_move(XMotionEvent ev) {}



/*
 *
 * Class appwindow
 *
 */

appwindow::appwindow(xconnection *connection,
                     char *winstr, char *iconstr,
                     int win_wd, int win_ht,
                     char *resource_class, char *resource_name) :
	xwindow(connection, NULL, 0,0, win_wd, win_ht, 0,0,0),
        disp(connection->display()) {
  XWMHints *wmhints;
  XClassHint *classhints;
  XSizeHints *sizehints;
  XTextProperty win_name, icon_name;

  // Setup window manager hints
  wmhints = XAllocWMHints();
  if (!wmhints) throw exception("Cannot allocate WM hints");

  wmhints->initial_state = NormalState;
  wmhints->input = True;
  wmhints->flags |= StateHint | InputHint;
  wmhints->icon_pixmap = 0;
/*  wmhints->flags = IconPixmapHint; */
/* NOTE: if we're gonna set icon_pixmap to NULL, we should NOT be setting
 * IconPixmapHint!!! That was why XScavenger was showing that oogly messed
 * up "icon" (or non-icon :-P) */

  classhints = XAllocClassHint();
  if (!classhints) throw exception("Cannot allocate class hints");

  classhints->res_name = resource_class;
  classhints->res_class = resource_name;

  sizehints = XAllocSizeHints();
  if (!sizehints) throw exception("Cannot allocate size hints");

  sizehints->flags = PSize | PMinSize | PMaxSize;
  sizehints->min_width  = sizehints->max_width  = wd;
  sizehints->min_height = sizehints->max_height = ht;

  if (!XStringListToTextProperty(&winstr, 1, &win_name))
    throw exception("Error while creating TextProperty");
  if (!XStringListToTextProperty(&iconstr, 1, &icon_name))
    throw exception("Error while creating TextProperty");

  // Set these as our WM settings
  // FIXME:
  // - a lot of stuff here needs to be generalized. But I can't be bothered
  //   to do that yet.
  XSetWMProperties(disp, win, &win_name, &icon_name, NULL, 0, sizehints,
                   wmhints, classhints);

  // Cleanup
  XFree((void *)wmhints);
  XFree((void *)classhints);
  XFree((void *)sizehints);
  XFree((void *)win_name.value);
  XFree((void *)icon_name.value);

  // Map window
  XMapWindow(disp, win);
}

appwindow::~appwindow() {}



/*
 *
 * Class tiled_bckgnd
 *
 */

// FIXME: should use generic image loader
tiled_bckgnd::tiled_bckgnd(xconnection *connection, Drawable drawable,
                           char *xpmfile)
	: conn(connection), d(drawable) {
  Display *disp = conn->display();
  XpmAttributes attr;
  XGCValues gcvals;

  // XPM creation attributes
  attr.colormap = DefaultColormap(disp, conn->screen());
  attr.closeness = 32768;		// FIXME: should be in graphics manager
  attr.valuemask = XpmColormap | XpmCloseness;

  // (Note: NULL is passed for shapemask since we don't care about that)
  if (XpmReadFileToPixmap(disp, d, xpmfile, &tile, NULL, &attr)
      != XpmSuccess) {
    throw exception("@Unable to load tile pixmap from: %s", xpmfile);
  }

  // GC creation attributes
  gcvals.tile = tile;
  gcvals.fill_style = FillTiled;

  tilegc = XCreateGC(disp, d, GCFillStyle | GCTile, &gcvals);
}

tiled_bckgnd::~tiled_bckgnd() {
  Display *disp=conn->display();

  XFreePixmap(disp, tile);
  XFreeGC(disp, tilegc);
}

void tiled_bckgnd::paint(int x, int y, int w, int h) {
  XFillRectangle(conn->display(), d, tilegc, x,y, w,h);
}

