
/*  WINGs WMText: multi-line/font/color/graphic text widget,  by Nwanua. */

#include "WINGsP.h"
#include <ctype.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>

#define DO_BLINK 0

/* TODO:
 * -  verify what happens with XK_return in insertTextInt...
 * -  selection code... selects can be funny if it crosses over. use rect?
 * -       also inspect behaviour for WACenter and WARight
 * -  what if a widget grabs the click... howto say: "pressed me"?
 *       note that WMCreateEventHandler takes one data, but need widget & tPtr
 * -  FIX: graphix blocks MUST be skipped if monoFont even though they exist!
 * -  check if support for Horizontal Scroll is complete
 * -  Tabs now are simply replaced by 4 spaces...
 * -  redo blink code to reduce paint event... use pixmap buffer...
 * -  add paragraph support (full) and '\n' code in getStream..
*/

/* a Section is a section of a TextBlock that describes what parts
       of a TextBlock has been laid out on which "line"...
   o  this greatly aids redraw, scroll and selection.
   o  this is created during layoutLine, but may be later modified.
   o  there may be many Sections per TextBlock, hence the array */
typedef struct {
	unsigned int x, y;	/* where to draw it from */
	unsigned short w, h;	/* its width and height  */
	unsigned short begin;	/* where the layout begins */
	unsigned short end;	/* where it ends */
	unsigned short max_d;	/* a quick hack for layOut if(laidOut) */
	unsigned short last:1;	/* is it the last section on a "line"? */
	unsigned int _y:31;	/* the "line" it and other textblocks are on */
} Section;

/* a TextBlock is a node in a doubly-linked list of TextBlocks containing:
    o text for the block, color and font
    o or a pointer to the pixmap
    o OR a pointer to the widget and the (text) description for its graphic
*/

typedef struct _TextBlock {
	struct _TextBlock *next;	/* next text block in linked list */
	struct _TextBlock *prior;	/* prior text block in linked list */

	char *text;		/* pointer to text (could be kanji) */
	/* or to the object's description */
	union {
		WMFont *font;	/* the font */
		WMWidget *widget;	/* the embedded widget */
		WMPixmap *pixmap;	/* the pixmap */
	} d;			/* description */

	unsigned short used;	/* number of chars in this block */
	unsigned short allocated;	/* size of allocation (in chars) */
	WMColor *color;		/* the color */

	Section *sections;	/* the region for layouts (a growable array) */
	/* an _array_! of size _nsections_ */

	unsigned short s_begin;	/* where the selection begins */
	unsigned short s_end;	/* where it ends */

	unsigned int first:1;	/* first TextBlock in paragraph */
	unsigned int blank:1;	/* ie. blank paragraph */
	unsigned int kanji:1;	/* is of 16-bit characters or not */
	unsigned int graphic:1;	/* graphic or text: text=0 */
	unsigned int object:1;	/* embedded object or pixmap */
	unsigned int underlined:1;	/* underlined or not */
	unsigned int selected:1;	/* selected or not */
	unsigned int nsections:8;	/* over how many "lines" a TextBlock wraps */
	int script:8;		/* script in points: negative for subscript */
	unsigned int marginN:8;	/* which of the margins in the tPtr to use */
	unsigned int nClicks:2;	/* single, double, triple clicks */
	unsigned int RESERVED:7;
} TextBlock;

/* I'm lazy: visible.h vs. visible.size.height :-) */
typedef struct {
	int y, x, h, w;
} myRect;

typedef struct W_Text {
	W_Class widgetClass;	/* the class number of this widget */
	W_View *view;		/* the view referring to this instance */

	WMRuler *ruler;		/* the ruler widget to manipulate paragraphs */

	WMScroller *vS;		/* the vertical scroller */
	unsigned int vpos;	/* the current vertical position */
	unsigned int prevVpos;	/* the previous vertical position */

	WMScroller *hS;		/* the horizontal scroller */
	unsigned int hpos;	/* the current horizontal position */
	unsigned int prevHpos;	/* the previous horizontal position */

	WMFont *dFont;		/* the default font */
	WMColor *dColor;	/* the default color */
	WMPixmap *dBulletPix;	/* the default pixmap for bullets */

	WMColor *fgColor;	/* The current foreground color */
	WMColor *bgColor;	/* The background color */

	GC stippledGC;		/* the GC to overlay selected graphics with */
	Pixmap db;		/* the buffer on which to draw */
	WMPixmap *bgPixmap;	/* the background pixmap */

	myRect visible;		/* the actual rectangle that can be drawn into */
	myRect cursor;		/* the position and (height) of cursor */
	myRect sel;		/* the selection rectangle */

	WMPoint clicked;	/* where in the _document_ was clicked */

	unsigned short tpos;	/* the position in the currentTextBlock */
	unsigned short docWidth;	/* the width of the entire document */
	unsigned int docHeight;	/* the height of the entire document */

	TextBlock *firstTextBlock;
	TextBlock *lastTextBlock;
	TextBlock *currentTextBlock;

	WMArray *gfxItems;	/* a nice array of graphic items */

#if DO_BLINK
	WMHandlerID timerID;	/* for nice twinky-winky */
#endif

	WMAction *parser;
	WMAction *writer;
	WMTextDelegate *delegate;
	Time lastClickTime;

	WMRulerMargins *margins;	/* an array of margins */

	unsigned int nMargins:7;	/* the total number of margins in use */
	struct {
		unsigned int monoFont:1;	/* whether to ignore formats and graphic  */
		unsigned int focused:1;	/* whether this instance has input focus */
		unsigned int editable:1;	/* "silly user, you can't edit me" */
		unsigned int ownsSelection:1;	/* "I ownz the current selection!" */
		unsigned int pointerGrabbed:1;	/* "heh, gib me pointer" */
		unsigned int extendSelection:1;	/* shift-drag to select more regions */

		unsigned int rulerShown:1;	/* whether the ruler is shown or not */
		unsigned int frozen:1;	/* whether screen updates are to be made */
		unsigned int cursorShown:1;	/* whether to show the cursor */
		unsigned int acceptsGraphic:1;	/* accept graphic when dropped */
		unsigned int horizOnDemand:1;	/* if a large image should appear */
		unsigned int needsLayOut:1;	/* in case of Append/Deletes */
		unsigned int ignoreNewLine:1;	/* turn it into a ' ' in streams > 1 */
		unsigned int indentNewLine:1;	/* add "    " for a newline typed */
		unsigned int laidOut:1;	/* have the TextBlocks all been laid out */
		unsigned int waitingForSelection:1;	/* I don't wanna wait in vain... */
		unsigned int prepend:1;	/* prepend=1, append=0 (for parsers) */
		WMAlignment alignment:2;	/* the alignment for text */
		WMReliefType relief:3;	/* the relief to display with */
		unsigned int isOverGraphic:2;	/* the mouse is over a graphic */
		unsigned int first:1;	/* for plain text parsing, newline? */
		/* unsigned int RESERVED:1; */
	} flags;

	WMArray *xdndSourceTypes;
	WMArray *xdndDestinationTypes;
} Text;

#define NOTIFY(T,C,N,A) {\
    WMNotification *notif = WMCreateNotification(N,T,A);\
    if ((T)->delegate && (T)->delegate->C)\
        (*(T)->delegate->C)((T)->delegate,notif);\
    WMPostNotification(notif);\
    WMReleaseNotification(notif);}

#define TYPETEXT 0

#if 0
/* just to print blocks of text not terminated by \0 */
static void output(char *ptr, int len)
{
	char *s;

	s = wmalloc(len + 1);
	memcpy(s, ptr, len);
	s[len] = 0;
	/* printf(" s is [%s] (%d)\n",  s, strlen(s)); */
	printf("[%s]\n", s);
	wfree(s);
}
#endif

#if DO_BLINK
#define CURSOR_BLINK_ON_DELAY   600
#define CURSOR_BLINK_OFF_DELAY  400
#endif

#define STIPPLE_WIDTH 8
#define STIPPLE_HEIGHT 8
static char STIPPLE_BITS[] = {
	0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa
};

static char *default_bullet[] = {
	"6 6 4 1",
	"   c None s None",
	".  c black",
	"X  c white",
	"o  c #808080",
	" ...  ",
	".XX.. ",
	".XX..o",
	".....o",
	" ...oo",
	"  ooo "
};

static void handleEvents(XEvent * event, void *data);
static void layOutDocument(Text * tPtr);
static void updateScrollers(Text * tPtr);

static int getMarginNumber(Text * tPtr, WMRulerMargins * margins)
{
	unsigned int i = 0;

	for (i = 0; i < tPtr->nMargins; i++) {

		if (WMIsMarginEqualToMargin(&tPtr->margins[i], margins))
			return i;
	}

	return -1;
}

static int newMargin(Text * tPtr, WMRulerMargins * margins)
{
	int n;

	if (!margins) {
		tPtr->margins[0].retainCount++;
		return 0;
	}

	n = getMarginNumber(tPtr, margins);

	if (n == -1) {

		if (tPtr->nMargins >= 127) {
			n = tPtr->nMargins - 1;
			return n;
		}

		tPtr->margins = wrealloc(tPtr->margins, (++tPtr->nMargins) * sizeof(WMRulerMargins));

		n = tPtr->nMargins - 1;
		tPtr->margins[n].left = margins->left;
		tPtr->margins[n].first = margins->first;
		tPtr->margins[n].body = margins->body;
		tPtr->margins[n].right = margins->right;
		/* for each tab... */
		tPtr->margins[n].retainCount = 1;
	} else {
		tPtr->margins[n].retainCount++;
	}

	return n;
}

static Bool sectionWasSelected(Text * tPtr, TextBlock * tb, XRectangle * rect, int s)
{
	unsigned short i, w, lw, selected = False, extend = False;
	myRect sel;

	/* if selection rectangle completely encloses the section */
	if ((tb->sections[s]._y >= tPtr->visible.y + tPtr->sel.y)
	    && (tb->sections[s]._y + tb->sections[s].h <= tPtr->visible.y + tPtr->sel.y + tPtr->sel.h)) {
		sel.x = 0;
		sel.w = tPtr->visible.w;
		selected = extend = True;

		/* or if it starts on a line and then goes further down */
	} else if ((tb->sections[s]._y <= tPtr->visible.y + tPtr->sel.y)
		   && (tb->sections[s]._y + tb->sections[s].h <= tPtr->visible.y + tPtr->sel.y + tPtr->sel.h)
		   && (tb->sections[s]._y + tb->sections[s].h >= tPtr->visible.y + tPtr->sel.y)) {
		sel.x = WMAX(tPtr->sel.x, tPtr->clicked.x);
		sel.w = tPtr->visible.w;
		selected = extend = True;

		/* or if it begins before a line, but ends on it */
	} else if ((tb->sections[s]._y >= tPtr->visible.y + tPtr->sel.y)
		   && (tb->sections[s]._y + tb->sections[s].h >= tPtr->visible.y + tPtr->sel.y + tPtr->sel.h)
		   && (tb->sections[s]._y <= tPtr->visible.y + tPtr->sel.y + tPtr->sel.h)) {

		if (1 || tPtr->sel.x + tPtr->sel.w > tPtr->clicked.x)
			sel.w = tPtr->sel.x + tPtr->sel.w;
		else
			sel.w = tPtr->sel.x;

		sel.x = 0;
		selected = True;

		/* or if the selection rectangle lies entirely within a line */
	} else if ((tb->sections[s]._y <= tPtr->visible.y + tPtr->sel.y)
		   && (tPtr->sel.w >= 2)
		   && (tb->sections[s]._y + tb->sections[s].h >= tPtr->visible.y + tPtr->sel.y + tPtr->sel.h)) {
		sel.x = tPtr->sel.x;
		sel.w = tPtr->sel.w;
		selected = True;
	}

	if (selected) {
		selected = False;

		/* if not within (modified) selection rectangle */
		if (tb->sections[s].x > sel.x + sel.w || tb->sections[s].x + tb->sections[s].w < sel.x)
			return False;

		if (tb->graphic) {
			if (tb->sections[s].x + tb->sections[s].w <= sel.x + sel.w && tb->sections[s].x >= sel.x) {
				rect->width = tb->sections[s].w;
				rect->x = tb->sections[s].x;
				selected = True;
			}
		} else {

			i = tb->sections[s].begin;
			lw = 0;

			if (0 && tb->sections[s].x >= sel.x) {
				tb->s_begin = tb->sections[s].begin;
				goto _selEnd;
			}

			while (++i <= tb->sections[s].end) {

				w = WMWidthOfString(tb->d.font, &(tb->text[i - 1]), 1);
				lw += w;

				if (lw + tb->sections[s].x >= sel.x || i == tb->sections[s].end) {
					lw -= w;
					i--;
					tb->s_begin = (tb->selected ? WMIN(tb->s_begin, i) : i);
					break;
				}
			}

			if (i > tb->sections[s].end) {
				printf("WasSelected: (i > tb->sections[s].end) \n");
				return False;
			}

 _selEnd:		rect->x = tb->sections[s].x + lw;
			lw = 0;
			while (++i <= tb->sections[s].end) {

				w = WMWidthOfString(tb->d.font, &(tb->text[i - 1]), 1);
				lw += w;

				if (lw + rect->x >= sel.x + sel.w || i == tb->sections[s].end) {

					if (i != tb->sections[s].end) {
						lw -= w;
						i--;
					}

					rect->width = lw;
					if (tb->sections[s].last && sel.x + sel.w
					    >= tb->sections[s].x + tb->sections[s].w && extend) {
						rect->width += (tPtr->visible.w - rect->x - lw);
					}

					tb->s_end = (tb->selected ? WMAX(tb->s_end, i) : i);
					selected = True;
					break;
				}
			}
		}
	}

	if (selected) {
		rect->y = tb->sections[s]._y - tPtr->vpos;
		rect->height = tb->sections[s].h;
		if (tb->graphic) {
			printf("DEBUG: graphic s%d h%d\n", s, tb->sections[s].h);
		}
	}
	return selected;

}

static void setSelectionProperty(WMText * tPtr, WMFont * font, WMColor * color, int underlined)
{
	TextBlock *tb;
	int isFont = False;

	tb = tPtr->firstTextBlock;
	if (!tb || !tPtr->flags.ownsSelection)
		return;

	if (font && (!color || underlined == -1))
		isFont = True;

	while (tb) {
		if (tPtr->flags.monoFont || tb->selected) {

			if (tPtr->flags.monoFont || (tb->s_end - tb->s_begin == tb->used)
			    || tb->graphic) {

				if (isFont) {
					if (!tb->graphic) {
						WMReleaseFont(tb->d.font);
						tb->d.font = WMRetainFont(font);
					}
				} else if (underlined != -1) {
					tb->underlined = underlined;
				} else {
					WMReleaseColor(tb->color);
					tb->color = WMRetainColor(color);
				}

			} else if (tb->s_end <= tb->used && tb->s_begin < tb->s_end) {

				TextBlock *midtb, *otb = tb;

				if (underlined != -1) {
					midtb = (TextBlock *) WMCreateTextBlockWithText(tPtr,
											&(tb->text[tb->s_begin]),
											tb->d.font, tb->color,
											False,
											(tb->s_end - tb->s_begin));
				} else {
					midtb = (TextBlock *) WMCreateTextBlockWithText(tPtr,
											&(tb->text[tb->s_begin]),
											(isFont ? font : tb->d.
											 font),
											(isFont ? tb->
											 color : color), False,
											(tb->s_end - tb->s_begin));
				}

				if (midtb) {
					if (underlined != -1) {
						midtb->underlined = underlined;
					} else {
						midtb->underlined = otb->underlined;
					}

					midtb->selected = !True;
					midtb->s_begin = 0;
					midtb->s_end = midtb->used;
					tPtr->currentTextBlock = tb;
					WMAppendTextBlock(tPtr, midtb);
					tb = tPtr->currentTextBlock;
				}

				if (otb->used - otb->s_end > 0) {
					TextBlock *ntb;
					ntb = (TextBlock *)
					    WMCreateTextBlockWithText(tPtr,
								      &(otb->text[otb->s_end]), otb->d.font,
								      otb->color, False, otb->used - otb->s_end);

					if (ntb) {
						ntb->underlined = otb->underlined;
						ntb->selected = False;
						WMAppendTextBlock(tPtr, ntb);
						tb = tPtr->currentTextBlock;
					}
				}

				if (midtb) {
					tPtr->currentTextBlock = midtb;
				}

				otb->selected = False;
				otb->used = otb->s_begin;
			}
		}

		tb = tb->next;
	}

	tPtr->flags.needsLayOut = True;
	WMThawText(tPtr);

	/* in case the size changed... */
	if (isFont && tPtr->currentTextBlock) {
		TextBlock *tb = tPtr->currentTextBlock;

		printf("%d %d %d\n", tPtr->sel.y, tPtr->sel.h, tPtr->sel.w);
		tPtr->sel.y = 3 + tb->sections[0]._y;
		tPtr->sel.h = tb->sections[tb->nsections - 1]._y - tb->sections[0]._y;
		tPtr->sel.w = tb->sections[tb->nsections - 1].w;
		if (tb->sections[tb->nsections - 1]._y != tb->sections[0]._y) {
			tPtr->sel.x = 0;
		}
		printf("%d %d %d\n\n\n", tPtr->sel.y, tPtr->sel.h, tPtr->sel.w);
	}

}

static Bool removeSelection(Text * tPtr)
{
	TextBlock *tb = NULL;
	Bool first = False;

	if (!(tb = tPtr->firstTextBlock))
		return False;

	while (tb) {
		if (tb->selected) {
			if (!first && !tb->graphic) {
				WMReleaseFont(tPtr->dFont);
				tPtr->dFont = WMRetainFont(tb->d.font);
				first = True;
			}

			if ((tb->s_end - tb->s_begin == tb->used) || tb->graphic) {
				tPtr->currentTextBlock = tb;
				if (tb->next) {
					tPtr->tpos = 0;
				} else if (tb->prior) {
					if (tb->prior->graphic)
						tPtr->tpos = 1;
					else
						tPtr->tpos = tb->prior->used;
				} else
					tPtr->tpos = 0;

				WMDestroyTextBlock(tPtr, WMRemoveTextBlock(tPtr));
				tb = tPtr->currentTextBlock;
				continue;

			} else if (tb->s_end <= tb->used) {
				memmove(&(tb->text[tb->s_begin]), &(tb->text[tb->s_end]), tb->used - tb->s_end);
				tb->used -= (tb->s_end - tb->s_begin);
				tb->selected = False;
				tPtr->tpos = tb->s_begin;
			}

		}

		tb = tb->next;
	}
	return True;
}

static TextBlock *getFirstNonGraphicBlockFor(TextBlock * tb, short dir)
{
	TextBlock *hold = tb;

	if (!tb)
		return NULL;

	while (tb) {
		if (!tb->graphic)
			break;
		tb = (dir ? tb->next : tb->prior);
	}

	if (!tb) {
		tb = hold;
		while (tb) {
			if (!tb->graphic)
				break;
			tb = (dir ? tb->prior : tb->next);
		}
	}

	if (!tb)
		return NULL;

	return tb;
}

static Bool updateStartForCurrentTextBlock(Text * tPtr, int x, int y, int *dir, TextBlock * tb)
{
	if (tPtr->flags.monoFont && tb->graphic) {
		tb = getFirstNonGraphicBlockFor(tb, *dir);
		if (!tb)
			return 0;

		if (tb->graphic) {
			tPtr->currentTextBlock = (dir ? tPtr->lastTextBlock : tPtr->firstTextBlock);
			tPtr->tpos = 0;
			return 0;
		}
	}

	if (!tb->sections) {
		layOutDocument(tPtr);
		return 0;
	}

	*dir = !(y <= tb->sections[0].y);
	if (*dir) {
		if ((y <= tb->sections[0]._y + tb->sections[0].h)
		    && (y >= tb->sections[0]._y)) {
			/* if it's on the same line */
			if (x < tb->sections[0].x)
				*dir = 0;
		}
	} else {
		if ((y <= tb->sections[tb->nsections - 1]._y + tb->sections[tb->nsections - 1].h)
		    && (y >= tb->sections[tb->nsections - 1]._y)) {
			/* if it's on the same line */
			if (x > tb->sections[tb->nsections - 1].x)
				*dir = 1;
		}
	}

	return 1;
}

static void paintText(Text * tPtr)
{
	TextBlock *tb;
	WMFont *font;
	char *text;
	int len, y, c, s, done = False, prev_y = -23, dir /* 1 = down */ ;
	WMScreen *scr = tPtr->view->screen;
	Display *dpy = tPtr->view->screen->display;
	Window win = tPtr->view->window;
	WMColor *color;

	if (!tPtr->view->flags.realized || !tPtr->db || tPtr->flags.frozen)
		return;

	XFillRectangle(dpy, tPtr->db, WMColorGC(tPtr->bgColor), 0, 0, tPtr->visible.w, tPtr->visible.h);

	if (tPtr->bgPixmap) {
		WMDrawPixmap(tPtr->bgPixmap, tPtr->db,
			     (tPtr->visible.w - tPtr->visible.x - tPtr->bgPixmap->width) / 2,
			     (tPtr->visible.h - tPtr->visible.y - tPtr->bgPixmap->height) / 2);
	}

	if (!(tb = tPtr->currentTextBlock)) {
		if (!(tb = tPtr->firstTextBlock)) {
			goto _copy_area;
		}
	}

	done = False;

	/* first, which direction? Don't waste time looking all over,
	   since the parts to be drawn will most likely be near what
	   was previously drawn */
	if (!updateStartForCurrentTextBlock(tPtr, 0, tPtr->vpos, &dir, tb))
		goto _copy_area;

	while (tb) {

		if (tb->graphic && tPtr->flags.monoFont)
			goto _getSibling;

		if (dir) {
			if (tPtr->vpos <= tb->sections[tb->nsections - 1]._y + tb->sections[tb->nsections - 1].h)
				break;
		} else {
			if (tPtr->vpos >= tb->sections[tb->nsections - 1]._y + tb->sections[tb->nsections - 1].h)
				break;
		}

 _getSibling:
		if (dir) {
			if (tb->next)
				tb = tb->next;
			else
				break;
		} else {
			if (tb->prior)
				tb = tb->prior;
			else
				break;
		}
	}

	/* first, place all text that can be viewed */
	while (!done && tb) {
		if (tb->graphic) {
			tb = tb->next;
			continue;
		}

		tb->selected = False;

		for (s = 0; s < tb->nsections && !done; s++) {

			if (tb->sections[s]._y > tPtr->vpos + tPtr->visible.h) {
				done = True;
				break;
			}

			if (tb->sections[s].y + tb->sections[s].h < tPtr->vpos)
				continue;

			if (tPtr->flags.monoFont) {
				font = tPtr->dFont;
				color = tPtr->fgColor;
			} else {
				font = tb->d.font;
				color = tb->color;
			}

			if (tPtr->flags.ownsSelection) {
				XRectangle rect;

				if (sectionWasSelected(tPtr, tb, &rect, s)) {
					tb->selected = True;
					XFillRectangle(dpy, tPtr->db, WMColorGC(scr->gray),
						       rect.x, rect.y, rect.width, rect.height);
				}
			}

			prev_y = tb->sections[s]._y;

			len = tb->sections[s].end - tb->sections[s].begin;
			text = &(tb->text[tb->sections[s].begin]);
			y = tb->sections[s].y - tPtr->vpos;
			WMDrawString(scr, tPtr->db, color, font, tb->sections[s].x - tPtr->hpos, y, text, len);

			if (!tPtr->flags.monoFont && tb->underlined) {
				XDrawLine(dpy, tPtr->db, WMColorGC(color),
					  tb->sections[s].x - tPtr->hpos,
					  y + font->y + 1,
					  tb->sections[s].x + tb->sections[s].w - tPtr->hpos, y + font->y + 1);
			}
		}
		tb = (!done ? tb->next : NULL);
	}

	/* now , show all graphic items that can be viewed */
	c = WMGetArrayItemCount(tPtr->gfxItems);
	if (c > 0 && !tPtr->flags.monoFont) {
		int j, h;

		for (j = 0; j < c; j++) {
			tb = (TextBlock *) WMGetFromArray(tPtr->gfxItems, j);

			/* if it's not viewable, and mapped, unmap it */
			if (tb->sections[0]._y + tb->sections[0].h <= tPtr->vpos
			    || tb->sections[0]._y >= tPtr->vpos + tPtr->visible.h) {

				if (tb->object) {
					if ((W_VIEW(tb->d.widget))->flags.mapped) {
						WMUnmapWidget(tb->d.widget);
					}
				}
			} else {
				/* if it's viewable, and not mapped, map it */
				if (tb->object) {
					W_View *view = W_VIEW(tb->d.widget);

					if (!view->flags.realized)
						WMRealizeWidget(tb->d.widget);
					if (!view->flags.mapped) {
						XMapWindow(view->screen->display, view->window);
						XFlush(view->screen->display);
						view->flags.mapped = 1;
					}
				}

				if (tb->object) {
					WMMoveWidget(tb->d.widget,
						     tb->sections[0].x + tPtr->visible.x - tPtr->hpos,
						     tb->sections[0].y + tPtr->visible.y - tPtr->vpos);
					h = WMWidgetHeight(tb->d.widget) + 1;

				} else {
					WMDrawPixmap(tb->d.pixmap, tPtr->db,
						     tb->sections[0].x - tPtr->hpos,
						     tb->sections[0].y - tPtr->vpos);
					h = tb->d.pixmap->height + 1;

				}

				if (tPtr->flags.ownsSelection) {
					XRectangle rect;

					if (sectionWasSelected(tPtr, tb, &rect, 0)) {
						Drawable d = (0 && tb->object ?
							      (WMWidgetView(tb->d.widget))->window : tPtr->db);

						tb->selected = True;
						XFillRectangle(dpy, d, tPtr->stippledGC,
							       /*XFillRectangle(dpy, tPtr->db, tPtr->stippledGC, */
							       rect.x, rect.y, rect.width, rect.height);
					}
				}

				if (!tPtr->flags.monoFont && tb->underlined) {
					XDrawLine(dpy, tPtr->db, WMColorGC(tb->color),
						  tb->sections[0].x - tPtr->hpos,
						  tb->sections[0].y + h - tPtr->vpos,
						  tb->sections[0].x + tb->sections[0].w - tPtr->hpos,
						  tb->sections[0].y + h - tPtr->vpos);
				}
			}
		}
	}

 _copy_area:
	if (tPtr->flags.editable && tPtr->flags.cursorShown && tPtr->cursor.x != -23 && tPtr->flags.focused) {
		int y = tPtr->cursor.y - tPtr->vpos;
		XDrawLine(dpy, tPtr->db, WMColorGC(tPtr->fgColor),
			  tPtr->cursor.x, y, tPtr->cursor.x, y + tPtr->cursor.h);
	}

	XCopyArea(dpy, tPtr->db, win, WMColorGC(tPtr->bgColor), 0, 0,
		  tPtr->visible.w, tPtr->visible.h, tPtr->visible.x, tPtr->visible.y);

	W_DrawRelief(scr, win, 0, 0, tPtr->view->size.width, tPtr->view->size.height, tPtr->flags.relief);

	if (tPtr->ruler && tPtr->flags.rulerShown)
		XDrawLine(dpy, win, WMColorGC(tPtr->fgColor), 2, 42, tPtr->view->size.width - 4, 42);

}

static void mouseOverObject(Text * tPtr, int x, int y)
{
	TextBlock *tb;
	Bool result = False;

	x -= tPtr->visible.x;
	x += tPtr->hpos;
	y -= tPtr->visible.y;
	y += tPtr->vpos;

	if (tPtr->flags.ownsSelection) {
		if (tPtr->sel.x <= x
		    && tPtr->sel.y <= y && tPtr->sel.x + tPtr->sel.w >= x && tPtr->sel.y + tPtr->sel.h >= y) {
			tPtr->flags.isOverGraphic = 1;
			result = True;
		}
	}

	if (!result) {
		int j, c = WMGetArrayItemCount(tPtr->gfxItems);

		if (c < 1)
			tPtr->flags.isOverGraphic = 0;

		for (j = 0; j < c; j++) {
			tb = (TextBlock *) WMGetFromArray(tPtr->gfxItems, j);

			if (!tb || !tb->sections) {
				tPtr->flags.isOverGraphic = 0;
				return;
			}

			if (!tb->object) {
				if (tb->sections[0].x <= x
				    && tb->sections[0].y <= y
				    && tb->sections[0].x + tb->sections[0].w >= x
				    && tb->sections[0].y + tb->d.pixmap->height >= y) {
					tPtr->flags.isOverGraphic = 3;
					result = True;
					break;
				}
			}
		}

	}

	if (!result)
		tPtr->flags.isOverGraphic = 0;

	tPtr->view->attribs.cursor = (result ? tPtr->view->screen->defaultCursor : tPtr->view->screen->textCursor);
	{
		XSetWindowAttributes attribs;
		attribs.cursor = tPtr->view->attribs.cursor;
		XChangeWindowAttributes(tPtr->view->screen->display, tPtr->view->window, CWCursor, &attribs);
	}
}

#if DO_BLINK

static void blinkCursor(void *data)
{
	Text *tPtr = (Text *) data;

	if (tPtr->flags.cursorShown) {
		tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor, data);
	} else {
		tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor, data);
	}
	paintText(tPtr);
	tPtr->flags.cursorShown = !tPtr->flags.cursorShown;
}
#endif

static void updateCursorPosition(Text * tPtr)
{
	TextBlock *tb = NULL;
	int x, y, h, s;

	if (tPtr->flags.needsLayOut)
		layOutDocument(tPtr);

	if (!(tb = tPtr->currentTextBlock)) {
		if (!(tb = tPtr->firstTextBlock)) {
			WMFont *font = tPtr->dFont;
			tPtr->tpos = 0;
			tPtr->cursor.h = font->height + abs(font->height - font->y);

			tPtr->cursor.y = 2;
			tPtr->cursor.x = 2;
			return;
		}
	}

	if (tb->blank) {
		tPtr->tpos = 0;
		y = tb->sections[0].y;
		h = tb->sections[0].h;
		x = tb->sections[0].x;

	} else if (tb->graphic) {
		y = tb->sections[0].y;
		h = tb->sections[0].h;
		x = tb->sections[0].x;
		if (tPtr->tpos == 1)
			x += tb->sections[0].w;

	} else {
		if (tPtr->tpos > tb->used)
			tPtr->tpos = tb->used;

		for (s = 0; s < tb->nsections - 1; s++) {

			if (tPtr->tpos >= tb->sections[s].begin && tPtr->tpos <= tb->sections[s].end)
				break;
		}

		y = tb->sections[s]._y;
		h = tb->sections[s].h;
		x = tb->sections[s].x + WMWidthOfString((tPtr->flags.monoFont ? tPtr->dFont : tb->d.font),
							&tb->text[tb->sections[s].begin],
							tPtr->tpos - tb->sections[s].begin);
	}

	tPtr->cursor.y = y;
	tPtr->cursor.h = h;
	tPtr->cursor.x = x;

	/* scroll the bars if the cursor is not visible */
	if (tPtr->flags.editable && tPtr->cursor.x != -23) {
		if (tPtr->cursor.y + tPtr->cursor.h > tPtr->vpos + tPtr->visible.y + tPtr->visible.h) {
			tPtr->vpos +=
			    (tPtr->cursor.y + tPtr->cursor.h + 10
			     - (tPtr->vpos + tPtr->visible.y + tPtr->visible.h));
		} else if (tPtr->cursor.y < tPtr->vpos + tPtr->visible.y) {
			tPtr->vpos -= (tPtr->vpos + tPtr->visible.y - tPtr->cursor.y);
		}

	}

	updateScrollers(tPtr);
}

static void cursorToTextPosition(Text * tPtr, int x, int y)
{
	TextBlock *tb = NULL;
	int done = False, s, pos, len, _w, _y, dir = 1;	/* 1 == "down" */
	char *text;

	if (tPtr->flags.needsLayOut)
		layOutDocument(tPtr);

	y += (tPtr->vpos - tPtr->visible.y);
	if (y < 0)
		y = 0;

	x -= (tPtr->visible.x - 2);
	if (x < 0)
		x = 0;

	/* clicked is relative to document, not window... */
	tPtr->clicked.x = x;
	tPtr->clicked.y = y;

	if (!(tb = tPtr->currentTextBlock)) {
		if (!(tb = tPtr->firstTextBlock)) {
			WMFont *font = tPtr->dFont;
			tPtr->tpos = 0;
			tPtr->cursor.h = font->height + abs(font->height - font->y);
			tPtr->cursor.y = 2;
			tPtr->cursor.x = 2;
			return;
		}
	}

	/* first, which direction? Most likely, newly clicked
	   position will be close to previous */
	if (!updateStartForCurrentTextBlock(tPtr, x, y, &dir, tb))
		return;

	s = (dir ? 0 : tb->nsections - 1);
	if (y >= tb->sections[s]._y && y <= tb->sections[s]._y + tb->sections[s].h) {
		goto _doneV;
	}

	/* get the first (or last) section of the TextBlock that
	   lies about the vertical click point */
	done = False;
	while (!done && tb) {

		if (tPtr->flags.monoFont && tb->graphic) {
			if ((dir ? tb->next : tb->prior))
				tb = (dir ? tb->next : tb->prior);
			continue;
		}

		s = (dir ? 0 : tb->nsections - 1);
		while (!done && (dir ? (s < tb->nsections) : (s >= 0))) {

			if ((dir ? (y <= tb->sections[s]._y + tb->sections[s].h) : (y >= tb->sections[s]._y))) {
				done = True;
			} else {
				dir ? s++ : s--;
			}
		}

		if (!done) {
			if ((dir ? tb->next : tb->prior)) {
				tb = (dir ? tb->next : tb->prior);
			} else {
				pos = tb->used;
				break;	/* goto _doneH; */
			}
		}
	}

	if (s < 0 || s >= tb->nsections) {
		s = (dir ? tb->nsections - 1 : 0);
	}

 _doneV:
	/* we have the line, which TextBlock on that line is it? */
	pos = (dir ? 0 : tb->sections[s].begin);
	if (tPtr->flags.monoFont && tb->graphic) {
		TextBlock *hold = tb;
		tb = getFirstNonGraphicBlockFor(hold, dir);

		if (!tb) {
			tPtr->tpos = 0;
			tb = hold;
			s = 0;
			goto _doNothing;
		}
	}

	if (tb->blank)
		_w = 0;

	_y = tb->sections[s]._y;

	while (tb) {

		if (tPtr->flags.monoFont && tb->graphic) {
			tb = (dir ? tb->next : tb->prior);
			continue;
		}

		if (dir) {
			if (tb->graphic) {
				if (tb->object)
					_w = WMWidgetWidth(tb->d.widget) - 5;
				else
					_w = tb->d.pixmap->width - 5;

				if (tb->sections[0].x + _w >= x)
					break;
			} else {
				text = &(tb->text[tb->sections[s].begin]);
				len = tb->sections[s].end - tb->sections[s].begin;
				_w = WMWidthOfString(tb->d.font, text, len);
				if (tb->sections[s].x + _w >= x)
					break;

			}
		} else {
			if (tb->sections[s].x <= x)
				break;
		}

		if ((dir ? tb->next : tb->prior)) {
			TextBlock *nxt = (dir ? tb->next : tb->prior);
			if (tPtr->flags.monoFont && nxt->graphic) {
				nxt = getFirstNonGraphicBlockFor(nxt, dir);
				if (!nxt) {
					pos = (dir ? 0 : tb->sections[s].begin);
					tPtr->cursor.x = tb->sections[s].x;
					goto _doneH;
				}
			}

			if (_y != nxt->sections[dir ? 0 : nxt->nsections - 1]._y) {
				/* this must be the last/first on this line. stop */
				pos = (dir ? tb->sections[s].end : 0);
				tPtr->cursor.x = tb->sections[s].x;
				if (!tb->blank) {
					if (tb->graphic) {
						if (tb->object)
							tPtr->cursor.x += WMWidgetWidth(tb->d.widget);
						else
							tPtr->cursor.x += tb->d.pixmap->width;
					} else if (pos > tb->sections[s].begin) {
						tPtr->cursor.x +=
						    WMWidthOfString(tb->d.font,
								    &(tb->text[tb->sections[s].begin]),
								    pos - tb->sections[s].begin);
					}
				}
				goto _doneH;
			}
		}

		if ((dir ? tb->next : tb->prior)) {
			tb = (dir ? tb->next : tb->prior);
		} else {
			done = True;
			break;
		}

		if (tb)
			s = (dir ? 0 : tb->nsections - 1);
	}

	/* we have said TextBlock, now where within it? */
	if (tb) {
		if (tb->graphic) {
			int gw = (tb->object ? WMWidgetWidth(tb->d.widget) : tb->d.pixmap->width);

			tPtr->cursor.x = tb->sections[0].x;

			if (x > tPtr->cursor.x + gw / 2) {
				pos = 1;
				tPtr->cursor.x += gw;
			} else {
				printf("first %d\n", tb->first);
				if (tb->prior) {
					if (tb->prior->graphic)
						pos = 1;
					else
						pos = tb->prior->used;
					tb = tb->prior;
				} else
					pos = 0;

			}

			s = 0;
			goto _doneH;

		} else {
			WMFont *f = tb->d.font;
			len = tb->sections[s].end - tb->sections[s].begin;
			text = &(tb->text[tb->sections[s].begin]);

			_w = x - tb->sections[s].x;
			pos = 0;

			while (pos < len && WMWidthOfString(f, text, pos + 1) < _w)
				pos++;

			tPtr->cursor.x = tb->sections[s].x + (pos ? WMWidthOfString(f, text, pos) : 0);

			pos += tb->sections[s].begin;
		}
	}

 _doneH:
	if (tb->graphic) {
		tPtr->tpos = (pos <= 1) ? pos : 0;
	} else {
		tPtr->tpos = (pos < tb->used) ? pos : tb->used;
	}
 _doNothing:
	if (!tb)
		printf("...for this app will surely crash :-)\n");

	tPtr->currentTextBlock = tb;
	tPtr->cursor.h = tb->sections[s].h;
	tPtr->cursor.y = tb->sections[s]._y;

	/* scroll the bars if the cursor is not visible */
	if (tPtr->flags.editable && tPtr->cursor.x != -23) {
		if (tPtr->cursor.y + tPtr->cursor.h > tPtr->vpos + tPtr->visible.y + tPtr->visible.h) {
			tPtr->vpos +=
			    (tPtr->cursor.y + tPtr->cursor.h + 10
			     - (tPtr->vpos + tPtr->visible.y + tPtr->visible.h));
			updateScrollers(tPtr);
		} else if (tPtr->cursor.y < tPtr->vpos + tPtr->visible.y) {
			tPtr->vpos -= (tPtr->vpos + tPtr->visible.y - tPtr->cursor.y);
			updateScrollers(tPtr);
		}

	}

}

static void updateScrollers(Text * tPtr)
{

	if (tPtr->flags.frozen)
		return;

	if (tPtr->vS) {
		if (tPtr->docHeight <= tPtr->visible.h) {
			WMSetScrollerParameters(tPtr->vS, 0, 1);
			tPtr->vpos = 0;
		} else {
			float hmax = (float)(tPtr->docHeight);
			WMSetScrollerParameters(tPtr->vS,
						((float)tPtr->vpos) / (hmax - (float)tPtr->visible.h),
						(float)tPtr->visible.h / hmax);
		}
	} else
		tPtr->vpos = 0;

	if (tPtr->hS) {
		if (tPtr->docWidth <= tPtr->visible.w) {
			WMSetScrollerParameters(tPtr->hS, 0, 1);
			tPtr->hpos = 0;
		} else {
			float wmax = (float)(tPtr->docWidth);
			WMSetScrollerParameters(tPtr->hS,
						((float)tPtr->hpos) / (wmax - (float)tPtr->visible.w),
						(float)tPtr->visible.w / wmax);
		}
	} else
		tPtr->hpos = 0;
}

static void scrollersCallBack(WMWidget * w, void *self)
{
	Text *tPtr = (Text *) self;
	Bool scroll = False;
	int which;

	if (!tPtr->view->flags.realized || tPtr->flags.frozen)
		return;

	if (w == tPtr->vS) {
		int height;
		height = tPtr->visible.h;

		which = WMGetScrollerHitPart(tPtr->vS);
		switch (which) {

		case WSDecrementLine:
			if (tPtr->vpos > 0) {
				if (tPtr->vpos > 16)
					tPtr->vpos -= 16;
				else
					tPtr->vpos = 0;
				scroll = True;
			}
			break;

		case WSIncrementLine:{
				int limit = tPtr->docHeight - height;
				if (tPtr->vpos < limit) {
					if (tPtr->vpos < limit - 16)
						tPtr->vpos += 16;
					else
						tPtr->vpos = limit;
					scroll = True;
				}
			}
			break;

		case WSDecrementPage:
			if (((int)tPtr->vpos - (int)height) >= 0)
				tPtr->vpos -= height;
			else
				tPtr->vpos = 0;

			scroll = True;
			break;

		case WSIncrementPage:
			tPtr->vpos += height;
			if (tPtr->vpos > (tPtr->docHeight - height))
				tPtr->vpos = tPtr->docHeight - height;
			scroll = True;
			break;

		case WSKnob:
			tPtr->vpos = WMGetScrollerValue(tPtr->vS)
			    * (float)(tPtr->docHeight - height);
			scroll = True;
			break;

		case WSKnobSlot:
		case WSNoPart:
			break;
		}
		scroll = (tPtr->vpos != tPtr->prevVpos);
		tPtr->prevVpos = tPtr->vpos;
	}

	if (w == tPtr->hS) {
		int width = tPtr->visible.w;

		which = WMGetScrollerHitPart(tPtr->hS);
		switch (which) {

		case WSDecrementLine:
			if (tPtr->hpos > 0) {
				if (tPtr->hpos > 16)
					tPtr->hpos -= 16;
				else
					tPtr->hpos = 0;
				scroll = True;
			}
			break;

		case WSIncrementLine:{
				int limit = tPtr->docWidth - width;
				if (tPtr->hpos < limit) {
					if (tPtr->hpos < limit - 16)
						tPtr->hpos += 16;
					else
						tPtr->hpos = limit;
					scroll = True;
				}
			}
			break;

		case WSDecrementPage:
			if (((int)tPtr->hpos - (int)width) >= 0)
				tPtr->hpos -= width;
			else
				tPtr->hpos = 0;

			scroll = True;
			break;

		case WSIncrementPage:
			tPtr->hpos += width;
			if (tPtr->hpos > (tPtr->docWidth - width))
				tPtr->hpos = tPtr->docWidth - width;
			scroll = True;
			break;

		case WSKnob:
			tPtr->hpos = WMGetScrollerValue(tPtr->hS)
			    * (float)(tPtr->docWidth - width);
			scroll = True;
			break;

		case WSKnobSlot:
		case WSNoPart:
			break;
		}
		scroll = (tPtr->hpos != tPtr->prevHpos);
		tPtr->prevHpos = tPtr->hpos;
	}

	if (scroll) {
		updateScrollers(tPtr);
		paintText(tPtr);
	}
}

typedef struct {
	TextBlock *tb;
	unsigned short begin, end;	/* what part of the text block */
} myLineItems;

static int layOutLine(Text * tPtr, myLineItems * items, int nitems, int x, int y)
{
	int i, j = 0, lw = 0, line_height = 0, max_d = 0, len, n;
	WMFont *font;
	char *text;
	TextBlock *tb, *tbsame = NULL;

	if (!items || nitems == 0)
		return 0;

	for (i = 0; i < nitems; i++) {
		tb = items[i].tb;

		if (tb->graphic) {
			if (!tPtr->flags.monoFont) {
				if (tb->object) {
					WMWidget *wdt = tb->d.widget;
					line_height = WMAX(line_height, WMWidgetHeight(wdt));
					if (tPtr->flags.alignment != WALeft)
						lw += WMWidgetWidth(wdt);
				} else {
					line_height = WMAX(line_height, tb->d.pixmap->height + max_d);
					if (tPtr->flags.alignment != WALeft)
						lw += tb->d.pixmap->width;
				}
			}

		} else {
			font = (tPtr->flags.monoFont) ? tPtr->dFont : tb->d.font;
			/*max_d = WMAX(max_d, abs(font->height-font->y)); */
			max_d = 2;
			line_height = WMAX(line_height, font->height + max_d);
			text = &(tb->text[items[i].begin]);
			len = items[i].end - items[i].begin;
			if (tPtr->flags.alignment != WALeft)
				lw += WMWidthOfString(font, text, len);
		}
	}

	if (tPtr->flags.alignment == WARight) {
		j = tPtr->visible.w - lw;
	} else if (tPtr->flags.alignment == WACenter) {
		j = (int)((float)(tPtr->visible.w - lw)) / 2.0;
	}

	for (i = 0; i < nitems; i++) {
		tb = items[i].tb;

		if (tbsame == tb) {	/* extend it, since it's on same line */
			tb->sections[tb->nsections - 1].end = items[i].end;
			n = tb->nsections - 1;
		} else {
			tb->sections = wrealloc(tb->sections, (++tb->nsections) * sizeof(Section));
			n = tb->nsections - 1;
			tb->sections[n]._y = y + max_d;
			tb->sections[n].max_d = max_d;
			tb->sections[n].x = x + j;
			tb->sections[n].h = line_height;
			tb->sections[n].begin = items[i].begin;
			tb->sections[n].end = items[i].end;
		}

		tb->sections[n].last = (i + 1 == nitems);

		if (tb->graphic) {
			if (!tPtr->flags.monoFont) {
				if (tb->object) {
					WMWidget *wdt = tb->d.widget;
					tb->sections[n].y = max_d + y + line_height - WMWidgetHeight(wdt);
					tb->sections[n].w = WMWidgetWidth(wdt);
				} else {
					tb->sections[n].y = y + line_height + max_d - tb->d.pixmap->height;
					tb->sections[n].w = tb->d.pixmap->width;
				}
				x += tb->sections[n].w;
			}
		} else {
			font = (tPtr->flags.monoFont) ? tPtr->dFont : tb->d.font;
			len = items[i].end - items[i].begin;
			text = &(tb->text[items[i].begin]);

			tb->sections[n].y = y + line_height - font->y;
			tb->sections[n].w =
			    WMWidthOfString(font,
					    &(tb->text[tb->sections[n].begin]),
					    tb->sections[n].end - tb->sections[n].begin);

			x += WMWidthOfString(font, text, len);
		}

		tbsame = tb;
	}

	return line_height;

}

static void layOutDocument(Text * tPtr)
{
	TextBlock *tb;
	myLineItems *items = NULL;
	unsigned int itemsSize = 0, nitems = 0, begin, end;
	WMFont *font;
	unsigned int x, y = 0, lw = 0, width = 0, bmargin;
	char *start = NULL, *mark = NULL;

	if (tPtr->flags.frozen || (!(tb = tPtr->firstTextBlock)))
		return;

	assert(tPtr->visible.w > 20);

	tPtr->docWidth = tPtr->visible.w;
	x = tPtr->margins[tb->marginN].first;
	bmargin = tPtr->margins[tb->marginN].body;

	/* only partial layOut needed: re-Lay only affected textblocks  */
	if (tPtr->flags.laidOut) {
		tb = tPtr->currentTextBlock;

		/* search backwards for textblocks on same line */
		while (tb->prior) {
			if (!tb->sections || tb->nsections < 1) {
				tb = tPtr->firstTextBlock;
				tPtr->flags.laidOut = False;
				y = 0;
				goto _layOut;
			}

			if (!tb->prior->sections || tb->prior->nsections < 1) {
				tb = tPtr->firstTextBlock;
				tPtr->flags.laidOut = False;
				y = 0;
				goto _layOut;
			}

			if (tb->sections[0]._y != tb->prior->sections[tb->prior->nsections - 1]._y) {
				break;
			}
			tb = tb->prior;
		}

		if (tb->prior && tb->prior->sections && tb->prior->nsections > 0) {
			y = tb->prior->sections[tb->prior->nsections - 1]._y +
			    tb->prior->sections[tb->prior->nsections - 1].h -
			    tb->prior->sections[tb->prior->nsections - 1].max_d;
		} else {
			y = 0;
		}
	}

 _layOut:
	while (tb) {

		if (tb->sections && tb->nsections > 0) {
			wfree(tb->sections);
			tb->sections = NULL;
			tb->nsections = 0;
		}

		if (tb->first && tb->blank && tb->next && !tb->next->first) {
			TextBlock *next = tb->next;
			tPtr->currentTextBlock = tb;
			WMDestroyTextBlock(tPtr, WMRemoveTextBlock(tPtr));
			tb = next;
			tb->first = True;
			continue;
		}

		if (tb->first && tb != tPtr->firstTextBlock) {
			y += layOutLine(tPtr, items, nitems, x, y);
			x = tPtr->margins[tb->marginN].first;
			bmargin = tPtr->margins[tb->marginN].body;
			nitems = 0;
			lw = 0;
		}

		if (tb->graphic) {
			if (!tPtr->flags.monoFont) {
				if (tb->object)
					width = WMWidgetWidth(tb->d.widget);
				else
					width = tb->d.pixmap->width;

				if (width > tPtr->docWidth)
					tPtr->docWidth = width;

				lw += width;
				if (lw >= tPtr->visible.w - x) {
					y += layOutLine(tPtr, items, nitems, x, y);
					nitems = 0;
					x = bmargin;
					lw = width;
				}

				if (nitems + 1 > itemsSize) {
					items = wrealloc(items, (++itemsSize) * sizeof(myLineItems));
				}

				items[nitems].tb = tb;
				items[nitems].begin = 0;
				items[nitems].end = 0;
				nitems++;
			}

		} else if ((start = tb->text)) {
			begin = end = 0;
			font = tPtr->flags.monoFont ? tPtr->dFont : tb->d.font;

			while (start) {
				mark = strchr(start, ' ');
				if (mark) {
					end += (int)(mark - start) + 1;
					start = mark + 1;
				} else {
					end += strlen(start);
					start = mark;
				}

				if (end > tb->used)
					end = tb->used;

				if (end - begin > 0) {

					width = WMWidthOfString(font, &tb->text[begin], end - begin);

					/* if it won't fit, char wrap it */
					if (width >= tPtr->visible.w) {
						char *t = &tb->text[begin];
						int l = end - begin, i = 0;
						do {
							width = WMWidthOfString(font, t, ++i);
						} while (width < tPtr->visible.w && i < l);
						if (i > 2)
							i--;
						end = begin + i;
						start = &tb->text[end];
					}

					lw += width;
				}

				if (lw >= tPtr->visible.w - x) {
					y += layOutLine(tPtr, items, nitems, x, y);
					lw = width;
					x = bmargin;
					nitems = 0;
				}

				if (nitems + 1 > itemsSize) {
					items = wrealloc(items, (++itemsSize) * sizeof(myLineItems));
				}

				items[nitems].tb = tb;
				items[nitems].begin = begin;
				items[nitems].end = end;
				nitems++;

				begin = end;
			}
		}

		/* not yet fully ready. but is already VERY FAST for a 3Mbyte file ;-) */
		if (0 && tPtr->flags.laidOut
		    && tb->next && tb->next->sections && tb->next->nsections > 0
		    && (tPtr->vpos + tPtr->visible.h < tb->next->sections[0]._y)) {
			if (tPtr->lastTextBlock->sections && tPtr->lastTextBlock->nsections > 0) {
				TextBlock *ltb = tPtr->lastTextBlock;
				int ly = ltb->sections[ltb->nsections - 1]._y;
				int lh = ltb->sections[ltb->nsections - 1].h;
				int ss, sd;

				lh += 1 + tPtr->visible.y + ltb->sections[ltb->nsections - 1].max_d;
				printf("it's %d\n", tPtr->visible.y + ltb->sections[ltb->nsections - 1].max_d);

				y += layOutLine(tPtr, items, nitems, x, y);
				ss = ly + lh - y;
				sd = tPtr->docHeight - y;

				printf("dif %d-%d: %d\n", ss, sd, ss - sd);
				y += tb->next->sections[0]._y - y;
				nitems = 0;
				printf("nitems%d\n", nitems);
				if (ss - sd != 0)
					y = tPtr->docHeight + ss - sd;

				break;
			} else {
				tPtr->flags.laidOut = False;
			}
		}

		tb = tb->next;
	}

	if (nitems > 0)
		y += layOutLine(tPtr, items, nitems, x, y);

	if (tPtr->docHeight != y + 10) {
		tPtr->docHeight = y + 10;
		updateScrollers(tPtr);
	}

	if (tPtr->docWidth > tPtr->visible.w && !tPtr->hS) {
		XEvent event;

		tPtr->flags.horizOnDemand = True;
		WMSetTextHasHorizontalScroller((WMText *) tPtr, True);
		event.type = Expose;
		handleEvents(&event, (void *)tPtr);

	} else if (tPtr->docWidth <= tPtr->visible.w && tPtr->hS && tPtr->flags.horizOnDemand) {
		tPtr->flags.horizOnDemand = False;
		WMSetTextHasHorizontalScroller((WMText *) tPtr, False);
	}

	tPtr->flags.laidOut = True;

	if (items && itemsSize > 0)
		wfree(items);

}

static void textDidResize(W_ViewDelegate * self, WMView * view)
{
	Text *tPtr = (Text *) view->self;
	unsigned short w = tPtr->view->size.width;
	unsigned short h = tPtr->view->size.height;
	unsigned short rh = 0, vw = 0, rel;

	rel = (tPtr->flags.relief == WRFlat);

	if (tPtr->ruler && tPtr->flags.rulerShown) {
		WMMoveWidget(tPtr->ruler, 2, 2);
		WMResizeWidget(tPtr->ruler, w - 4, 40);
		rh = 40;
	}

	if (tPtr->vS) {
		WMMoveWidget(tPtr->vS, 1 - (rel ? 1 : 0), rh + 1 - (rel ? 1 : 0));
		WMResizeWidget(tPtr->vS, 20, h - rh - 2 + (rel ? 2 : 0));
		vw = 20;
		WMSetRulerOffset(tPtr->ruler, 22);
	} else
		WMSetRulerOffset(tPtr->ruler, 2);

	if (tPtr->hS) {
		if (tPtr->vS) {
			WMMoveWidget(tPtr->hS, vw, h - 21);
			WMResizeWidget(tPtr->hS, w - vw - 1, 20);
		} else {
			WMMoveWidget(tPtr->hS, vw + 1, h - 21);
			WMResizeWidget(tPtr->hS, w - vw - 2, 20);
		}
	}

	tPtr->visible.x = (tPtr->vS) ? 24 : 4;
	tPtr->visible.y = (tPtr->ruler && tPtr->flags.rulerShown) ? 43 : 3;
	tPtr->visible.w = tPtr->view->size.width - tPtr->visible.x - 8;
	tPtr->visible.h = tPtr->view->size.height - tPtr->visible.y;
	tPtr->visible.h -= (tPtr->hS) ? 20 : 0;
	tPtr->margins[0].right = tPtr->visible.w;

	if (tPtr->view->flags.realized) {

		if (tPtr->db) {
			XFreePixmap(tPtr->view->screen->display, tPtr->db);
			tPtr->db = (Pixmap) NULL;
		}

		if (tPtr->visible.w < 40)
			tPtr->visible.w = 40;
		if (tPtr->visible.h < 20)
			tPtr->visible.h = 20;

		if (!tPtr->db) {
			tPtr->db = XCreatePixmap(tPtr->view->screen->display,
						 tPtr->view->window, tPtr->visible.w,
						 tPtr->visible.h, tPtr->view->screen->depth);
		}
	}

	WMThawText(tPtr);
}

W_ViewDelegate _TextViewDelegate = {
	NULL,
	NULL,
	textDidResize,
	NULL,
};

#define TEXT_BUFFER_INCR 8
#define reqBlockSize(requested)  (requested + TEXT_BUFFER_INCR)

static void clearText(Text * tPtr)
{
	tPtr->vpos = tPtr->hpos = 0;
	tPtr->docHeight = tPtr->docWidth = 0;
	tPtr->cursor.x = -23;

	if (!tPtr->firstTextBlock)
		return;

	while (tPtr->currentTextBlock)
		WMDestroyTextBlock(tPtr, WMRemoveTextBlock(tPtr));

	tPtr->firstTextBlock = NULL;
	tPtr->currentTextBlock = NULL;
	tPtr->lastTextBlock = NULL;
	WMEmptyArray(tPtr->gfxItems);
}

/* possibly remove a single character from the currentTextBlock,
 or if there's a selection, remove it...
 note that Delete and Backspace are treated differently */
static void deleteTextInteractively(Text * tPtr, KeySym ksym)
{
	TextBlock *tb;
	Bool back = (Bool) (ksym == XK_BackSpace);
	Bool done = 1, wasFirst = 0;

	if (!tPtr->flags.editable)
		return;

	if (!(tb = tPtr->currentTextBlock))
		return;

	if (tPtr->flags.ownsSelection) {
		if (removeSelection(tPtr))
			layOutDocument(tPtr);
		return;
	}

	wasFirst = tb->first;
	if (back && tPtr->tpos < 1) {
		if (tb->prior) {
			if (tb->prior->blank) {
				tPtr->currentTextBlock = tb->prior;
				WMRemoveTextBlock(tPtr);
				tPtr->currentTextBlock = tb;
				tb->first = True;
				layOutDocument(tPtr);
				return;
			} else {
				if (tb->blank) {
					TextBlock *prior = tb->prior;
					tPtr->currentTextBlock = tb;
					WMRemoveTextBlock(tPtr);
					tb = prior;
				} else {
					tb = tb->prior;
				}

				if (tb->graphic)
					tPtr->tpos = 1;
				else
					tPtr->tpos = tb->used;

				tPtr->currentTextBlock = tb;
				done = 1;
				if (wasFirst) {
					if (tb->next)
						tb->next->first = False;
					layOutDocument(tPtr);
					return;
				}
			}
		}
	}

	if ((tb->used > 0) && ((back ? tPtr->tpos > 0 : 1))
	    && (tPtr->tpos <= tb->used) && !tb->graphic) {
		if (back)
			tPtr->tpos--;
		memmove(&(tb->text[tPtr->tpos]), &(tb->text[tPtr->tpos + 1]), tb->used - tPtr->tpos);
		tb->used--;
		done = 0;
	}

	/* if there are no characters left to back over in the textblock,
	   but it still has characters to the right of the cursor: */
	if ((back ? (tPtr->tpos == 0 && !done) : (tPtr->tpos >= tb->used))
	    || tb->graphic) {

		/* no more chars, and it's marked as blank? */
		if (tb->blank) {
			TextBlock *sibling = (back ? tb->prior : tb->next);

			if (tb->used == 0 || tb->graphic)
				WMDestroyTextBlock(tPtr, WMRemoveTextBlock(tPtr));

			if (sibling) {
				tPtr->currentTextBlock = sibling;
				if (tb->graphic)
					tPtr->tpos = (back ? 1 : 0);
				else
					tPtr->tpos = (back ? sibling->used : 0);
			}
			/* no more chars, so mark it as blank */
		} else if (tb->used == 0) {
			tb->blank = 1;
		} else if (tb->graphic) {
			Bool hasNext = (tb->next != NULL);

			WMDestroyTextBlock(tPtr, WMRemoveTextBlock(tPtr));
			if (hasNext) {
				tPtr->tpos = 0;
			} else if (tPtr->currentTextBlock) {
				tPtr->tpos = (tPtr->currentTextBlock->graphic ? 1 : tPtr->currentTextBlock->used);
			}
		} else
			printf("DEBUG: unaccounted for... catch this!\n");
	}

	layOutDocument(tPtr);
}

static void insertTextInteractively(Text * tPtr, char *text, int len)
{
	TextBlock *tb;
	char *newline = NULL;

	if (!tPtr->flags.editable) {
		return;
	}

	if (len < 1 || !text)
		return;

	if (tPtr->flags.ignoreNewLine && *text == '\n' && len == 1)
		return;

	if (tPtr->flags.ownsSelection)
		removeSelection(tPtr);

	if (tPtr->flags.ignoreNewLine) {
		int i;
		for (i = 0; i < len; i++) {
			if (text[i] == '\n')
				text[i] = ' ';
		}
	}

	tb = tPtr->currentTextBlock;
	if (!tb || tb->graphic) {
		tPtr->tpos = 0;
		WMAppendTextStream(tPtr, text);
		layOutDocument(tPtr);
		return;
	}

	if ((newline = strchr(text, '\n'))) {
		int nlen = (int)(newline - text);
		int s = tb->used - tPtr->tpos;

		if (!tb->blank && nlen > 0) {
			char *save = NULL;

			if (s > 0) {
				save = wmalloc(s);
				memcpy(save, &tb->text[tPtr->tpos], s);
				tb->used -= (tb->used - tPtr->tpos);
			}
			insertTextInteractively(tPtr, text, nlen);
			newline++;
			WMAppendTextStream(tPtr, newline);
			if (s > 0) {
				insertTextInteractively(tPtr, save, s);
				wfree(save);
			}
		} else {
			if (tPtr->tpos > 0 && tPtr->tpos < tb->used && !tb->graphic && tb->text) {

				unsigned short savePos = tPtr->tpos;
				void *ntb = WMCreateTextBlockWithText(tPtr, &tb->text[tPtr->tpos],
								      tb->d.font, tb->color, True,
								      tb->used - tPtr->tpos);

				if (tb->sections[0].end == tPtr->tpos)
					WMAppendTextBlock(tPtr, WMCreateTextBlockWithText(tPtr,
											  NULL, tb->d.font,
											  tb->color, True, 0));

				tb->used = savePos;
				WMAppendTextBlock(tPtr, ntb);
				tPtr->tpos = 0;

			} else if (tPtr->tpos == tb->used) {
				if (tPtr->flags.indentNewLine) {
					WMAppendTextBlock(tPtr, WMCreateTextBlockWithText(tPtr,
											  "    ", tb->d.font,
											  tb->color, True, 4));
					tPtr->tpos = 4;
				} else {
					WMAppendTextBlock(tPtr, WMCreateTextBlockWithText(tPtr,
											  NULL, tb->d.font,
											  tb->color, True, 0));
					tPtr->tpos = 0;
				}
			} else if (tPtr->tpos == 0) {
				if (tPtr->flags.indentNewLine) {
					WMPrependTextBlock(tPtr, WMCreateTextBlockWithText(tPtr,
											   "    ", tb->d.font,
											   tb->color, True, 4));
				} else {
					WMPrependTextBlock(tPtr, WMCreateTextBlockWithText(tPtr,
											   NULL, tb->d.font,
											   tb->color, True, 0));
				}
				tPtr->tpos = 0;
				if (tPtr->currentTextBlock->next)
					tPtr->currentTextBlock = tPtr->currentTextBlock->next;
			}
		}
	} else {
		if (tb->used + len >= tb->allocated) {
			tb->allocated = reqBlockSize(tb->used + len);
			tb->text = wrealloc(tb->text, tb->allocated);
		}

		if (tb->blank) {
			memcpy(tb->text, text, len);
			tb->used = len;
			tPtr->tpos = len;
			tb->text[tb->used] = 0;
			tb->blank = False;

		} else {
			memmove(&(tb->text[tPtr->tpos + len]), &tb->text[tPtr->tpos], tb->used - tPtr->tpos + 1);
			memmove(&tb->text[tPtr->tpos], text, len);
			tb->used += len;
			tPtr->tpos += len;
			tb->text[tb->used] = 0;
		}

	}

	layOutDocument(tPtr);
}

static void selectRegion(Text * tPtr, int x, int y)
{

	if (x < 0 || y < 0)
		return;

	y += (tPtr->flags.rulerShown ? 40 : 0);
	y += tPtr->vpos;
	if (y > 10)
		y -= 10;	/* the original offset */

	x -= tPtr->visible.x - 2;
	if (x < 0)
		x = 0;

	tPtr->sel.x = WMAX(0, WMIN(tPtr->clicked.x, x));
	tPtr->sel.w = abs(tPtr->clicked.x - x);
	tPtr->sel.y = WMAX(0, WMIN(tPtr->clicked.y, y));
	tPtr->sel.h = abs(tPtr->clicked.y - y);

	tPtr->flags.ownsSelection = True;
	paintText(tPtr);
}

static void releaseSelection(Text * tPtr)
{
	TextBlock *tb = tPtr->firstTextBlock;

	while (tb) {
		tb->selected = False;
		tb = tb->next;
	}
	tPtr->flags.ownsSelection = False;
	WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);

	paintText(tPtr);
}

WMData *requestHandler(WMView * view, Atom selection, Atom target, void *cdata, Atom * type)
{
	Text *tPtr = view->self;
	Display *dpy = tPtr->view->screen->display;
	Atom _TARGETS;
	Atom TEXT = XInternAtom(dpy, "TEXT", False);
	Atom COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", False);
	WMData *data = NULL;

	if (target == XA_STRING || target == TEXT || target == COMPOUND_TEXT) {
		char *text = WMGetTextSelectedStream(tPtr);

		if (text) {
			data = WMCreateDataWithBytes(text, strlen(text));
			WMSetDataFormat(data, TYPETEXT);
		}
		*type = target;
		return data;
	} else
		printf("didn't get it\n");

	_TARGETS = XInternAtom(dpy, "TARGETS", False);
	if (target == _TARGETS) {
		Atom *ptr;

		ptr = wmalloc(4 * sizeof(Atom));
		ptr[0] = _TARGETS;
		ptr[1] = XA_STRING;
		ptr[2] = TEXT;
		ptr[3] = COMPOUND_TEXT;

		data = WMCreateDataWithBytes(ptr, 4 * 4);
		WMSetDataFormat(data, 32);

		*type = target;
		return data;
	}

	return NULL;
}

static void lostHandler(WMView * view, Atom selection, void *cdata)
{
	releaseSelection((WMText *) view->self);
}

static WMSelectionProcs selectionHandler = {
	requestHandler, lostHandler, NULL
};

static void ownershipObserver(void *observerData, WMNotification * notification)
{
	if (observerData != WMGetNotificationClientData(notification))
		lostHandler(WMWidgetView(observerData), XA_PRIMARY, NULL);
}

static void autoSelectText(Text * tPtr, int clicks)
{
	int x, start;
	TextBlock *tb;
	char *mark = NULL, behind, ahead;

	if (!(tb = tPtr->currentTextBlock))
		return;

	if (clicks == 2) {

		switch (tb->text[tPtr->tpos]) {
		case ' ':
			return;
			/*
			   case '<': case '>': behind = '<'; ahead = '>'; break;
			   case '{': case '}': behind = '{'; ahead = '}'; break;
			   case '[': case ']': behind = '['; ahead = ']'; break;
			 */
		default:
			behind = ahead = ' ';
		}

		tPtr->sel.y = tPtr->cursor.y + 5;
		tPtr->sel.h = 6;	/*tPtr->cursor.h-10; */

		if (tb->graphic) {
			tPtr->sel.x = tb->sections[0].x;
			tPtr->sel.w = tb->sections[0].w;
		} else {
			WMFont *font = tPtr->flags.monoFont ? tPtr->dFont : tb->d.font;

			start = tPtr->tpos;
			while (start > 0 && tb->text[start - 1] != behind)
				start--;

			x = tPtr->cursor.x;
			if (tPtr->tpos > start) {
				x -= WMWidthOfString(font, &tb->text[start], tPtr->tpos - start);
			}
			tPtr->sel.x = (x < 0 ? 0 : x) + 1;

			if ((mark = strchr(&tb->text[start], ahead))) {
				tPtr->sel.w = WMWidthOfString(font, &tb->text[start],
							      (int)(mark - &tb->text[start]));
			} else if (tb->used > start) {
				tPtr->sel.w = WMWidthOfString(font, &tb->text[start], tb->used - start);
			}
		}

	} else if (clicks == 3) {
		TextBlock *cur = tb;

		while (tb && !tb->first) {
			tb = tb->prior;
		}
		tPtr->sel.y = tb->sections[0]._y;

		tb = cur;
		while (tb->next && !tb->next->first) {
			tb = tb->next;
		}
		tPtr->sel.h = tb->sections[tb->nsections - 1]._y + 5 - tPtr->sel.y;

		tPtr->sel.x = 0;
		tPtr->sel.w = tPtr->docWidth;
		tPtr->clicked.x = 0;	/* only for now, fix sel. code */
	}

	if (!tPtr->flags.ownsSelection) {
		WMCreateSelectionHandler(tPtr->view, XA_PRIMARY, tPtr->lastClickTime, &selectionHandler, NULL);
		tPtr->flags.ownsSelection = True;
	}
	paintText(tPtr);

}

# if 0
static void fontChanged(void *observerData, WMNotification * notification)
{
	WMText *tPtr = (WMText *) observerData;
	WMFont *font = (WMFont *) WMGetNotificationClientData(notification);
	printf("fontChanged\n");

	if (!tPtr || !font)
		return;

	if (tPtr->flags.ownsSelection)
		WMSetTextSelectionFont(tPtr, font);
}
#endif

static void handleTextKeyPress(Text * tPtr, XEvent * event)
{
	char buffer[64];
	KeySym ksym;
	int control_pressed = False;
	TextBlock *tb = NULL;

	if (((XKeyEvent *) event)->state & ControlMask)
		control_pressed = True;
	buffer[XLookupString(&event->xkey, buffer, 63, &ksym, NULL)] = 0;

	switch (ksym) {

	case XK_Home:
		if ((tPtr->currentTextBlock = tPtr->firstTextBlock))
			tPtr->tpos = 0;
		updateCursorPosition(tPtr);
		paintText(tPtr);
		break;

	case XK_End:
		if ((tPtr->currentTextBlock = tPtr->lastTextBlock)) {
			if (tPtr->currentTextBlock->graphic)
				tPtr->tpos = 1;
			else
				tPtr->tpos = tPtr->currentTextBlock->used;
		}
		updateCursorPosition(tPtr);
		paintText(tPtr);
		break;

	case XK_Left:
		if (!(tb = tPtr->currentTextBlock))
			break;
		if (tb->graphic)
			goto L_imaGFX;

		if (tPtr->tpos == 0) {
 L_imaGFX:
			if (tb->prior) {
				tPtr->currentTextBlock = tb->prior;
				if (tPtr->currentTextBlock->graphic)
					tPtr->tpos = 1;
				else
					tPtr->tpos = tPtr->currentTextBlock->used;

				if (!tb->first && tPtr->tpos > 0)
					tPtr->tpos--;
			} else
				tPtr->tpos = 0;
		} else
			tPtr->tpos--;
		updateCursorPosition(tPtr);
		paintText(tPtr);
		break;

	case XK_Right:
		if (!(tb = tPtr->currentTextBlock))
			break;
		if (tb->graphic)
			goto R_imaGFX;
		if (tPtr->tpos == tb->used) {
 R_imaGFX:
			if (tb->next) {
				tPtr->currentTextBlock = tb->next;
				tPtr->tpos = 0;
				if (!tb->next->first && tb->next->used > 0)
					tPtr->tpos++;
			} else {
				if (tb->graphic)
					tPtr->tpos = 1;
				else
					tPtr->tpos = tb->used;
			}
		} else
			tPtr->tpos++;
		updateCursorPosition(tPtr);
		paintText(tPtr);
		break;

	case XK_Down:
		cursorToTextPosition(tPtr, tPtr->cursor.x + tPtr->visible.x,
				     tPtr->clicked.y + tPtr->cursor.h - tPtr->vpos);
		paintText(tPtr);
		break;

	case XK_Up:
		cursorToTextPosition(tPtr, tPtr->cursor.x + tPtr->visible.x,
				     tPtr->visible.y + tPtr->cursor.y - tPtr->vpos - 3);
		paintText(tPtr);
		break;

	case XK_BackSpace:
	case XK_Delete:
#ifdef XK_KP_Delete
	case XK_KP_Delete:
#endif
		deleteTextInteractively(tPtr, ksym);
		updateCursorPosition(tPtr);
		paintText(tPtr);
		break;

	case XK_Control_R:
	case XK_Control_L:
		control_pressed = True;
		break;

	case XK_Tab:
		insertTextInteractively(tPtr, "    ", 4);
		updateCursorPosition(tPtr);
		paintText(tPtr);
		break;

	case XK_Return:
		*buffer = '\n';
	default:
		if (*buffer != 0 && !control_pressed) {
			insertTextInteractively(tPtr, buffer, strlen(buffer));
			updateCursorPosition(tPtr);
			paintText(tPtr);

		} else if (control_pressed && ksym == XK_r) {
			Bool i = !tPtr->flags.rulerShown;
			WMShowTextRuler(tPtr, i);
			tPtr->flags.rulerShown = i;
		} else if (control_pressed && *buffer == '\a') {
			XBell(tPtr->view->screen->display, 0);
		} else {
			WMRelayToNextResponder(tPtr->view, event);
		}
	}

	if (!control_pressed && tPtr->flags.ownsSelection) {
		releaseSelection(tPtr);
	}
}

static void pasteText(WMView * view, Atom selection, Atom target, Time timestamp, void *cdata, WMData * data)
{
	Text *tPtr = (Text *) view->self;
	char *text;

	tPtr->flags.waitingForSelection = 0;

	if (data) {
		text = (char *)WMDataBytes(data);

		if (tPtr->parser) {
			(tPtr->parser) (tPtr, (void *)text);
			layOutDocument(tPtr);
		} else
			insertTextInteractively(tPtr, text, strlen(text));
		updateCursorPosition(tPtr);
		paintText(tPtr);

	} else {
		int n;

		text = XFetchBuffer(tPtr->view->screen->display, &n, 0);

		if (text) {
			text[n] = 0;
			if (tPtr->parser) {
				(tPtr->parser) (tPtr, (void *)text);
				layOutDocument(tPtr);
			} else
				insertTextInteractively(tPtr, text, n);
			updateCursorPosition(tPtr);
			paintText(tPtr);

			XFree(text);
		}
	}

}

static void handleActionEvents(XEvent * event, void *data)
{
	Text *tPtr = (Text *) data;
	Display *dpy = event->xany.display;
	KeySym ksym;

	switch (event->type) {
	case KeyPress:
		ksym = XLookupKeysym((XKeyEvent *) event, 0);
		if (ksym == XK_Shift_R || ksym == XK_Shift_L) {
			tPtr->flags.extendSelection = True;
			return;
		}

		if (tPtr->flags.focused) {
			XGrabPointer(dpy, W_VIEW(tPtr)->window, False,
				     PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
				     GrabModeAsync, GrabModeAsync, None,
				     tPtr->view->screen->invisibleCursor, CurrentTime);
			tPtr->flags.pointerGrabbed = True;
			handleTextKeyPress(tPtr, event);

		}
		break;

	case KeyRelease:
		ksym = XLookupKeysym((XKeyEvent *) event, 0);
		if (ksym == XK_Shift_R || ksym == XK_Shift_L) {
			tPtr->flags.extendSelection = False;
			return;
			/* end modify flag so selection can be extended */
		}
		break;

	case MotionNotify:

		if (tPtr->flags.pointerGrabbed) {
			tPtr->flags.pointerGrabbed = False;
			XUngrabPointer(dpy, CurrentTime);
		}

		if (tPtr->flags.waitingForSelection)
			break;

		if ((event->xmotion.state & Button1Mask)) {

			if (WMIsDraggingFromView(tPtr->view)) {
				WMDragImageFromView(tPtr->view, event);
				break;
			}

			if (!tPtr->flags.ownsSelection) {
				WMCreateSelectionHandler(tPtr->view,
							 XA_PRIMARY, event->xbutton.time, &selectionHandler, NULL);
				tPtr->flags.ownsSelection = True;
			}
			selectRegion(tPtr, event->xmotion.x, event->xmotion.y);
			break;
		}

		mouseOverObject(tPtr, event->xmotion.x, event->xmotion.y);
		break;

	case ButtonPress:

		if (tPtr->flags.pointerGrabbed) {
			tPtr->flags.pointerGrabbed = False;
			XUngrabPointer(dpy, CurrentTime);
			break;
		}

		if (tPtr->flags.waitingForSelection)
			break;

		if (tPtr->flags.extendSelection && tPtr->flags.ownsSelection) {
			selectRegion(tPtr, event->xmotion.x, event->xmotion.y);
			return;
		}

		if (tPtr->flags.ownsSelection)
			releaseSelection(tPtr);

		if (event->xbutton.button == Button1) {
			TextBlock *tb = tPtr->currentTextBlock;

			if (WMIsDoubleClick(event)) {

				tPtr->lastClickTime = event->xbutton.time;
				if (tb && tb->graphic && !tb->object) {
					if (tPtr->delegate && tPtr->delegate->didDoubleClickOnPicture) {
						char *desc;

						desc = wmalloc(tb->used + 1);
						memcpy(desc, tb->text, tb->used);
						desc[tb->used] = 0;
						(*tPtr->delegate->didDoubleClickOnPicture) (tPtr->delegate, desc);
						wfree(desc);
					}
				} else {
					autoSelectText(tPtr, 2);
				}
				break;
			} else if (event->xbutton.time - tPtr->lastClickTime < WINGsConfiguration.doubleClickDelay) {
				tPtr->lastClickTime = event->xbutton.time;
				autoSelectText(tPtr, 3);
				break;
			}

			if (!tPtr->flags.focused) {
				WMSetFocusToWidget(tPtr);
				tPtr->flags.focused = True;
			} else if (tb && tPtr->flags.isOverGraphic && tb->graphic && !tb->object && tb->d.pixmap) {

				WMSetViewDragImage(tPtr->view, tb->d.pixmap);
				WMDragImageFromView(tPtr->view, event);
				break;
			}

			tPtr->lastClickTime = event->xbutton.time;
			cursorToTextPosition(tPtr, event->xmotion.x, event->xmotion.y);
			paintText(tPtr);
		}

		if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
			WMScrollText(tPtr, 16);
			break;
		}

		if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
			WMScrollText(tPtr, -16);
			break;
		}

		if (event->xbutton.button == Button2) {
			char *text = NULL;
			int n;

			if (!tPtr->flags.editable) {
				XBell(dpy, 0);
				break;
			}

			if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
						event->xbutton.time, pasteText, NULL)) {

				text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
				tPtr->flags.waitingForSelection = 0;

				if (text) {
					text[n] = 0;

					if (tPtr->parser) {
						(tPtr->parser) (tPtr, (void *)text);
						layOutDocument(tPtr);
					} else
						insertTextInteractively(tPtr, text, n);

					XFree(text);
#if 0
					NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
					       (void *)WMInsertTextEvent);
#endif
					updateCursorPosition(tPtr);
					paintText(tPtr);

				} else {
					tPtr->flags.waitingForSelection = True;
				}
			}
			break;
		}

	case ButtonRelease:
		if (tPtr->flags.pointerGrabbed) {
			tPtr->flags.pointerGrabbed = False;
			XUngrabPointer(dpy, CurrentTime);
			break;
		}

		if (tPtr->flags.waitingForSelection)
			break;

		if (WMIsDraggingFromView(tPtr->view))
			WMDragImageFromView(tPtr->view, event);
	}

}

static void handleEvents(XEvent * event, void *data)
{
	Text *tPtr = (Text *) data;

	switch (event->type) {
	case Expose:

		if (event->xexpose.count != 0)
			break;

		if (tPtr->hS) {
			if (!(W_VIEW(tPtr->hS))->flags.realized)
				WMRealizeWidget(tPtr->hS);
		}

		if (tPtr->vS) {
			if (!(W_VIEW(tPtr->vS))->flags.realized)
				WMRealizeWidget(tPtr->vS);
		}

		if (tPtr->ruler) {
			if (!(W_VIEW(tPtr->ruler))->flags.realized)
				WMRealizeWidget(tPtr->ruler);

		}

		if (!tPtr->db)
			textDidResize(tPtr->view->delegate, tPtr->view);

		paintText(tPtr);
		break;

	case FocusIn:
		if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))
		    != tPtr->view)
			return;
		tPtr->flags.focused = True;
#if DO_BLINK
		if (tPtr->flags.editable && !tPtr->timerID) {
			tPtr->timerID = WMAddTimerHandler(12 + 0 * CURSOR_BLINK_ON_DELAY, blinkCursor, tPtr);
		}
#endif

		break;

	case FocusOut:
		tPtr->flags.focused = False;
		paintText(tPtr);
#if DO_BLINK
		if (tPtr->timerID) {
			WMDeleteTimerHandler(tPtr->timerID);
			tPtr->timerID = NULL;
		}
#endif
		break;

	case DestroyNotify:
		clearText(tPtr);
		if (tPtr->db)
			XFreePixmap(tPtr->view->screen->display, tPtr->db);
		if (tPtr->gfxItems)
			WMEmptyArray(tPtr->gfxItems);
#if DO_BLINK
		if (tPtr->timerID)
			WMDeleteTimerHandler(tPtr->timerID);
#endif
		WMReleaseFont(tPtr->dFont);
		WMReleaseColor(tPtr->dColor);
		WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
		WMRemoveNotificationObserver(tPtr);

		WMFreeArray(tPtr->xdndSourceTypes);
		WMFreeArray(tPtr->xdndDestinationTypes);

		wfree(tPtr);

		break;

	}
}

static void insertPlainText(Text * tPtr, char *text)
{
	char *start, *mark;
	void *tb = NULL;

	start = text;
	while (start) {
		mark = strchr(start, '\n');
		if (mark) {
			tb = WMCreateTextBlockWithText(tPtr,
						       start, tPtr->dFont,
						       tPtr->dColor, tPtr->flags.first, (int)(mark - start));
			start = mark + 1;
			tPtr->flags.first = True;
		} else {
			if (start && strlen(start)) {
				tb = WMCreateTextBlockWithText(tPtr, start, tPtr->dFont,
							       tPtr->dColor, tPtr->flags.first, strlen(start));
			} else
				tb = NULL;
			tPtr->flags.first = False;
			start = mark;
		}

		if (tPtr->flags.prepend)
			WMPrependTextBlock(tPtr, tb);
		else
			WMAppendTextBlock(tPtr, tb);
	}
}

static void rulerMoveCallBack(WMWidget * w, void *self)
{
	Text *tPtr = (Text *) self;

	if (!tPtr)
		return;
	if (W_CLASS(tPtr) != WC_Text)
		return;

	paintText(tPtr);
}

static void rulerReleaseCallBack(WMWidget * w, void *self)
{
	Text *tPtr = (Text *) self;

	if (!tPtr)
		return;
	if (W_CLASS(tPtr) != WC_Text)
		return;

	WMThawText(tPtr);
	return;
}

static WMArray *dropDataTypes(WMView * self)
{
	return ((Text *) self->self)->xdndSourceTypes;
}

static WMDragOperationType wantedDropOperation(WMView * self)
{
	return WDOperationCopy;
}

static Bool acceptDropOperation(WMView * self, WMDragOperationType allowedOperation)
{
	return (allowedOperation == WDOperationCopy);
}

static WMData *fetchDragData(WMView * self, char *type)
{
	TextBlock *tb = ((WMText *) self->self)->currentTextBlock;
	char *desc;
	WMData *data;

	if (strcmp(type, "text/plain")) {
		if (!tb)
			return NULL;

		desc = wmalloc(tb->used + 1);
		memcpy(desc, tb->text, tb->used);
		desc[tb->used] = 0;
		data = WMCreateDataWithBytes(desc, strlen(desc) + 1);

		wfree(desc);

		return data;
	}

	return NULL;
}

static WMDragSourceProcs _DragSourceProcs = {
	dropDataTypes,
	wantedDropOperation,
	NULL,
	acceptDropOperation,
	NULL,
	NULL,
	fetchDragData
};

static WMArray *requiredDataTypes(WMView * self, WMDragOperationType request, WMArray * sourceDataTypes)
{
	return ((Text *) self->self)->xdndDestinationTypes;
}

static WMDragOperationType allowedOperation(WMView * self, WMDragOperationType request, WMArray * sourceDataTypes)
{
	return WDOperationCopy;
}

static void performDragOperation(WMView * self, WMArray * dropData, WMArray * operations, WMPoint * dropLocation)
{
	WMText *tPtr = (WMText *) self->self;
	WMData *data;
	char *colorName;
	WMColor *color;

	if (tPtr) {

		/* only one required type, implies only one drop data */

		/* get application/X-color if any */
		data = (WMData *) WMPopFromArray(dropData);
		if (data != NULL) {
			colorName = (char *)WMDataBytes(data);
			color = WMCreateNamedColor(W_VIEW_SCREEN(self), colorName, True);

			if (color) {
				WMSetTextSelectionColor(tPtr, color);
				WMReleaseColor(color);
			}
		}
	}
}

static WMDragDestinationProcs _DragDestinationProcs = {
	NULL,
	requiredDataTypes,
	allowedOperation,
	NULL,
	performDragOperation,
	NULL
};

char *getStream(WMText * tPtr, int sel, int array)
{
	TextBlock *tb = NULL;
	char *text = NULL;
	unsigned long where = 0;

	if (!tPtr)
		return NULL;

	if (!(tb = tPtr->firstTextBlock))
		return NULL;

	if (tPtr->writer) {
		(tPtr->writer) (tPtr, (void *)text);
		return text;
	}

	tb = tPtr->firstTextBlock;
	while (tb) {

		if (!tb->graphic || (tb->graphic && !tPtr->flags.monoFont)) {

			if (!sel || (tb->graphic && tb->selected)) {

				if (!tPtr->flags.ignoreNewLine && (tb->first || tb->blank)
				    && tb != tPtr->firstTextBlock) {
					text = wrealloc(text, where + 1);
					text[where++] = '\n';
				}

				if (tb->blank)
					goto _gSnext;

				if (tb->graphic && array) {
					text = wrealloc(text, where + 4);
					text[where++] = 0xFA;
					text[where++] = (tb->used >> 8) & 0x0ff;
					text[where++] = tb->used & 0x0ff;
					text[where++] = tb->allocated;	/* extra info */
				}
				text = wrealloc(text, where + tb->used);
				memcpy(&text[where], tb->text, tb->used);
				where += tb->used;

			} else if (sel && tb->selected) {

				if (!tPtr->flags.ignoreNewLine && tb->blank) {
					text = wrealloc(text, where + 1);
					text[where++] = '\n';
				}

				if (tb->blank)
					goto _gSnext;

				text = wrealloc(text, where + (tb->s_end - tb->s_begin));
				memcpy(&text[where], &tb->text[tb->s_begin], tb->s_end - tb->s_begin);
				where += tb->s_end - tb->s_begin;

			}

		}
 _gSnext:	tb = tb->next;
	}

	/* +1 for the end of string, let's be nice */
	text = wrealloc(text, where + 1);
	text[where] = 0;
	return text;
}

static void releaseStreamObjects(void *data)
{
	if (data)
		wfree(data);
}

WMArray *getStreamObjects(WMText * tPtr, int sel)
{
	WMArray *array = WMCreateArrayWithDestructor(4, releaseStreamObjects);
	WMData *data;
	char *stream;
	unsigned short len;
	char *start, *fa, *desc;

	stream = getStream(tPtr, sel, 1);
	if (!stream)
		return NULL;

	start = stream;
	while (start) {

		fa = strchr(start, 0xFA);
		if (fa) {
			if ((int)(fa - start) > 0) {
				desc = start;
				desc[(int)(fa - start)] = 0;
				data = WMCreateDataWithBytes((void *)desc, (int)(fa - start));
				WMSetDataFormat(data, TYPETEXT);
				WMAddToArray(array, (void *)data);
			}

			len = *(fa + 1) * 0xff + *(fa + 2);
			data = WMCreateDataWithBytes((void *)(fa + 4), len);
			WMSetDataFormat(data, *(fa + 3));
			WMAddToArray(array, (void *)data);
			start = fa + len + 4;

		} else {
			if (start && strlen(start)) {
				data = WMCreateDataWithBytes((void *)start, strlen(start));
				WMSetDataFormat(data, TYPETEXT);
				WMAddToArray(array, (void *)data);
			}
			start = fa;
		}
	}

	wfree(stream);
	return array;
}

#define XDND_TEXT_DATA_TYPE "text/plain"
#define XDND_COLOR_DATA_TYPE "application/X-color"
static WMArray *getXdndSourceTypeArray()
{
	WMArray *types = WMCreateArray(1);
	WMAddToArray(types, XDND_TEXT_DATA_TYPE);
	return types;
}

static WMArray *getXdndDestinationTypeArray()
{
	WMArray *types = WMCreateArray(1);
	WMAddToArray(types, XDND_COLOR_DATA_TYPE);
	return types;
}

WMText *WMCreateTextForDocumentType(WMWidget * parent, WMAction * parser, WMAction * writer)
{
	Text *tPtr;
	Display *dpy;
	WMScreen *scr;
	XGCValues gcv;

	tPtr = wmalloc(sizeof(Text));
	memset(tPtr, 0, sizeof(Text));
	tPtr->widgetClass = WC_Text;
	tPtr->view = W_CreateView(W_VIEW(parent));
	if (!tPtr->view) {
		perror("could not create text's view\n");
		wfree(tPtr);
		return NULL;
	}

	dpy = tPtr->view->screen->display;
	scr = tPtr->view->screen;

	tPtr->view->self = tPtr;
	tPtr->view->attribs.cursor = scr->textCursor;
	tPtr->view->attribFlags |= CWOverrideRedirect | CWCursor;
	W_ResizeView(tPtr->view, 250, 200);

	tPtr->dColor = WMBlackColor(scr);
	tPtr->fgColor = WMBlackColor(scr);
	tPtr->bgColor = WMWhiteColor(scr);
	W_SetViewBackgroundColor(tPtr->view, tPtr->bgColor);

	gcv.graphics_exposures = False;
	gcv.foreground = W_PIXEL(scr->gray);
	gcv.background = W_PIXEL(scr->darkGray);
	gcv.fill_style = FillStippled;
	/* why not use scr->stipple here? */
	gcv.stipple = XCreateBitmapFromData(dpy, W_DRAWABLE(scr), STIPPLE_BITS, STIPPLE_WIDTH, STIPPLE_HEIGHT);
	tPtr->stippledGC = XCreateGC(dpy, W_DRAWABLE(scr),
				     GCForeground | GCBackground | GCStipple
				     | GCFillStyle | GCGraphicsExposures, &gcv);

	tPtr->ruler = NULL;
	tPtr->vS = NULL;
	tPtr->hS = NULL;

	tPtr->dFont = WMSystemFontOfSize(scr, 12);

	tPtr->view->delegate = &_TextViewDelegate;

	tPtr->delegate = NULL;

#if DO_BLINK
	tPtr->timerID = NULL;
#endif

	WMCreateEventHandler(tPtr->view, ExposureMask | StructureNotifyMask
			     | EnterWindowMask | LeaveWindowMask | FocusChangeMask, handleEvents, tPtr);

	WMCreateEventHandler(tPtr->view, ButtonReleaseMask | ButtonPressMask
			     | KeyReleaseMask | KeyPressMask | Button1MotionMask, handleActionEvents, tPtr);

	WMAddNotificationObserver(ownershipObserver, tPtr, WMSelectionOwnerDidChangeNotification, tPtr);

	WMSetViewDragSourceProcs(tPtr->view, &_DragSourceProcs);
	WMSetViewDragDestinationProcs(tPtr->view, &_DragDestinationProcs);

	{
		WMArray *types = WMCreateArray(2);
		WMAddToArray(types, "application/X-color");
		WMAddToArray(types, "application/X-image");
		WMRegisterViewForDraggedTypes(tPtr->view, types);
	}

	/*WMAddNotificationObserver(fontChanged, tPtr,
	   WMFontPanelDidChangeNotification, tPtr); */

	tPtr->firstTextBlock = NULL;
	tPtr->lastTextBlock = NULL;
	tPtr->currentTextBlock = NULL;
	tPtr->tpos = 0;

	tPtr->gfxItems = WMCreateArray(4);

	tPtr->parser = parser;
	tPtr->writer = writer;

	tPtr->sel.x = tPtr->sel.y = 2;
	tPtr->sel.w = tPtr->sel.h = 0;

	tPtr->clicked.x = tPtr->clicked.y = 2;

	tPtr->visible.x = tPtr->visible.y = 2;
	tPtr->visible.h = tPtr->view->size.height;
	tPtr->visible.w = tPtr->view->size.width - 4;

	tPtr->cursor.x = -23;

	tPtr->docWidth = 0;
	tPtr->docHeight = 0;
	tPtr->dBulletPix = WMCreatePixmapFromXPMData(tPtr->view->screen, default_bullet);
	tPtr->db = (Pixmap) NULL;
	tPtr->bgPixmap = NULL;

	tPtr->margins = WMGetRulerMargins(NULL);
	tPtr->margins->right = tPtr->visible.w;
	tPtr->nMargins = 1;

	tPtr->flags.rulerShown = False;
	tPtr->flags.monoFont = False;
	tPtr->flags.focused = False;
	tPtr->flags.editable = True;
	tPtr->flags.ownsSelection = False;
	tPtr->flags.pointerGrabbed = False;
	tPtr->flags.extendSelection = False;
	tPtr->flags.frozen = False;
	tPtr->flags.cursorShown = True;
	tPtr->flags.acceptsGraphic = False;
	tPtr->flags.horizOnDemand = False;
	tPtr->flags.needsLayOut = False;
	tPtr->flags.ignoreNewLine = False;
	tPtr->flags.indentNewLine = False;
	tPtr->flags.laidOut = False;
	tPtr->flags.ownsSelection = False;
	tPtr->flags.waitingForSelection = False;
	tPtr->flags.prepend = False;
	tPtr->flags.isOverGraphic = False;
	tPtr->flags.relief = WRSunken;
	tPtr->flags.isOverGraphic = 0;
	tPtr->flags.alignment = WALeft;
	tPtr->flags.first = True;

	tPtr->xdndSourceTypes = getXdndSourceTypeArray();
	tPtr->xdndDestinationTypes = getXdndDestinationTypeArray();

	return tPtr;
}

void WMPrependTextStream(WMText * tPtr, char *text)
{
	CHECK_CLASS(tPtr, WC_Text);

	if (!text) {
		if (tPtr->flags.ownsSelection)
			releaseSelection(tPtr);
		clearText(tPtr);
		updateScrollers(tPtr);
		return;
	}

	tPtr->flags.prepend = True;
	if (text && tPtr->parser)
		(tPtr->parser) (tPtr, (void *)text);
	else
		insertPlainText(tPtr, text);

	tPtr->flags.needsLayOut = True;
	tPtr->tpos = 0;
	if (!tPtr->flags.frozen) {
		layOutDocument(tPtr);
	}
}

void WMAppendTextStream(WMText * tPtr, char *text)
{
	CHECK_CLASS(tPtr, WC_Text);

	if (!text) {
		if (tPtr->flags.ownsSelection)
			releaseSelection(tPtr);
		clearText(tPtr);
		updateScrollers(tPtr);
		return;
	}

	tPtr->flags.prepend = False;
	if (text && tPtr->parser)
		(tPtr->parser) (tPtr, (void *)text);
	else
		insertPlainText(tPtr, text);

	tPtr->flags.needsLayOut = True;
	if (tPtr->currentTextBlock) {
		if (tPtr->currentTextBlock->graphic)
			tPtr->tpos = 1;
		else
			tPtr->tpos = tPtr->currentTextBlock->used;
	}

	if (!tPtr->flags.frozen) {
		layOutDocument(tPtr);
	}
}

char *WMGetTextStream(WMText * tPtr)
{
	CHECK_CLASS(tPtr, WC_Text);

	return getStream(tPtr, 0, 0);
}

char *WMGetTextSelectedStream(WMText * tPtr)
{
	CHECK_CLASS(tPtr, WC_Text);

	return getStream(tPtr, 1, 0);
}

WMArray *WMGetTextObjects(WMText * tPtr)
{
	CHECK_CLASS(tPtr, WC_Text);

	return getStreamObjects(tPtr, 0);
}

WMArray *WMGetTextSelectedObjects(WMText * tPtr)
{
	CHECK_CLASS(tPtr, WC_Text);

	return getStreamObjects(tPtr, 1);
}

void WMSetTextDelegate(WMText * tPtr, WMTextDelegate * delegate)
{
	CHECK_CLASS(tPtr, WC_Text);

	tPtr->delegate = delegate;
}

void *WMCreateTextBlockWithObject(WMText * tPtr, WMWidget * w,
				  char *description, WMColor * color,
				  unsigned short first, unsigned short extraInfo)
{
	TextBlock *tb;

	if (!w || !description || !color)
		return NULL;

	tb = wmalloc(sizeof(TextBlock));

	tb->text = wstrdup(description);
	tb->used = strlen(description);
	tb->blank = False;
	tb->d.widget = w;
	tb->color = WMRetainColor(color);
	tb->marginN = newMargin(tPtr, NULL);
	tb->allocated = extraInfo;
	tb->first = first;
	tb->kanji = False;
	tb->graphic = True;
	tb->object = True;
	tb->underlined = False;
	tb->selected = False;
	tb->script = 0;
	tb->sections = NULL;
	tb->nsections = 0;
	tb->prior = NULL;
	tb->next = NULL;

	return tb;
}

void *WMCreateTextBlockWithPixmap(WMText * tPtr, WMPixmap * p,
				  char *description, WMColor * color,
				  unsigned short first, unsigned short extraInfo)
{
	TextBlock *tb;

	if (!p || !description || !color)
		return NULL;

	tb = wmalloc(sizeof(TextBlock));

	tb->text = wstrdup(description);
	tb->used = strlen(description);
	tb->blank = False;
	tb->d.pixmap = WMRetainPixmap(p);
	tb->color = WMRetainColor(color);
	tb->marginN = newMargin(tPtr, NULL);
	tb->allocated = extraInfo;
	tb->first = first;
	tb->kanji = False;
	tb->graphic = True;
	tb->object = False;
	tb->underlined = False;
	tb->selected = False;
	tb->script = 0;
	tb->sections = NULL;
	tb->nsections = 0;
	tb->prior = NULL;
	tb->next = NULL;

	return tb;
}

void *WMCreateTextBlockWithText(WMText * tPtr, char *text, WMFont * font, WMColor * color,
				unsigned short first, unsigned short len)
{
	TextBlock *tb;

	if (!font || !color)
		return NULL;

	tb = wmalloc(sizeof(TextBlock));

	tb->allocated = reqBlockSize(len);
	tb->text = (char *)wmalloc(tb->allocated);
	memset(tb->text, 0, tb->allocated);

	if (len < 1 || !text || (*text == '\n' && len == 1)) {
		*tb->text = ' ';
		tb->used = 1;
		tb->blank = True;
	} else {
		memcpy(tb->text, text, len);
		tb->used = len;
		tb->blank = False;
	}
	tb->text[tb->used] = 0;

	tb->d.font = WMRetainFont(font);
	tb->color = WMRetainColor(color);
	tb->marginN = newMargin(tPtr, NULL);
	tb->first = first;
	tb->kanji = False;
	tb->graphic = False;
	tb->underlined = False;
	tb->selected = False;
	tb->script = 0;
	tb->sections = NULL;
	tb->nsections = 0;
	tb->prior = NULL;
	tb->next = NULL;
	return tb;
}

void
WMSetTextBlockProperties(WMText * tPtr, void *vtb, unsigned int first,
			 unsigned int kanji, unsigned int underlined, int script, WMRulerMargins * margins)
{
	TextBlock *tb = (TextBlock *) vtb;
	if (!tb)
		return;

	tb->first = first;
	tb->kanji = kanji;
	tb->underlined = underlined;
	tb->script = script;
	tb->marginN = newMargin(tPtr, margins);
}

void
WMGetTextBlockProperties(WMText * tPtr, void *vtb, unsigned int *first,
			 unsigned int *kanji, unsigned int *underlined, int *script, WMRulerMargins * margins)
{
	TextBlock *tb = (TextBlock *) vtb;
	if (!tb)
		return;

	if (first)
		*first = tb->first;
	if (kanji)
		*kanji = tb->kanji;
	if (underlined)
		*underlined = tb->underlined;
	if (script)
		*script = tb->script;
	if (margins)
		margins = &tPtr->margins[tb->marginN];
}

void WMPrependTextBlock(WMText * tPtr, void *vtb)
{
	TextBlock *tb = (TextBlock *) vtb;

	if (!tb)
		return;

	if (tb->graphic) {
		if (tb->object) {
			WMWidget *w = tb->d.widget;
			if (W_CLASS(w) != WC_TextField && W_CLASS(w) != WC_Text) {
				(W_VIEW(w))->attribs.cursor = tPtr->view->screen->defaultCursor;
				(W_VIEW(w))->attribFlags |= CWOverrideRedirect | CWCursor;
			}
		}
		WMAddToArray(tPtr->gfxItems, (void *)tb);
		tPtr->tpos = 1;

	} else {
		tPtr->tpos = tb->used;
	}

	if (!tPtr->lastTextBlock || !tPtr->firstTextBlock) {
		tb->next = tb->prior = NULL;
		tb->first = True;
		tPtr->lastTextBlock = tPtr->firstTextBlock = tPtr->currentTextBlock = tb;
		return;
	}

	if (!tb->first) {
		tb->marginN = tPtr->currentTextBlock->marginN;
	}

	tb->next = tPtr->currentTextBlock;
	tb->prior = tPtr->currentTextBlock->prior;
	if (tPtr->currentTextBlock->prior)
		tPtr->currentTextBlock->prior->next = tb;

	tPtr->currentTextBlock->prior = tb;
	if (!tb->prior)
		tPtr->firstTextBlock = tb;

	tPtr->currentTextBlock = tb;
}

void WMAppendTextBlock(WMText * tPtr, void *vtb)
{
	TextBlock *tb = (TextBlock *) vtb;

	if (!tb)
		return;

	if (tb->graphic) {
		if (tb->object) {
			WMWidget *w = tb->d.widget;
			if (W_CLASS(w) != WC_TextField && W_CLASS(w) != WC_Text) {
				(W_VIEW(w))->attribs.cursor = tPtr->view->screen->defaultCursor;
				(W_VIEW(w))->attribFlags |= CWOverrideRedirect | CWCursor;
			}
		}
		WMAddToArray(tPtr->gfxItems, (void *)tb);
		tPtr->tpos = 1;

	} else {
		tPtr->tpos = tb->used;
	}

	if (!tPtr->lastTextBlock || !tPtr->firstTextBlock) {
		tb->next = tb->prior = NULL;
		tb->first = True;
		tPtr->lastTextBlock = tPtr->firstTextBlock = tPtr->currentTextBlock = tb;
		return;
	}

	if (!tb->first) {
		tb->marginN = tPtr->currentTextBlock->marginN;
	}

	tb->next = tPtr->currentTextBlock->next;
	tb->prior = tPtr->currentTextBlock;
	if (tPtr->currentTextBlock->next)
		tPtr->currentTextBlock->next->prior = tb;

	tPtr->currentTextBlock->next = tb;

	if (!tb->next)
		tPtr->lastTextBlock = tb;

	tPtr->currentTextBlock = tb;
}

void *WMRemoveTextBlock(WMText * tPtr)
{
	TextBlock *tb = NULL;

	if (!tPtr->firstTextBlock || !tPtr->lastTextBlock || !tPtr->currentTextBlock) {
		return NULL;
	}

	tb = tPtr->currentTextBlock;
	if (tb->graphic) {
		WMRemoveFromArray(tPtr->gfxItems, (void *)tb);

		if (tb->object) {
			WMUnmapWidget(tb->d.widget);
		}
	}

	if (tPtr->currentTextBlock == tPtr->firstTextBlock) {
		if (tPtr->currentTextBlock->next)
			tPtr->currentTextBlock->next->prior = NULL;

		tPtr->firstTextBlock = tPtr->currentTextBlock->next;
		tPtr->currentTextBlock = tPtr->firstTextBlock;

	} else if (tPtr->currentTextBlock == tPtr->lastTextBlock) {
		tPtr->currentTextBlock->prior->next = NULL;
		tPtr->lastTextBlock = tPtr->currentTextBlock->prior;
		tPtr->currentTextBlock = tPtr->lastTextBlock;
	} else {
		tPtr->currentTextBlock->prior->next = tPtr->currentTextBlock->next;
		tPtr->currentTextBlock->next->prior = tPtr->currentTextBlock->prior;
		tPtr->currentTextBlock = tPtr->currentTextBlock->next;
	}

	return (void *)tb;
}

#if 0
static void destroyWidget(WMWidget * widget)
{
	WMDestroyWidget(widget);
	// -- never do this -- wfree(widget);
}
#endif

void WMDestroyTextBlock(WMText * tPtr, void *vtb)
{
	TextBlock *tb = (TextBlock *) vtb;
	if (!tb)
		return;

	if (tb->graphic) {
		if (tb->object) {
			/* naturally, there's a danger to destroying widgets whose action
			 * brings us here: ie. press a button to destroy it...
			 * need to find a safer way. till then... this stays commented out */
			/* 5 months later... destroy it 10 seconds after now which should
			 * be enough time for the widget's action to be completed... :-) */
			/* This is a bad assumption. Just destroy the widget here.
			 * if the caller needs it, it can protect it with W_RetainView()
			 * WMAddTimerHandler(10000, destroyWidget, (void *)tb->d.widget);*/
			WMDestroyWidget(tb->d.widget);
		} else {
			WMReleasePixmap(tb->d.pixmap);
		}
	} else {
		WMReleaseFont(tb->d.font);
	}

	WMReleaseColor(tb->color);
	/* isn't this going to memleak if nsections==0? if (tb->sections && tb->nsections > 0) */
	if (tb->sections)
		wfree(tb->sections);
	wfree(tb->text);
	wfree(tb);
}

void WMSetTextForegroundColor(WMText * tPtr, WMColor * color)
{
	if (tPtr->fgColor)
		WMReleaseColor(tPtr->fgColor);

	tPtr->fgColor = WMRetainColor(color ? color : tPtr->view->screen->black);

	paintText(tPtr);
}

void WMSetTextBackgroundColor(WMText * tPtr, WMColor * color)
{
	if (tPtr->bgColor)
		WMReleaseColor(tPtr->bgColor);

	tPtr->bgColor = WMRetainColor(color ? color : tPtr->view->screen->white);
	W_SetViewBackgroundColor(tPtr->view, tPtr->bgColor);

	paintText(tPtr);
}

void WMSetTextBackgroundPixmap(WMText * tPtr, WMPixmap * pixmap)
{
	if (tPtr->bgPixmap)
		WMReleasePixmap(tPtr->bgPixmap);

	if (pixmap)
		tPtr->bgPixmap = WMRetainPixmap(pixmap);
	else
		tPtr->bgPixmap = NULL;
}

void WMSetTextRelief(WMText * tPtr, WMReliefType relief)
{
	tPtr->flags.relief = relief;
	textDidResize(tPtr->view->delegate, tPtr->view);
}

void WMSetTextHasHorizontalScroller(WMText * tPtr, Bool shouldhave)
{
	if (shouldhave && !tPtr->hS) {
		tPtr->hS = WMCreateScroller(tPtr);
		(W_VIEW(tPtr->hS))->attribs.cursor = tPtr->view->screen->defaultCursor;
		(W_VIEW(tPtr->hS))->attribFlags |= CWOverrideRedirect | CWCursor;
		WMSetScrollerArrowsPosition(tPtr->hS, WSAMinEnd);
		WMSetScrollerAction(tPtr->hS, scrollersCallBack, tPtr);
		WMMapWidget(tPtr->hS);
	} else if (!shouldhave && tPtr->hS) {
		WMUnmapWidget(tPtr->hS);
		WMDestroyWidget(tPtr->hS);
		tPtr->hS = NULL;
	}

	tPtr->hpos = 0;
	tPtr->prevHpos = 0;
	textDidResize(tPtr->view->delegate, tPtr->view);
}

void WMSetTextHasRuler(WMText * tPtr, Bool shouldhave)
{
	if (shouldhave && !tPtr->ruler) {
		tPtr->ruler = WMCreateRuler(tPtr);
		(W_VIEW(tPtr->ruler))->attribs.cursor = tPtr->view->screen->defaultCursor;
		(W_VIEW(tPtr->ruler))->attribFlags |= CWOverrideRedirect | CWCursor;
		WMSetRulerReleaseAction(tPtr->ruler, rulerReleaseCallBack, tPtr);
		WMSetRulerMoveAction(tPtr->ruler, rulerMoveCallBack, tPtr);
	} else if (!shouldhave && tPtr->ruler) {
		WMShowTextRuler(tPtr, False);
		WMDestroyWidget(tPtr->ruler);
		tPtr->ruler = NULL;
	}
	textDidResize(tPtr->view->delegate, tPtr->view);
}

void WMShowTextRuler(WMText * tPtr, Bool show)
{
	if (!tPtr->ruler)
		return;

	if (tPtr->flags.monoFont)
		show = False;

	tPtr->flags.rulerShown = show;
	if (show) {
		WMMapWidget(tPtr->ruler);
	} else {
		WMUnmapWidget(tPtr->ruler);
	}

	textDidResize(tPtr->view->delegate, tPtr->view);
}

Bool WMGetTextRulerShown(WMText * tPtr)
{
	if (!tPtr->ruler)
		return False;

	return tPtr->flags.rulerShown;
}

void WMSetTextHasVerticalScroller(WMText * tPtr, Bool shouldhave)
{
	if (shouldhave && !tPtr->vS) {
		tPtr->vS = WMCreateScroller(tPtr);
		(W_VIEW(tPtr->vS))->attribs.cursor = tPtr->view->screen->defaultCursor;
		(W_VIEW(tPtr->vS))->attribFlags |= CWOverrideRedirect | CWCursor;
		WMSetScrollerArrowsPosition(tPtr->vS, WSAMaxEnd);
		WMSetScrollerAction(tPtr->vS, scrollersCallBack, tPtr);
		WMMapWidget(tPtr->vS);
	} else if (!shouldhave && tPtr->vS) {
		WMUnmapWidget(tPtr->vS);
		WMDestroyWidget(tPtr->vS);
		tPtr->vS = NULL;
	}

	tPtr->vpos = 0;
	tPtr->prevVpos = 0;
	textDidResize(tPtr->view->delegate, tPtr->view);
}

Bool WMScrollText(WMText * tPtr, int amount)
{
	Bool scroll = False;

	if (amount == 0 || !tPtr->view->flags.realized)
		return False;

	if (amount < 0) {
		if (tPtr->vpos > 0) {
			if (tPtr->vpos > abs(amount))
				tPtr->vpos += amount;
			else
				tPtr->vpos = 0;
			scroll = True;
		}
	} else {
		int limit = tPtr->docHeight - tPtr->visible.h;
		if (tPtr->vpos < limit) {
			if (tPtr->vpos < limit - amount)
				tPtr->vpos += amount;
			else
				tPtr->vpos = limit;
			scroll = True;
		}
	}

	if (scroll && tPtr->vpos != tPtr->prevVpos) {
		updateScrollers(tPtr);
		paintText(tPtr);
	}
	tPtr->prevVpos = tPtr->vpos;
	return scroll;
}

Bool WMPageText(WMText * tPtr, Bool direction)
{
	if (!tPtr->view->flags.realized)
		return False;

	return WMScrollText(tPtr, direction ? tPtr->visible.h : -tPtr->visible.h);
}

void WMSetTextEditable(WMText * tPtr, Bool editable)
{
	tPtr->flags.editable = editable;
}

int WMGetTextEditable(WMText * tPtr)
{
	return tPtr->flags.editable;
}

void WMSetTextIndentNewLines(WMText * tPtr, Bool indent)
{
	tPtr->flags.indentNewLine = indent;
}

void WMSetTextIgnoresNewline(WMText * tPtr, Bool ignore)
{
	tPtr->flags.ignoreNewLine = ignore;
}

Bool WMGetTextIgnoresNewline(WMText * tPtr)
{
	return tPtr->flags.ignoreNewLine;
}

void WMSetTextUsesMonoFont(WMText * tPtr, Bool mono)
{
	if (mono) {
		if (tPtr->flags.rulerShown)
			WMShowTextRuler(tPtr, False);
		if (tPtr->flags.alignment != WALeft)
			tPtr->flags.alignment = WALeft;
	}

	tPtr->flags.monoFont = mono;
	textDidResize(tPtr->view->delegate, tPtr->view);
}

Bool WMGetTextUsesMonoFont(WMText * tPtr)
{
	return tPtr->flags.monoFont;
}

void WMSetTextDefaultFont(WMText * tPtr, WMFont * font)
{
	if (tPtr->dFont)
		WMReleaseFont(tPtr->dFont);

	if (font) {
		tPtr->dFont = WMRetainFont(font);
	} else {
		tPtr->dFont = WMSystemFontOfSize(tPtr->view->screen, 12);
	}
}

WMFont *WMGetTextDefaultFont(WMText * tPtr)
{
	return WMRetainFont(tPtr->dFont);
}

void WMSetTextDefaultColor(WMText * tPtr, WMColor * color)
{
	if (tPtr->dColor)
		WMReleaseColor(tPtr->dColor);

	if (color) {
		tPtr->dColor = WMRetainColor(color);
	} else {
		tPtr->dColor = WMBlackColor(tPtr->view->screen);
	}
}

WMColor *WMGetTextDefaultColor(WMText * tPtr)
{
	return tPtr->dColor;
}

void WMSetTextAlignment(WMText * tPtr, WMAlignment alignment)
{
	if (tPtr->flags.monoFont)
		tPtr->flags.alignment = WALeft;
	else
		tPtr->flags.alignment = alignment;
	WMThawText(tPtr);
}

int WMGetTextInsertType(WMText * tPtr)
{
	return tPtr->flags.prepend;
}

void WMSetTextSelectionColor(WMText * tPtr, WMColor * color)
{
	setSelectionProperty(tPtr, NULL, color, -1);
}

WMColor *WMGetTextSelectionColor(WMText * tPtr)
{
	TextBlock *tb;

	tb = tPtr->currentTextBlock;

	if (!tb || !tPtr->flags.ownsSelection)
		return NULL;

	if (!tb->selected)
		return NULL;

	return tb->color;
}

void WMSetTextSelectionFont(WMText * tPtr, WMFont * font)
{
	setSelectionProperty(tPtr, font, NULL, -1);
}

WMFont *WMGetTextSelectionFont(WMText * tPtr)
{
	TextBlock *tb;

	tb = tPtr->currentTextBlock;

	if (!tb || !tPtr->flags.ownsSelection)
		return NULL;

	if (!tb->selected)
		return NULL;

	if (tb->graphic) {
		tb = getFirstNonGraphicBlockFor(tb, 1);
		if (!tb)
			return NULL;
	}
	return (tb->selected ? tb->d.font : NULL);
}

void WMSetTextSelectionUnderlined(WMText * tPtr, int underlined)
{
	/* // check this */
	if (underlined != 0 && underlined != 1)
		return;

	setSelectionProperty(tPtr, NULL, NULL, underlined);
}

int WMGetTextSelectionUnderlined(WMText * tPtr)
{
	TextBlock *tb;

	tb = tPtr->currentTextBlock;

	if (!tb || !tPtr->flags.ownsSelection)
		return 0;

	if (!tb->selected)
		return 0;

	return tb->underlined;
}

void WMFreezeText(WMText * tPtr)
{
	tPtr->flags.frozen = True;
}

void WMThawText(WMText * tPtr)
{
	tPtr->flags.frozen = False;

	if (tPtr->flags.monoFont) {
		int j, c = WMGetArrayItemCount(tPtr->gfxItems);
		TextBlock *tb;

		/* make sure to unmap widgets no matter where they are */
		/* they'll be later remapped if needed by paintText */
		for (j = 0; j < c; j++) {
			if ((tb = (TextBlock *) WMGetFromArray(tPtr->gfxItems, j))) {
				if (tb->object && ((W_VIEW(tb->d.widget))->flags.mapped))
					WMUnmapWidget(tb->d.widget);
			}
		}
	}

	tPtr->flags.laidOut = False;
	layOutDocument(tPtr);
	updateScrollers(tPtr);
	paintText(tPtr);
	tPtr->flags.needsLayOut = False;

}

/* find first occurence of a string */
static char *mystrstr(char *haystack, char *needle, unsigned short len, char *end, Bool caseSensitive)
{
	char *ptr;

	if (!haystack || !needle || !end)
		return NULL;

	for (ptr = haystack; ptr < end; ptr++) {
		if (caseSensitive) {
			if (*ptr == *needle && !strncmp(ptr, needle, len))
				return ptr;

		} else {
			if (tolower(*ptr) == tolower(*needle) && !strncasecmp(ptr, needle, len))
				return ptr;

		}
	}
	return NULL;
}

/* find last occurence of a string */
static char *mystrrstr(char *haystack, char *needle, unsigned short len, char *end, Bool caseSensitive)
{
	char *ptr;

	if (!haystack || !needle || !end)
		return NULL;

	for (ptr = haystack - 2; ptr > end; ptr--) {
		if (caseSensitive) {
			if (*ptr == *needle && !strncmp(ptr, needle, len))
				return ptr;
		} else {
			if (tolower(*ptr) == tolower(*needle) && !strncasecmp(ptr, needle, len))
				return ptr;

		}
	}
	return NULL;
}

Bool WMFindInTextStream(WMText * tPtr, char *needle, Bool direction, Bool caseSensitive)
{
	TextBlock *tb;
	char *mark = NULL;
	unsigned short pos;

#if 0
	if (!(tb = tPtr->currentTextBlock)) {
		if (!(tb = ((direction > 0) ? tPtr->firstTextBlock : tPtr->lastTextBlock))) {
			return False;
		}
	} else {
		/*  if(tb != ((direction>0) ?tPtr->firstTextBlock : tPtr->lastTextBlock))
		   tb = (direction>0) ? tb->next : tb->prior; */
		if (tb != tPtr->lastTextBlock)
			tb = tb->prior;
	}
#endif
	tb = tPtr->currentTextBlock;
	pos = tPtr->tpos;

	while (tb) {
		if (!tb->graphic) {

			if (direction > 0) {
				if (pos + 1 < tb->used)
					pos++;

				if (tb->used - pos > 0 && pos > 0) {
					mark = mystrstr(&tb->text[pos], needle,
							strlen(needle), &tb->text[tb->used], caseSensitive);

				} else {
					tb = tb->next;
					pos = 0;
					continue;
				}

			} else {
				if (pos - 1 > 0)
					pos--;

				if (pos > 0) {
					mark = mystrrstr(&tb->text[pos], needle,
							 strlen(needle), tb->text, caseSensitive);
				} else {
					tb = tb->prior;
					if (!tb)
						return False;
					pos = tb->used;
					continue;
				}
			}

			if (mark) {
				WMFont *font = tPtr->flags.monoFont ? tPtr->dFont : tb->d.font;

				tPtr->tpos = (int)(mark - tb->text);
				tPtr->currentTextBlock = tb;
				updateCursorPosition(tPtr);
				tPtr->sel.y = tPtr->cursor.y + 5;
				tPtr->sel.h = tPtr->cursor.h - 10;
				tPtr->sel.x = tPtr->cursor.x + 1;
				tPtr->sel.w = WMIN(WMWidthOfString(font,
								   &tb->text[tPtr->tpos], strlen(needle)),
						   tPtr->docWidth - tPtr->sel.x);
				tPtr->flags.ownsSelection = True;
				paintText(tPtr);

				return True;
			}

		}
		tb = (direction > 0) ? tb->next : tb->prior;
		if (tb) {
			pos = (direction > 0) ? 0 : tb->used;
		}
	}

	return False;
}

Bool WMReplaceTextSelection(WMText * tPtr, char *replacement)
{
	if (!tPtr->flags.ownsSelection)
		return False;

	removeSelection(tPtr);

	if (replacement) {
		insertTextInteractively(tPtr, replacement, strlen(replacement));
		updateCursorPosition(tPtr);
		paintText(tPtr);
	}

	return True;

}
