Subject: new menuhit.c

From: Chris Siebenmann <cks>
Date: Wed, 9 Dec 1992 16:34:38 -0500

Apparently I forgot to point this out to the list. Some time back,
Mike Haertel ([email-masked]) posted a reimplementation
of libXg/menuhit.c that did scrolling menus to comp.editors. Since
the article has no doubt long since expired (posted 25 Nov), I've
attached it here. I've been running this code for some time without
problems.

	- cks
/*
Article 7064 of comp.editors:
Path: utcsri!rpi!usenet.coe.montana.edu!ogicse!cs.uoregon.edu!mystix.cs.uoregon.edu!mike
From: [email-masked] (Michael John Haertel)
Newsgroups: comp.editors
Subject: Scrolling menus for Sam
Summary: use at your own risk
Message-ID: <[email-masked]>
Date: 25 Nov 92 08:13:02 GMT
Article-I.D.: cs.1992Nov25.081302.26508
Sender: [email-masked] (Netnews Owner)
Organization: University of Oregon Computer and Information Sciences Dept.
Lines: 188

One day in a particularly large source directory I typed "sam *.[ch]",
and the resulting menu of buffers exceeded what could fit vertically
in my screen.  So I decided to implement scrolling menus.  Enclosed
is a replacement for "menuhit.c" in the libXg/ subdirectory of the
Sam distribution.  It is a from-scratch implementation by me rather
than being a modification of the old menuhit.c.  It is a two hour
hack so don't expect anything polished.  Use it at your own risk...

	Mike

*/
#include "libg.h"

enum {
	Scrollbar = 15,		/* width of scrollbar in pixels */
	Scrollthresh = 12,	/* #items before we introduce a scrollbar. */
	Border = 1,		/* thickness of border on menu */
	Inset = 2,		/* inset of actual menu */
	Vskip = 1,		/* space between lines */
};

static char *
genitem(Menu *menu, int i)
{
	if (menu->item)
		return menu->item[i];
	else
		return (*menu->gen)(i);
}

static Rectangle
adjrect(Rectangle r)
{
	if (r.max.x > screen.r.max.x)
		r = rsubp(r, Pt(r.max.x - screen.r.max.x, 0));
	if (r.max.y > screen.r.max.y)
		r = rsubp(r, Pt(0, r.max.y - screen.r.max.y));
	if (r.min.x < screen.r.min.x)
		r = raddp(r, Pt(screen.r.min.x - r.min.x, 0));
	if (r.min.y < screen.r.min.y)
		r = raddp(r, Pt(0, screen.r.min.y - r.min.y));
	return r;
}

static void
highlight(Rectangle menur, int imin, int item)
{
	if (item == -1)
		return;
	menur.min.y += (item - imin) * (font->height + Vskip);
	menur.max.y = menur.min.y + font->height;
	bitblt(&screen, menur.min, &screen, menur, ~D);
}

static void
drawbar(Rectangle r, int imin, int imax, int nitems, Fcode f)
{
	int h, b, e;
	Rectangle dorect;

	if (nitems == 0)
		return;
	h = r.max.y - r.min.y;
	b = (int) ((float) imin / nitems * h);
	e = (int) ((float) imax / nitems * h);
	dorect.min.x = r.min.x;
	dorect.max.x = r.max.x;
	dorect.min.y = r.min.y + b;
	dorect.max.y = r.min.y + e;
	bitblt(&screen, dorect.min, &screen, dorect, f);
}

static void
draw(Menu *m, Rectangle r, Rectangle scrollr, int imin, int imax, int nitems)
{
	char *s;
	Point p;
	Rectangle liner;
	int i;

	if (Dx(scrollr) != 0)
		bitblt(&screen, scrollr.min, &screen, Rpt(scrollr.min, r.max), Zero);
	else
		bitblt(&screen, r.min, &screen, r, Zero);
	if (Dx(scrollr) != 0) {
		bitblt(&screen, scrollr.min, &screen, scrollr, Zero);
		drawbar(scrollr, imin, imax, nitems, ~D);
	}
	liner.min = r.min;
	liner.max = add(r.min, Pt(Dx(r), font->height));
	p = Pt(0, font->height + Vskip);
	for (i = imin; i < imax; ++i) {
		s = genitem(m, i);
		string(&screen, add(liner.min, Pt(Dx(liner) / 2 - strwidth(font, s) / 2, 0)), font, s, F);
		liner = raddp(liner, p);
	}
}

int
menuhit(int but, Mouse *mouse, Menu *menu)
{
	Rectangle totalr, menur, scrollr, itemr;
	int n, w, maxw, imin, imax, newimin, newimax, lasthit, hit;
	char *s;
	Bitmap *saveb;
	Point p;

	maxw = 0;
	for (n = 0; genitem(menu, n); ++n) {
		w = strwidth(font, genitem(menu, n));
		if (w > maxw)
			maxw = w;
	}
	if (n == 0)
		return -1;	/* maybe should wait for mouse released? */
	lasthit = menu->lasthit;
	if (lasthit < 0 || lasthit >= n)
		lasthit = 0;
	imin = 0;
	imax = n > Scrollthresh ? Scrollthresh : n;
	if (imax <= lasthit)
		imin = lasthit - Scrollthresh / 2, imax = imin + Scrollthresh;
	if (imax > n)
		imin -= imax - n, imax -= imax - n;
	totalr.min = Pt(0,0);
	totalr.max.x = maxw + (n > Scrollthresh ? Scrollbar : 0) + 2 * Inset;
	totalr.max.y = (imax - imin) * (font->height + Vskip) - Vskip + 2 * Inset;
	p.x = (n > Scrollthresh ? Scrollbar : 0) + maxw / 2 + Inset;
	p.y = (lasthit - imin) * (font->height + Vskip) + (font->height + Vskip) / 2 + Inset;
	totalr = raddp(totalr, sub(mouse->xy, p));
	totalr = adjrect(totalr);
	menur = inset(totalr, Inset);
	if (n > Scrollthresh) {
		scrollr = menur;
		scrollr.max.x = menur.min.x + Scrollbar - 1;
		menur.min.x += Scrollbar;
	} else
		scrollr.max.x = scrollr.min.x;
	saveb = balloc(totalr, screen.ldepth);
	if (!saveb)
		saveb = &screen;
	bitblt(saveb, saveb->r.min, &screen, totalr, S);
	border(&screen, totalr, Border, F);
	border(&screen, inset(totalr, Border), Inset - Border, Zero);
	draw(menu, menur, scrollr, imin, imax, n);
	cursorset(add(totalr.min, p));
	highlight(menur, imin, lasthit);
	for (;;) {
		*mouse = emouse();
		if ((mouse->buttons & (1 << but - 1)) == 0)
			break;
		if (ptinrect(mouse->xy, menur))
			hit = (mouse->xy.y - menur.min.y) / (font->height + Vskip) + imin;
		else
			hit = -1;
		if (hit != lasthit) {
			highlight(menur, imin, lasthit);
			if (hit == imin && imin > 0) {
				--imin, --imax;
				draw(menu, menur, scrollr, imin, imax, n);
				cursorset(add(mouse->xy, Pt(0, font->height + Vskip)));
			} else if (hit == imax - 1 && imax < n) {
				++imin, ++imax;
				draw(menu, menur, scrollr, imin, imax, n);
				cursorset(sub(mouse->xy, Pt(0, font->height + Vskip)));
			}
			highlight(menur, imin, hit);
		}
		if (ptinrect(mouse->xy, scrollr)) {
			newimin = (float) (mouse->xy.y - scrollr.min.y) / Dy(scrollr) * n;
			newimax = newimin + Scrollthresh;
			while (newimax > n)
				--newimin, --newimax;
			if (imin != newimin) {
				imin = newimin;
				imax = newimax;
				draw(menu, menur, scrollr, imin, imax, n);
			}
		}
		lasthit = hit;
	}
	if (lasthit != -1)
		menu->lasthit = lasthit;
	bitblt(&screen, totalr.min, saveb, totalr, S);
	bfree(saveb);
	return lasthit;
}