ctrls.cc
//*************************************************************************
// MODULE : Ctrls - Class bodies code for Pb, Rb and Sys objects *
// AUTHOR : Ron Chernich *
// PURPOSE: Provides MS Windows-like GUI controls for graphic DOS mode *
// programs, specifically: *
// Check Box (Cb) *
// Push Buttons (Pb) *
// Radio Buttons (Rb) *
// System Controls (Sys) *
// HISTORY: *
// 19-JAN-93 First (MSC/C++ 7.00) version *
// 24-JAN-93 Modified to use Clipping where speed was low. *
// 02-FEB-94 ALT-F5 redraw key added to system control gadget. *
// 04-FEB-94 BIOS_GET_TICKS macro changed to Timer::GetTock func call *
//*************************************************************************
#include "ctrls.hh"
//////////////////////////////////////////////////////////////////////////
// Routine to label a Push Button, Radio Button or Check Box ..
//
// An optional ampersand in the string is used as an escape char to place an
// underline under the following character (which is the accelerator). Labels
// longer than 128 characters will be truncated (character-wise). All labels
// are clipped (horizontally and vertically) into the passed bounding
// rectangle. The text justification relative to x1/x2 is specified in
// . You can fool this routine in several ways, but it was designed
// to be fast, not bullet proof.
//
static void CtrlTxtLab (char *st, rect &r, UINT16 uMode, UINT16 nColor)
{
#define MAXLEN 128
INT16 x, y, dx, dy, xln;
char *cp, str[MAXLEN], st1[2];
if ((dx = MAX(0, r.lr.x-r.ul.x)) && (dy = MAX(0, r.lr.y-r.ul.y))) {
xln = -1;
y = r.ul.y + ((dy - GfxTextHeight()) >> 1) + 1;
strncpy(str, st, MAXLEN-1);
str[MIN(strlen(st),MAXLEN-1)] = '\0';
if (cp = strchr(str, '&')) {
*cp = '\0';
xln = GfxTextExtent(str);
strcpy(cp, cp+1);
st1[0] = *cp, st1[1] = '\0';
}
x = r.ul.x + 1;
if (uMode == CTLS_Centre)
x += (dx - GfxTextExtent(str)) >> 1;
if (uMode == CTLS_RJustify)
x += (dx - GfxTextExtent(str));
GfxTextColor(nColor);
GfxSetClip(r.ul.x, r.ul.y, r.lr.x, r.lr.y);
Mickey.AutoPointer(r.ul.x, r.ul.y, r.lr.x, r.lr.y);
GfxText(x, y, str, GFX_Transparent);
if (xln >= 0) {
x += xln;
y += (GfxTextHeight() - GFX_UlnOffset);
GfxMoveTo(x, y), GfxLineTo(x+GfxTextExtent(st1), y);
}
Mickey.ShowPointer();
GfxClrClip();
}
}
//////////////
// A (horrible) time consumer - used to ensure hardware independant
// graphic button activation simulation..
//
static void CtrlDelay (UINT32 lTks)
{
UINT32 lLim, lNow;
lNow = Clock.GetTocks();
lLim = lNow + lTks;
while (lLim > lNow)
lNow = Clock.GetTocks();
}
//////////////////////////////////////////////////////////////////////////
// Perform initial rendering of passed push button
//
void PbCtrl::Render (btn *p)
{
INT16 x1, y1, x2, y2;
x1 = p->r.ul.x + 1;
y1 = p->r.ul.y + 1;
x2 = p->r.lr.x - 1;
y2 = p->r.lr.y - 1;
GfxRect(x1, y1, x2, y2, GFX_Fill, _White);
GfxTextColor(_DarkGrey);
GfxMoveTo(x1, y2), GfxLineTo(x2, y2), GfxLineTo(x2, y1);
GfxMoveTo(x1, y2-1), GfxLineTo(x2-1, y2-1), GfxLineTo(x2-1, y1);
GfxTextColor(_BrightWhite);
GfxMoveTo(x1, y2), GfxLineTo(x1, y1), GfxLineTo(x2, y1);
GfxMoveTo(x1+1, y2-1), GfxLineTo(x1+1, y1+1), GfxLineTo(x2-1, y1+1);
GfxTextColor(_Black);
GfxMoveTo(x1, y1-1), GfxLineTo(x2, y1-1);
GfxMoveTo(x1-1, y1), GfxLineTo(x1-1, y2);
GfxMoveTo(x1, y2+1), GfxLineTo(x2, y2+1);
GfxMoveTo(x2+1, y1), GfxLineTo(x2+1, y2);
if (p->pst) {
rect rr = p->r;
rr.ul.y += 2, rr.lr.y -= 2, rr.ul.x += 4, rr.lr.x -= 4;
CtrlTxtLab(p->pst->StrGet(), rr, CTLS_Centre);
}
}
/////////////
// Toggle button status and redraw its graphic state accordingly.
//
void PbCtrl::Toggle (btn *p)
{
PIMBUF pImage;
INT16 x1, y1, x2, y2;
x1 = p->r.ul.x + 1, y1 = p->r.ul.y + 1;
x2 = p->r.lr.x - 1, y2 = p->r.lr.y - 1;
p->status = (p->status & 0xfffe) | ((p->status & 0x01) ^ 0x01);
if (p->status & 0x01) {
if (pImage = GfxGetImage(x1+2, y1+2, x2-2, y2-2)) {
GfxPutImage(x1+4, y1+4, pImage);
GfxFreeImage(pImage);
}
GfxTextColor(_DarkGrey);
GfxMoveTo(x1, y2), GfxLineTo(x1, y1), GfxLineTo(x2, y1);
GfxTextColor(_White);
GfxMoveTo(x1+1, y2), GfxLineTo(x1+1, y1+1), GfxLineTo(x2, y1+1);
GfxMoveTo(x1+2, y2), GfxLineTo(x1+2, y1+2), GfxLineTo(x2, y1+2);
GfxMoveTo(x1+3, y2), GfxLineTo(x1+3, y1+3), GfxLineTo(x2, y1+3);
}
else {
if (pImage = GfxGetImage(x1+4, y1+4, x2, y2)) {
GfxPutImage(x1+2, y1+2, pImage);
GfxFreeImage(pImage);
}
GfxTextColor(_DarkGrey);
GfxMoveTo(x1+1, y2), GfxLineTo(x2, y2), GfxLineTo(x2, y1-1);
GfxMoveTo(x1+2, y2-1), GfxLineTo(x2-1, y2-1), GfxLineTo(x2-1, y1-1);
GfxTextColor(_BrightWhite);
GfxMoveTo(x1, y2), GfxLineTo(x1, y1), GfxLineTo(x2, y1);
GfxMoveTo(x1+1, y2-1), GfxLineTo(x1+1, y1+1), GfxLineTo(x2-1, y1+1);
}
}
/////////////
// Create a new push button as defined by the passed params and draw it.
// The four coords define the upper-left and lower right limits of the
// button graphic (we assume the deltas are positive!).
// RETURNS: TRUE .. button added to list
// FALSE .. unable to create button
//
BOOL PbCtrl::Create (UINT16 n, char *st,
INT16 x1, INT16 y1, INT16 x2, INT16 y2)
{
btn *pb;
BOOL bOk;
rect r(point(x1, y1), point(x1+x2, y1+y2));
if (bOk = BtnNew(n, st, r)) {
pb = (btn*)DblGetTail();
Render(pb);
}
return bOk;
}
///////////////
// Remove button with passed ID from the list (if it exists)
// RETURNS: TRUE .. button destroyed
// FALSE .. did not exist!
//
BOOL PbCtrl::Destroy (UINT16 n)
{
BOOL bRes = BtnFind(n);
if (bRes)
BtnKill(n);
return bRes;
}
//////////////////////
// Whack through the list to see if any button corresponds to the passed
// ALT-key scan code. If so, graphiically "click" it and return its ID.
// RETURNS: ZERO if code unknown, or ID of button pressed.
//
UINT16 PbCtrl::Scan (char ch)
{
if (BtnHit(ch)) {
Mickey.AutoPointer(pb->r.ul.x, pb->r.ul.y, pb->r.lr.x, pb->r.lr.y);
Toggle(pb);
CtrlDelay();
Toggle(pb);
Mickey.ShowPointer();
return pb->Id;
}
return 0;
}
///////////////
// Same as above, but using a point as the event trigger..
// RETURNS: ZERO if code unknown, or ID of Push button clicked..
//
UINT16 PbCtrl::Scan (point& Pt)
{
if (BtnHit(Pt)) {
Mickey.HidePointer();
Toggle(pb);
CtrlDelay();
Toggle(pb);
Mickey.ShowPointer();
return pb->Id;
}
return 0;
}
///////////////
// Repaint all defined push buttons
//
void PbCtrl::Refresh (void)
{
btn *pb;
pb = (btn*)DblGetHead();
while (pb) {
Render(pb);
pb = (btn*)DblGetNext();
}
}
//////////////////////////////////////////////////////////////////////////
// Draw and optionally label a Radio Button.
//
void RbCtrl::Render (btn *p)
{
p->status = (p->status & 0xfffe) | ((p->status & 0x01) ^ 0x01);
Toggle(p);
if (p->pst) {
rect rr = p->r;
rr.ul.x += (RB_Radius << 1) + GfxTextExtent(" ");
CtrlTxtLab(p->pst->StrGet(), rr, CTLS_LJustify);
}
}
/////////////////
// This constructor could have been in the class definition, but a little
// explaination is in order.. Every "group" (instance) of Radio Buttons
// must act in a mutually exclusive way (only one ON at any one time).
// When an instance is created, a default param sets the state to OFF, but
// this can be over-ridden. The boolean is cleared after
// the first button is created in the ON state, preventing a (dumb) user
// from creating an impossible situation with further ON buttons.
//
RbCtrl::RbCtrl ()
{
bNoDefault = TRUE;
}
/////////////
// Toggle button status and redraw its graphic state accordingly.
//
void RbCtrl::Toggle (btn *p)
{
BOOL bOn;
INT16 x1, y1;
x1 = p->r.ul.x + RB_Radius;
y1 = p->r.ul.y + ((p->r.lr.y - p->r.ul.y) >> 1);
p->status = (p->status & 0xfffe) | ((p->status & 0x01) ^ 0x01);
bOn = (BOOL)(p->status & 0x01);
Mickey.AutoPointer(p->r.ul.x, p->r.ul.y,
p->r.ul.x+(RB_Radius << 1), p->r.ul.y+(RB_Radius << 1));
GfxCircle(x1, y1, RB_LedRad, GFX_Fill, (bOn ? _Red : _White));
GfxArc(x1, y1, RB_Radius, 225.0, 45.0, (bOn ? _DarkGrey : _BrightWhite));
GfxArc(x1, y1, RB_Radius, 45.0, 225.0, (bOn ? _BrightWhite : _DarkGrey));
Mickey.ShowPointer();
}
/////////////
// Create a new Radio Button as defined by the passed params and draw it.
// The coords define the centre of the actual button. A "Hot" area is
// developed from this to include the button label.
// RETURNS: TRUE .. button added to list
// FALSE .. unable to create button
//
BOOL RbCtrl::Create (UINT16 n, char *st, INT16 x, INT16 y, BOOL bDefault)
{
btn *rb;
BOOL bOk;
rect r;
r.ul.x = MAX(0, x - RB_Radius);
r.ul.y = MAX(0, y - MAX(RB_Radius, GfxTextHeight() >> 1));
r.lr.y = y + MAX(RB_Radius, GfxTextHeight() >> 1);
r.lr.x = x + GfxTextExtent(" ") + (RB_Radius << 1);
r.lr.x += GfxTextExtent(st);
if (strchr(st, '&'))
r.lr.x -= GfxTextExtent("&");
if (bOk = BtnNew(n, st, r)) {
rb = (btn*)DblGetTail();
if (bDefault)
if (bNoDefault) {
bNoDefault = FALSE;
rb->status |= 0x01;
}
Render(rb);
}
return bOk;
}
///////////////
// Remove button with passed ID from the list (if it exists)
// RETURNS: TRUE .. button destroyed
// FALSE .. did not exist!
//
BOOL RbCtrl::Destroy (UINT16 n)
{
BOOL bRes = BtnFind(n);
if (bRes)
BtnKill(n);
return bRes;
}
//////////////////////
// Wreave through the list to see if any button corresponds to the passed
// ALT-key scan code. If so, graphically set "off" all other buttons
// (there should only be one!) and set it "on", returning its ID.
// RETURNS: ZERO if code unknown, or ID of radio button set.
//
UINT16 RbCtrl::Scan (char ch)
{
if (BtnHit(ch)) {
btn *p = (btn*)DblGetHead();
while (p) {
if (p->status & 0x01) {
if (p != pb) {
Toggle(p);
break;
}
}
p = (btn*)DblGetNext();
}
if ((pb->status & 0x01) == 0)
Toggle(pb);
return pb->Id;
}
return 0;
}
///////////////
// Same as above, but using a point as the event trigger..
// RETURNS: ZERO if code unknown, or ID of radio button set.
//
UINT16 RbCtrl::Scan (point& Pt)
{
if (BtnHit(Pt)) {
btn *p = (btn*)DblGetHead();
while (p) {
if (p->status & 0x01) {
if (p != pb) {
Toggle(p);
break;
}
}
p = (btn*)DblGetNext();
}
if ((pb->status & 0x01) == 0)
Toggle(pb);
return pb->Id;
}
return 0;
}
///////////////
// Repaint all defined radio buttons
//
void RbCtrl::Refresh (void)
{
btn *pb;
pb = (btn*)DblGetHead();
while (pb) {
Render(pb);
pb = (btn*)DblGetNext();
}
}
//////////////////////////////////////////////////////////////////////////
// Draw and optionally label a Check Box.
//
void CbCtrl::Render (btn *p)
{
INT16 x, y;
x = p->r.ul.x;
y = p->r.ul.y + ((p->r.lr.y - p->r.ul.y - CB_Height) >> 1);
GfxTextColor(_DarkGrey);
GfxMoveTo(x+CB_Width, y), GfxLineTo(x, y), GfxLineTo(x, y+CB_Height);
GfxTextColor(_BrightWhite);
GfxLineTo(x+CB_Width, y+CB_Height), GfxLineTo(x+CB_Width, y+1);
p->status ^= 0x01;
Toggle(p);
if (p->pst) {
rect rr = p->r;
rr.ul.x += CB_Width + GfxTextExtent(" ");
CtrlTxtLab(p->pst->StrGet(), rr, CTLS_LJustify);
}
}
/////////////
// Toggle Check Box status and redraw its graphic state accordingly.
//
void CbCtrl::Toggle (btn *p)
{
INT16 x, y;
x = p->r.ul.x + 1;
y = p->r.ul.y + ((p->r.lr.y - p->r.ul.y - CB_Height) >> 1) + 1;
Mickey.AutoPointer(x, y, x+CB_Height, y+CB_Width);
GfxRect(x, y, x+CB_Width-2, y+CB_Height-2, GFX_Fill, _White);
p->status = (p->status & 0xfffe) | ((p->status & 0x01) ^ 0x01);
if (p->status & 0x01) {
GfxTextColor(_Black);
GfxMoveTo(x, y), GfxLineTo(x+CB_Width-2, y+CB_Height-2);
GfxMoveTo(x+CB_Width-2, y), GfxLineTo(x, y+CB_Height-2);
}
Mickey.ShowPointer();
}
/////////////
// Create a new Check Box as defined by the passed params and draw it.
// The coords define the centre of the actual Box. A "Hot" area is
// developed from this to include the control's label.
// RETURNS: TRUE .. button added to list
// FALSE .. unable to create button
//
BOOL CbCtrl::Create (UINT16 n, char *st, INT16 x, INT16 y, BOOL bSet)
{
btn *cb;
BOOL bOk;
rect r;
r.ul.x = MAX(0, x - (CB_Width >> 1));
r.ul.y = MAX(0, y - (MAX(CB_Height, GfxTextHeight()) >> 1));
r.lr.y = y + (MAX(CB_Height, GfxTextHeight()) >> 1);
r.lr.x = x + CB_Width + GfxTextExtent(" ") + GfxTextExtent(st);
if (strchr(st, '&'))
r.lr.x -= GfxTextExtent("&");
if (bOk = BtnNew(n, st, r)) {
cb = (btn*)DblGetTail();
if (bSet)
cb->status |= 0x01;
Render(cb);
}
return bOk;
}
///////////////
// Remove button with passed ID from the list (if it exists)
// RETURNS: TRUE .. button destroyed
// FALSE .. did not exist!
//
BOOL CbCtrl::Destroy (UINT16 n)
{
BOOL bRes = BtnFind(n);
if (bRes)
BtnKill(n);
return bRes;
}
//////////////////////
// Wander through the list to see if any Box corresponds to the
// passed ALT-key scan code. If so, graphically toggle its state.
// RETURNS: ZERO if code unknown, or ID of Check Box toggled.
//
UINT16 CbCtrl::Scan (char ch)
{
if (BtnHit(ch)) {
Toggle(pb);
return pb->Id;
}
return 0;
}
///////////////
// Same as above, but using a point as the event trigger..
// RETURNS: ZERO if code unknown, or ID of radio button set.
//
UINT16 CbCtrl::Scan (point& Pt)
{
if (BtnHit(Pt)) {
Toggle(pb);
return pb->Id;
}
return 0;
}
///////////////////
// Routine to return the state (on/off) of check box with the passed ID
// RETURNS: TRUE .. state stored in passed reference var
// FALSE .. unknown check box ID
//
BOOL CbCtrl::State (UINT16 id, BOOL &bState)
{
if (BtnFind(id)) {
bState = (BOOL)(pb->status & 0x01);
return TRUE;
}
return FALSE;
}
///////////////
// Repaint all defined check boxes
//
void CbCtrl::Refresh (void)
{
btn *pb;
pb = (btn*)DblGetHead();
while (pb) {
Render(pb);
pb = (btn*)DblGetNext();
}
}
//////////////////////////////////////////////////////////////////////////
// Here is the system menu gadget. For a change we need a constructor..
// This will draw a DESK TOP Title area with optional close gadget and
// title text.
//
SysMenu::SysMenu (char *st, BOOL bCloseGadget)
{
RenderTB();
if (bCloseGadget) {
rect rr(0, 0, SYS_Width-1, SYS_Height-1);
if (BtnNew(ID_CLOSE, "", rr)) {
btn *sb = (btn*)DblGetTail();
sb->HotKey = ALT_F4;
RenderCG(FALSE);
}
rr.lr.x = rr.lr.y = 0;
if (BtnNew(ID_HELP, "", rr)) {
btn *sb = (btn*)DblGetTail();
sb->HotKey = F1_KEY;
}
if (BtnNew(ID_REDRAW, "", rr)) {
btn *sb = (btn*)DblGetTail();
sb->HotKey = ALT_F5;
}
}
if (st) {
stTitle = st;
rect rt(SYS_Width, 0, GFX_Xmin-1, SYS_Height-1);
CtrlTxtLab(st, rt, CTLS_Centre, _BrightWhite);
}
}
///////////////////
// Draw the Title bar
//
void SysMenu::RenderTB (void)
{
rect r(0, 0, GFX_Xmin-1, SYS_Height-1);
GfxTextColor(_Black);
GfxMoveTo(0, SYS_Height), GfxLineTo(GFX_Xmin-1, SYS_Height);
if (SYS_BarPatFgnd == SYS_BarPatBkgnd)
GfxRect(0, 0, GFX_Xmin-1, SYS_Height, GFX_Fill, SYS_BarPatBkgnd);
else {
GfxPattern(GFX_HalfTone);
GfxTextColorBg(SYS_BarPatFgnd);
GfxRect(0, 0, GFX_Xmin-1, SYS_Height, GFX_Fill, SYS_BarPatBkgnd);
GfxPattern(GFX_Solid);
}
if (stTitle.StrGetLen()) {
r.ul.x = SYS_Width;
char *psz = stTitle.StrGet();
CtrlTxtLab(psz, r, CTLS_Centre, _BrightWhite);
}
}
///////////////////
// Draw the close gadget in one of its two states
//
void SysMenu::RenderCG (BOOL bSel)
{
UINT16 nBg = (bSel ? _DarkGrey : _White),
nBar = (bSel ? _BrightWhite : _Black),
nShad = (bSel ? _White : _DarkGrey);
Mickey.AutoPointer(0, 0, SYS_Width, SYS_Height);
GfxRect(0, 0, SYS_Width-1, SYS_Height-1, GFX_Fill, nBg);
GfxRect(4, 7, 13, 10, GFX_Fill, _BrightWhite);
GfxRect(3, 7, 14, 10, GFX_Frame, nBar);
GfxTextColor(nShad);
GfxMoveTo(5, 11), GfxLineTo(15, 11), GfxLineTo(15, 9);
GfxTextColor(_Black);
GfxMoveTo(SYS_Width, SYS_Height), GfxLineTo(SYS_Width, 0);
Mickey.ShowPointer();
}
//////////////////////
// Walk through the system menu gadget list to see if anything corresponds
// to the passed Hot Key code. Possibilities are system gadgets (Iconize,
// Maximise/Minimise and Close) or a user defined Pop-up Menu. Some, you
// may notice, are not yet implemented (NYI).
// RETURNS: ZERO if code unknown, or ID of item selected.
//
UINT16 SysMenu::Scan (char ch)
{
if (BtnHit(ch)) {
if (pb->Id == ALT_F4) {
RenderCG(TRUE);
CtrlDelay();
RenderCG(FALSE);
}
return pb->Id;
}
return 0;
}
///////////////
// This does the double click for the close gadget. On the click that "sets"
// the graphic to clicked, ws store the time. On the next click, we see if
// the difference is within the double-click delay and respond accordingly.
// RETURNS: ZERO if code unknown or clicks too far apart, ID otherwise..
//
UINT16 SysMenu::Scan (point& Pt)
{
static long lDblClk1, lDblClk2;
static BOOL bState = TRUE;
if (BtnHit(Pt)) {
Mickey.HidePointer();
RenderCG(bState);
Mickey.ShowPointer();
if (bState) {
lDblClk1 = Clock.GetTocks();
bState = FALSE;
return 0;
}
lDblClk2 = Clock.GetTocks();
bState = TRUE;
return (lDblClk2 - lDblClk1 <= SYS_DBLclick) ? pb->Id : 0;
}
return 0;
}
////////////////
// Change the Application title. Bit tricky.. we need to erase the area
// occupied by the previous title (if any) having regard for whether the
// title bar is a plain color or patterned. Both this and the text
// replacement (again, if any) need to adjust the location to account for
// possible presence of the close gadget.
//
void SysMenu::SysTitle (char* st, rect &r)
{
INT16 nOffset = r.ul.x + (BtnHit(ALT_F4) ? SYS_Width : 0);
if (stTitle.StrGetLen()) {
INT16 y1 = r.ul.y + ((SYS_Height - GfxTextHeight()) >> 1);
INT16 y2 = y1 + GfxTextHeight();
INT16 x2 = GfxTextExtent(stTitle.StrGet());
INT16 x1 = nOffset + ((r.lr.x - nOffset - x2) >> 1);
x2 += x1;
Mickey.AutoPointer(x1, y1, x2, y2);
if (SYS_BarPatFgnd == SYS_BarPatBkgnd)
GfxRect(x1, y1, x2, y2, GFX_Fill, SYS_BarPatBkgnd);
else {
GfxSetClip(x1, y1, x2, y2);
GfxPattern(GFX_HalfTone);
GfxTextColorBg(SYS_BarPatFgnd);
GfxRect(x1, y1, x2, y2, GFX_Fill, SYS_BarPatBkgnd);
GfxPattern(GFX_Solid);
GfxClrClip();
}
Mickey.ShowPointer();
}
stTitle = st;
if (stTitle.StrGetLen()) {
rect r(nOffset, 0, GFX_Xmin-1, SYS_Height-1);
CtrlTxtLab(st, r, CTLS_Centre, _BrightWhite);
}
}
///////////////
// Repaint all defined system gadgets, menus, etc..
//
void SysMenu::Refresh (void)
{
RenderTB();
if (BtnHit(ALT_F4))
RenderCG(FALSE);
}
/********************************** eof **********************************/