Microsoft-3D-Movie-Maker/kauai/SRC/KIDHELP.CPP

1403 lines
33 KiB
C++
Raw Normal View History

2022-05-04 01:31:19 +02:00
/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
/***************************************************************************
Author: ShonK
Project: Kauai
Reviewed:
Copyright (c) Microsoft Corporation
Code for implementing help balloons in kidspace.
***************************************************************************/
#include "kidframe.h"
ASSERTNAME
BEGIN_CMD_MAP(TXHG, TXRG)
ON_CID_GEN(cidSelIdle, pvNil, pvNil)
ON_CID_ME(cidActivateSel, pvNil, pvNil)
END_CMD_MAP_NIL()
RTCLASS(TXHD)
RTCLASS(TXHG)
RTCLASS(HBAL)
RTCLASS(HBTN)
const achar kchHelpString = '~';
/***************************************************************************
Constructor for a help text document.
***************************************************************************/
TXHD::TXHD(PRCA prca, PDOCB pdocb, ulong grfdoc) : TXHD_PAR(pdocb, grfdoc)
{
AssertPo(prca, 0);
_prca = prca;
_prca->AddRef();
_htop.cnoBalloon = cnoNil;
_htop.cnoScript = cnoNil;
_htop.ckiSnd.ctg = ctgNil;
_htop.ckiSnd.cno = cnoNil;
}
/***************************************************************************
Destructor for a help text document.
***************************************************************************/
TXHD::~TXHD(void)
{
ReleasePpo(&_prca);
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a TXHD.
***************************************************************************/
void TXHD::AssertValid(ulong grf)
{
TXHD_PAR::AssertValid(0);
AssertPo(_prca, 0);
}
/***************************************************************************
Mark memory for the TXHD.
***************************************************************************/
void TXHD::MarkMem(void)
{
AssertValid(0);
TXHD_PAR::MarkMem();
MarkMemObj(_prca);
}
#endif //DEBUG
/***************************************************************************
Static method to read a help text document from the given (pcfl, ctg, cno)
and using the given prca as the source for pictures and buttons.
***************************************************************************/
PTXHD TXHD::PtxhdReadChunk(PRCA prca, PCFL pcfl, CTG ctg, CNO cno,
PSTRG pstrg, ulong grftxhd)
{
AssertPo(prca, 0);
AssertPo(pcfl, 0);
PTXHD ptxhd;
if (pvNil == (ptxhd = NewObj TXHD(prca)) ||
!ptxhd->_FReadChunk(pcfl, ctg, cno, pstrg, grftxhd))
{
PushErc(ercHelpReadFailed);
ReleasePpo(&ptxhd);
}
AssertNilOrPo(ptxhd, fobjAssertFull);
return ptxhd;
}
/***************************************************************************
Read the given chunk into this TXRD.
***************************************************************************/
bool TXHD::_FReadChunk(PCFL pcfl, CTG ctg, CNO cno, PSTRG pstrg, ulong grftxhd)
{
AssertPo(pcfl, 0);
AssertNilOrPo(pstrg, 0);
BLCK blck;
KID kid;
HTOPF htopf;
long stid, lw;
long cp, cpMac, cpMin;
STN stn;
bool fRet = fFalse;
if (pcfl->FForest(ctg, cno))
{
CKI cki;
if (pvNil == (pcfl = pcfl->PcflReadForest(ctg, cno, fFalse)))
goto LFail;
if (!pcfl->FGetCkiCtg(ctg, 0, &cki))
goto LFail;
cno = cki.cno;
}
else
pcfl->AddRef();
// The old version of HTOP didn't have the ckiSnd - accept both old and new
// versions.
htopf.htop.ckiSnd.ctg = ctgNil;
htopf.htop.ckiSnd.cno = cnoNil;
if (!pcfl->FFind(ctg, cno, &blck) || !blck.FUnpackData() ||
size(HTOPF) != blck.Cb() && offset(HTOPF, htop.ckiSnd) != blck.Cb() ||
!blck.FRead(&htopf))
{
goto LFail;
}
if (htopf.bo == kboOther)
SwapBytesBom(&htopf.htop, kbomHtop);
else if (htopf.bo != kboCur)
goto LFail;
if (!pcfl->FGetKidChidCtg(ctg, cno, 0, kctgRichText, &kid))
goto LFail;
if (!TXHD_PAR::_FReadChunk(pcfl, kid.cki.ctg, kid.cki.cno,
FPure(grftxhd & ftxhdCopyText)))
{
goto LFail;
}
if ((grftxhd & ftxhdExpandStrings) && pvNil != pstrg)
{
SetInternal();
SuspendUndo();
cpMac = CpMac();
for (cp = 0; cp < cpMac; )
{
if (_ChFetch(cp) != kchHelpString)
{
cp++;
continue;
}
cpMin = cp++;
for (stid = 0; cp < cpMac && FIn(lw = _ChFetch(cp) - '0', 0, 10); cp++)
stid = stid * 10 + lw;
if (!pstrg->FGet(stid, &stn))
Warn("string missing");
if (FReplaceRgch(stn.Psz(), stn.Cch(), cpMin, cp - cpMin, fdocNil))
{
cp = cpMin + stn.Cch();
cpMac = CpMac();
}
}
}
fRet = fTrue;
_htop = htopf.htop;
AssertThis(0);
LFail:
// Release our hold on the CFL
ReleasePpo(&pcfl);
return fRet;
}
/***************************************************************************
Do any necessary munging of the AG entry on open. Return false if
we don't recognize this argument type.
***************************************************************************/
bool TXHD::_FOpenArg(long icact, byte sprm, short bo, short osk)
{
CTG ctg;
CNO cno;
long cb;
long rglw[2];
long clw;
if (TXHD_PAR::_FOpenArg(icact, sprm, bo, osk))
return fTrue;
cb = _pagcact->Cb(icact);
switch (sprm)
{
case sprmGroup:
if (cb < size(byte) + size(CNO))
return fFalse;
if (bo == kboOther)
{
_pagcact->GetRgb(icact, size(byte), size(CNO), &cno);
SwapBytesRglw(&cno, 1);
_pagcact->PutRgb(icact, size(byte), size(CNO), &cno);
}
break;
case sprmObject:
if (cb < size(CTG))
return fFalse;
_pagcact->GetRgb(icact, 0, size(CTG), &ctg);
if (bo == kboOther)
{
SwapBytesRglw(&ctg, 1);
_pagcact->PutRgb(icact, 0, size(CTG), &ctg);
}
cb -= size(CTG);
switch (ctg)
{
case kctgMbmp:
case kctgEditControl:
clw = 1;
goto LSwapBytes;
case kctgGokd:
clw = 2;
LSwapBytes:
AssertIn(clw, 1, CvFromRgv(rglw) + 1);
if (cb < clw * size(long))
return fFalse;
if (bo == kboOther)
{
_pagcact->GetRgb(icact, size(CTG), clw * size(long), rglw);
SwapBytesRglw(rglw, clw);
_pagcact->PutRgb(icact, size(CTG), clw * size(long), rglw);
}
break;
default:
return fFalse;
}
break;
default:
return fFalse;
}
return fTrue;
}
/***************************************************************************
Save a help topic to the given chunky file. Fill in *pcki with where
we put the root chunk.
***************************************************************************/
bool TXHD::FSaveToChunk(PCFL pcfl, CKI *pcki, bool fRedirectText)
{
AssertThis(0);
AssertPo(pcfl, 0);
AssertVarMem(pcki);
BLCK blck;
CKI cki;
HTOPF htopf;
pcki->ctg = kctgHelpTopic;
htopf.bo = kboCur;
htopf.osk = koskCur;
htopf.htop = _htop;
if (!pcfl->FAdd(size(HTOPF), pcki->ctg, &pcki->cno, &blck))
{
PushErc(ercHelpSaveFailed);
return fFalse;
}
if (!blck.FWrite(&htopf))
goto LFail;
if (!TXHD_PAR::FSaveToChunk(pcfl, &cki, fRedirectText))
goto LFail;
//add the text chunk and write it
if (!pcfl->FAdoptChild(pcki->ctg, pcki->cno, cki.ctg, cki.cno))
{
pcfl->Delete(cki.ctg, cki.cno);
LFail:
pcfl->Delete(pcki->ctg, pcki->cno);
PushErc(ercHelpSaveFailed);
return fFalse;
}
return fTrue;
}
/***************************************************************************
Get the bounding rectangle for the given object.
***************************************************************************/
bool TXHD::_FGetObjectRc(long icact, byte sprm, PGNV pgnv, PCHP pchp, RC *prc)
{
AssertThis(0);
AssertIn(icact, 0, _pagcact->IvMac());
AssertIn(sprm, sprmMinObj, 0x100);
AssertPo(pgnv, 0);
AssertVarMem(pchp);
AssertVarMem(prc);
long cb;
PMBMP pmbmp;
PCRF pcrf;
KID kid;
long rglw[2];
if (sprmObject != sprm)
return fFalse;
Assert(size(CTG) == size(long), 0);
cb = _pagcact->Cb(icact);
if (cb < size(rglw))
return fFalse;
_pagcact->GetRgb(icact, 0, size(rglw), rglw);
switch ((CTG)rglw[0])
{
case kctgMbmp:
pmbmp = (PMBMP)_prca->PbacoFetch(rglw[0], rglw[1], MBMP::FReadMbmp);
goto LHaveMbmp;
case kctgGokd:
pcrf = _prca->PcrfFindChunk(rglw[0], rglw[1]);
if (pvNil == pcrf)
return fFalse;
if (!pcrf->Pcfl()->FGetKidChidCtg(rglw[0], rglw[1],
0x10000, kctgMbmp, &kid))
{
return fFalse;
}
pmbmp = (PMBMP)pcrf->PbacoFetch(kid.cki.ctg, kid.cki.cno, MBMP::FReadMbmp);
LHaveMbmp:
if (pvNil == pmbmp)
return fFalse;
pmbmp->GetRc(prc);
ReleasePpo(&pmbmp);
prc->Offset(-prc->xpLeft, -prc->ypBottom);
return fTrue;
case kctgEditControl:
pgnv->SetFont(pchp->onn, pchp->grfont, pchp->dypFont, tahLeft, tavBaseline);
pgnv->GetRcFromRgch(prc, pvNil, 0);
prc->Inset(0, -1);
prc->xpLeft = 0;
prc->xpRight = rglw[1];
return fTrue;
default:
return fFalse;
}
return fTrue;
}
/***************************************************************************
Draw the given object.
***************************************************************************/
bool TXHD::_FDrawObject(long icact, byte sprm, PGNV pgnv, long *pxp, long yp,
PCHP pchp, RC *prcClip)
{
AssertIn(icact, 0, _pagcact->IvMac());
Assert(sprm >= sprmObject, 0);
AssertPo(pgnv, 0);
AssertVarMem(pxp);
AssertVarMem(pchp);
AssertVarMem(prcClip);
long cb;
RC rc;
PMBMP pmbmp;
PCRF pcrf;
KID kid;
long rglw[2];
bool fDrawMbmp = fTrue;
if (sprmObject != sprm)
return fFalse;
cb = _pagcact->Cb(icact);
if (cb < size(rglw))
return fFalse;
_pagcact->GetRgb(icact, 0, size(rglw), rglw);
switch ((CTG)rglw[0])
{
case kctgMbmp:
pmbmp = (PMBMP)_prca->PbacoFetch(rglw[0], rglw[1], MBMP::FReadMbmp);
goto LHaveMbmp;
case kctgGokd:
fDrawMbmp = !_fHideButtons;
pcrf = _prca->PcrfFindChunk(rglw[0], rglw[1]);
if (pvNil == pcrf)
return fFalse;
if (!pcrf->Pcfl()->FGetKidChidCtg(rglw[0], rglw[1],
ChidFromSnoDchid(ksnoInit, 0), kctgMbmp, &kid))
{
return fFalse;
}
pmbmp = (PMBMP)pcrf->PbacoFetch(kid.cki.ctg, kid.cki.cno, MBMP::FReadMbmp);
LHaveMbmp:
if (pvNil == pmbmp)
return fFalse;
pmbmp->GetRc(&rc);
rc.Offset(*pxp - rc.xpLeft, yp - rc.ypBottom);
if (kacrClear != pchp->acrBack)
pgnv->FillRc(&rc, pchp->acrBack);
if (fDrawMbmp)
pgnv->DrawMbmp(pmbmp, &rc);
ReleasePpo(&pmbmp);
if (pchp->grfont & fontBoxed)
{
pgnv->SetPenSize(1, 1);
pgnv->FrameRcApt(&rc, &vaptGray, pchp->acrFore, kacrClear);
}
*pxp += rc.Dxp();
return fTrue;
case kctgEditControl:
pgnv->SetFont(pchp->onn, pchp->grfont, pchp->dypFont, tahLeft, tavBaseline);
pgnv->GetRcFromRgch(&rc, pvNil, 0, 0, yp);
rc.Inset(0, -1);
rc.xpLeft = *pxp;
rc.xpRight = rc.xpLeft + rglw[1];
*pxp = rc.xpRight;
pgnv->SetPenSize(1, 1);
pgnv->FrameRc(&rc, kacrBlack);
rc.Inset(1, 1);
pgnv->FillRc(&rc, pchp->acrBack);
return fTrue;
default:
return fFalse;
}
return fTrue;
}
/***************************************************************************
Insert a picture into the help text document.
***************************************************************************/
bool TXHD::FInsertPicture(CNO cno, void *pvExtra, long cbExtra,
long cp, long ccpDel, PCHP pchp, ulong grfdoc)
{
AssertThis(0);
AssertPvCb(pvExtra, cbExtra);
AssertIn(cp, 0, CpMac());
AssertIn(ccpDel, 0, CpMac() - cp);
AssertNilOrVarMem(pchp);
CKI cki;
void *pv = &cki;
bool fRet = fFalse;
cki.ctg = kctgMbmp;
cki.cno = cno;
if (cbExtra > 0)
{
if (!FAllocPv(&pv, size(CKI) + cbExtra, fmemNil, mprNormal))
return fFalse;
CopyPb(&cki, pv, size(CKI));
CopyPb(pvExtra, PvAddBv(pv, size(CKI)), cbExtra);
}
fRet = FInsertObject(pv, size(CKI) + cbExtra, cp, ccpDel, pchp, grfdoc);
if (pv != &cki)
FreePpv(&pv);
return fRet;
}
/***************************************************************************
Insert a new button
***************************************************************************/
bool TXHD::FInsertButton(CNO cno, CNO cnoTopic, void *pvExtra, long cbExtra,
long cp, long ccpDel, PCHP pchp, ulong grfdoc)
{
AssertThis(0);
AssertPvCb(pvExtra, cbExtra);
AssertIn(cp, 0, CpMac());
AssertIn(ccpDel, 0, CpMac() - cp);
AssertNilOrVarMem(pchp);
byte rgb[size(CKI) + size(long)];
CKI *pcki = (CKI *)rgb;
CNO *pcnoTopic = (CNO *)(pcki + 1);;
void *pv = rgb;
bool fRet = fFalse;
pcki->ctg = kctgGokd;
pcki->cno = cno;
*pcnoTopic = cnoTopic;
if (cbExtra > 0)
{
if (!FAllocPv(&pv, size(rgb) + cbExtra, fmemNil, mprNormal))
return fFalse;
CopyPb(rgb, pv, size(rgb));
CopyPb(pvExtra, PvAddBv(pv, size(rgb)), cbExtra);
}
fRet = FInsertObject(pv, size(rgb) + cbExtra, cp, ccpDel, pchp, grfdoc);
if (pv != rgb)
FreePpv(&pv);
return fRet;
}
/***************************************************************************
Group the given text into the given group. lw == 0 indicates no group.
Any non-zero number is a group.
***************************************************************************/
bool TXHD::FGroupText(long cp1, long cp2, byte bGroup, CNO cnoTopic,
PSTN pstnTopic)
{
AssertThis(0);
AssertNilOrPo(pstnTopic, 0);
AssertIn(cp1, 0, CpMac());
AssertIn(cp2, 0, CpMac());
SPVM spvm;
SortLw(&cp1, &cp2);
if (cp1 == cp2)
return fTrue;
if (!FSetUndo(cp1, cp2, cp2 - cp1))
return fFalse;
spvm.sprm = sprmGroup;
spvm.lwMask = -1;
if (bGroup == 0)
{
// means no grouping
spvm.lw = 0;
}
else
{
byte rgb[size(byte) + size(CNO) + kcbMaxDataStn];
long cb = size(byte) + size(CNO);
rgb[0] = bGroup;
CopyPb(&cnoTopic, rgb + size(byte), size(CNO));
if (pvNil != pstnTopic && pstnTopic->Cch() > 0)
{
pstnTopic->GetData(rgb + cb);
cb += pstnTopic->CbData();
}
if (!_FEnsureInAg(sprmGroup, rgb, cb, &spvm.lw))
{
CancelUndo();
return fFalse;
}
}
if (!_pglmpe->FEnsureSpace(2))
{
_ReleaseRgspvm(&spvm, 1);
CancelUndo();
return fFalse;
}
_ApplyRgspvm(cp1, cp2 - cp1, &spvm, 1);
CommitUndo();
AssertThis(0);
InvalAllDdg(cp1, cp2 - cp1, cp2 - cp1);
return fTrue;
}
/***************************************************************************
Determine if the given cp is in a grouped text range.
***************************************************************************/
bool TXHD::FGrouped(long cp, long *pcpMin, long *pcpLim, byte *pbGroup,
CNO *pcnoTopic, PSTN pstnTopic)
{
AssertThis(0);
AssertIn(cp, 0, CpMac());
AssertNilOrVarMem(pcpMin);
AssertNilOrVarMem(pcpLim);
AssertNilOrVarMem(pbGroup);
AssertNilOrVarMem(pcnoTopic);
AssertNilOrPo(pstnTopic, 0);
MPE mpe;
byte bGroup = 0;
if (!_FFindMpe(_SpcpFromSprmCp(sprmGroup, cp), &mpe, pcpLim))
{
mpe.lw = 0;
mpe.spcp = 0;
}
if (mpe.lw > 0)
{
byte *prgb;
long cb;
prgb = (byte *)_pagcact->PvLock(mpe.lw - 1, &cb);
cb -= size(byte) + size(CNO); // group number, cnoTopic
if (cb < 0)
goto LFail;
bGroup = prgb[0];
if (bGroup == 0)
{
LFail:
Bug("bad group data");
_pagcact->Unlock();
goto LNotGrouped;
}
if (pvNil != pcnoTopic)
CopyPb(prgb + size(byte), pcnoTopic, size(CNO));
if (pvNil != pstnTopic)
{
if (cb > 0)
pstnTopic->FSetData(prgb + size(byte) + size(CNO), cb);
else
pstnTopic->SetNil();
}
_pagcact->Unlock();
}
else
{
LNotGrouped:
if (pvNil != pcnoTopic)
*pcnoTopic = cnoNil;
if (pvNil != pstnTopic)
pstnTopic->SetNil();
}
if (pvNil != pbGroup)
*pbGroup = bGroup;
if (pvNil != pcpMin)
*pcpMin = _CpFromSpcp(mpe.spcp);
return bGroup != 0;
}
/***************************************************************************
Get the help topic information.
***************************************************************************/
void TXHD::GetHtop(PHTOP phtop)
{
AssertThis(0);
AssertVarMem(phtop);
*phtop = _htop;
}
/***************************************************************************
Set the topic info.
***************************************************************************/
void TXHD::SetHtop(PHTOP phtop)
{
AssertThis(0);
AssertVarMem(phtop);
_htop = *phtop;
SetDirty();
}
/***************************************************************************
Constructor for a TXHG.
***************************************************************************/
TXHG::TXHG(PWOKS pwoks, PTXHD ptxhd, PGCB pgcb) : TXRG(ptxhd, pgcb)
{
AssertBaseThis(0);
AssertPo(pwoks, 0);
_pwoks = pwoks;
}
/***************************************************************************
Create a new help topic display gob.
***************************************************************************/
PTXHG TXHG::PtxhgNew(PWOKS pwoks, PTXHD ptxhd, PGCB pgcb)
{
PTXHG ptxhg;
if (pvNil == (ptxhg = NewObj TXHG(pwoks, ptxhd, pgcb)))
return pvNil;
if (!ptxhg->_FInit())
{
ReleasePpo(&ptxhg);
return pvNil;
}
return ptxhg;
}
/***************************************************************************
Inititalize the display gob for a help balloon topic.
***************************************************************************/
bool TXHG::_FInit(void)
{
AssertBaseThis(0);
PRCA prca;
long cp, cb;
void *pv;
CKI *pcki;
long dxp;
CNO cno;
long xp, ypBase;
CNO cnoTopic;
byte bGroup;
long lwMax;
RTVN rtvn;
long hid;
CHP chp;
RC rc;
EDPAR edpar;
STN stn;
PTXHD ptxhd = Ptxhd();
if (!TXHG_PAR::_FInit())
return fFalse;
// find the max of the group numbers
lwMax = 0;
for (cp = 0; cp < ptxhd->CpMac(); )
{
ptxhd->FGrouped(cp, pvNil, &cp, &bGroup);
lwMax = LwMax((long)bGroup, lwMax);
}
// find a base hid that covers lwMax buttons
_hidBase = 0;
if (lwMax > 0)
{
_hidBase = CMH::HidUnique(lwMax) - 1;
stn = PszLit("_gidBase");
rtvn.SetFromStn(&stn);
if (!FAssignRtvm(PgobPar()->Ppglrtvm(), &rtvn, _hidBase))
return fFalse;
}
prca = Ptxhd()->Prca();
for (cp = 0; Ptxhd()->FFetchObject(cp, &cp, &pv, &cb); cp++)
{
if (pvNil == pv)
continue;
if (cb < size(CTG))
goto LContinue;
switch (*(CTG *)pv)
{
case kctgEditControl:
if (cb < size(ECOS))
goto LContinue;
dxp = ((ECOS *)pv)->dxp;
FreePpv(&pv);
// get the bounding rectangle
_GetXpYpFromCp(cp, pvNil, pvNil, &xp, &ypBase, fFalse);
_FetchChp(cp, &chp);
_pgnv->SetFont(chp.onn, chp.grfont, chp.dypFont,
tahLeft, tavBaseline);
_pgnv->GetRcFromRgch(&rc, pvNil, 0);
rc.Offset(0, ypBase + chp.dypOffset);
rc.xpLeft = xp + 1;
rc.xpRight = xp + dxp - 1;
Ptxhd()->FGrouped(cp, pvNil, pvNil, &bGroup);
if (bGroup == 0 ||
_pwoks->PcmhFromHid(hid = _hidBase + bGroup) != pvNil)
{
hid = CMH::HidUnique();
}
if (chp.acrBack == kacrClear)
chp.acrBack = kacrWhite;
edpar.Set(hid, this, fgobNil, kginMark, &rc, pvNil, chp.onn,
chp.grfont, chp.dypFont, tahLeft, tavTop,
chp.acrFore, chp.acrBack);
if (pvNil == EDSL::PedslNew(&edpar))
return fFalse;
break;
case kctgGokd:
if (cb < size(CKI) + size(CNO))
goto LContinue;
pcki = (CKI *)pv;
cno = pcki->cno;
cnoTopic = *(CNO *)(pcki + 1);
FreePpv(&pv);
_GetXpYpFromCp(cp, pvNil, pvNil, &xp, &ypBase, fFalse);
_FetchChp(cp, &chp);
Ptxhd()->FGrouped(cp, pvNil, pvNil, &bGroup,
cnoTopic == cnoNil ? &cnoTopic : pvNil);
if (bGroup == 0 ||
_pwoks->PcmhFromHid(hid = _hidBase + bGroup) != pvNil)
{
hid = CMH::HidUnique();
}
if (pvNil == HBTN::PhbtnNew(_pwoks, this, hid, cno, prca, bGroup,
cnoTopic, xp, ypBase + chp.dypOffset))
{
return fFalse;
}
break;
default:
LContinue:
FreePpv(&pv);
break;
}
}
AssertThis(0);
return fTrue;
}
/***************************************************************************
Return whether the point is over hot (marked text).
***************************************************************************/
bool TXHG::FPtIn(long xp, long yp)
{
AssertThis(0);
if (!TXHG_PAR::FPtIn(xp, yp))
return fFalse;
return FGroupFromPt(xp, yp);
}
/***************************************************************************
Track the mouse.
***************************************************************************/
bool TXHG::FCmdTrackMouse(PCMD_MOUSE pcmd)
{
AssertThis(0);
AssertVarMem(pcmd);
pcmd->grfcust = _pwoks->GrfcustAdjust(pcmd->grfcust);
if (pcmd->cid == cidMouseDown)
{
// first response to mouse down
Assert(vpcex->PgobTracking() == pvNil, "mouse already being tracked!");
if (!FGroupFromPt(pcmd->xp, pcmd->yp, &_bTrack, &_cnoTrack))
return fTrue;
vpcex->TrackMouse(this);
SetCursor(pcmd->grfcust);
_grfcust = pcmd->grfcust;
}
else
{
Assert(vpcex->PgobTracking() == this, "not tracking mouse!");
Assert(pcmd->cid == cidTrackMouse, 0);
if (!(pcmd->grfcust & fcustMouse))
{
byte bGroup;
CNO cnoTopic;
vpcex->EndMouseTracking();
if (FGroupFromPt(pcmd->xp, pcmd->yp, &bGroup, &cnoTopic) &&
bGroup == _bTrack && cnoTopic == _cnoTrack)
{
DoHit(bGroup, cnoTopic, _grfcust, hidNil);
}
}
}
return fTrue;
}
/***************************************************************************
An edit control got a bad key.
***************************************************************************/
bool TXHG::FCmdBadKey(PCMD_BADKEY pcmd)
{
AssertThis(0);
AssertVarMem(pcmd);
if (!FIn(pcmd->hid, _hidBase + 1, _hidBase + 257))
return fFalse;
pcmd->grfcust = _pwoks->GrfcustAdjust(pcmd->grfcust);
_FRunScript((byte)(pcmd->hid - _hidBase), pcmd->grfcust, pcmd->hid,
(achar)pcmd->ch);
return fTrue;
}
/***************************************************************************
Return the number of the group text that the given point is in.
***************************************************************************/
bool TXHG::FGroupFromPt(long xp, long yp, byte *pbGroup, CNO *pcnoTopic)
{
AssertThis(0);
AssertNilOrVarMem(pbGroup);
AssertNilOrVarMem(pcnoTopic);
long cp;
if (!_FGetCpFromPt(xp, yp, &cp, fFalse))
return 0;
return Ptxhd()->FGrouped(cp, pvNil, pvNil, pbGroup, pcnoTopic);
}
/***************************************************************************
A child button was hit, take action.
***************************************************************************/
void TXHG::DoHit(byte bGroup, CNO cnoTopic, ulong grfcust, long hidHit)
{
AssertThis(0);
long lwRet = 0;
// run the script
if (!_FRunScript(bGroup, grfcust, hidHit, chNil, cnoTopic, &lwRet))
return;
if (cnoNil != cnoTopic && !lwRet)
_pwoks->PhbalNew(PgobPar()->PgobPar(), Ptxhd()->Prca(), cnoTopic);
}
/***************************************************************************
Run the script. Returns false iff the TXHG doesn't exist after
running the script.
***************************************************************************/
bool TXHG::_FRunScript(byte bGroup, ulong grfcust, long hidHit, achar ch,
CNO cnoTopic, long *plwRet)
{
AssertThis(0);
AssertNilOrVarMem(plwRet);
PSCPT pscpt;
PSCEG psceg;
HTOP htop;
bool fRet = fTrue;
PTXHD ptxhd = Ptxhd();
PRCA prca = ptxhd->Prca();
if (pvNil != plwRet)
*plwRet = 0;
ptxhd->GetHtop(&htop);
if (cnoNil == htop.cnoScript)
return fTrue;
pscpt = (PSCPT)prca->PbacoFetch(kctgScript, htop.cnoScript,
SCPT::FReadScript);
if (pvNil != pscpt && pvNil != (psceg = _pwoks->PscegNew(prca, this)))
{
AssertPo(pscpt, 0);
AssertPo(psceg, 0);
PWOKS pwoks = _pwoks;
long grid = Grid();
long rglw[5];
rglw[0] = (long)bGroup;
rglw[1] = grfcust;
rglw[2] = hidHit;
rglw[3] = (long)(byte)ch;
rglw[4] = cnoTopic;
//be careful not to use TXHG variables here in case the TXHG is
//freed while the script is running.
if (!psceg->FRunScript(pscpt, rglw, 5, plwRet) && pvNil != plwRet)
*plwRet = 0;
ReleasePpo(&psceg);
fRet = (this == pwoks->PgobFromGrid(grid));
}
ReleasePpo(&pscpt);
return fRet;
}
/***************************************************************************
This handles cidMouseMove.
***************************************************************************/
bool TXHG::FCmdMouseMove(PCMD_MOUSE pcmd)
{
AssertThis(0);
AssertVarMem(pcmd);
ulong grfcust = _pwoks->GrfcustAdjust(pcmd->grfcust);
if (FGroupFromPt(pcmd->xp, pcmd->yp))
grfcust |= fcustHotText;
SetCursor(grfcust);
return fTrue;
}
/***************************************************************************
Set the cursor for this TXHG and the given cursor state.
***************************************************************************/
void TXHG::SetCursor(ulong grfcust)
{
AssertThis(0);
PGOB pgob;
for (pgob = this; ; )
{
pgob = pgob->PgobPar();
if (pvNil == pgob)
{
vpappb->SetCurs(pvNil);
break;
}
if (pgob->FIs(kclsGOK))
{
((PGOK)pgob)->SetCursor(grfcust | fcustChildGok);
break;
}
}
}
/***************************************************************************
Create a new help topic balloon based on the given topic number.
***************************************************************************/
PHBAL HBAL::PhbalCreate(PWOKS pwoks, PGOB pgobPar, PRCA prca,
CNO cnoTopic, PHTOP phtop)
{
AssertPo(pwoks, 0);
AssertPo(pgobPar, 0);
AssertPo(prca, 0);
AssertNilOrVarMem(phtop);
PCRF pcrf;
PTXHD ptxhd;
PHBAL phbal;
pcrf = prca->PcrfFindChunk(kctgHelpTopic, cnoTopic);
if (pvNil == pcrf)
return pvNil;
ptxhd = TXHD::PtxhdReadChunk(prca, pcrf->Pcfl(), kctgHelpTopic, cnoTopic,
pwoks->Pstrg());
if (pvNil == ptxhd)
return pvNil;
ptxhd->HideButtons();
phbal = PhbalNew(pwoks, pgobPar, prca, ptxhd, phtop);
ReleasePpo(&ptxhd);
return phbal;
}
/***************************************************************************
Static method to create a new help balloon based on the given help
topic document and htop.
***************************************************************************/
PHBAL HBAL::PhbalNew(PWOKS pwoks, PGOB pgobPar, PRCA prca,
PTXHD ptxhd, PHTOP phtop)
{
AssertPo(pwoks, 0);
AssertPo(pgobPar, 0);
AssertPo(ptxhd, 0);
AssertPo(prca, 0);
AssertNilOrVarMem(phtop);
HTOP htop;
GCB gcb;
PHBAL phbal;
long grid;
ptxhd->GetHtop(&htop);
if (pvNil != phtop)
{
// merge the given htop with the topic's htop.
if (cnoNil != phtop->cnoBalloon)
htop.cnoBalloon = phtop->cnoBalloon;
if (hidNil != phtop->hidThis)
htop.hidThis = phtop->hidThis;
if (hidNil != phtop->hidTarget)
htop.hidTarget = phtop->hidTarget;
if (cnoNil != phtop->cnoScript)
htop.cnoScript = phtop->cnoScript;
htop.dxp += phtop->dxp;
htop.dyp += phtop->dyp;
if (cnoNil != phtop->ckiSnd.cno && ctgNil != phtop->ckiSnd.ctg)
htop.ckiSnd = phtop->ckiSnd;
}
if (htop.hidThis == hidNil)
htop.hidThis = CMH::HidUnique();
else if (pvNil != (phbal = (PHBAL)pwoks->PcmhFromHid(htop.hidThis)))
{
if (!phbal->FIs(kclsHBAL))
{
Bug("command handler with this ID already exists");
return pvNil;
}
AssertPo(phbal, 0);
#ifdef REVIEW //shonk: this makes little sense and is bug-prone
if (htop.cnoBalloon == phbal->_pgokd->Cno() && prca == phbal->_prca)
{
// same hid, same GOKD, same prca, so just change the topic
if (!phbal->FSetTopic(ptxhd, &htop, prca))
return pvNil;
return phbal;
}
#endif //REVIEW
// free the balloon and create the new one.
ReleasePpo(&phbal);
}
gcb.Set(htop.hidThis, pgobPar, fgobNil, kginMark);
if (pvNil == (phbal = NewObj HBAL(&gcb)))
return pvNil;
grid = phbal->Grid();
if (!phbal->_FInit(pwoks, ptxhd, &htop, prca))
{
ReleasePpo(&phbal);
return pvNil;
}
if (!phbal->_FEnterState(ksnoInit))
{
Warn("HBAL immediately destroyed!");
return pvNil;
}
// initialize the topic
phbal->_ptxhg->DoHit(0, cnoNil, fcustNil, hidNil);
if (phbal != pwoks->PgobFromGrid(grid))
{
Warn("HBAL immediately destroyed 2!");
return pvNil;
}
AssertPo(phbal, 0);
return phbal;
}
/***************************************************************************
Constructor for a help balloon.
***************************************************************************/
HBAL::HBAL(GCB *pgcb) : HBAL_PAR(pgcb)
{
}
/***************************************************************************
Initialize the help balloon.
***************************************************************************/
bool HBAL::_FInit(PWOKS pwoks, PTXHD ptxhd, HTOP *phtop, PRCA prca)
{
AssertBaseThis(0);
AssertPo(ptxhd, 0);
AssertVarMem(phtop);
AssertPo(prca, 0);
if (!HBAL_PAR::_FInit(pwoks, phtop->cnoBalloon, prca))
return fFalse;
return _FSetTopic(ptxhd, phtop, prca);
}
/***************************************************************************
Set the topic for this balloon. Returns false if setting the topic
fails or if the balloon is instantly killed by a script.
***************************************************************************/
bool HBAL::FSetTopic(PTXHD ptxhd, PHTOP phtop, PRCA prca)
{
AssertThis(0);
AssertPo(ptxhd, 0);
AssertVarMem(phtop);
AssertPo(prca, 0);
if (!_FSetTopic(ptxhd, phtop, prca))
return fFalse;
return _FEnterState(ksnoInit);
}
/***************************************************************************
Set the topic in the help balloon. Don't enter the initial state.
***************************************************************************/
bool HBAL::_FSetTopic(PTXHD ptxhd, PHTOP phtop, PRCA prca)
{
AssertBaseThis(0);
AssertPo(ptxhd, 0);
AssertVarMem(phtop);
AssertPo(prca, 0);
PGOB pgob;
GCB gcb;
PT pt, ptReg;
STN stn;
RTVN rtvn;
PTXHG ptxhgSave = _ptxhg;
// create the topic DDG.
gcb.Set(CMH::HidUnique(), this, fgobNil, kginMark);
if (pvNil == (_ptxhg = TXHG::PtxhgNew(_pwoks, ptxhd, &gcb)))
goto LFail;
// set the sound variables
stn = PszLit("_ctgSound");
rtvn.SetFromStn(&stn);
if (!FAssignRtvm(_ptxhg->Ppglrtvm(), &rtvn, phtop->ckiSnd.ctg))
goto LFail;
stn = PszLit("_cnoSound");
rtvn.SetFromStn(&stn);
if (!FAssignRtvm(_ptxhg->Ppglrtvm(), &rtvn, phtop->ckiSnd.cno))
{
LFail:
ReleasePpo(&_ptxhg);
// restore the previous topic DDG
_ptxhg = ptxhgSave;
return fFalse;
}
ReleasePpo(&ptxhgSave);
_ptxhg->GetNaturalSize(&_dxpPref, &_dypPref);
if (hidNil == phtop->hidTarget ||
pvNil == (pgob = _pwoks->PgobFromHid(phtop->hidTarget)))
{
pgob = PgobPar();
}
if (pgob->FIs(kclsGOK))
((PGOK)pgob)->GetPtReg(&pt);
else
{
RC rc;
pgob->GetRc(&rc, cooParent);
pt.xp = rc.XpCenter();
pt.yp = rc.YpCenter();
}
pgob->MapPt(&pt, cooParent, cooGlobal);
// point the balloon at the gob
PgobPar()->MapPt(&pt, cooGlobal, cooLocal);
GetPtReg(&ptReg);
_SetGorp(_pgorp, pt.xp - ptReg.xp + phtop->dxp,
pt.yp - ptReg.yp + phtop->dyp);
return fTrue;
}
/***************************************************************************
Our representation is changing, so make sure we stay inside our parent
and reposition the TXHG.
***************************************************************************/
void HBAL::_SetGorp(PGORP pgorp, long dxp, long dyp)
{
RC rc1, rc2, rc3;
HBAL_PAR::_SetGorp(pgorp, dxp, dyp);
// make sure we stay inside our parent
GetRc(&rc1, cooParent);
PgobPar()->GetRc(&rc2, cooLocal);
rc3.FIntersect(&rc1, &rc2);
if (rc3 != rc1)
{
rc1.PinToRc(&rc2);
SetPos(&rc1);
}
// position the TXHG.
GetRcContent(&rc1);
rc2.Set(0, 0, _dxpPref, _dypPref);
rc2.CenterOnRc(&rc1);
_ptxhg->SetPos(&rc2);
}
/***************************************************************************
Constructor for a help balloon button.
***************************************************************************/
HBTN::HBTN(GCB *pgcb) : HBTN_PAR(pgcb)
{
}
/***************************************************************************
Create a new help balloon button
***************************************************************************/
PHBTN HBTN::PhbtnNew(PWOKS pwoks, PGOB pgobPar, long hid, CNO cno, PRCA prca,
byte bGroup, CNO cnoTopic, long xpLeft, long ypBottom)
{
AssertPo(pwoks, 0);
AssertNilOrPo(pgobPar, 0);
Assert(hid != hidNil, "nil ID");
AssertPo(prca, 0);
GCB gcb;
PHBTN phbtn;
RC rcAbs;
if (pvNil != pwoks->PcmhFromHid(hid))
{
Bug("command handler with this ID already exists");
return pvNil;
}
gcb.Set(hid, pgobPar, fgobNil, kginMark);
if (pvNil == (phbtn = NewObj HBTN(&gcb)))
return pvNil;
phbtn->_bGroup = bGroup;
phbtn->_cnoTopic = cnoTopic;
if (!phbtn->_FInit(pwoks, cno, prca))
{
ReleasePpo(&phbtn);
return pvNil;
}
if (!phbtn->_FEnterState(ksnoInit))
{
Warn("GOK immediately destroyed!");
return pvNil;
}
phbtn->GetRc(&rcAbs, cooParent);
rcAbs.Offset(xpLeft - rcAbs.xpLeft, ypBottom - rcAbs.ypBottom);
phbtn->SetPos(&rcAbs, pvNil);
AssertPo(phbtn, 0);
return phbtn;
}
/***************************************************************************
Test whether the given point is in this button or its related text.
***************************************************************************/
bool HBTN::FPtIn(long xp, long yp)
{
AssertThis(0);
PTXHG ptxhg;
PT pt(xp, yp);
byte bGroup;
CNO cnoTopic;
if (HBTN_PAR::FPtIn(xp, yp))
return fTrue;
if (_bGroup == 0 || !PgobPar()->FIs(kclsTXHG))
return fFalse;
ptxhg = (PTXHG)PgobPar();
MapPt(&pt, cooLocal, cooParent);
if (!ptxhg->FGroupFromPt(pt.xp, pt.yp, &bGroup, &cnoTopic))
return fFalse;
return bGroup == _bGroup && cnoTopic == _cnoTopic;
}
/***************************************************************************
The button has been clicked on. Tell the TXHG to do its thing.
***************************************************************************/
bool HBTN::FCmdClicked(PCMD_MOUSE pcmd)
{
AssertThis(0);
AssertVarMem(pcmd);
PTXHG ptxhg;
long hid = Hid();
if (!PgobPar()->FIs(kclsTXHG))
{
Bug("why isn't my parent a TXHG?");
return fTrue;
}
ptxhg = (PTXHG)PgobPar();
ptxhg->DoHit(_bGroup, _cnoTopic, pcmd->grfcust, hid);
return fTrue;
}