/*
 * @(#)Mball2d.c
 *
 * Copyright 2024  David A. Bagley, bagleyd AT verizon.net
 *
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/* Methods file for Mball2d */

#include "MballP.h"
#include "Mball2dP.h"

#ifndef WINVER
static Boolean setValuesPuzzle2D(Widget current,
	Widget request, Widget renew);
static void resizePuzzle2D(Mball2DWidget w);
static void initializePuzzle2D(Widget request, Widget renew);
static void exposePuzzle2D(Widget renew,
	XEvent *event, Region region);
static void movePuzzleTtl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTtr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBbl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBbr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBr(MballWidget w,
	XEvent *event, char **args, int nArgs);

static char translations2D[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 Shift<KeyPress>2: Sound()\n\
 <KeyPress>F11: MoveCcw()\n\
 <KeyPress>KP_Divide: MoveCcw()\n\
 <KeyPress>R5: MoveCcw()\n\
 <KeyPress>Home: MoveTl()\n\
 <KeyPress>KP_7: MoveTl()\n\
 <KeyPress>R7: MoveTl()\n\
 <KeyPress>Num_Lock: MoveTtl()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>KP_Multiply: MoveTtr()\n\
 <KeyPress>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>F12: MoveCw()\n\
 <KeyPress>Begin: MoveCw()\n\
 <KeyPress>KP_5: MoveCw()\n\
 <KeyPress>R11: MoveCw()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>End: MoveBl()\n\
 <KeyPress>KP_1: MoveBl()\n\
 <KeyPress>R13: MoveBl()\n\
 <KeyPress>KP_0: MoveBbl()\n\
 <KeyPress>KP_Insert: MoveBbl()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <KeyPress>KP_Decimal: MoveBbr()\n\
 <KeyPress>KP_Delete: MoveBbr()\n\
 <KeyPress>Next: MoveBr()\n\
 <KeyPress>KP_3: MoveBr()\n\
 <KeyPress>R15: MoveBr()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn2Down>: PracticeMaybe()\n\
 <Btn2Down>(2+): Practice2()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>r: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>p: Practice()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>2: Wedge2()\n\
 <KeyPress>4: Wedge4()\n\
 <KeyPress>6: Wedge6()\n\
 <KeyPress>8: Wedge8()\n\
 <KeyPress>0: Wedge10()\n\
 <KeyPress>=: Wedge12()\n\
 <KeyPress>.: Wedge12()\n\
 <KeyPress>v: View()\n\
 <KeyPress>t: Perspective()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

/* '=' is the 2nd key to the right of '0' on keyboard
 * '.' follows '0' on keypad */

static XtActionsRec actionsList2D[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveCcw", (XtActionProc) movePuzzleCcw},
	{(char *) "MoveTl", (XtActionProc) movePuzzleTl},
	{(char *) "MoveTtl", (XtActionProc) movePuzzleTtl},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveTtr", (XtActionProc) movePuzzleTtr},
	{(char *) "MoveTr", (XtActionProc) movePuzzleTr},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveCw", (XtActionProc) movePuzzleCw},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBbl", (XtActionProc) movePuzzleBbl},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "MoveBbr", (XtActionProc) movePuzzleBbr},
	{(char *) "MoveBr", (XtActionProc) movePuzzleBr},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "PracticeMaybe", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "Practice2", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeMaybe", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "Randomize2", (XtActionProc) randomizePuzzleWithDoubleClick},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Practice", (XtActionProc) practicePuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(char *) "Orientize", (XtActionProc) orientizePuzzle},
	{(char *) "Wedge2", (XtActionProc) wedge2ModePuzzle},
	{(char *) "Wedge4", (XtActionProc) wedge4ModePuzzle},
	{(char *) "Wedge6", (XtActionProc) wedge6ModePuzzle},
	{(char *) "Wedge8", (XtActionProc) wedge8ModePuzzle},
	{(char *) "Wedge10", (XtActionProc) wedge10ModePuzzle},
	{(char *) "Wedge12", (XtActionProc) wedge12ModePuzzle},
	{(char *) "View", (XtActionProc) viewPuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Perspective", (XtActionProc) perspectivePuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resources2D[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(MballWidget, core.width),
	 XtRString, (caddr_t) "300"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(MballWidget, core.height),
	 XtRString, (caddr_t) "600"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.frameColor),
	 XtRString, (caddr_t) "cyan" /*XtDefaultForeground*/},
	{XtNwedgeColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[0]),
	 XtRString, (caddr_t) "yellow"},
	{XtNwedgeColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[1]),
	 XtRString, (caddr_t) "green"},
	{XtNwedgeColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[2]),
	 XtRString, (caddr_t) "SeaGreen"},
	{XtNwedgeColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[3]),
	 XtRString, (caddr_t) "blue"},
	{XtNwedgeColor4, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[4]),
	 XtRString, (caddr_t) "cyan"},
	{XtNwedgeColor5, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[5]),
	 XtRString, (caddr_t) "magenta"},
	{XtNwedgeColor6, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[6]),
	 XtRString, (caddr_t) "red"},
	{XtNwedgeColor7, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[7]),
	 XtRString, (caddr_t) "orange"},
	{XtNwedgeColor8, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[8]),
	 XtRString, (caddr_t) "pink"},
	{XtNwedgeColor9, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[9]),
	 XtRString, (caddr_t) "tan"},
	{XtNwedgeColor10, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[10]),
	 XtRString, (caddr_t) "LightSteelBlue"},
	{XtNwedgeColor11, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[11]),
	 XtRString, (caddr_t) "IndianRed"},
	{XtNpieceBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.borderColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultForeground*/},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNwedges, XtCWedges, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.wedges),
	 XtRString, (caddr_t) "8"}, /* DEFAULT_WEDGES */
	{XtNbands, XtCBands, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.bands),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_BANDS */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.orient),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_ORIENT */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.practice),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_PRACTICE */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.base),
	 XtRString, (caddr_t) "16"}, /* DEFAULT_BASE */
	{XtNperspective, XtCPerspective, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.perspective),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_PERSPECTIVE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNwedge, XtCWedge, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentWedge),
	 XtRString, (caddr_t) "-1"},
	{XtNband, XtCBand, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentBand),
	 XtRString, (caddr_t) "-1"},
	{XtNdirection, XtCDirection, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentDirection),
	 XtRString, (caddr_t) "-1"},
	{XtNcontrol, XtCControl, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentControl),
	 XtRString, (caddr_t) "0"},
	{XtNfast, XtCFast, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentFast),
	 XtRString, (caddr_t) "1"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(MballWidget, mball.select),
	 XtRCallback, (caddr_t) NULL}
};

Mball2DClassRec mball2dClassRec =
{
	{
		(WidgetClass) & mballClassRec,		/* superclass */
		(char *) "Mball2D",	/* class name */
		sizeof (MballRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzle2D,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		actionsList2D,	/* actions */
		XtNumber(actionsList2D),	/* num actions */
		resources2D,	/* resources */
		XtNumber(resources2D),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		NULL,	/* destroy */
		(XtWidgetProc) resizePuzzle2D,	/* resize */
		(XtExposeProc) exposePuzzle2D,	/* expose */
		(XtSetValuesFunc) setValuesPuzzle2D,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		XtInheritAcceptFocus,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		translations2D,	/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	},
	{
		0		/* ignore */
	}
};

WidgetClass mball2dWidgetClass = (WidgetClass) & mball2dClassRec;
#endif

static Pixmap dr = 0; /* dummy for future double buffering */

static void
letterPosition(Mball2DWidget w, int wedge, int band, int lengthx, int lengthy,
		int *dx, int *dy)
{
	double angle, radius;

	angle = (2 * wedge + 1) * M_PI / w->mball.wedges;
	if (w->mball.perspective) {
		if (((w->mball.bands & 1) == 1) && band == w->mball.bands / 2)
			radius = sin(M_PI * (0.25 + band) / w->mball.bands) / 2.0;
		else
			radius = sin(M_PI * (0.5 + band) / w->mball.bands) / 2.0;
	} else {
		if (((w->mball.bands & 1) == 1) && band == w->mball.bands / 2)
			radius = (4.0 * band + 1.0) / (4.0 * w->mball.bands);
		else
			radius = (2.0 * band + 1.0) / (2.0 * w->mball.bands);
	}
	*dx = lengthx / 2 + (int) (lengthx * radius * cos(angle - M_PI / 2));
	*dy = lengthy / 2 + (int) (lengthy * radius * sin(angle - M_PI / 2));
}

#if defined(WINVER) && (WINVER <= 0x030a)	/* if WINDOWS 3.1 or less */
#define FLOODFILL 1
#endif

static int
int2String(Mball2DWidget w, char *buf, int number, int base, Boolean capital)
{
	int digit, mult = base, last, position;
	int a, i, j, s, r;

	if (capital) {
		a = 'A', i = 'I', j = 'J', s = 'S', r = 'R';
	} else {
		a = 'a', i = 'i', j = 'j', s = 's', r = 'r';
	}
	if (number < 0) {
		char *buff;

		intCat(&buff, "int2String: 0 > ", number);
		DISPLAY_WARNING(buff);
		free(buff);
		return 0;
	}
	last = 1;
	while (number >= mult) {
		last++;
		mult *= base;
	}
	for (position = 0; position < last; position++) {
		mult /= base;
		digit = number / mult;
		number -= digit * mult;
		buf[position] = (char) (digit + '0');
		if (buf[position] > '9') {      /* ASCII */
			buf[position] = (char) (buf[position] + (a - '9' - 1));
		} else if (buf[position] < '0') {       /* EBCDIC */
			buf[position] = (char) (buf[position] + (a - '9' - 1));
			if (buf[position] > i)
				buf[position] = (char) (buf[position] + (j - i - 1));
			if (buf[position] > r)
				buf[position] = (char) (buf[position] + (s - r - 1));
		}
	}
	buf[last] = '\0';
	return last;
}

#ifndef FLOODFILL
static void
offsetSect(const Mball2DWidget w, const int wedge, int *dx, int *dy)
{
	double angle = (2 * wedge + 1) * M_PI / w->mball.wedges
		- M_PI / 2.0;

	*dx = (int) (w->mball.dr * cos(angle));
	*dy = (int) (w->mball.dr * sin(angle));
}
#endif

#ifdef WINVER
#ifdef FLOODFILL
/* Using a dangerous flood fill.  Probably faster then filling a sector by
 * drawing many arcs of width 1 */
static void
SECTOR(Mball2DWidget w, const GC pieceGC, const GC borderGC,
		const int letterx, const int lettery)
{
	w->mball.hBrush = CreateSolidBrush(pieceGC);
	w->mball.hOldBrush = (HBRUSH) SelectObject(w->core.hDC,
		w->mball.hBrush);
	(void) FloodFill(w->core.hDC, letterx, lettery, borderGC);
	(void) SelectObject(w->core.hDC, w->mball.hOldBrush);
	(void) DeleteObject(w->mball.hBrush);
}

#else
static void
SECTOR(Mball2DWidget w, const GC pieceGC, const GC borderGC,
		const int xo, const int yo,
		const int width1, const int height1,
		const int width2, const int height2,
		const int angle1, const int angle2)
{
	int d, r1 = MIN(width1, height1) / 2;
	int r2 = MIN(width2, height2) / 2;
	int t = MAX(r2 - r1 - 8, 1);

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	} if (r1 < 0)
		r1 = -3;
	if (r1 <= 3) {
		d = MAX(2 * (r2 - 1), 3);
		DRAWPIE(w, pieceGC, xo - d / 2, yo - d / 2, d, d,
			angle1, angle2);
	} else {
		int a1 = angle1, a2 = angle2;

		d = MAX(r1 + r2 + 2, 2);

		/* MS weirdness here flat caps on wrong side of arc if
		   angle is 0, 90, 180 or 270 degrees. */
		/* https://forums.codegear.com/message.jspa?messageID=118084 */
		if (angle1 % 90 == 0) {
			if (angle1 != 0) {
				a1 -= 1;
				a2 += 1;
			} else {
				a1 = 359;
				a2 += 1;
			}
		}
		if ((angle1 + angle2) % 90 == 0) {
			a2 += 1;
		}
		DRAWARC2(w, dr, pieceGC, t, xo - d / 2, yo - d / 2, d, d,
			a1, a2);
		d = MAX(2 * (r1 + 3), 1);
		DRAWARC(w, dr, borderGC, 1, xo - d / 2, yo - d / 2, d, d,
			angle1, angle2);
	}
	DRAWARC(w, dr, borderGC, 1, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
#if 0
	{
		double ang, x, y;
	if (r1 <= 0)
		r1 = 0;
	ang = RADIANS((double) angle1);
	x = cos(ang);
	y = -sin(ang);
	DRAWLINE(w, dr, pieceGC,
		(int) (r1 * x) + xo, (int) (r1 * y) + yo,
		(int) (r2 * x) + xo, (int) (r2 * y) + yo);
	ang = RADIANS((double) angle1+angle2);
	x = cos(ang);
	y = -sin(ang);
	DRAWLINE(w, dr, pieceGC,
		(int) (r1 * x) + xo, (int) (r1 * y) + yo,
		(int) (r2 * x) + xo, (int) (r2 * y) + yo);
	}
#endif
}
#endif

static void
drawSect(const Mball2DWidget w, const GC pieceGC, const GC borderGC,
		const int r, const int wedge,
		const int startx, const int starty,
		const int lengthx, const int lengthy)
{
	int dx, dy, diam = 2 * w->mball.dr;
	double u, v;

#ifdef FLOODFILL
	letterPosition(w, wedge, r, lengthx, lengthy, &dx, &dy);
#ifdef DEBUG
	SetPixel(w->core.hDC, dx + startx, dy + starty, piece);
#else
	SECTOR(w, pieceGC, borderGC, dx + startx, dy + starty);
#endif
#else
	offsetSect(w, wedge, &dx, &dy);
	if ((w->mball.bands & 1) == 1) {
		if (r == w->mball.bands / 2) {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = 0.5;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / (w->mball.bands + 1);
			}
			SECTOR(w, pieceGC, borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		} else {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / w->mball.bands;
			}
			SECTOR(w, pieceGC, borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		}
	} else {
		if (w->mball.perspective) {
			u = sin(M_PI * r / w->mball.bands) / 2.0;
			v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
		} else {
			u = (double) r / w->mball.bands;
			v = (double) (r + 1) / w->mball.bands;
		}
		SECTOR(w, pieceGC, borderGC,
			startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
			(int) (u * 2 * lengthx - diam),
			(int) (u * 2 * lengthy - diam),
			(int) (v * 2 * lengthx - diam),
			(int) (v * 2 * lengthy - diam),
			CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
			-FULL_CIRCLE / w->mball.wedges);
	}
#endif
}

void
drawSector2D(const Mball2DWidget w, const int sector, const int offset)
{
	int wedge = sector % w->mball.wedges;
	int band = sector / w->mball.wedges;
	int startx, starty, lengthx, lengthy, i, l;
	GC pieceGC, borderGC;

	startx = 1 + w->mball.puzzleOffset.x;
	starty = 1 + w->mball.puzzleOffset.y;
	lengthx = w->mball.viewLength - w->mball.delta +
		w->mball.puzzleOffset.x;
	lengthy = w->mball.viewLength - w->mball.delta +
		w->mball.puzzleOffset.y;
	if (offset == 0) {
		pieceGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
#ifdef FLOODFILL
		borderGC = (w->mball.focus) ? w->mball.frameGC :
			w->mball.borderGC;
#else
		borderGC = w->mball.borderGC;
#endif
	} else {
		pieceGC = w->mball.borderGC;
#ifdef FLOODFILL
		borderGC = (w->mball.focus) ? w->mball.frameGC :
			w->mball.borderGC;
#else
		borderGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
#endif
	}
	if (band < (w->mball.bands + 1) / 2) {
		drawSect(w, pieceGC, borderGC, band, wedge,
			startx, starty, lengthx - startx, lengthy - starty);
	}
	if (band + 1 > w->mball.bands / 2) {
		if (w->mball.vertical) {
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band,
				(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
				startx, lengthy + 3, lengthx - startx, lengthy - starty);
		} else {
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band, w->mball.wedges - 1 - wedge,
				lengthx + 3, starty, lengthx - startx, lengthy - starty);
		}
	}
	if (w->mball.mono) {
		int letterX, letterY;
		char buf[3];

		if (band < (w->mball.bands + 1) / 2) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + 6 + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			if (w->mball.orient && !w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, FALSE);
				buf[last] = w->mball.wedgeChar[l - 1];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				l = w->mball.mballLoc[wedge][band].wedge + 1;
				buf[0] = w->mball.wedgeChar[l - 1];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + 5 + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 8 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			if (w->mball.orient && w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, FALSE);
				buf[last] = w->mball.wedgeChar[l - 1];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				l = w->mball.mballLoc[wedge][band].wedge + 1;
				buf[0] = w->mball.wedgeChar[l - 1];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	} else if (w->mball.orient) {
		int letterX, letterY;
		char buf[2];

		if (band < (w->mball.bands + 1) / 2 &&
				!w->mball.mballLoc[wedge][band].direction) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + 5 + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, TRUE);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2 &&
				w->mball.mballLoc[wedge][band].direction) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + 5 + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 8 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, TRUE);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	}
}

#else

static void
XFillSector(Display * display, Drawable drawable, GC gc, int xo, int yo,
		int width1, int height1, int width2, int height2,
		int angle1, int angle2)
{
	int d, r1 = MIN(width1, height1) / 2, r2 = MIN(width2, height2) / 2;
	int w = MAX(r2 - r1 - 8, 1);

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	}
	if (r1 < 0)
		r1 = -3;
	d = MAX(r1 + r2 + 2, 2);
	XSetLineAttributes(display, gc, w, LineSolid, CapNotLast, JoinRound);
	XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
	XSetLineAttributes(display, gc, 1, LineSolid, CapNotLast, JoinRound);
}

static void
XDrawSector(Display * display, Drawable drawable, GC gc,
		const int xo, const int yo,
		const int width1, const int height1,
		const int width2, const int height2,
		const int angle1, const int angle2)
{
	int d, r1 = MIN(width1, height1) / 2, r2 = MIN(width2, height2) / 2;

	/*double ang, x, y; */

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	}
	if (r1 < 0)
		r1 = -3;
	d = MAX(2 * (r1 + 3), 1);
	XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
	d = MAX(2 * (r2 - 1), 3);
	XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
#if 0
	ang = RADIANS((double) angle1 / MULT);
	x = cos(ang);
	y = sin(ang);
	XDrawLine(display, drawable, gc,
		(int) ((double) r1 * x) + xo, (int) ((double) r1 * y) + yo,
		(int) ((double) r2 * x) + xo, (int) ((double) r2 * y) + yo);
	ang = RADIANS((double) angle2 / MULT);
	x = cos(ang);
	y = sin(ang);
	XDrawLine(display, drawable, gc,
		(int) ((double) r1 * x) + xo, (int) ((double) r1 * y) + yo,
		(int) ((double) r2 * x) + xo, (int) ((double) r2 * y) + yo);
#endif
}

#if 0
static void
XFillSector(Display *display, Drawable drawable, GC gc,
		const int xo, const int yo,
		const int width1, const int height1,
		const int width2, const int height2,
		const int angle1, const int angle2)
{
	int d, r1 = MIN(width1, height1) / 2, r2 = MIN(width2, height2) / 2;

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	}
	if (r1 < 0)
		r1 = -3;
	for (d = 2 * (r1 + 3); d < 2 * (r2 - 1); d++)
		XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
			angle1, angle2);
}
#endif

static void
drawSect(const Mball2DWidget w, GC pieceGC, GC borderGC,
		const int r, const int wedge,
		const int startx, const int starty,
		const int lengthx, const int lengthy)
{
	int dx, dy, diam = 2 * w->mball.dr;
	double u, v;

	offsetSect(w, wedge, &dx, &dy);
	if ((w->mball.bands & 1) == 1) {
		if (r == w->mball.bands / 2) {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = 0.5;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / (w->mball.bands + 1);
			}
			XFillSector(XtDisplay(w), XtWindow(w), pieceGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
			XDrawSector(XtDisplay(w), XtWindow(w), borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		} else {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / w->mball.bands;
			}
			XFillSector(XtDisplay(w), XtWindow(w), pieceGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
			XDrawSector(XtDisplay(w), XtWindow(w), borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		}
	} else {
		if (w->mball.perspective) {
			u = sin(M_PI * r / w->mball.bands) / 2.0;
			v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
		} else {
			u = (double) r / w->mball.bands;
			v = (double) (r + 1) / w->mball.bands;
		}
		XFillSector(XtDisplay(w), XtWindow(w), pieceGC,
			startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
			(int) (u * 2 * lengthx - diam),
			(int) (u * 2 * lengthy - diam),
			(int) (v * 2 * lengthx - diam),
			(int) (v * 2 * lengthy - diam),
			CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
			-FULL_CIRCLE / w->mball.wedges);
		XDrawSector(XtDisplay(w), XtWindow(w), borderGC,
			startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
			(int) (u * 2 * lengthx - diam),
			(int) (u * 2 * lengthy - diam),
			(int) (v * 2 * lengthx - diam),
			(int) (v * 2 * lengthy - diam),
			CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
			-FULL_CIRCLE / w->mball.wedges);
	}
}

void
drawSector2D(const Mball2DWidget w, const int sector, const int offset)
{
	int wedge = sector % w->mball.wedges;
	int band = sector / w->mball.wedges;
	GC pieceGC, borderGC;
	int startx, starty, lengthx, lengthy, i, l;

	startx = 1 + w->mball.puzzleOffset.x;
	starty = 1 + w->mball.puzzleOffset.y;
	lengthx = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.x;
	lengthy = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.y;
	if (offset) {
		borderGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
		if (w->mball.mono) {
			pieceGC = w->mball.inverseGC;
		} else {
			pieceGC = w->mball.borderGC;
		}
	} else {
		pieceGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
		borderGC = w->mball.borderGC;
	}
	/* if the number of bands is odd
	 * the band can straddle both hemispheres */
	if (band < (w->mball.bands + 1) / 2)
		drawSect(w, pieceGC, borderGC, band, wedge,
			startx, starty, lengthx - startx, lengthy - starty);
	if (band + 1 > w->mball.bands / 2) {
		if (w->mball.vertical)
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band,
				(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
				startx, lengthy + 3, lengthx - startx, lengthy - starty);
		else
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band,
				w->mball.wedges - 1 - wedge,
				lengthx + 3, starty, lengthx - startx, lengthy - starty);
	}
	if (w->mball.mono) {
		int letterX, letterY;
		char buf[66];

		if (offset) {
			borderGC = w->mball.borderGC;
		} else {
			borderGC = w->mball.inverseGC;
		}
		if (band < (w->mball.bands + 1) / 2) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			if (w->mball.orient && !w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, False);
				buf[last] = w->mball.wedgeName[l - 1][0];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				buf[0] = w->mball.wedgeName[w->mball.mballLoc[wedge][band].wedge][0];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 3 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			if (w->mball.orient && w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, False);
				buf[last] = w->mball.wedgeName[l - 1][0];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				buf[0] = w->mball.wedgeName[w->mball.mballLoc[wedge][band].wedge][0];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	} else if (w->mball.orient) {
		int letterX, letterY;
		char buf[65];

		if (band < (w->mball.bands + 1) / 2 &&
				!w->mball.mballLoc[wedge][band].direction) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, True);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2 &&
				w->mball.mballLoc[wedge][band].direction) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 3 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, True);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	}
}
#endif

void
drawWedge2D(Mball2DWidget w, int wedge)
{
	int band;

	for (band = 0; band < w->mball.bands; band++)
		drawSector2D(w, wedge + w->mball.wedges * band, FALSE);
}

static void
drawRadar(const Mball2DWidget w, GC gc, int thick,
		const int startx, const int starty,
		const int lengthx, const int lengthy)
{
	int b, i, stx, sty, lenx, leny;
	double angle, increment, u;

	DRAWARC(w, dr, gc, thick, startx, starty,
		lengthx, lengthy, 0, FULL_CIRCLE);
	if ((w->mball.bands & 1) == 1) {
		stx = startx - lengthx / (2 * w->mball.bands) +
			(w->mball.bands / 2 + 1) * lengthx / w->mball.bands;
		sty = starty - lengthy / (2 * w->mball.bands) +
			(w->mball.bands / 2 + 1) * lengthy / w->mball.bands;
		for (b = 1; b < w->mball.bands / 2 + 1; b++) {
			if (w->mball.perspective) {
				u = sin(M_PI * b / w->mball.bands) / 2.0;
			} else {
				u = (double) b / w->mball.bands;
			}
			DRAWARC(w, dr, gc, thick,
				(int) (stx - lengthx * u),
				(int) (sty - lengthy * u),
				(int) (2 * lengthx * u),
				(int) (2 * lengthy * u),
				0, FULL_CIRCLE);
		}
	} else {
		stx = startx + (w->mball.bands / 2) * lengthx / w->mball.bands;
		sty = starty + (w->mball.bands / 2) * lengthy / w->mball.bands;
		for (b = 1; b < w->mball.bands / 2; b++) {
			if (w->mball.perspective) {
				u = sin(M_PI * b / w->mball.bands) / 2.0;
			} else {
				u = (double) b / w->mball.bands;
			}
			DRAWARC(w, dr, gc, thick,
				(int) (stx - lengthx * u),
				(int) (sty - lengthy * u),
				(int) (2 * lengthx * u),
				(int) (2 * lengthy * u),
				0, FULL_CIRCLE);
		}
	}
	increment = RADIANS(NUM_DEGREES) / w->mball.wedges;
	angle = RADIANS(RT_ANG);
	stx = startx + lengthx / 2;
	sty = starty + lengthy / 2;
	lenx = lengthx;
	leny = lengthy;
	for (i = 0; i < w->mball.wedges; i++) {
		DRAWLINE(w, dr, gc,
			stx, sty,
			stx + (int) (lenx * cos(angle) / 2.0),
			sty + (int) (leny * sin(angle) / 2.0));
		angle += increment;
	}
}

void
eraseFrame2D(const Mball2DWidget w)
{
	FILLRECTANGLE(w, dr, w->mball.inverseGC,
		0, 0, w->core.width, w->core.height);
}

void
drawFrame2D(Mball2DWidget w, Boolean focus)
{
	int startx, starty, lengthx, lengthy;
	GC gc = (focus) ? w->mball.frameGC : w->mball.borderGC;

	startx = 1 + w->mball.puzzleOffset.x;
	starty = 1 + w->mball.puzzleOffset.y;
	lengthx = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.x;
	lengthy = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.y;
	drawRadar(w, gc, 0, startx, starty, lengthx - startx, lengthy - starty);
	if (w->mball.vertical) {
		DRAWLINE(w, dr, w->mball.frameGC, 0, lengthy + 1,
			w->core.width, lengthy + 1);
		drawRadar(w, gc, 0, startx, lengthy + 3,
			lengthx - startx, lengthy - starty);
	} else {
		DRAWLINE(w, dr, w->mball.frameGC, lengthx + 1, 0,
			lengthx + 1, w->core.height);
		drawRadar(w, gc, 0, lengthx + 3, starty,
			lengthx - startx, lengthy - starty);
	}
}

static void
resizePieces(Mball2DWidget w)
{
	w->mball.mballLength = w->mball.wedgeLength / (2 * w->mball.wedges) -
		w->mball.delta - 1;
	w->mball.letterOffset.x = -2;
	w->mball.letterOffset.y = 4;
	w->mball.dr = w->mball.wedges;
}

Boolean
positionToSector2D(Mball2DWidget w, int x, int y,
		int *sector, int *view)
{
	double angle, radius;
	int wedge, band;
	int winX = x - w->mball.puzzleOffset.x;
	int winY = y - w->mball.puzzleOffset.y;

	if (w->mball.vertical && winY > w->mball.viewLength - 1) {
		winY -= (w->mball.viewLength - 1);
		*view = DOWN;
	} else if (!w->mball.vertical && winX > w->mball.viewLength - 1) {
		winX -= (w->mball.viewLength - 1);
		*view = DOWN;
	} else
		*view = UP;
	winX -= (w->mball.wedgeLength + 1) / 2;
	winY -= (w->mball.wedgeLength + 1) / 2;
	radius = sqrt((double) winX * winX + winY * winY);
	if (winY >= 0)
		angle = atan2((double) -winX, (double) winY) + M_PI;
	else if (winX < 0)
		angle = 2 * M_PI - atan2((double) -winX, (double) -winY);
	else
		angle = -atan2((double) -winX, (double) -winY);
	if (w->mball.perspective) {
		int i;

		band = (w->mball.bands - 1) >> 1;
		for (i = 0; i < ((w->mball.bands - 1) >> 1); i++) {
			/*if (i == w->mball.bands >> 1 && w->mball.bands % 2 == 1 &&
					radius < ((w->mball.wedgeLength + 1) >> 1)) {
				band = i;
				break;
			} else*/ if (radius < w->mball.wedgeLength * sin(M_PI *
					(i + 1) / w->mball.bands) / 2.0) {
				band = i;
				break;
			}
		}
	} else {
		band = (int) (radius * w->mball.bands / w->mball.wedgeLength);
		if (band > (w->mball.bands - 1) >> 1)
			band--;
	}
	wedge = (int) (angle * w->mball.wedges / (2.0 * M_PI));
	if (*view == DOWN) {
		if (w->mball.vertical)
			wedge = (3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges;
		else
			wedge = (w->mball.wedges - 1 - wedge) % w->mball.wedges;
		band = w->mball.bands - 1 - band;
	}
	*sector = wedge + w->mball.wedges * band;
	if (radius > w->mball.wedgeLength / 2 + w->mball.delta)
		return False;
	return True;
}

#ifndef WINVER
static
#endif
void
resizePuzzle2D(Mball2DWidget w)
{
	int tempLength;
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif

	w->mball.delta = 4;
	w->mball.vertical = (w->core.height >= w->core.width);
	if (w->mball.vertical)
		tempLength = MIN(w->core.height / 2, w->core.width);
	else
		tempLength = MIN(w->core.height, w->core.width / 2);
	w->mball.mballLength = MAX((tempLength - w->mball.delta + 1) /
				w->mball.wedges, 0);
	w->mball.wedgeLength = w->mball.wedges * w->mball.mballLength;
	w->mball.viewLength = w->mball.wedgeLength + w->mball.delta;
	w->mball.viewMiddle = w->mball.viewLength / 2;
	if (w->mball.vertical) {
		w->mball.puzzleSize.x = w->mball.viewLength - 1;
		w->mball.puzzleSize.y = 2 * w->mball.viewLength - w->mball.delta - 2;
	} else {
		w->mball.puzzleSize.x = 2 * w->mball.viewLength - w->mball.delta - 2;
		w->mball.puzzleSize.y = w->mball.viewLength - 1;
	}
	w->mball.puzzleOffset.x = ((int) w->core.width - w->mball.puzzleSize.x) / 2;
	w->mball.puzzleOffset.y = ((int) w->core.height - w->mball.puzzleSize.y) / 2;
	resizePieces(w);
}

#ifndef WINVER
static
#endif
void
sizePuzzle2D(Mball2DWidget w)
{
	resetPieces((MballWidget) w);
	resizePuzzle2D(w);
}

#ifndef WINVER
static Boolean
setValuesPuzzle2D(Widget current, Widget request, Widget renew)
{
	Mball2DWidget c = (Mball2DWidget) current, w = (Mball2DWidget) renew;
	Boolean redraw = False;

	if (w->mball.wedges != c->mball.wedges ||
			w->mball.bands != c->mball.bands) {
		sizePuzzle2D(w);
		redraw = True;
	}
	if (w->mball.mballLength != c->mball.mballLength) {
		resizePuzzle2D(w);
		redraw = True;
	}
	return (redraw);
}
#endif

#ifndef WINVER
static
#endif
void
initializePuzzle2D(
#ifdef WINVER
Mball2DWidget w
#else
Widget request, Widget renew
#endif
)
{
#ifndef WINVER
	Mball2DWidget w = (Mball2DWidget) renew;

	setAllColors((MballWidget) w);
#endif
	w->mball.dim = 2;
	resizePuzzle2D(w);
}

#ifndef WINVER
static
#endif
void
exposePuzzle2D(
#ifdef WINVER
Mball2DWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifndef WINVER
	Mball2DWidget w = (Mball2DWidget) renew;

	if (!w->core.visible)
		return;
#endif
	eraseFrame2D(w);
	drawFrame2D(w, w->mball.focus);
	drawAllWedges((MballWidget) w);
}

#ifndef WINVER
static void
movePuzzleTl(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TL,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTtl(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TTL,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTop(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TOP,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTtr(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TTR,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTr(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TR,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleLeft(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, LEFT,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleRight(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, RIGHT,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBl(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BL,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBbl(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BBL,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBottom(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BOTTOM,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBbr(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BBR,
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBr(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BR,
		(int) (event->xkey.state & ControlMask));
}
#endif
