/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* 
   outline.c --- the outline widget hack.
   Created: Terry Weissman <terry@netscape.com>, 24-Jun-95.
 */


#include "mozilla.h"
#include "xfe.h"
#include <msgcom.h>

#include "outline.h"
#include "dragdrop.h"

#include <Xfe/Xfe.h>			/* for xfe widgets and utilities */

#ifndef _STDC_
#define _STDC_ 1
#define HACKED_STDC 1
#endif

#define XmLBACKWARDS_COMPATIBILITY

#include <XmL/GridP.h>
#include <Xm/Label.h>		/* For the drag window pixmap label */

#ifdef HACKED_STDC
#undef HACKED_STDC
#undef _STDC_
#endif

#include "icons.h"
#include "icondata.h"

#ifdef FREEIF
#undef FREEIF
#endif
#define FREEIF(obj) do { if (obj) { XP_FREE (obj); obj = 0; }} while (0)

#define PIXMAP_WIDTH 18		/* #### */
#define PIXMAP_INDENT 10

#define LINE_AREA_WIDTH 7

#define FLIPPY_WIDTH 14		/* #### */

#define MIN_COLUMN_WIDTH 10	/* So that the user can manipulate it. */
#define MAX_COLUMN_WIDTH 10000	/* So that we don't overflow any 16-bit
				   numbers. */

static Widget DataWidget = NULL;

typedef struct fe_OutlineInfo {
  Widget widget;
  Widget drag_widget;
  int numcolumns;
  int numrows;
  fe_OutlineGetDataFunc datafunc;
  fe_OutlineClickFunc clickfunc;
  fe_OutlineIconClickFunc iconfunc;
  MSG_Pane *pane;
  void* closure;

  int lastx;
  int lasty;
  Time lastdowntime;
  Time lastuptime;
  XP_Bool ignoreevents;
  EventMask activity;

  /* indented column specific stuff. */
  int *column_indented;	/* the columns that are to be indented. */
  int indent_amount;		/* the amount to indent each subline. */

  /* drag specific stuff. */
  XP_Bool drag_enabled;
  fe_icon *drag_icon;
  void* dragscrolltimer;
  int dragscrolldir;
  int dragrow;
  XP_Bool dragrowbox;
  GC draggc;
  fe_dnd_Type drag_type;

  XP_Bool allowiconresize;
  int iconcolumnwidth;
  int* columnwidths;
  int* columnIndex;		/* Which column to display where.  If
				   columnIndex[1] == 3, then display the third
				   column's data in column 1.  Column zero is
				   the icon column.*/
  char** posinfo;		/* Pointer to a string where we keep the
				   current column ordering and widths, in the
				   form to be saved in the preferences file.*/
  int dragcolumn;
  Pixmap dragColumnPixmap;
  unsigned char clickrowtype;
  const char** headers;		/* What headers to display on the columns.
				   Entry zero is for the icon column. */
  XP_Bool* headerhighlight;

  void* visibleTimer;
  int visibleLine;

  fe_OutlineDesc Data;
  int DataRow;
} fe_OutlineInfo;


static fe_icon pixmapFlippyFolded = { 0 };
static fe_icon pixmapFlippyExpanded = { 0 };

#if 0
static fe_icon pixmapMarked = { 0 };
static fe_icon pixmapUnmarked = { 0 };
#endif

static fe_dnd_Source dragsource = {0};

/* static variables to make the conversion from our style
   to fontlist tags easier (and less of a memory problem.) */

static char *ITALIC_STYLE = "ITALIC";
static char *BOLD_STYLE = "BOLD";
static char *DEFAULT_STYLE = XmFONTLIST_DEFAULT_TAG; /* Needs to be this for i18n to work. */

static char *
fe_outline_style_to_tag(fe_OutlineTextStyle style)
{
  switch (style) 
    {
    case FE_OUTLINE_Italic:
      return ITALIC_STYLE;
    case FE_OUTLINE_Bold:
      return BOLD_STYLE;
    case FE_OUTLINE_Default:
      return DEFAULT_STYLE;
    default:
      XP_ASSERT(0);
      return DEFAULT_STYLE;
    }
}

static void
fe_make_outline_drag_widget (fe_OutlineInfo *info,
			     fe_OutlineDesc *data, int x, int y)
{
#if 0
  Display *dpy = XtDisplay (info->widget);
  XmString str;
  int str_w, str_h;
#endif
  Visual *v = 0;
  Colormap cmap = 0;
  Cardinal depth = 0;
  Widget label;
  XmFontList fontList;
  /*fe_icon *icon;*/
  Widget shell;
  Pixmap dragPixmap;

  if (dragsource.widget) return;

  shell = info->widget;
  while (XtParent(shell) && !XtIsShell(shell)) {
    shell = XtParent(shell);
  }

  XtVaGetValues (shell, XtNvisual, &v, XtNcolormap, &cmap,
		 XtNdepth, &depth, 0);

  XtVaGetValues (info->widget, XmNfontList, &fontList, NULL);

  /* ### free the string
     str = XmStringCreateLocalized(data->str[5]);
     str_w = XmStringWidth (fontList, str);
     str_h = XmStringHeight (fontList, str);
     */

  dragsource.type = info->clickrowtype == XmCONTENT ? info->drag_type : FE_DND_COLUMN;
  dragsource.closure = info->widget;

  if (info->dragColumnPixmap)
    {
      XFreePixmap(XtDisplay(info->widget), info->dragColumnPixmap);
      info->dragColumnPixmap = 0;
    }

  if (dragsource.type == FE_DND_COLUMN) {
    XRectangle cellrect;
    XGCValues gcv;

    XmLGridRowColumnToXY(info->widget, 
			 XmHEADING, 0, 
			 XmCONTENT, info->dragcolumn, 
			 True, &cellrect);

    /* if the column extends past the right hand side of the widget, clip
       it. */
    {
      Dimension clipwidth;
      Widget vertscrollbar;

      XtVaGetValues(info->widget,
		    XmNverticalScrollBar, &vertscrollbar,
		    NULL);

      clipwidth = XfeWidth(info->widget);

      if (vertscrollbar && XtIsManaged(vertscrollbar))
	clipwidth -= XfeWidth(vertscrollbar);

      if (cellrect.x + cellrect.width > (int)clipwidth)
	cellrect.width = clipwidth - cellrect.x;
    }

#ifndef DONT_DRAG_COLUMNS
    cellrect.height = XfeHeight(info->widget) - cellrect.y;
#endif

    if (!info->draggc)
      info->draggc = XCreateGC(XtDisplay(info->widget),
			       XtWindow(info->widget),
			       0, &gcv);

    dragPixmap = info->dragColumnPixmap = XCreatePixmap(XtDisplay(info->widget),
							XtWindow(info->widget),
							cellrect.width,
							cellrect.height,
							depth);

    XCopyArea(XtDisplay(info->widget), XtWindow(info->widget),
	      info->dragColumnPixmap, info->draggc,
	      cellrect.x, cellrect.y, cellrect.width, cellrect.height,
	      0,0);

    dragsource.widget = XtVaCreateWidget ("drag_win",
					  overrideShellWidgetClass,
					  info->widget,
					  XmNwidth, cellrect.width,
					  XmNheight, cellrect.height,
					  XmNvisual, v,
					  XmNcolormap, cmap,
					  XmNdepth, depth,
					  XmNborderWidth, 0,
					  NULL);
  } else {

    XP_ASSERT(info->drag_icon != NULL);
    if (info->drag_icon == NULL) return;

    dragPixmap = info->drag_icon->pixmap;

    dragsource.widget = XtVaCreateWidget ("drag_win",
					  overrideShellWidgetClass,
					  info->widget,
					  XmNwidth, info->drag_icon->width,
					  XmNheight, info->drag_icon->height,
					  XmNvisual, v,
					  XmNcolormap, cmap,
					  XmNdepth, depth,
					  XmNborderWidth, 0,
					  NULL);
  }


  label = XtVaCreateManagedWidget ("label",
				   xmLabelWidgetClass,
				   dragsource.widget,
				   XmNlabelType, XmPIXMAP,
				   XmNlabelPixmap, dragPixmap,
				   NULL);

  /* XmStringFree(str); */
}


static void
fe_destroy_outline_drag_widget (void)
{
  if (!dragsource.widget) return;
  XtDestroyWidget (dragsource.widget);
  dragsource.widget = NULL;
}

static fe_OutlineInfo*
fe_get_info(Widget outline)
{
  fe_OutlineInfo* result = NULL;
  XtVaGetValues(outline, XmNuserData, &result, 0);
  assert(result->widget == outline);
  return result;
}


static void
PixmapDraw(Widget w, Pixmap pixmap, Pixmap mask,
	   int pixmapWidth, int pixmapHeight, unsigned char alignment, GC gc,
	   XRectangle *rect, XRectangle *clipRect)
{
  Display *dpy;
  Window win;
  int needsClip;
  int x, y, width, height;

  if (pixmap == XmUNSPECIFIED_PIXMAP) return;
  if (rect->width <= 4 || rect->height <= 4) return;
  if (clipRect->width < 3 || clipRect->height < 3) return;
  if (!XtIsRealized(w)) return;
  dpy = XtDisplay(w);
  win = XtWindow(w);

  width = pixmapWidth;
  height = pixmapHeight;
  if (!width || !height) {
    alignment = XmALIGNMENT_TOP_LEFT;
    width = clipRect->width - 4;
    height = clipRect->height - 4;
  }

  if (alignment == XmALIGNMENT_TOP_LEFT ||
      alignment == XmALIGNMENT_LEFT ||
      alignment == XmALIGNMENT_BOTTOM_LEFT) {
    x = rect->x + 2;
  } else if (alignment == XmALIGNMENT_TOP ||
	     alignment == XmALIGNMENT_CENTER ||
	     alignment == XmALIGNMENT_BOTTOM) {
    x = rect->x + ((int)rect->width - width) / 2;
  } else {
    x = rect->x + rect->width - width - 2;
  }

  if (alignment == XmALIGNMENT_TOP ||
      alignment == XmALIGNMENT_TOP_LEFT ||
      alignment == XmALIGNMENT_TOP_RIGHT) {
    y = rect->y + 2;
  } else if (alignment == XmALIGNMENT_LEFT ||
	     alignment == XmALIGNMENT_CENTER ||
	     alignment == XmALIGNMENT_RIGHT) {
    y = rect->y + ((int)rect->height - height) / 2;
  } else {
    y = rect->y + rect->height - height - 2;
  }

  needsClip = 1;
  if (clipRect->x <= x &&
      clipRect->y <= y &&
      clipRect->x + clipRect->width >= x + width &&
      clipRect->y + clipRect->height >= y + height) {
    needsClip = 0;
  }

  if (needsClip) {
    XSetClipRectangles(dpy, gc, 0, 0, clipRect, 1, Unsorted);
  } else if (mask) {
    XSetClipMask(dpy, gc, mask);
    XSetClipOrigin(dpy, gc, x, y);
  }
  XSetGraphicsExposures(dpy, gc, False);
  XCopyArea(dpy, pixmap, win, gc, 0, 0, width, height, x, y);
  XSetGraphicsExposures(dpy, gc, True);
  if (needsClip || mask) {
    XSetClipMask(dpy, gc, None);
  }
}




extern XmString fe_StringChopCreate(char* message, char* tag,
				    XmFontList font_list, int maxwidth);

static void
fe_outline_celldraw(Widget widget, XtPointer clientData, XtPointer callData)
{
  fe_OutlineInfo* info = (fe_OutlineInfo*) clientData;
  XmLGridCallbackStruct* call = (XmLGridCallbackStruct*) callData;
  XmLGridDrawStruct *draw = call->drawInfo;
  Boolean drawSelected = draw->drawSelected;
  XmLCGridRow *row;
  XmLCGridColumn *column;
  unsigned char alignment;
  Pixel fg;
  XmFontList fontList;
  XmString str = NULL;
  const char* ptr;
  fe_icon* data = NULL;
  XRectangle r, textRect;
  int sourcecol;

  if (call->rowType != XmCONTENT) return;
  
  row = XmLGridGetRow(widget, call->rowType, call->row);
  column = XmLGridGetColumn(widget, call->columnType, call->column);

  /* fill in useful things into the data sent to the datafunc. */
  info->Data.column_headers = (const char **)info->headers;
  info->Data.numcolumns = info->numcolumns;
  info->Data.selected = (XP_Bool)drawSelected;

  if (DataWidget != widget || call->row != info->DataRow) {
    if (!(*info->datafunc)(widget, info->closure, call->row, &info->Data, 
			   0)) {
      info->DataRow = -1;
      return;
    }
    DataWidget = widget;
    info->DataRow = call->row;
  }

  sourcecol = info->columnIndex[call->column];

  if (sourcecol == 0) 
    {
      /* Draw the flippy, if any. */
      if (info->Data.flippy != FE_OUTLINE_Leaf) 
        {
          switch (info->Data.flippy) {
          case FE_OUTLINE_Folded:	data = &pixmapFlippyFolded; break;
          case FE_OUTLINE_Expanded:	data = &pixmapFlippyExpanded; break;
          default:
	    XP_ASSERT(0);
          }
          r = *draw->cellRect;
          r.width = data->width;
          PixmapDraw(widget, data->pixmap, data->mask, data->width, data->height,
		     XmALIGNMENT_LEFT, draw->gc, &r, call->clipRect);
        }
  
#if 0
      /* Draw the indented icon */
      r = *draw->cellRect;
      data = fe_outline_lookup_icon(Data.icon);
      XP_ASSERT(data);
      if (!data) return;
    
      r.x += Data.depth * PIXMAP_INDENT + FLIPPY_WIDTH;
      r.width = data->width;
      if (r.x + r.width > draw->cellRect->x + draw->cellRect->width) {
	char buf[10];
	XRectangle rect;
	rect = *draw->cellRect;
	rect.width -= r.width;
	PR_snprintf(buf, sizeof (buf), "%d", Data.depth);
        XtVaGetValues(widget,
		      XmNrowPtr, row,
		      XmNcolumnPtr, column,
		      XmNcellForeground, &fg,
		      XmNcellFontList, &fontList,
		      NULL);
        str = XmStringCreate(buf, DEFAULT_STYLE);
        XSetForeground(XtDisplay(widget), draw->gc,
		       drawSelected ? draw->selectForeground : fg);
        XmLStringDraw(widget, str, draw->stringDirection, fontList,
		      XmALIGNMENT_RIGHT, draw->gc, &rect, &rect);
        r.x = draw->cellRect->x + draw->cellRect->width - r.width;
      }
      PixmapDraw(widget, data->pixmap, data->mask, data->width, data->height,
		 XmALIGNMENT_LEFT, draw->gc, &r, call->clipRect);
#endif

    } 
  else /* sourcecol != 0 */
    {
      ptr = info->Data.str[sourcecol - 1];

      /* if we're the indented column, we push everything over by the indented
	 amount -- the depth * the indent_amount */
      if (info->column_indented[call->column] & FE_OUTLINE_IndentedColumn)
	{
	  Dimension indent_amount = info->Data.depth * info->indent_amount;

	  draw->cellRect->x += indent_amount;
	  draw->cellRect->width -= indent_amount;
	}

      /* if there's an icon, we need to push the text over. */
      if (info->Data.type[sourcecol - 1] & FE_OUTLINE_Icon)
	{
          PixmapDraw(widget, info->Data.icons[sourcecol - 1]->pixmap, 
		     info->Data.icons[sourcecol - 1]->mask, 
		     info->Data.icons[sourcecol - 1]->width, 
		     info->Data.icons[sourcecol - 1]->height,
		     XmALIGNMENT_LEFT, draw->gc, draw->cellRect,
		     call->clipRect);

	  textRect.x = draw->cellRect->x + info->Data.icons[sourcecol - 1]->width + 4;
	  textRect.y = draw->cellRect->y;
	  textRect.width = draw->cellRect->width - info->Data.icons[sourcecol - 1]->height - 4;
	  textRect.height = draw->cellRect->height;
	}
      else
	{
	  textRect = *draw->cellRect;
	}

      if (info->Data.type[sourcecol - 1] & FE_OUTLINE_String)
        {
          XtVaGetValues(widget,
		        XmNrowPtr, row,
		        XmNcolumnPtr, column,
		        XmNcellForeground, &fg,
		        XmNcellAlignment, &alignment,
		        XmNcellFontList, &fontList,
		        NULL);
          if (call->clipRect->width > 4) 
            {
	      /* Impose some spacing between columns.  What a hack. ### */
	      call->clipRect->width -= 4;

	      if (info->Data.type[sourcecol - 1] == FE_OUTLINE_ChoppedString) 
                {
	          str = fe_StringChopCreate((char*) ptr, fe_outline_style_to_tag(info->Data.style),
					    fontList,
				            call->clipRect->width);
	        } 
              else 
                {
	          str = XmStringCreate((char *) ptr, fe_outline_style_to_tag(info->Data.style));
	        }
	      
	      XSetForeground(XtDisplay(widget), draw->gc,
		             drawSelected ? draw->selectForeground : fg);
	      XmLStringDraw(widget, str, draw->stringDirection, fontList, alignment,
		            draw->gc, &textRect, call->clipRect);
	      call->clipRect->width += 4;
            }
        } 
    }
  if (call->row == info->dragrow && sourcecol > 0) 
    {
      int y;
      int x2 = draw->cellRect->x + draw->cellRect->width - 1;
      XP_Bool rightedge = FALSE;
      if (call->column == info->numcolumns) 
        {
          rightedge = TRUE;
          if (str) 
            {
	      int xx = draw->cellRect->x + XmStringWidth(fontList, str) + 4;
	      if (x2 > xx) x2 = xx;
            }
        }
      if (info->draggc == NULL) 
        {
          XGCValues gcv;
#if 0
          Pixel foreground;
#endif
          gcv.foreground = fg;
          info->draggc = XCreateGC(XtDisplay(widget), XtWindow(widget),
			           GCForeground, &gcv);
        }
      y = draw->cellRect->y + draw->cellRect->height - 1;
      XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
	        draw->cellRect->x, y, x2, y);
      if (info->dragrowbox) 
        {
          int y0 = draw->cellRect->y;
          if (call->column == 1) 
            {
	      XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
		        draw->cellRect->x, y0, draw->cellRect->x, y);
            }
          if (rightedge) 
            {
	      XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
		        x2, y0, x2, y);
            }	
          XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
		    draw->cellRect->x, y0, x2, y0);
    
        }
    }
  if (str) XmStringFree(str);
}


static void
fe_outline_click(Widget widget, XtPointer clientData, XtPointer callData)
{
  fe_OutlineInfo* info = (fe_OutlineInfo*) clientData;
  XmLGridCallbackStruct* call = (XmLGridCallbackStruct*) callData;
  XEvent* event;
  int row;
  unsigned int state;
  int sourcecol = info->columnIndex[call->column];

  event = call->event;
  if (!event)
    state = 0;
  else if (event->type == ButtonPress || event->type == ButtonRelease)
    state = event->xbutton.state;
  else if (event->type == KeyPress || event->type == KeyRelease)
    state = event->xkey.state;
  else
    state = 0;

  if (call->rowType == XmHEADING) 
    row = -1;
  else
    row = call->row;
  (*info->clickfunc)(widget, info->closure, row,
		     sourcecol - 1,
		     info->headers ? info->headers[sourcecol] : NULL,
		     1, call->reason == XmCR_ACTIVATE ? 2 : 1,
		     (state & ShiftMask) != 0, (state & ControlMask) != 0,
		     0);
}


static int last_motion_x = 0;
static int last_motion_y = 0;

static void
UpdateData (Widget widget, fe_OutlineInfo *info, int row)
{
  if (widget != DataWidget || row != info->DataRow) {
    if (!(*info->datafunc)(widget, info->closure, row, &info->Data, 0)) {
      return;
    }
    DataWidget = widget;
    info->DataRow = row;
  }
}

static XP_Bool
RowIsSelected(fe_OutlineInfo* info, int row)
{
  int* position;
  int n = XmLGridGetSelectedRowCount(info->widget);
  XP_Bool result = FALSE;
  int i;
  if (n > 0) {
    position = XP_ALLOC(sizeof(int) * n);
    if (position) {
      XmLGridGetSelectedRows(info->widget, position, n);
      for (i=0 ; i<n ; i++) {
	if (position[i] == row) {
	  result = TRUE;
	  break;
	}
      }
      XP_FREE(position);
    }
  }
  return result;
}


static void
fe_outline_visible_timer(void* closure)
{
  fe_OutlineInfo* info = (fe_OutlineInfo*) closure;
  info->visibleTimer = NULL;
  if (info->visibleLine >= 0) {
    /* First and check that the user isn't still busy with his mouse.  If
       he is, then we'll do this stuff when activity goes to 0. */
    if (info->activity == 0) {
      fe_OutlineMakeVisible(info->widget, info->visibleLine);
      info->visibleLine = -1;
    }
  }
}


static void
SendClick(fe_OutlineInfo* info, XEvent* event, XP_Bool only_if_not_selected)
{
  int x = event->xbutton.x;
  int y = event->xbutton.y;
  int numclicks = 1;
  unsigned char rowtype;
  unsigned char coltype;
  int row;
  int column;
  unsigned int state = 0;

  if (!only_if_not_selected &&
      abs(x - info->lastx) < 5 && abs(y - info->lasty) < 5 &&
      (info->lastdowntime - info->lastuptime <=
       XtGetMultiClickTime(XtDisplay(info->widget)))) {
    numclicks = 2;		/* ### Allow triple clicks? */
  }
  info->lastx = x;
  info->lasty = y;

  if (XmLGridXYToRowColumn(info->widget, x, y,
			   &rowtype, &row, &coltype, &column) >= 0) {
    if (rowtype == XmHEADING) row = -1;
    if (coltype == XmCONTENT) {
      info->activity = ButtonPressMask;
      info->ignoreevents = True;
      if (column < 1 && !only_if_not_selected && row >= 0) {
	UpdateData(info->widget, info, row);
	if (1 /*### Pixel compare the click with where we drew icon*/) {
	  if (numclicks == 1) {
	    (*info->iconfunc)(info->widget, info->closure, row, 0);
	  }
	}
	return;
      }
      state = event->xbutton.state;
      if (!only_if_not_selected || !RowIsSelected(info, row)) {
	int sourcecol = info->columnIndex[column];
	if (numclicks == 1) {
	  /* The user just clicked.  If he's in the middle of a double-click,
	     then we don't want calls to fe_OutlineMakeVisible to cause things
	     to scroll before the double-click finishes.  So, we set a
	     timer to go off; if fe_OutlineMakeVisible gets called before the
	     timer expires, we put off the call until the timer goes off. */
	  if (info->visibleTimer) {
	    FE_ClearTimeout(info->visibleTimer);
	  }
	  info->visibleTimer =
	    FE_SetTimeout(fe_outline_visible_timer, info,
			  XtGetMultiClickTime(XtDisplay(info->widget)) + 10);
	}	
	if (row != -1)
	  fe_OutlineSelect(info->widget, row, True);

	(*info->clickfunc)(info->widget, info->closure, row, sourcecol - 1,
			   info->headers ? info->headers[sourcecol] : NULL,
			   event->xbutton.button, numclicks,
			   (state & ShiftMask) != 0,
			   (state & ControlMask) != 0, 0);

      }
    }
  }
}



static void
ButtonEvent(Widget widget, XtPointer closure, XEvent* event, Boolean* c)
{
  fe_OutlineInfo* info = (fe_OutlineInfo*) closure;
  int x = event->xbutton.x;
  int y = event->xbutton.y;
  unsigned char rowtype;
  unsigned char coltype;
  int row;
  int column;

  info->ignoreevents = False;

  switch (event->type) {
  case ButtonPress:
    /* Always ignore btn3. Btn3 is for popups. - dp */
    if (event->xbutton.button == 3) break;

    if (XmLGridXYToRowColumn(info->widget, x, y,
			     &rowtype, &row, &coltype, &column) >= 0) {
      if (rowtype == XmHEADING) {
	if (XmLGridPosIsResize(info->widget, x, y)) {
	  return;
	}
      }
      info->clickrowtype = rowtype;
      info->dragcolumn = column;
      info->activity |= ButtonPressMask;
      info->ignoreevents = True;
    }
    last_motion_x = x;
    last_motion_y = y;
    info->lastdowntime = event->xbutton.time;
    break;

  case ButtonRelease:

    /* fe_SetCursor (info->context, False); */

    if (info->activity & ButtonPressMask) {
      if (info->activity & PointerMotionMask) {
	/* handle the drop */

	fe_dnd_DoDrag(&dragsource, event, FE_DND_DROP);
	fe_dnd_DoDrag(&dragsource, event, FE_DND_END);

	fe_destroy_outline_drag_widget();

      } else {
	SendClick(info, event, FALSE);
      }
    }
    info->lastuptime = event->xbutton.time;
    info->activity = 0;
    if (info->visibleLine >= 0 && info->visibleTimer == NULL) {
      fe_OutlineMakeVisible(info->widget, info->visibleLine);
      info->visibleLine = -1;
    }

    break;

  case MotionNotify:
    if (!info->drag_enabled)
      break;

    if (!(info->activity & PointerMotionMask) &&
	(abs(x - last_motion_x) < 5 && abs(y - last_motion_y) < 5)) {
      /* We aren't yet dragging, and the mouse hasn't moved enough for
	 this to be considered a drag. */
      break;
    }


    if (info->activity & ButtonPressMask) {
      /* ok, the pointer moved while a button was held.
       * we're gonna drag some stuff.
       */
      info->ignoreevents = True;
      if (!(info->activity & PointerMotionMask)) {
	if (info->drag_type == FE_DND_NONE &&
	    info->clickrowtype == XmCONTENT) {
	  /* We don't do drag'n'drop in this context.  Do any any visibility
	     scrolling that we may have been putting off til the end of user
	     activity. */
	  info->activity = 0;
	  if (info->visibleLine >= 0 && info->visibleTimer == NULL) {
	    fe_OutlineMakeVisible(info->widget, info->visibleLine);
	    info->visibleLine = -1;
	  }
	  break;
	}

	/* First time.  If the item we're pointing at isn't
	   selected, deliver a click message for it (which ought to
	   select it.) */
	  
	if (info->clickrowtype == XmCONTENT) {
	  /* Hack things so we click where the mouse down was, not where
	     the first notify event was.  Bleah. */
	  event->xbutton.x = last_motion_x;
	  event->xbutton.y = last_motion_y;
	  SendClick(info, event, TRUE);
	  event->xbutton.x = x;
	  event->xbutton.y = y;
	}

	/* Create a drag source. */
	fe_make_outline_drag_widget (info, &info->Data, x, y);
	fe_dnd_DoDrag(&dragsource, event, FE_DND_START);
	info->activity |= PointerMotionMask;
      }
	
      fe_dnd_DoDrag(&dragsource, event, FE_DND_DRAG);

      /* Now, force all the additional mouse motion events that are
	 lingering around on the server to come to us, so that Xt can
	 compress them away.  Yes, XSync really does improve performance
	 in this case, not hurt it. */
      XSync(XtDisplay(info->widget), False);

    }
    last_motion_x = x;
    last_motion_y = y;
    break;
  }
  if (info->ignoreevents) *c = False;    
}

static void
fe_outline_destroy_cb(Widget widget, XtPointer clientData, XtPointer callData)
{
  fe_OutlineInfo *info = (fe_OutlineInfo*)clientData;

  /* first we must have something to free... */
  XP_ASSERT(info);

  /* we must also be sure we're freeing the right stuff. */
  XP_ASSERT(info->widget == widget);

  FREEIF(info->Data.type);
  FREEIF(info->Data.icons);
  FREEIF(info->Data.str);

  FREEIF(info->columnIndex);
  FREEIF(info->column_indented);
  FREEIF(info->headerhighlight);

  XP_FREE(info);
}

static void
fe_set_default_column_widths(Widget widget) {
  fe_OutlineInfo* info = fe_get_info(widget);
  int i;
  short avgwidth, avgheight;
  XmFontList fontList;
  XtVaGetValues(widget, XmNfontList, &fontList, NULL);
  XmLFontListGetDimensions(fontList, &avgwidth, &avgheight, TRUE);

  for (i=0 ; i<info->numcolumns ; i++) {
    int width = info->columnwidths[i] * avgwidth;
    if (width < MIN_COLUMN_WIDTH) width = MIN_COLUMN_WIDTH;
    if (width > MAX_COLUMN_WIDTH) width = MAX_COLUMN_WIDTH;
    info->columnIndex[i] = i;

    if (i == 0) info->iconcolumnwidth = width;

    XtVaSetValues(widget,
		  XmNcolumn, i,
		  XmNcolumnSizePolicy, XmCONSTANT,
		  XmNcolumnWidth, width,
		  NULL);
  }
}

Widget
fe_GridCreate(MWContext* context, Widget parent, String name,
	      ArgList av, Cardinal ac,
	      int maxindentdepth,
	      int numcolumns, int* columnwidths,
	      fe_OutlineGetDataFunc datafunc,
	      fe_OutlineClickFunc clickfunc,
	      fe_OutlineIconClickFunc iconfunc,
	      void* closure,
	      char** posinfo, int tag, XP_Bool isOutline)
{
  Widget result;
  fe_OutlineInfo* info;

  XP_ASSERT(numcolumns >= 0);

  info = XP_NEW_ZAP(fe_OutlineInfo);
  if (info == NULL) return NULL; /* ### */

  XtSetArg(av[ac], XmNuserData, info); ac++;

  info->numcolumns = numcolumns;

  info->datafunc = datafunc;
  info->clickfunc = clickfunc;
  info->iconfunc = iconfunc;
  info->closure = closure;
  info->dragrow = -1;
  info->posinfo = posinfo;
  info->visibleLine = -1;
  info->DataRow = -1;

  info->Data.type = XP_CALLOC(numcolumns, sizeof(fe_OutlineType));
  info->Data.icons = XP_CALLOC(numcolumns, sizeof(fe_icon*));
  info->Data.str = XP_CALLOC(numcolumns, sizeof(char*));

  info->lastx = -999;
  info->columnwidths = columnwidths;

  XtSetArg(av[ac], XmNselectionPolicy, XmSELECT_MULTIPLE_ROW); ac++;

  if (isOutline) {
    columnwidths[numcolumns - 1] = 9999; /* Make the last column always really
					    wide, so we always are ready to
					    show something no matter how wide
					    the window gets. */

#ifdef MOZ_MAIL_NEWS
    if ((context->type == MWContextMail 
	 && fe_globalPrefs.mail_pane_style == FE_PANES_HORIZONTAL)
	|| ((context->type == MWContextNews
	     && fe_globalPrefs.news_pane_style == FE_PANES_HORIZONTAL))) {
      XtSetArg(av[ac], XmNhorizontalSizePolicy, XmCONSTANT); ac++;
    }
    else
#endif /* MOZ_MAIL_NEWS */
    {
      XtSetArg(av[ac], XmNhorizontalSizePolicy, XmVARIABLE); ac++;
    }
  } else {
    XtSetArg(av[ac], XmNhorizontalSizePolicy, XmCONSTANT); ac++;
  }

  result = XmLCreateGrid(parent, name, av, ac);
  info->widget = result;

  XtAddCallback(result, XmNdestroyCallback, fe_outline_destroy_cb, info);

  XtVaSetValues(result,
		XmNcellDefaults, True,
		XmNcellLeftBorderType, XmBORDER_NONE,
		XmNcellRightBorderType, XmBORDER_NONE,
		XmNcellTopBorderType, XmBORDER_NONE,
		XmNcellBottomBorderType, XmBORDER_NONE,
		XmNcellAlignment, XmALIGNMENT_LEFT,
		0);

  XmLGridAddColumns(result, XmCONTENT, -1, info->numcolumns);

  info->allowiconresize = (maxindentdepth > 0);

  info->columnIndex = XP_CALLOC(numcolumns, sizeof(int));

  fe_set_default_column_widths(result);

  XtInsertEventHandler(result, ButtonPressMask | ButtonReleaseMask
		       | PointerMotionMask, False,
		       ButtonEvent, info, XtListHead);

  XtAddCallback(result, XmNcellDrawCallback, fe_outline_celldraw, info);
  /*  XtAddCallback(result, XmNselectCallback, fe_outline_click, info);
      XtAddCallback(result, XmNactivateCallback, fe_outline_click, info);*/

  /* initialize the column indentation stuff. */
  info->column_indented = XP_CALLOC(numcolumns, sizeof(int *));
  info->indent_amount = 10;

  /* initialize the drag and drop stuff. */
  fe_OutlineDisableDrag(result);
  info->dragColumnPixmap = 0;

  if (!pixmapFlippyFolded.pixmap) {
    Pixel pixel;
    XtVaGetValues(result, XmNbackground, &pixel, NULL);

    fe_MakeIcon(context, pixel, &pixmapFlippyFolded, NULL,
		HFolder.width, HFolder.height,
		HFolder.mono_bits, HFolder.color_bits, HFolder.mask_bits,
		FALSE);
    fe_MakeIcon(context, pixel, &pixmapFlippyExpanded, NULL,
		HFolderO.width, HFolderO.height,
		HFolderO.mono_bits, HFolderO.color_bits, HFolderO.mask_bits,
		FALSE);
#if 0 
    /* no marks. */
    fe_MakeIcon(context, pixel, &pixmapMarked, NULL,
		HMarked.width, HMarked.height,
		HMarked.mono_bits, HMarked.color_bits, HMarked.mask_bits,
		FALSE);
    fe_MakeIcon(context, pixel, &pixmapUnmarked, NULL,
		HUMarked.width, HUMarked.height,
		HUMarked.mono_bits, HUMarked.color_bits, HUMarked.mask_bits,
		FALSE);
#endif
  }

  return result;
}



static void
fe_outline_remember_columns(Widget widget)
{
  fe_OutlineInfo* info = fe_get_info(widget);
  XmLCGridColumn* col;
  int i;
  Dimension width;
  char* ptr;
  int length = 0;
  FREEIF(*(info->posinfo));
  for (i=0 ; i < info->numcolumns ; i++) {
    length += strlen(info->headers[i]) + 14;
  }
  *(info->posinfo) = XP_ALLOC(length);
  ptr = *(info->posinfo);
  for (i=0 ; i<info->numcolumns ; i++) {
    col = XmLGridGetColumn(widget, XmCONTENT, i);
    XtVaGetValues(widget, XmNcolumnPtr, col, XmNcolumnWidth, &width, 0);
    PR_snprintf(ptr, *(info->posinfo) + length - ptr,
		"%s:%d;", info->headers[info->columnIndex[i]],
		(int) width);
    ptr += strlen(ptr);
  }
  if (ptr > *(info->posinfo)) ptr[-1] = '\0';
}


static void
fe_outline_resize(Widget widget, XtPointer clientData, XtPointer callData)
{
  /* The user has resized a column.  Unfortunately, if they resize it
     to width 0, they will never be able to grab it to resize it
     bigger.  So, we will implement a minimum width per column. */

  fe_OutlineInfo* info = (fe_OutlineInfo*) clientData;
  XmLGridCallbackStruct* call = (XmLGridCallbackStruct*) callData;
  XmLCGridColumn* col;
  Dimension width;
  if (call->reason != XmCR_RESIZE_COLUMN) return;
  if (!info->allowiconresize && call->column == 0) {
    XtVaSetValues(widget, XmNcolumn, call->column, XmNcolumnWidth,
		  info->iconcolumnwidth, 0);
  } else {
    col = XmLGridGetColumn(widget, call->columnType, call->column);
    XtVaGetValues(widget, XmNcolumnPtr, col, XmNcolumnWidth, &width, 0);
    if (width < MIN_COLUMN_WIDTH) {
      XtVaSetValues(widget, XmNcolumn, call->column,
		    XmNcolumnWidth, MIN_COLUMN_WIDTH, 0);
    }
  }
  fe_outline_remember_columns(widget);
}

void
fe_OutlineSetMaxDepth(Widget outline, int maxdepth)
{
  fe_OutlineInfo* info = fe_get_info(outline);
  int value = (maxdepth - 1) * PIXMAP_INDENT + PIXMAP_WIDTH + FLIPPY_WIDTH;
  XP_ASSERT(!info->allowiconresize);
  XP_ASSERT(maxdepth > 0);
  if (value != info->iconcolumnwidth) {
    info->iconcolumnwidth = value;
    XtVaSetValues(outline, XmNcolumn, 0, XmNcolumnWidth, value, 0);
  }
}


static XmString
fe_outline_get_header(char *widget, char *header, XP_Bool highlight)
{
  char		clas[512];
  XrmDatabase	db;
  char		name[512];
  char		*type;
  XrmValue	value;
  XmString	xms;

  db = XtDatabase(fe_display);
  (void) PR_snprintf(clas, sizeof(clas), "%s.MailNewsColumns.Pane.Column",
		     fe_progclass);
  (void) PR_snprintf(name, sizeof(name), "%s.mailNewsColumns.%s.%s",
		     fe_progclass, widget, header);
  if (XrmGetResource(db, name, clas, &type, &value))
    {
      xms = XmStringCreate((char *) value.addr,
			   highlight ? BOLD_STYLE : DEFAULT_STYLE);
    }
  else
    {
      xms = XmStringCreate(header,
			   highlight ? BOLD_STYLE : DEFAULT_STYLE);
    }

  return xms;
}


void 
fe_OutlineSetHeaders(Widget widget, fe_OutlineHeaderDesc *desc)
{				/* Fix i18n in here ### */
  fe_OutlineInfo* info = fe_get_info(widget);
  int i;
  XmString str;
  char* ptr;
  char* ptr2;
  char* ptr3;
  int value;
  int width;

  ptr = *info->posinfo;
  for (i=0 ; i<info->numcolumns ; i++) {
    if (ptr == NULL) {
      FREEIF(*info->posinfo);
      break;
    }
    ptr2 = XP_STRCHR(ptr, ';');
    if (ptr2) *ptr2 = '\0';
    ptr3 = XP_STRCHR(ptr, ':');
    if (!ptr3) {
      FREEIF(*info->posinfo);
      break;
    }
    *ptr3 = '\0';
    for (value = 0 ; value < info->numcolumns ; value++) {
      if (strcmp(desc->header_strings[value], ptr) == 0) break;
    }
    if (value > info->numcolumns) {
      FREEIF(*info->posinfo);
      break;
    }
    info->columnIndex[i] = value;
    width = atoi(ptr3 + 1);
    *ptr3 = ':';
    if (i == info->numcolumns) width = MAX_COLUMN_WIDTH; /* Last column is
							    always huge.*/
    if (width < MIN_COLUMN_WIDTH) width = MIN_COLUMN_WIDTH;
    if (width > MAX_COLUMN_WIDTH) width = MAX_COLUMN_WIDTH;
    XtVaSetValues(widget,
		  XmNcolumn, i,
		  XmNcolumnSizePolicy, XmCONSTANT,
		  XmNcolumnWidth, width,
		  0);
    if (ptr2) *ptr2++ = ';';
    ptr = ptr2;
  }

  if (*info->posinfo) {
    /* Check that every column was mentioned, and no duplicates occurred. */
    int* check = XP_CALLOC(info->numcolumns, sizeof(int));
    for (i=0 ; i<info->numcolumns ; i++) check[i] = 0;
    for (i=0 ; i<info->numcolumns ; i++) {
      int w = info->columnIndex[i];
      if (w < 0 || w > info->numcolumns || check[w]) {
	FREEIF(*info->posinfo);
	break;
      }
      check[w] = 1;
    }
    XP_FREE(check);
  }

  if (!*info->posinfo) fe_set_default_column_widths(widget);

  info->headers = desc->header_strings;
  info->headerhighlight = (XP_Bool*)
    XP_ALLOC((info->numcolumns) * sizeof(XP_Bool));
  XP_MEMSET(info->headerhighlight, 0,
	    (info->numcolumns) * sizeof(XP_Bool));
  XmLGridAddRows(widget, XmHEADING, 0, 1);
  for (i=0 ; i<info->numcolumns ; i++) {
    char* name = (char *) desc->header_strings[info->columnIndex[i]];

    if (desc->type[info->columnIndex[i]] & FE_OUTLINE_String) 
      {
	str = fe_outline_get_header(XtName(widget), name, 0);
	XtVaSetValues(widget, XmNcolumn, i, XmNrowType, XmHEADING, XmNrow, 0,
		      XmNcellLeftBorderType, XmBORDER_LINE,
		      XmNcellRightBorderType, XmBORDER_LINE,
		      XmNcellTopBorderType, XmBORDER_LINE,
		      XmNcellBottomBorderType, XmBORDER_LINE,
		      XmNcellString, str, 0);
	XmStringFree(str);
      } 
    else if (desc->type[info->columnIndex[i]] & FE_OUTLINE_Icon) 
      {
	XtVaSetValues(widget, XmNcolumn, i, XmNrowType, XmHEADING, XmNrow, 0,
		      XmNcellLeftBorderType, XmBORDER_LINE,
		      XmNcellRightBorderType, XmBORDER_LINE,
		      XmNcellTopBorderType, XmBORDER_LINE,
		      XmNcellBottomBorderType, XmBORDER_LINE,
		      XmNcellType, XmPIXMAP_CELL, XmNcellPixmap, desc->icons[info->columnIndex[i]]->pixmap,
		      0);
      }
   
    /* keep track of the column information -- whether or not to indent, draw
       lines, etc. */
    info->column_indented[i] = desc->type[info->columnIndex[i]];
  }
  XtVaSetValues(widget, XmNallowColumnResize, True, 0);
  XtAddCallback(widget, XmNresizeCallback, fe_outline_resize, info);
}


void
fe_OutlineChangeHeaderLabel(Widget widget, const char* headername,
			    const char* label)
{
  fe_OutlineInfo* info = fe_get_info(widget);
  int i;
  int j;
  XmString str;
  if (label == NULL) label = headername;
  for (i=0 ; i<info->numcolumns ; i++) {
    unsigned char celltype;
    XtVaGetValues(widget, XmNcellType, &celltype, 0);

    if (XP_STRCMP(info->headers[i], headername) == 0) {
      for (j=0 ; j<info->numcolumns ; j++) {
	if (info->columnIndex[j] != i) continue;
	str = XmStringCreate((char*/*NOOP*/)label,
			     info->headerhighlight[i] ? BOLD_STYLE : DEFAULT_STYLE);
	XtVaSetValues(widget, XmNcolumn, j, XmNrowType, XmHEADING, XmNrow, 0,
		      XmNcellString, str, 
		      XmNcellType, XmSTRING_CELL,
		      XmNcellEditable, False, /* hmm.. */
		      0);
	XmStringFree(str);
	return;
      }
    }
  }
  XP_ASSERT(0);
}


void
fe_OutlineSetHeaderHighlight(Widget widget, const char* header, XP_Bool value)
{
  fe_OutlineInfo* info = fe_get_info(widget);
  int i, j;
  XmString str;
  for (i=0 ; i<info->numcolumns ; i++) {
    if (XP_STRCMP(info->headers[i], header) == 0) {
      if (info->headerhighlight[i] == value) return;
      info->headerhighlight[i] = value;
      for (j=0 ; j<info->numcolumns ; j++) {
	if (info->columnIndex[j] == i) {
	  str = fe_outline_get_header(XtName(widget), (char *)info->headers[i], value);
	  XtVaSetValues(widget, XmNcolumn, j, XmNrowType, XmHEADING,
			XmNrow, 0, XmNcellString, str, 0);
	  XmStringFree(str);
	  return;
	}
      }
      XP_ASSERT(0);
    }
  }
  /* should we really do this?
   - toshok 
  XP_ASSERT(0);
  */
}



void
fe_OutlineChange(Widget widget, int first, int length, int newnumrows)
{
  fe_OutlineInfo* info = fe_get_info(widget);
  int i;
  info->DataRow = -1;
  if (newnumrows != info->numrows) {
    if (newnumrows > info->numrows) {
      XmLGridAddRows(widget, XmCONTENT, -1, newnumrows - info->numrows);
    } else {
      XmLGridDeleteRows(widget, XmCONTENT, newnumrows,
			info->numrows - newnumrows);
    }
    info->numrows = newnumrows;
    length = newnumrows - first;
  }
  if (first == 0 && length == newnumrows) {
    XmLGridRedrawAll(widget);
  } else {
    for (i=first ; i<first + length ; i++) {
      XmLGridRedrawRow(widget, XmCONTENT, i);
    }
  }
  XFlush(XtDisplay(widget));
}


void
fe_OutlineSelect(Widget widget, int row, Boolean exclusive)
{
  if (exclusive) XmLGridDeselectAllRows(widget, False);
  XmLGridSelectRow(widget, row, False);
}


void
fe_OutlineUnselect(Widget widget, int row)
{
  XmLGridDeselectRow(widget, row, False);
}

void
fe_OutlineUnselectAll(Widget widget)
{
  XmLGridDeselectAllRows(widget, False);
}


void
fe_OutlineMakeVisible(Widget widget, int visible)
{
  fe_OutlineInfo* info = fe_get_info(widget);
  int firstrow, lastrow;
  XRectangle rect;
  Dimension height, shadowthickness;
  if (visible < 0) return;
  if (info->visibleTimer) {
    info->visibleLine = visible;
    return;
  }
  XtVaGetValues(widget, XmNscrollRow, &firstrow, XmNheight, &height,
		XmNshadowThickness, &shadowthickness, NULL);
  height -= shadowthickness;
  for (lastrow = firstrow + 1 ; ; lastrow++) {
    if (XmLGridRowColumnToXY(widget, XmCONTENT, lastrow, XmCONTENT, 0,
			     True, &rect) < 0)
      {
	break;
      }
    if (rect.y + rect.height >= (int)height) break;
  }
  if (visible >= firstrow && visible < lastrow) return;
  firstrow = visible - ((lastrow - firstrow) / 2);
  if (firstrow < 0) firstrow = 0;
  XtVaSetValues(widget, XmNscrollRow, firstrow, 0);
}


int
fe_OutlineGetSelection(Widget outline, MSG_ViewIndex* sellist, int sizesellist)
{
  int count = XmLGridGetSelectedRowCount(outline);
  if (sellist && count > 0) {
    int status;
    XP_ASSERT(count <= sizesellist);
    if (sizeof(MSG_ViewIndex) == sizeof(int)) {
      status = XmLGridGetSelectedRows(outline, (int*) sellist, count);
    } else {
      int* positions = (int*) XP_ALLOC(count * sizeof(int));
      int i;
      status = XmLGridGetSelectedRows(outline, positions, count);
      if (status == 0) {
	for (i=0 ; i<count ; i++) {
	  sellist[i] = positions[i];
	}
      }
      XP_FREE(positions);
    }
    if (status < 0) return status;
  }
  return count;
}


int
fe_OutlineRootCoordsToRow(Widget outline, int x, int y, XP_Bool* nearbottom)
{
  Position rootx;
  Position rooty;
  int row;
  int column;
  unsigned char rowtype;
  unsigned char coltype;
  XtTranslateCoords(outline, 0, 0, &rootx, &rooty);
  x -= rootx;
  y -= rooty;
  if (x < 0 || y < 0 ||
      x >= XfeWidth(outline) || y >= XfeHeight(outline)) {
    return -1;
  }
  if (XmLGridXYToRowColumn(outline, x, y, &rowtype, &row,
			   &coltype, &column) < 0) {
    return -1;
  }
  if (rowtype != XmCONTENT || coltype != XmCONTENT) return -1;
  if (nearbottom) {
    int row2;
    *nearbottom = (XmLGridXYToRowColumn(outline, x, y + 5, &rowtype, &row2,
					&coltype, &column) >= 0 &&
		   row2 > row);
  }
  return row;
}


#define SCROLLMARGIN 50
#define INITIALWAIT 500
#define REPEATINTERVAL 100

static void
fe_outline_drag_scroll(void* closure)
{
  fe_OutlineInfo* info = (fe_OutlineInfo*) closure;
  int row;
  info->dragscrolltimer = FE_SetTimeout(fe_outline_drag_scroll, info,
					REPEATINTERVAL);
  XtVaGetValues(info->widget, XmNscrollRow, &row, 0);
  row += info->dragscrolldir;
  XtVaSetValues(info->widget, XmNscrollRow, row, 0);
}

void
fe_OutlineHandleDragEvent(Widget outline, XEvent* event, fe_dnd_Event type,
			  fe_dnd_Source* source)
{
  fe_OutlineInfo* info = fe_get_info(outline);
  XP_Bool doscroll = FALSE;
  Position rootx;
  Position rooty;
  int x, y;
  unsigned char rowtype;
  unsigned char coltype;
  int row;
  int column;
  XmLCGridColumn* col;
  Dimension width;
  int i;
  int delta;
  int tmp;
  int total;

  if (source->type == FE_DND_COLUMN) {
    if (type == FE_DND_DROP && source->closure == outline) 
      {
	/* first we make sure that the drop happens within a valid row/column
	   pair. */

	XtTranslateCoords(outline, 0, 0, &rootx, &rooty);

	x = event->xbutton.x_root - rootx;
	y = event->xbutton.y_root - rooty;
	
	if (XmLGridXYToRowColumn(info->widget, x, y,
				 &rowtype, &row, &coltype, &column) < 0)
	  return;
	
	if (column != info->dragcolumn) 
	  {
	    /* Get rid of the hack that makes the last column really wide.  Make
	       it be exactly as wide as it appears, so that if it no longer
	       ends up as the last column, it has a reasonable width. */
	    total = XfeWidth(outline);
	    for (i=0 ; i<info->numcolumns ; i++) 
	      {
		col = XmLGridGetColumn(outline, XmCONTENT, i);
		XtVaGetValues(outline,
			      XmNcolumnPtr, col,
			      XmNcolumnWidth, &width, 0
			      );
		total -= ((int) width); /* Beware any unsigned lossage... */
	      }
	    if (total < MIN_COLUMN_WIDTH) total = MIN_COLUMN_WIDTH;
	    width = total;
	    if (width > MAX_COLUMN_WIDTH) width = MAX_COLUMN_WIDTH;
	    XtVaSetValues(outline, XmNcolumn, info->numcolumns - 1,
			  XmNcolumnWidth, width, 0);

	    if (info->dragcolumn < column) 
	      {
		delta = 1;
	      } 
	    else 
	      {
		delta = -1;
	      }

	    /* save off the column number we dragged */
	    tmp = info->columnIndex[info->dragcolumn];

	    /* move all the other columns out of the way. */
	    for (i=info->dragcolumn ; i != column ; i += delta) 
	      {
		info->columnIndex[i] = info->columnIndex[i + delta];
	      }
	    
	    /* replace the column we dragged in the place we dropped
	       it. */
	    info->columnIndex[column] = tmp;
	    
	    XmLGridMoveColumns(outline, column, info->dragcolumn, 1);
	    
	    /* Now restore the hack of having the last column be wide. */
	    XtVaSetValues(outline, XmNcolumn, info->numcolumns - 1,
			  XmNcolumnWidth, MAX_COLUMN_WIDTH, 0);
	    
	    fe_outline_remember_columns(outline);
	  }
      }

    return;
  }
  
  if (type != FE_DND_DRAG && type != FE_DND_END) return;

  if (type == FE_DND_DRAG) {
    XtTranslateCoords(outline, 0, 0, &rootx, &rooty);
    x = event->xbutton.x_root - rootx;
    y = event->xbutton.y_root - rooty;
    doscroll = (x >= 0 && x < XfeWidth(outline) &&
		((y < 0 && y >= -SCROLLMARGIN) ||
		 (y >= XfeHeight(outline) &&
		  y < XfeHeight(outline) + SCROLLMARGIN)));
    info->dragscrolldir = y > XfeHeight(outline) / 2 ? 1 : -1;
  }
  if (!doscroll) {
    if (info->dragscrolltimer) {
      FE_ClearTimeout(info->dragscrolltimer);
      info->dragscrolltimer = NULL;
    }
  } else {
    if (!info->dragscrolltimer) {
      info->dragscrolltimer = FE_SetTimeout(fe_outline_drag_scroll, info,
					    INITIALWAIT);
    }
  }
}


void
fe_OutlineSetDragFeedback(Widget outline, int row, XP_Bool usebox)
{
  fe_OutlineInfo* info = fe_get_info(outline);
  int old = info->dragrow;
  if (old == row && info->dragrowbox == usebox) return;
  info->dragrowbox = usebox;
  info->dragrow = row;
  if (old >= 0) XmLGridRedrawRow(outline, XmCONTENT, old);
  if (row >= 0 && row != old) XmLGridRedrawRow(outline, XmCONTENT, row);
}


Widget
fe_OutlineCreate(MWContext* context, Widget parent, String name,
		 ArgList av, Cardinal ac,
		 int maxindentdepth,
		 int numcolumns, int* columnwidths,
		 fe_OutlineGetDataFunc datafunc,
		 fe_OutlineClickFunc clickfunc,
		 fe_OutlineIconClickFunc iconfunc,
		 void* closure,
		 char** posinfo, int tag)
{
  /*int i;*/

  return fe_GridCreate(context, parent, name, av, ac, maxindentdepth,
		       numcolumns, columnwidths, datafunc, clickfunc, 
		       iconfunc, closure, posinfo, tag, True); 
  /* last var = isOutline: attach TRUE for Outline, attach FALSE for Grid */
}

void fe_OutlineEnableDrag(Widget outline, fe_icon *dragicon, fe_dnd_Type dragtype)
{
  fe_OutlineInfo* info = fe_get_info(outline);

  info->drag_enabled = True;
  info->drag_icon = dragicon;
  info->drag_type = dragtype;
}

void fe_OutlineDisableDrag(Widget outline)
{
  fe_OutlineInfo* info = fe_get_info(outline);
  
  info->drag_enabled = False;
  info->drag_icon = NULL;
  info->drag_type = FE_DND_NONE;
}
