#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <hesiod.h>
#include <X11/IntrinsicP.h>
#include <X11/Shell.h>
#include <Xm/Xm.h>
#include <Xm/DrawingA.h>
#include <Xm/Text.h>
#include <Xm/List.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/ArrowB.h>
#include <Mu.h>
#include "MuMenu.h"
#include "structs.h"

/* #define verbose */

#define FIRSTDAY 0
#define VISDAYS 7
#define FIRSTDIVISION 18
#define VISDIVISIONS 18

#define DAYS 7
#define HOURS 24
#define HDIVISIONS 2

extern Widget top[2]; /* to go into include... */
extern char *suffix();
extern char *month();

typedef struct
{
  int when[DAYS][HOURS*HDIVISIONS];
} busy;

int poppedUp = 0, exposed = 0;

extern CalendarSelection displayed;
CalendarSelection sched;
Widget apptShell, apptForm, apptMenu, userList, name, user, freeTable;
Widget schedHeader, next, prev, userlistlab;
static int showing = 0;
static Widget apptstog = NULL;

extern Widget mainShell;
extern event *events;
busy busytimes;

#define MAXUSERS 50
int numusers = 0;
int listmap[MAXUSERS];

char *usernames[MAXUSERS];
busy *busies[MAXUSERS];
int ignore[MAXUSERS];
event *userevents[MAXUSERS];

extern Display *disp;
extern Window root;
extern Screen *screen;
extern screennum;
GC gc, dotgc;
GC gcs[5];
int fg, bg;
int erase = 0;
char errors[2000];

void eventsToBusy();
void redraw();
void addToUsers();
void updateGraph();

int drawgw, drawgh;
int drawlm = 60, drawtm = 14; /* left and top margins */

/* if ever called other than on startup, must free data structures */
void readpeople()
{
  FILE *f;
  char filename[50];
  char buffer[50];
  Arg args[4];

  numusers = 0;

  XtSetArg( args[0], XmNselectedItems, NULL );
  XtSetArg( args[1], XmNselectedItemCount, 0 );
  XtSetArg( args[2], XmNitems, NULL );
  XtSetArg( args[3], XmNitemCount, 0 );
  XtSetValues( userList, args, 4 );

  sprintf( filename, "%s/.people", getenv( "HOME" ) );

  f = fopen( filename, "r" );
  if ( f == NULL )
    return;

  while ( !feof( f ) )
    {
      fscanf( f, "%s\n", buffer );
      addToUsers( buffer );
    }
  fclose( f );
}

void writepeople()
{
  FILE *f;
  char filename[50];
  int i;

  if ( !poppedUp )
    return;
  sprintf( filename, "%s/.people", getenv( "HOME" ) );

  f = fopen( filename, "w" );
  for ( i = 0; i < numusers; i++ )
    fprintf( f, "%s\n", usernames[listmap[i]] );

  fclose( f );
}

void appts( widget, closure, call_data )
     Widget widget;
     caddr_t closure, call_data;
{
  Arg arg;

  if (apptstog == NULL)
    apptstog = MuGetWidget("appstog");

  if ( showing )
    XtPopdown( apptShell );	/* "hide" will get called automatically... */
  else
    {
      showing = 1;
      if ( !poppedUp )
	{
	  poppedUp = 1;
	  readpeople();
	  erase = 1; /* need this? bug? */
	}

      XtPopup( apptShell, XtGrabNone );
      XtSetKeyboardFocus( apptShell, name );

      sched = displayed;
      firstOfWeek( &sched.month, &sched.day, &sched.year );
      setHeader();
      updateGraph();
      XtSetArg(arg, XmNset, showing);
      XtSetValues(apptstog, &arg, 1);
    }
}

void hide( widget, closure, call_data )
     Widget widget;
     caddr_t closure, call_data;
{
  Arg arg;

  if (apptstog == NULL)
    apptstog = MuGetWidget("appstog");
  showing = 0;
  XtSetArg(arg, XmNset, showing);
  XtSetValues(apptstog, &arg, 1);
}

void flush( widget, closure, call_data )
Widget widget;
caddr_t closure, call_data;
{
  int i;
  
  for ( i = 0; i < numusers; i++ )
    {
      ignore[i] = 0;
      if ( busies[i] != NULL )
	{
	  XtFree( (char *)busies[i] );
	  busies[i] = NULL;
	}
      if ( userevents[i] != NULL )
	freeEvents( &userevents[i] );
    }
  updateGraph();
}

char *appthelptext[3] = {
"   Type a username in the box labeled username and press return to add\n\
it to the list. The grid at the top is for showing times that people\n\
are busy. If you select (by clicking on) a username in the list, it\n\
will add their busy times to the ones already displayed in the grid.\n\
Similarly, clicking again will deselect the user and remove their busy\n\
times.\n\
\n\
   Non-white areas are shaded to indicate the number of people who\n\
have something scheduled at that time. If an area is black, that means\n\
four or more people have something scheduled then.\n\
\n\
   Thus, to find possible times to make appointments with a group of\n\
people (according to their calendars, anyway), select all of the\n\
people you want in the list, and the white areas leftover will be the\n\
open times of the group. At this time, you must do the legwork for\n\
actually making the appointment yourself.\n\
\n\
   Note that at this time, for someone to be able to see the times\n\
you are busy, they must have read access to your calendar file\n\
(.tc in the top of your home directory).",
"   The first time you select a given user, their calendar file is\n\
loaded in. Subsequent times, it uses the previously loaded file. Thus,\n\
if you selected someone at the beginning of the day, left the program\n\
running for a couple of hours, and came back, the data might be stale.\n\
The reread calendar files option will force the program to reload\n\
calendars so you're sure to have current information."
};

void apptHelp( widget, closure, call_data )
Widget widget;
int closure;
caddr_t call_data;
{
  MyHelp( 1, appthelptext[closure-1] );
}

void nextprevproc( widget, closure, call_data )
Widget widget;
int closure;
int call_data;
{
  offsetDays( closure, &sched.month, &sched.day, &sched.year );
  setHeader();
  updateGraph();
  redraw();
}

/* return the date of the first Monday of the week */
firstOfWeek( month, day, year )
int *month, *day, *year;
{
  int c[42], i;

  cal( *month, *year, c );

  for ( i = 0; i < 42; i++ )
    if ( *day == c[i] )
      break; /* this had better not fail */

  i -= i%7; /* truncate to Sunday */
  i += 1; /* make that Monday! */
  if ( c[i] == 0 )
    {
      *month = *month - 1;
      if ( *month < 1 )
	{
	  *month = 12;
	  *year--;
	}
      cal( *month, *year, c );
      i = 36;
      while ( c[i] == 0 )
	i -= 7;
    }
  *day = c[i];
}

offsetDays( num, month, day, year )
int num, *month, *day, *year;
{
  int c[42], i, j, inc;
  int clue;

  inc = (num<0) ? -1 : 1;
  if ( num < 0 )
    num *= -1;

  cal( *month, *year, c );

  for ( i = 0; i < 42; i++ )
    if ( *day == c[i] )
      break; /* this had better not fail */

  for ( j = 0; j < num; j++ )
    {
      i += inc;

      clue = 0;
      if ( i < 0 || i > 41 )
	clue = 1;
      else
	if ( c[i] == 0 )
	  clue = 1;

      if ( clue )
	{
	  if ( inc == 1 )
	    {
	      (*month)++;
	      if ( *month > 12 )
		{
		  *month = 1;
		  (*year)++;
		}
	      cal( *month, *year, c );
	      i = i%7; /* this is safe; c[i] will be 1 */
	    }
	  else
	    {
	      (*month)--;
	      if ( *month < 1 )
		{
		  (*month) = 12;
		  (*year)--; /* bug - decrement to zero; lots of places */
		} /* insert big-bang graphic here :) */
	      cal( *month, *year, c );
	      i += 7; /* forces it non-negative so % will work */
	      i = i%7 + 35;
	      while ( c[i] == 0 )
		i -= 7;
	    }
	}
    }
  *day = c[i];
}

void eventsToBusy( events, bsy, month, day, year )
event *events;
busy *bsy;
int month, day, year;
{
  simpleEvent *s, *p;
  int c[42];
  int i, j, rolled = 0;
  int st, et, div;

  for ( i = 0; i < DAYS; i++ )
    for ( j = 0; j < HOURS*HDIVISIONS; j++ )
      bsy->when[i][j] = 0;

  cal( month, year, c );
  
  for ( i = 0; i < 42; i++ )
    if ( day == c[i] )
      break; /* this had better not fail */

  for (j = 0; j < DAYS; j++ )
    {
      if ( !rolled )
	{
	  if ( (day = c[i+j]) == 0 )
	    {
	      rolled = 1;
	      day = 1; /* 1752 */
	      month++;
	      if ( month > 12 )
		{
		  month = 1;
		  year++;
		}
	    }
	}
      else
	day++;

      if ( day != 0 )
	{
	  s = cvtEventToSimple( events, year, month, day );
	  p = s;

	  while ( p != NULL )
	    {
	      st = p->starttime.hour * 60 + p->starttime.minute;
	      et = p->endtime.hour * 60 + p->endtime.minute;
	      st = st / (60 / HDIVISIONS);
	      if ( et%(60/HDIVISIONS) == 0 )
		et -= (60/HDIVISIONS);
	      else
		et -= et%(60/HDIVISIONS);
	      et = et / (60/HDIVISIONS);

	      for ( div = st; div <= et; div++ )
		bsy->when[j][div] = 1;
	      p = p->next;
	    }
	  freeSimple( s );
	}
    }
}

static char *daysOfWeek[] = {
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
};

showDaysOfWeekD()
{
  int i;
  int x, y;
  char day[20];
  static int initialized = 0;
  static Font f;
  static XFontStruct *fs;
  CalTime t;

  if ( !initialized )
    {
      initialized = 1;
      f = XLoadFont( disp, "*-*schoolbook-medium-r-*-*-*-120-*" );
      fs = XQueryFont( disp, f );
      XSetFont( disp, gc, f );
    }

  for ( i = 0; i < 7; i++ )
    {
      x = drawlm + i * drawgw;
      sprintf( day, "%s", daysOfWeek[i] );
      x += (drawgw - XTextWidth( fs, day, strlen( day ))) / 2;

      XDrawString( disp, XtWindow( freeTable ),
		   gc,
	           x, fs->ascent,
		   day,
		   strlen( day ));
    }

  for ( i = 0; i < VISDIVISIONS/HDIVISIONS; i++ )
    {
      t.hour = ( FIRSTDIVISION/HDIVISIONS ) + i;
      t.minute = 0;
      t.second = 0;

      timeToStr( t, day );
      y = drawtm + i * HDIVISIONS * drawgh + fs->ascent;

      XDrawString( disp, XtWindow( freeTable ),
		   gc,
	           drawlm - XTextWidth( fs, day, strlen( day )) - 3, y,
		   day,
		   strlen( day ));
    }
}

#define min( a, b ) ( a < b ) ? a : b

void redraw()
{
  int w, h, x, y, day, div;

  w = freeTable->core.width - 1;
  h = freeTable->core.height - 1;

  drawgw = (w - drawlm)/ VISDAYS;
  drawgh = (h - drawtm)/ VISDIVISIONS;

  if ( erase )
    {
      XSetForeground( disp, gc, bg );
      XFillRectangle( disp, XtWindow( freeTable ), gc,
		      0, 0, w+1, h+1 );
      erase = 0;
      XSetForeground( disp, gc, fg );
    }

  for ( y = 0; y <= VISDIVISIONS; y++ )
    if ( (y + FIRSTDIVISION)%HDIVISIONS == 0 )
      XDrawLine( disp, XtWindow( freeTable ),
		 gc,
		 drawlm, drawtm + y * drawgh,
		 drawlm + VISDAYS * drawgw, drawtm + y * drawgh );
    else
      XDrawLine( disp, XtWindow( freeTable ),
		 dotgc,
		 drawlm, drawtm + y * drawgh,
		 drawlm + VISDAYS * drawgw, drawtm + y * drawgh );

  for ( x = 0; x <= VISDAYS; x++ )
    XDrawLine( disp, XtWindow( freeTable ),
	       gc,
	       drawlm + x * drawgw, drawtm,
	       drawlm + x * drawgw, drawtm + VISDIVISIONS * drawgh );

  for ( day = 0; day < VISDAYS; day++ )
    for ( div = FIRSTDIVISION;
	  div < FIRSTDIVISION + VISDIVISIONS; div++ )
      XFillRectangle( disp, XtWindow( freeTable ),
		      gcs[min( busytimes.when[day][div], 4 )],
		      drawlm + day * drawgw + 1,
		      (div - FIRSTDIVISION) * drawgh + 1 + drawtm,
		      drawgw-1, drawgh-1 );
}

void addToUsers( name )
char *name;
{
  int comp, i, j, pos = 0, last = 1;
  XmString stupidstr;

  for ( i = 0; i < numusers && last; i++ )
    {
      comp = strcasecmp( name, usernames[listmap[i]] );
      if ( comp == 0 )
	{
	  sprintf( errors, "User %s already in list.", name );
	  MyError( 1, errors );
	  return; /* sorta bad if user edits multiple duplicates into file */
	}
      else
	if ( comp < 0 ) /* name < usernames[i] */
	  {
	    last = 0;
	    pos = i + 1;
	    for ( j = numusers; j > i; j-- )
	      listmap[j] = listmap[j-1];
	    listmap[i] = numusers;
	    numusers++;
	    break;
	  }
    }
  if ( last )
    {
      listmap[numusers] = numusers;
      numusers++;
    }

  usernames[numusers-1] = XtNewString( name );
  busies[numusers-1] = NULL;
  userevents[numusers-1] = NULL;
  ignore[numusers-1] = 0;

  stupidstr = XmStringLtoRCreate( name,
				  XmSTRING_DEFAULT_CHARSET );

  XmListAddItem( userList, stupidstr, pos );
  XmStringFree( stupidstr );
}

void enterName( widget, closure, call_data )
Widget widget;
caddr_t closure, call_data;
{
  char *str;

  str = XmTextGetString( name );
  addToUsers( str );
  XtFree( str );

  XmTextSetString( name, NULL );
}

int getEvents( who, where )
char *who;
event **where;
{
  char **filsys;
  char filename[100], fallback[100], error[120];
  char *start;
  int cont = 1, e;
  struct stat info;

  MuSetWaitCursor( apptShell );

  e = HES_ER_OK;
  filsys = hes_resolve( who, "filsys" );
  if ( filsys == NULL )
    e = hes_error();
  if ( e != HES_ER_OK )
    switch ( e )
      {
      case 1:
	sprintf( error,
		"User %s not found in Hesiod database.\n", who );
	strcat( errors, error );
	cont = 0;
	break;
      default:
	sprintf( error, "Error %d from hes_resolve for user %s\n", e, who );
	strcat( errors, error );
	cont = 0;
	break;
      }
  else
    {
      sprintf( fallback, "%s", 1 + strrchr( filsys[0], ' ') );
      if ( !strncmp( filsys[0], "AFS", 3 ) )
	{
#ifdef verbose
	  fprintf( stdout, "User %s has AFS homedir; not attaching\n", who );
#endif
	  start = strchr( filsys[0], ' ' ) + 1;

	  sprintf( filename, "%.*s", strchr( start, ' ' ) - start, start );
	}
      else
	strcpy( filename, fallback );
      
      if ( -1 == stat( filename, &info ) )
	switch( errno )
	  {
	  case ENOENT:
#ifdef verbose
	    fprintf( stdout, "Attempting to attach %s... ", who );
	    fflush( stdout );
#endif

	    sprintf( error, "attach -q %s", who );
	    e = system( error );
	    strcpy( filename, fallback ); /* for afs falling to attach */
	    switch( e )
	      {
	      case 0:
#ifdef verbose
		fprintf( stdout, "attached.\n" );
#endif
		break;
	      default:
		if ( -1 == stat( filename, &info ) )
		  {
		    sprintf( error, "Unsuccessful attach of %s", user );
		    cont = 0;
		  }
		else
		  {
		    sprintf( error,
			    "Attach returned error %d for %s; continuing\n",
			    e, user );
		    strcat( errors, error );
		  }
		break;
	      }
	    break;
	  case EACCES:
	    sprintf( error, "No permission to read directory %s\n", filename );
	    strcat( errors, error );
	    cont = 0;
	    break;
	  default:
	    sprintf( error, "Error %d statting %s\n", errno, filename );
	    strcat( errors, error );
	    cont = 0;
	    break;
	  }

      if ( cont )
	{
	  strcat( filename, "/.tc" );
#ifdef verbose
	  fprintf( stdout, "Reading %s\n", filename );
#endif
	  if ( !addFileToEvents( filename, where ) )
	    {
	      cont = 0;
	      switch( errno )
		{
		case ENOENT:
		  sprintf( error, "%s has no calendar file.\n", who );
		  strcat( errors, error );
		  break;
		case EACCES:
		  sprintf( error,"No permission to read %s's calendar file.\n",
			   who );
		  strcat( errors, error );
		  break;
		default:
		  sprintf( error, "Error %d opening %s\n", errno, filename );
		  strcat( errors, error );
		  break;
		}
	    }
	}
    }
  MuSetStandardCursor( apptShell );
  return cont;
}

deletef()
{
  XmString ridiculous;
  int *tbl, num, i;

  XmListGetSelectedPositions( userList, &tbl, &num );

  if ( num )
    {
      i = listmap[tbl[0]];

      /* this is ridiculous... I hate motif. */

      ridiculous = XmStringLtoRCreate( usernames[i],
				       XmSTRING_DEFAULT_CHARSET );
      XmListDeleteItem( userList, ridiculous );
      XmStringFree( ridiculous );

      /* s.b. common flush code */
      XtFree( usernames[i] );

      if ( userevents[i] != NULL )
	freeEvents( &userevents[i] );

      if ( busies[i] != NULL )
	{
	  XtFree( (char *)busies[i] );
	  busies[i] = NULL;
	}

      for ( i = listmap[tbl[0]]; i < numusers-1; i++ )
	{
	  usernames[i] = usernames[i+1];
	  busies[i] = busies[i+1];
	  userevents[i] = userevents[i+1];
	  ignore[i] = ignore[i+1];
	}

      for ( i = 0; i < numusers; i++ )
	if ( listmap[i] > listmap[tbl[0]] )
	  listmap[i] -= 1;

      for ( i = tbl[0]; i < numusers; i++ )
	listmap[i] = listmap[i+1];

      numusers--;
    }
  XtFree( (char *)tbl );
  updateGraph();
}

void updateGraph()
{
  int *tbl, num, i, cont, which;
  char error[120];
  int day, div;

  for ( day = 0; day < DAYS; day++ )
    for ( div = 0; div < HOURS*HDIVISIONS; div++ )
      busytimes.when[day][div] = 0;

  XmListGetSelectedPositions( userList, &tbl, &num );

  errors[0] = '\0';
  for ( i = 0; i < num; i++ )
    {
      which = listmap[tbl[i]];
      cont = 1;

      if ( ignore[which] == 0 && busies[which] == NULL )
	{
	  cont = getEvents( usernames[which], &userevents[which] );
	  
	  if ( cont )
	    {
	      if ( userevents[which] == NULL )
		{
		  sprintf( error, /* bug - will get printed over and over */
			  "User %s has an empty calendar file.\n",
			  usernames[which] );
		  strcat( errors, error );
		  cont = 0;
		}
	      else
		busies[which] = (busy (*)) XtMalloc( sizeof(busy) );
	    }
	  else
	    ignore[which] = 1;
	}

      if ( cont && !ignore[which] )
	{
	  eventsToBusy( userevents[which], busies[which],
		        sched.month, sched.day, sched.year );

	  for ( day = 0; day < DAYS; day++ )
	    for ( div = 0; div < HOURS*HDIVISIONS; div++ )
	      if ( busies[which]->when[day][div] != 0 )
		busytimes.when[day][div]++;
	}
    }
  if ( errors[0] != '\0' )
    MyError( 1, errors );

  redraw();
      
  XtFree( (char *)tbl );
}

void listSel( widget, closure, info )
Widget widget;
caddr_t closure;
XmListCallbackStruct *info;
{
  updateGraph();
}

void freeExpose( widget, closure, info )
Widget widget;
caddr_t closure;
XmDrawingAreaCallbackStruct *info;
{
  exposed = 1;
  redraw();
  showDaysOfWeekD();
}

void freeResize( widget, closure, info )
Widget widget;
caddr_t closure;
XmDrawingAreaCallbackStruct *info;
{
  if ( exposed ) /* punt resize calls before realization */
    {
      erase = 1;
      redraw();
      showDaysOfWeekD();
    }
}

setHeader()
{
  XmString str;
  char temp[50];
  Arg arg;

  sprintf(temp, "Week of %s %d%s, %d",
	  month(sched.month),
	  sched.day,
	  suffix(sched.day),
	  sched.year);
  
  str = XmStringLtoRCreate(temp, XmSTRING_DEFAULT_CHARSET);
  XtSetArg(arg, XmNlabelString, str);
  XtSetValues(schedHeader, &arg, 1);
  XmStringFree(str);
}

void createApptWidgets( shell )
Widget shell;
{
  char *dashes = "\2\2";
  int n;
  long mask;
  Pixmap p0, p25, p50, p75;
  Arg args[2];

  apptShell = XtCreatePopupShell( "appts", topLevelShellWidgetClass,
				  shell, NULL, 0 );

  XtAddCallback( apptShell, XmNpopdownCallback, (XtCallbackProc)hide, NULL );
  top[1] = apptShell;

  apptForm = XtCreateManagedWidget( "form",
			      xmFormWidgetClass,
			      apptShell, NULL, 0 );

  /* Appointment window menu */
  apptMenu = MuCreateMenuBar( apptForm, "apptMenu", NULL, 0);
  XtManageChild(apptMenu);

  /* previous button widget */
  prev = XtCreateManagedWidget( "prev",
			       xmArrowButtonWidgetClass,
			       apptForm, NULL, 0 );

  XtAddCallback( prev, XmNactivateCallback,
		 (XtCallbackProc)nextprevproc, (XtPointer)-7 );

  /* next button widget */
  next = XtCreateManagedWidget( "next",
			       xmArrowButtonWidgetClass,
			       apptForm, NULL, 0 );

  XtAddCallback( next, XmNactivateCallback,
		 (XtCallbackProc)nextprevproc, (XtPointer)7 );

  /* Schedule header label */
  schedHeader = XtCreateManagedWidget( "toWeek",
				      xmLabelWidgetClass,
				      apptForm, NULL, 0 );

  /* Schedule */
  freeTable = XtCreateManagedWidget( "free",
				     xmDrawingAreaWidgetClass,
				     apptForm, NULL, 0 );

  XtAddCallback( freeTable, XmNexposeCallback,
		 (XtCallbackProc)freeExpose, NULL );
  XtAddCallback( freeTable, XmNresizeCallback,
		 (XtCallbackProc)freeResize, NULL );

/* user list widget label widget widget :) */
  userlistlab = XtCreateManagedWidget( "userList",
				       xmLabelWidgetClass,
				       apptForm, NULL, 0 );

  /* user list widget */

  n = 0;
  XtSetArg( args[n], XmNlistSizePolicy, XmCONSTANT ); n++;
  XtSetArg( args[n], XmNselectionPolicy, XmMULTIPLE_SELECT ); n++;

/*
  userList = XtCreateManagedWidget( "who",
				    xmListWidgetClass,
				    apptForm, args, n );
*/

  userList = XmCreateScrolledList( apptForm, "who", args, n );
  XtManageChild( userList );

  XtAddCallback( userList, XmNmultipleSelectionCallback,
		 (XtCallbackProc)listSel, NULL );

/* username widget label widget widget :) */
  user = XtCreateManagedWidget( "user",
			        xmLabelWidgetClass,
			        apptForm, NULL, 0 );

/* username input textedit widget */

  name = XtCreateManagedWidget( "name",
			         xmTextWidgetClass,
			         apptForm, NULL, 0 );
  XtAddCallback( name, XmNactivateCallback,
		 (XtCallbackProc)enterName, NULL );
  _XmGrabTheFocus(name, NULL);

  MuSetEmacsBindings( (Widget) name );


  n = 0;
  XtSetArg(args[n], XmNforeground, &fg); n++;
  XtSetArg(args[n], XmNbackground, &bg); n++;
  XtGetValues(freeTable, args, n);
/*
  fg = BlackPixel( disp, screennum );
  bg = WhitePixel( disp, screennum );
*/
  /* Initialize gc's for schedule notwidget */

  gc = DefaultGC( disp, screennum );
  dotgc = XCreateGC( disp, root, 0, NULL );
  gcs[0] = XCreateGC( disp, root, 0, NULL );
  gcs[1] = XCreateGC( disp, root, 0, NULL );
  gcs[2] = XCreateGC( disp, root, 0, NULL );
  gcs[3] = XCreateGC( disp, root, 0, NULL );
  gcs[4] = XCreateGC( disp, root, 0, NULL );

  mask = ( 1L << (GCLastBit + 1) ) - 1; /* really better than ~0? */
  XCopyGC( disp, gc, mask, dotgc );
  XCopyGC( disp, gc, mask, gcs[0] );
  XCopyGC( disp, gc, mask, gcs[1] );
  XCopyGC( disp, gc, mask, gcs[2] );
  XCopyGC( disp, gc, mask, gcs[3] );
  XCopyGC( disp, gc, mask, gcs[4] );

  p0 = XmGetPixmap( screen, "background", fg, bg );
  p25 = XmGetPixmap( screen, "25_foreground", fg, bg );
  p50 = XmGetPixmap( screen, "50_foreground", fg, bg );
  p75 = XmGetPixmap( screen, "75_foreground", fg, bg );

  XSetTile( disp, gcs[0], p0 );
  XSetTile( disp, gcs[1], p25 );
  XSetTile( disp, gcs[2], p50 );
  XSetTile( disp, gcs[3], p75 );

  XSetFillStyle( disp, gcs[0], FillTiled );
  XSetFillStyle( disp, gcs[1], FillTiled );
  XSetFillStyle( disp, gcs[2], FillTiled );
  XSetFillStyle( disp, gcs[3], FillTiled );

  XSetLineAttributes( disp, dotgc, 0, LineOnOffDash, CapButt, JoinMiter );
  XSetDashes( disp, dotgc, 0, dashes, 2 );
}
