/*
   NAME:     otools.cc
   AUTHOR:   Ole Gunnar Westgaard <olew@har.ifi.uio.no>
   DATE:     Jun  2 1995
             
*/

#include "otools.h"
#include "macros.h"
#include "protos.h"

#include ICON

//=============================================================================
//  Global variables
//=============================================================================

Display       *o_dpy;                                        // Display pointer
int            o_nr;                                          // Display number
char          *o_name;                                      // Application name
XFontStruct   *o_font;                                        // Font structure
uint             xMax,                                      // Window width - 1
                 yMax;                                     // Window height - 1
GC             o_gc;                                        // Graphics context
Window         o_win;                                                 // Window
Colormap       o_cmap;                                       // Window colormap
unsigned char  o_running   = 0;                        // Application running ?
Figlet        *o_selected  = NULL;                           // Selected figlet
Chain         *o_draw = NULL,                        // Chain to draw at expose
              *o_test = NULL;                         // Figlets with an action
void         (*o_DrawFirst) (int,int,int,int);                 // Before chains
void         (*o_DrawLast)  (int,int,int,int);                  // after chains
void         (*o_Configure) (int,int,int,int);              // configure window
void         (*o_Keyboard)  (char *);          // KeyPress information callback
void         (*o_EnterWindow) (int,int);    // Information when entering window
Color          o_color;                                      // Main colortable

//=============================================================================
//  Small utilities
//=============================================================================

//-----------------------------------------------------------------------------
// Return the value of a single hexadecimal character
unsigned char ox(char hex)
{
  if (hex >= '0' && hex <='9') return (hex - '0');
  if (hex >= 'a' && hex <='f') return (hex - 'a' + 10);
  if (hex >= 'A' && hex <='F') return (hex - 'A' + 10);
  return 0;
}

//-----------------------------------------------------------------------------
// Use this to allocate rgb-named table colors from X11, it will return the
// color closest to the one specified, great if the colormap is full??
// the value returned is the X11 colorindex, I think that this routine
// will not add colors to the cmap if they allready exist
unsigned long o_AllocNamedColor(char *color)
{
  XColor cx,cc;
  if (color[0] == '#') {
    if ( strlen(color) == 4 ) {
      cx.red   = ox(color[1]) << 12;
      cx.green = ox(color[2]) << 12;
      cx.blue  = ox(color[3]) << 12;
    }
    if ( strlen(color) == 7 ) {
      cx.red   = ox(color[1]) << 12 && ox(color[2]) << 8;
      cx.green = ox(color[3]) << 12 && ox(color[4]) << 8;
      cx.blue  = ox(color[5]) << 12 && ox(color[6]) << 8;
    }
    if ( strlen(color) == 10 ) {
      cx.red   = ox(color[1])<<12 && ox(color[2])<<8 && ox(color[3])<<4;
      cx.green = ox(color[4])<<12 && ox(color[5])<<8 && ox(color[6])<<4;
      cx.blue  = ox(color[7])<<12 && ox(color[8])<<8 && ox(color[9])<<4;
    }
    if ( XAllocColor(o_dpy,o_cmap,&cx) ) return cx.pixel;
    else {
      (void) fprintf(stderr,"%s: Cannot allocate '%s' color.\n",o_name,color);
      return 0;
    }
  } else {
    if ( XAllocNamedColor(o_dpy,o_cmap,color,&cc,&cx) ) return cc.pixel;
    else {
      (void) fprintf(stderr,"%s: Cannot allocate '%s' color.\n",o_name,color);
      return 0;
    }
  }
}

//-----------------------------------------------------------------------------
// Create and set the attributes of a Graphic Context, define every aspect of
// the GC, this has been setup to work with otools FIGLETS (!!)
void o_Context(Window win,GC *gc,XFontStruct *finfo,unsigned long color)
{
  unsigned long     mask = 0;
  XGCValues values;
  *gc = XCreateGC(o_dpy,win,mask,&values);
  XSetFont(o_dpy,*gc,finfo->fid);
  XSetForeground(o_dpy,*gc,color);
  XSetLineAttributes(o_dpy,*gc,0,LineSolid,CapButt,JoinMiter);
}

//-----------------------------------------------------------------------------
// To allocate a font in an XFontStruct pointer, used by o_Init() to initiate
// the first font specified, this font struct can then be used in a
// Graphics Context (GC), don't use this on a structure allready containing a
// font (use XFreeFont)
void o_Font(XFontStruct **finfo, char *fname)
{
  if ((*finfo = XLoadQueryFont(o_dpy,fname)) == NULL) {
    (void) fprintf(stderr,"%s: Cannot open '%s' font.\n",o_name,fname);
    exit(-1);
  }    
}

//=============================================================================
// More X functions
//=============================================================================

//-----------------------------------------------------------------------------
// The routine that clears buffers that otools used (not all!)... it is a 
// routine you should use just before you exit the application.
// It will throw you out of the loop in o_EnterLoop()
void o_Quit(void)
{
  o_running = 0;
  XUnloadFont(o_dpy,o_font->fid);
  XFreeGC(o_dpy,o_gc);
  XCloseDisplay(o_dpy);
}

//-----------------------------------------------------------------------------
// The main loop of the program, call sometime after o_Init(), it won't exit
// until o_running is set to False (0) ... After having set o_running to 0
// you have to start o_EnterLoop() again to enter the loop once more.
void o_EnterLoop(void)
{
  XEvent  report;
  o_running = 1;
  Chain *tc;
  while (o_running)
  {
    XNextEvent(o_dpy,&report);
    switch (report.type) {

    //-------------------------------------------------------------------------
    case Expose:                           // Expose and iconify of application
      if (o_DrawFirst) o_DrawFirst(report.xexpose.x,             // Draw expose
                                   report.xexpose.y,
                                   report.xexpose.width,
                                   report.xexpose.height);
      if (o_draw) o_draw->IntelligentDraw(
                                   report.xexpose.x,             // Draw expose
                                   report.xexpose.y,
                                   report.xexpose.width,
                                   report.xexpose.height);
      if (o_DrawLast) o_DrawLast  (report.xexpose.x,             // Draw expose
                                   report.xexpose.y,
                                   report.xexpose.width,
                                   report.xexpose.height);
      break;

    //-------------------------------------------------------------------------
    case ConfigureNotify:                // Changing position or size of window
      xMax = report.xconfigure.width -1;
      yMax = report.xconfigure.height -1;
      if (o_Configure) o_Configure(report.xconfigure.x,            // Configure
                                   report.xconfigure.y,
                                   report.xconfigure.width,
                                   report.xconfigure.height);
      break;

    //-------------------------------------------------------------------------
    case ButtonPress:                                         // Button pressed
      if ((! o_selected ) && (report.xbutton.button & 0x01)) {
        tc = o_test;
        while (tc && tc->fig) {
          if ( report.xbutton.x-1 >= tc->fig->xa &&
               report.xbutton.x-1 <= tc->fig->xb &&
               report.xbutton.y-1 >= tc->fig->ya &&
               report.xbutton.y-1 <= tc->fig->yb ) 
          {
            o_selected = tc->fig;
            o_selected->Press(report.xbutton.x-1,report.xbutton.y-1);
            break;                                      /* out of while loop */
          }
          else tc = tc->next;                           /* Check next figlet */
        }
      }
      break;

    //-------------------------------------------------------------------------
    case ButtonRelease:                                  // Release of a button
      if ( o_selected )	{
	o_selected->Release(report.xbutton.x-1,report.xbutton.y-1);
        o_selected = NULL;
      }
      break;

    //-------------------------------------------------------------------------
    case MotionNotify:                                   // Mousepointer motion
      if (report.xmotion.state & 0x200) {
        tc = o_test;
        while (tc && tc->fig) {
          if ( report.xbutton.x-1 >= tc->fig->xa &&
               report.xbutton.x-1 <= tc->fig->xb &&
               report.xbutton.y-1 >= tc->fig->ya &&
               report.xbutton.y-1 <= tc->fig->yb )
          {
            tc->fig->Motion(report.xbutton.x-1,report.xbutton.y-1);
            break;                                         // out of while loop
          }
          else tc = tc->next;
        }
      }
      if (o_selected)
        if (! o_selected->Motion(report.xbutton.x-1,report.xbutton.y-1))
          o_selected = NULL;
      break;

    //-------------------------------------------------------------------------
    case EnterNotify:
      if (o_EnterWindow) o_EnterWindow(report.xbutton.x-1,report.xbutton.y-1);
      break;

    //-------------------------------------------------------------------------
    case KeyPress:                                               // Key pressed
      {
	char buffer[10];
	int bufsize=10,count;
	KeySym keysym;
	XComposeStatus compose;
	count = XLookupString((struct XKeyEvent *) &report,
			      buffer,bufsize-1,&keysym,&compose);
	buffer[count]=0;
	if ((keysym == XK_Return) || (keysym == XK_KP_Enter) ||
	    (keysym == XK_Linefeed)) {
	  buffer[count++]='\n';
	  buffer[count]=0;
	}
	// ( ((keysym>=XK_KP_Space) && (keysym<=XK_KP_9)) ||    // Characters
	//   ((keysym>=XK_space) && (keysym<=XK_asciitilde)))
	// ((keysym >= XK_F1) && (keysym <= XK_F35))            // FunctionKeys
	// ((keysym >= XK_Shift_L) && (keysym <= XK_Hyper_R))   // ComposeKeys
	// ((keysym == XK_BackSpace) || (keysym == XK_Delete))  // DeleteKeys

	// o_tselect
	// Two functions handles the o_tselect pointer, o_SetTselect(Figlet*)
	// and o_DropTselect(). GetTselect will drop the focus of a focused
	// figlet (->DropFocus) and set the new focus (->SetFocus) for the new
	// figlet. DropTselect will remove (->DropFocus) the focus of the
	// tselected figlet, and set o_tselect to zero.
	// if o_tselect is not NULL then all keys will be reported to the
	// figlet pointed to. [ ->KeyPress(char *,KeySym *) ]
	
	// SetFocus()
	// Tells figlet that it has received some kind of focus, the right
	// mousebutton will probably do this on Press event.
	// A textfiglet might want to set o_tselect to point at itself.

	// DropFocus()
	// Tells figlet that it has lost focus, the figlet have to acknowlegde
	// this, and do whatever deinitializing needed. The left mousebutton
	// will probably do this on Release event. A textfiglet might want to
	// set o_tselect to NULL.

	// o_tselect->KeyPress(char *,KeySym *)
	// Tell figlet that it has been given a keypress, and that it should
	// handle it in any way it wants. Can be used to send fake KeyPresses
	// to different figlets (e.g. autotyping, TAB functions)

	if (!o_selected && o_Keyboard) o_Keyboard(buffer);   // Callback string
      }
      break;

    //-------------------------------------------------------------------------
    default:                                                      // DO nothing
      break;
    }
  }
}

//-----------------------------------------------------------------------------
// This routine must be run ONCE, before you start using otools, don't use it
// again in the same application.  It will initialize X and allocate
// structures neccessary for programming with otools. The magic of otools.
void o_Init(char *appname,char *iconname,char *fontname,
            unsigned int px,unsigned int py,unsigned int xs,unsigned int ys,
	    unsigned int xm,unsigned int ym,unsigned int xx,unsigned int yx,
            char *bd,char *lw,char *bg,char *hi,char *fg,char *dk,char *li,
            int pc,char *pv[])
{
  char   *dpyname = NULL;
  Pixmap  icon;
  o_name = pv[0];                   // Set name of application, for errors etc.
  if ( (o_dpy = XOpenDisplay(dpyname)) == NULL )         // Connect to X server
  {
    (void) fprintf(stderr,"%s: Cannot connect to '%s' Xserver.\n",
                   o_name,XDisplayName(dpyname));
    exit(-1);
  }
  o_nr   = DefaultScreen(o_dpy);                          // Get display number
  o_cmap = DefaultColormap(o_dpy,o_nr);            // Get it's default colormap
  xMax = xs - 1;                                        // X and Y window sizes
  yMax = ys - 1;
  o_color.Change(bd,lw,bg,hi,fg,dk,li);                 // Allocate main colors
  o_Font(&o_font,fontname);                           // Allocate the app. font
  o_win  = XCreateSimpleWindow(o_dpy,RootWindow(o_dpy,o_nr),   // Create window
                               px,py,xs,ys,1,
                               o_color.bd,o_color.bg);
  icon   = XCreateBitmapFromData(o_dpy,o_win,obitmap_bits,       // Create icon
                                 obitmap_width,obitmap_height);
  {                                                       // Window hints block
    XSizeHints    shints;
    XWMHints      wmhints;
    XClassHint    chints;
    XTextProperty windowName,iconName;
    if ( XStringListToTextProperty(&appname,1,&windowName) == 0 )    // WinName
    {
      (void) fprintf(stderr,"%s: Alloc for window name failed.\n",o_name);
      exit(-1);
    }
    if ( XStringListToTextProperty(&iconname,1,&iconName) == 0 )    // IconName
    {
      (void) fprintf(stderr,"%s: Alloc for icon name failed.\n",o_name);
      exit(-1);
    }
    shints.flags          = PPosition|PSize|PMinSize|PMaxSize;    // Size hints
    shints.min_width      = xm;
    shints.min_height     = ym;
    shints.max_width      = xx;
    shints.max_height     = yx;
    wmhints.initial_state = NormalState;                // Window manager hints
    wmhints.input         = True;
    wmhints.icon_pixmap   = icon;
    wmhints.flags         = StateHint | IconPixmapHint | InputHint ;
    chints.res_name       = appname;                             // Class hints
    chints.res_class      = "Basicwin";
    XSetWMProperties(o_dpy,o_win,&windowName,&iconName,pv,pc,      // Set hints
                     &shints,&wmhints,&chints);
  }
  /* This stuff is waiting to be used another time
  {
    XSetWindowAttributes wset;
    unsigned long vmask;
    vmask  = CWSaveUnder | CWBitGravity | CWBackingStore;
    wset.bit_gravity   = StaticGravity;
    wset.save_under    = True;
    wset.backing_store = WhenMapped;
    XChangeWindowAttributes(o_dpy,o_win,vmask,&wset);
  }
  */
  XSelectInput(o_dpy,o_win,ExposureMask | KeyPressMask | ButtonPressMask |
               ButtonReleaseMask | StructureNotifyMask | PointerMotionMask |
               EnterWindowMask);
  o_Context(o_win,&o_gc,o_font,o_color.fg);                           // Add GC
  XMapWindow(o_dpy,o_win);                    // Map window on display, show it
}
