mirror of
https://github.com/microsoft/Microsoft-3D-Movie-Maker.git
synced 2024-11-22 10:22:40 +01:00
1341 lines
32 KiB
C++
1341 lines
32 KiB
C++
/* Copyright (c) Microsoft Corporation.
|
|
Licensed under the MIT License. */
|
|
|
|
/***************************************************************************
|
|
Author: ShonK
|
|
Project: Kauai
|
|
Reviewed:
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Basic command classes: CEX (command dispatcher), CMH (command handler).
|
|
|
|
The command dispatcher (CEX) has a command (CMD) queue and a list of
|
|
command handlers (CMH). During normal operation (CEX::FDispatchNextCmd),
|
|
the CEX takes the next CMD from the queue, passes it to each CMH in its
|
|
list (by calling CMH::FDoCmd) until one of the handlers returns true.
|
|
If none of the handlers in the list returns true, the command is passed
|
|
to the handler specified in the CMD itself (cmd.pcmh, if not nil).
|
|
|
|
A CMH is placed in the handler list by a call to CEX::FAddCmh. The
|
|
cmhl parameter determines the order of CMH's in the list. The grfcmm
|
|
parameter indicates which targets the CMH wants to see commands for.
|
|
The options are fcmmThis, fcmmNobody, fcmmOthers.
|
|
|
|
The CEX class supports command stream recording and playback.
|
|
|
|
***************************************************************************/
|
|
#include "frame.h"
|
|
ASSERTNAME
|
|
|
|
|
|
// command map shared by every command handler
|
|
BEGIN_CMD_MAP_BASE(CMH)
|
|
END_CMD_MAP_NIL()
|
|
|
|
|
|
RTCLASS(CMH)
|
|
RTCLASS(CEX)
|
|
|
|
|
|
long CMH::_hidLast;
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a CMD.
|
|
***************************************************************************/
|
|
void CMD::AssertValid(ulong grf)
|
|
{
|
|
AssertThisMem();
|
|
AssertNilOrPo(pgg, 0);
|
|
AssertNilOrPo(pcmh, 0);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to return a hid (command handler ID) such that the numbers
|
|
{ hid, hid + 1, ... , hid + ccmh - 1 } are not currently in use and all
|
|
have their high bit set. To avoid possible conflicts, hard-wired
|
|
handler ID's should have their high bit clear. This guarantees that
|
|
the values will not be returned again in the near future (whether or not
|
|
they are in use).
|
|
|
|
This calls vpappb->PcmhFromHid to determine if a hid is in use. This
|
|
means that the returned hid is only unique over handlers that the
|
|
application class knows about.
|
|
***************************************************************************/
|
|
long CMH::HidUnique(long ccmh)
|
|
{
|
|
AssertIn(ccmh, 1, 1000);
|
|
long ccmhT;
|
|
|
|
_hidLast |= 0x80000000L;
|
|
for (ccmhT = ccmh; ccmhT-- > 0; )
|
|
{
|
|
_hidLast++;
|
|
if (!(_hidLast & 0x80000000L))
|
|
{
|
|
_hidLast = 0x80000000L;
|
|
ccmhT = ccmh;
|
|
continue;
|
|
}
|
|
if (pvNil != vpappb->PcmhFromHid(_hidLast))
|
|
ccmhT = ccmh;
|
|
}
|
|
|
|
return _hidLast - (ccmh - 1);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a command handler - set the handler id.
|
|
***************************************************************************/
|
|
CMH::CMH(long hid)
|
|
{
|
|
AssertBaseThis(0);
|
|
Assert(hid != hidNil, "bad hid");
|
|
_hid = hid;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor for a command handler - purge any global references to it
|
|
from the app. The app purges it from the command dispatcher.
|
|
***************************************************************************/
|
|
CMH::~CMH(void)
|
|
{
|
|
AssertThis(0);
|
|
if (pvNil != vpappb)
|
|
vpappb->BuryCmh(this);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a CMH.
|
|
***************************************************************************/
|
|
void CMH::AssertValid(ulong grf)
|
|
{
|
|
CMH_PAR::AssertValid(0);
|
|
Assert(_hid != hidNil, 0);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Protected virtual function to find a CMME (command map entry) for the
|
|
given command id.
|
|
***************************************************************************/
|
|
bool CMH::_FGetCmme(long cid, ulong grfcmmWanted, CMME *pcmme)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(pcmme);
|
|
Assert(cid != cidNil, "why is the cid nil?");
|
|
CMM *pcmm;
|
|
CMME *pcmmeT;
|
|
CMME *pcmmeDef = pvNil;
|
|
|
|
for (pcmm = Pcmm(); pcmm != pvNil; pcmm = pcmm->pcmmBase)
|
|
{
|
|
for (pcmmeT = pcmm->prgcmme; pcmmeT->cid != cidNil; pcmmeT++)
|
|
{
|
|
if (pcmmeT->cid == cid && (pcmmeT->grfcmm & grfcmmWanted))
|
|
{
|
|
*pcmme = *pcmmeT;
|
|
return fTrue;
|
|
}
|
|
}
|
|
|
|
// check for a default function
|
|
if (pcmmeT->pfncmd != pvNil && (pcmmeT->grfcmm & grfcmmWanted) &&
|
|
pcmmeDef == pvNil)
|
|
{
|
|
pcmmeDef = pcmmeT;
|
|
}
|
|
}
|
|
|
|
// no specific one found, return the default one
|
|
if (pcmmeDef != pvNil)
|
|
{
|
|
*pcmme = *pcmmeDef;
|
|
return fTrue;
|
|
}
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Determines whether this command handler can handle the given command. If
|
|
not, returns false (and does nothing else). If so, executes the command
|
|
and returns true.
|
|
|
|
NOTE: a true return indicates that the command was handled and should
|
|
not be passed on down the command handler list - regardless of the
|
|
success/failure of the execution of the command. Do not return false
|
|
to indicate that command execution failed.
|
|
***************************************************************************/
|
|
bool CMH::FDoCmd(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmd, 0);
|
|
CMME cmme;
|
|
ulong grfcmm;
|
|
|
|
if (pvNil == pcmd->pcmh)
|
|
grfcmm = fcmmNobody;
|
|
else if (this == pcmd->pcmh)
|
|
grfcmm = fcmmThis;
|
|
else
|
|
grfcmm = fcmmOthers;
|
|
|
|
if (!_FGetCmme(pcmd->cid, grfcmm, &cmme) || pvNil == cmme.pfncmd)
|
|
return fFalse;
|
|
|
|
return (this->*cmme.pfncmd)(pcmd);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Determines whether the command is enabled. If this command handler
|
|
doesn't normally handle the command, this returns false (and does
|
|
nothing else). Otherwise sets the grfeds and returns true.
|
|
***************************************************************************/
|
|
bool CMH::FEnableCmd(PCMD pcmd, ulong *pgrfeds)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmd, 0);
|
|
AssertVarMem(pgrfeds);
|
|
CMME cmme;
|
|
ulong grfcmm;
|
|
|
|
if (pvNil == pcmd->pcmh)
|
|
grfcmm = fcmmNobody;
|
|
else if (this == pcmd->pcmh)
|
|
grfcmm = fcmmThis;
|
|
else
|
|
grfcmm = fcmmOthers;
|
|
|
|
if (!_FGetCmme(pcmd->cid, grfcmm, &cmme) || pvNil == cmme.pfncmd)
|
|
return fFalse;
|
|
|
|
if (cmme.pfneds == pvNil)
|
|
{
|
|
if (cidNil == cmme.cid)
|
|
return fFalse;
|
|
*pgrfeds = fedsEnable;
|
|
}
|
|
else if (!(this->*cmme.pfneds)(pcmd, pgrfeds))
|
|
return fFalse;
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Command dispatcher constructor.
|
|
***************************************************************************/
|
|
CEX::CEX(void)
|
|
{
|
|
AssertBaseThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor for a CEX.
|
|
***************************************************************************/
|
|
CEX::~CEX(void)
|
|
{
|
|
AssertBaseThis(0);
|
|
CMD cmd;
|
|
long icmd;
|
|
|
|
if (pvNil != _pglcmd)
|
|
{
|
|
for (icmd = _pglcmd->IvMac(); icmd-- != 0; )
|
|
{
|
|
_pglcmd->Get(icmd, &cmd);
|
|
ReleasePpo(&cmd.pgg);
|
|
}
|
|
ReleasePpo(&_pglcmd);
|
|
}
|
|
|
|
ReleasePpo(&_pglcmhe);
|
|
ReleasePpo(&_pcfl);
|
|
ReleasePpo(&_pglcmdf);
|
|
ReleasePpo(&_cmdCur.pgg);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to create a new CEX object.
|
|
***************************************************************************/
|
|
PCEX CEX::PcexNew(long ccmdInit, long ccmhInit)
|
|
{
|
|
AssertIn(ccmdInit, 0, kcbMax);
|
|
AssertIn(ccmhInit, 0, kcbMax);
|
|
PCEX pcex;
|
|
|
|
if (pvNil == (pcex = NewObj CEX))
|
|
return pvNil;
|
|
|
|
if (!pcex->_FInit(ccmdInit, ccmhInit))
|
|
ReleasePpo(&pcex);
|
|
|
|
AssertNilOrPo(pcex, 0);
|
|
return pcex;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Initialization of the command dispatcher.
|
|
***************************************************************************/
|
|
bool CEX::_FInit(long ccmdInit, long ccmhInit)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertIn(ccmdInit, 0, kcbMax);
|
|
AssertIn(ccmhInit, 0, kcbMax);
|
|
|
|
if (pvNil == (_pglcmd = GL::PglNew(size(CMD), ccmdInit)) ||
|
|
pvNil == (_pglcmhe = GL::PglNew(size(CMHE), ccmhInit)))
|
|
{
|
|
return fFalse;
|
|
}
|
|
|
|
AssertThis(0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Start recording a macro to the given chunky file.
|
|
***************************************************************************/
|
|
void CEX::Record(PCFL pcfl)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcfl, 0);
|
|
|
|
if (_rs != rsNormal)
|
|
{
|
|
Bug("already recording or playing");
|
|
return;
|
|
}
|
|
|
|
_rs = rsRecording;
|
|
_rec = recNil;
|
|
_pcfl = pcfl;
|
|
_pcfl->AddRef();
|
|
_cno = cnoNil;
|
|
_icmdf = 0;
|
|
_chidLast = 0;
|
|
_cact = 0;
|
|
Assert(_pglcmdf == pvNil, "why isn't _pglcmdf nil?");
|
|
|
|
if ((_pglcmdf = GL::PglNew(size(CMDF), 100)) == pvNil)
|
|
_rec = recMemError;
|
|
else if (!_pcfl->FAdd(0, kctgMacro, &_cno))
|
|
{
|
|
_rec = recFileError;
|
|
_cno = cnoNil;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Stop recording a command stream, and write the command stream to
|
|
file. If there were any errors, delete the command stream from the
|
|
chunky file. Pushes a command notifying the world that recording
|
|
has stopped. The command (cidCexRecordDone) contains the error code
|
|
(rec), and cno in the first two lw's of the command. If the
|
|
rec is not recNil, the cno is cnoNil and wasn't actually created.
|
|
***************************************************************************/
|
|
void CEX::StopRecording(void)
|
|
{
|
|
AssertThis(0);
|
|
BLCK blck;
|
|
|
|
if (_rs != rsRecording)
|
|
return;
|
|
|
|
if (_rec == recNil)
|
|
{
|
|
long cb;
|
|
|
|
if (_cact > 1)
|
|
{
|
|
// rewrite the last one's _cact
|
|
CMDF cmdf;
|
|
|
|
_pglcmdf->Get(_icmdf, &cmdf);
|
|
cmdf.cact = _cact;
|
|
_pglcmdf->Put(_icmdf, &cmdf);
|
|
}
|
|
cb = _pglcmdf->CbOnFile();
|
|
if (!_pcfl->FPut(cb, kctgMacro, _cno, &blck) ||
|
|
!_pglcmdf->FWrite(&blck))
|
|
{
|
|
_rec = recFileError;
|
|
}
|
|
}
|
|
|
|
if (_rec != recNil && _cno != cnoNil)
|
|
{
|
|
_pcfl->Delete(kctgMacro, _cno);
|
|
_cno = cnoNil;
|
|
}
|
|
|
|
if (_cno != cnoNil)
|
|
{
|
|
if (_pcfl->FSave(kctgFramework))
|
|
_pcfl->SetTemp(fFalse);
|
|
else
|
|
{
|
|
_rec = recFileError;
|
|
_cno = cnoNil;
|
|
}
|
|
}
|
|
_rs = rsNormal;
|
|
ReleasePpo(&_pglcmdf);
|
|
ReleasePpo(&_pcfl);
|
|
|
|
PushCid(cidCexRecordDone, pvNil, pvNil, _rec, _cno);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Record a command.
|
|
***************************************************************************/
|
|
void CEX::RecordCmd(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmd, 0);
|
|
Assert(_rs == rsRecording, "not recording");
|
|
CMDF cmdf;
|
|
|
|
if (_rec != recNil)
|
|
return;
|
|
|
|
if (_cact > 0)
|
|
{
|
|
if (_cmd.pgg == pvNil && FEqualRgb(pcmd, &_cmd, size(_cmd)))
|
|
{
|
|
// commands are the same, just increment the _cact
|
|
if (pcmd->cid < cidMinNoRepeat || pcmd->cid >= cidLimNoRepeat)
|
|
_cact++;
|
|
return;
|
|
}
|
|
|
|
// new command is not the same as the previous one
|
|
if (_cact > 1)
|
|
{
|
|
//rewrite the previous one's _cact
|
|
_pglcmdf->Get(_icmdf, &cmdf);
|
|
cmdf.cact = _cact;
|
|
_pglcmdf->Put(_icmdf, &cmdf);
|
|
}
|
|
|
|
// increment _icmdf
|
|
_icmdf++;
|
|
_cact = 0;
|
|
}
|
|
|
|
// fill in the cmdf and save it in the list
|
|
cmdf.cid = pcmd->cid;
|
|
cmdf.hid = pcmd->pcmh == pvNil ? hidNil : pcmd->pcmh->Hid();
|
|
cmdf.cact = 1;
|
|
cmdf.chidGg = pcmd->pgg != pvNil ? ++_chidLast : 0;
|
|
CopyPb(pcmd->rglw, cmdf.rglw, kclwCmd * size(long));
|
|
|
|
if (!_pglcmdf->FInsert(_icmdf, &cmdf))
|
|
{
|
|
//out of memory
|
|
_rec = recMemError;
|
|
return;
|
|
}
|
|
|
|
// write the group and make it a child of the macro
|
|
if (pvNil != pcmd->pgg)
|
|
{
|
|
BLCK blck;
|
|
long cb;
|
|
CNO cno;
|
|
|
|
cb = pcmd->pgg->CbOnFile();
|
|
if (!_pcfl->FAddChild(kctgMacro, _cno, cmdf.chidGg,
|
|
cb, kctgGg, &cno, &blck))
|
|
{
|
|
goto LFileError;
|
|
}
|
|
if (!pcmd->pgg->FWrite(&blck))
|
|
{
|
|
_pcfl->DeleteChild(kctgMacro, _cno, kctgGg, cno, cmdf.chidGg);
|
|
LFileError:
|
|
_pglcmdf->Delete(_icmdf);
|
|
_rec = recFileError;
|
|
return;
|
|
}
|
|
}
|
|
_cact = 1;
|
|
_cmd = *pcmd;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Play back the command stream starting in the given pcfl with the given
|
|
cno.
|
|
***************************************************************************/
|
|
void CEX::Play(PCFL pcfl, CNO cno)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcfl, 0);
|
|
BLCK blck;
|
|
short bo, osk;
|
|
|
|
if (_rs != rsNormal)
|
|
{
|
|
Bug("already recording or playing");
|
|
return;
|
|
}
|
|
|
|
_rs = rsPlaying;
|
|
_rec = recNil;
|
|
_pcfl = pcfl;
|
|
_pcfl->AddRef();
|
|
_cno = cno;
|
|
_icmdf = 0;
|
|
_cact = 0;
|
|
Assert(_pglcmdf == pvNil, "why isn't _pglcmdf nil?");
|
|
|
|
if (!_pcfl->FFind(kctgMacro, _cno, &blck) ||
|
|
(_pglcmdf = GL::PglRead(&blck, &bo, &osk)) == pvNil)
|
|
{
|
|
_rec = recFileError;
|
|
StopPlaying();
|
|
}
|
|
else if (bo != kboCur || osk != koskCur)
|
|
{
|
|
_rec = recWrongPlatform;
|
|
StopPlaying();
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Stop play back of a command stream. Pushes a command notifying the
|
|
world that play back has stopped. The command (cidCexPlayDone) contains
|
|
the error code (rec), and cno in the first two lw's of the command.
|
|
***************************************************************************/
|
|
void CEX::StopPlaying(void)
|
|
{
|
|
AssertThis(0);
|
|
|
|
if (_rs != rsPlaying)
|
|
return;
|
|
|
|
if (_rec == recNil && (_cact > 0 || _icmdf < _pglcmdf->IvMac()))
|
|
_rec = recAbort;
|
|
|
|
PushCid(cidCexPlayDone, pvNil, pvNil, _rec, _cno);
|
|
|
|
_rs = rsNormal;
|
|
ReleasePpo(&_pcfl);
|
|
ReleasePpo(&_pglcmdf);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read the next command.
|
|
***************************************************************************/
|
|
bool CEX::_FReadCmd(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(pcmd);
|
|
Assert(_rs == rsPlaying, "not playing a command stream");
|
|
AssertPo(_pglcmdf, 0);
|
|
CMDF cmdf;
|
|
|
|
if (_cact > 0)
|
|
{
|
|
//this command is being repeated
|
|
*pcmd = _cmd;
|
|
_cact--;
|
|
return fTrue;
|
|
}
|
|
if (_icmdf >= _pglcmdf->IvMac())
|
|
goto LStop;
|
|
|
|
_pglcmdf->Get(_icmdf, &cmdf);
|
|
|
|
ClearPb(pcmd, size(*pcmd));
|
|
pcmd->cid = cmdf.cid;
|
|
pcmd->pcmh = vpappb->PcmhFromHid(cmdf.hid);
|
|
pcmd->pgg = pvNil;
|
|
CopyPb(cmdf.rglw, pcmd->rglw, kclwCmd * size(long));
|
|
|
|
if (cmdf.chidGg != 0)
|
|
{
|
|
BLCK blck;
|
|
KID kid;
|
|
short bo, osk;
|
|
|
|
Assert(cmdf.cact <= 1, 0);
|
|
|
|
//read the gg
|
|
if (!_pcfl->FGetKidChidCtg(kctgMacro, _cno, cmdf.chidGg,
|
|
kctgGg, &kid) ||
|
|
!_pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck) ||
|
|
pvNil == (pcmd->pgg = GG::PggRead(&blck, &bo, &osk)))
|
|
{
|
|
_rec = recFileError;
|
|
goto LStop;
|
|
}
|
|
if (bo != kboCur || osk != koskCur)
|
|
{
|
|
//don't know how to change byte order or translate strings
|
|
ReleasePpo(&pcmd->pgg);
|
|
_rec = recWrongPlatform;
|
|
goto LStop;
|
|
}
|
|
}
|
|
AssertPo(pcmd, 0);
|
|
|
|
_icmdf++;
|
|
if ((_cact = cmdf.cact - 1) > 0)
|
|
_cmd = *pcmd;
|
|
return fTrue;
|
|
|
|
//error handling
|
|
LStop:
|
|
StopPlaying();
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Determine whether it's OK to communicate with the CMH. Default is to
|
|
return true iff there is no current modal gob or the cmh is not a gob
|
|
or it is a gob in the tree of the modal gob.
|
|
***************************************************************************/
|
|
bool CEX::_FCmhOk(PCMH pcmh)
|
|
{
|
|
AssertNilOrPo(pcmh, 0);
|
|
PGOB pgob;
|
|
|
|
if (pvNil == _pgobModal || pvNil == pcmh || !pcmh->FIs(kclsGOB))
|
|
return fTrue;
|
|
|
|
for (pgob = (PGOB)pcmh; pgob != _pgobModal; pgob = pgob->PgobPar())
|
|
{
|
|
if (pvNil == pgob)
|
|
return fFalse;
|
|
}
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Add a command handler to the filter list. These command handlers get
|
|
a crack at every command whether or not it is for them. grfcmm
|
|
determines which targets the handler will see commands for (as in
|
|
command map entries). The cmhl is a command handler level - indicating
|
|
the priority of the command handler. Handlers with lower cmhl values
|
|
get first crack at commands. It is legal for a handler to be in the
|
|
list more than once (even with the same cmhl value).
|
|
***************************************************************************/
|
|
bool CEX::FAddCmh(PCMH pcmh, long cmhl, ulong grfcmm)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmh, 0);
|
|
CMHE cmhe;
|
|
long icmhe;
|
|
|
|
if (fcmmNil == (grfcmm & kgrfcmmAll))
|
|
{
|
|
// no sense adding this
|
|
Bug("why is grfcmm nil?");
|
|
return fFalse;
|
|
}
|
|
|
|
if (!_FCmhOk(pcmh))
|
|
return fFalse;
|
|
|
|
_FFindCmhl(cmhl, &icmhe);
|
|
cmhe.pcmh = pcmh;
|
|
cmhe.cmhl = cmhl;
|
|
cmhe.grfcmm = grfcmm;
|
|
if (!_pglcmhe->FInsert(icmhe, &cmhe))
|
|
return fFalse;
|
|
if (icmhe <= _icmheNext)
|
|
_icmheNext++;
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Removes the the handler (at the given cmhl level) from the handler list.
|
|
***************************************************************************/
|
|
void CEX::RemoveCmh(PCMH pcmh, long cmhl)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmh, 0);
|
|
long icmhe, ccmhe;
|
|
CMHE cmhe;
|
|
|
|
if (!_FFindCmhl(cmhl, &icmhe))
|
|
return;
|
|
|
|
for (ccmhe = _pglcmhe->IvMac(); icmhe < ccmhe; icmhe++)
|
|
{
|
|
_pglcmhe->Get(icmhe, &cmhe);
|
|
if (cmhe.cmhl != cmhl)
|
|
break;
|
|
if (cmhe.pcmh == pcmh)
|
|
{
|
|
_pglcmhe->Delete(icmhe);
|
|
if (icmhe < _icmheNext)
|
|
_icmheNext--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Remove all references to the handler from the command dispatcher,
|
|
including from the handler list and the command queue.
|
|
***************************************************************************/
|
|
void CEX::BuryCmh(PCMH pcmh)
|
|
{
|
|
AssertThis(0);
|
|
Assert(pcmh != pvNil, 0);
|
|
long icmhe, icmd;
|
|
CMHE cmhe;
|
|
CMD cmd;
|
|
|
|
if (_pgobModal == pcmh)
|
|
_pgobModal = pvNil;
|
|
|
|
if (_pgobTrack == pcmh)
|
|
{
|
|
#ifdef WIN
|
|
if (hNil != _hwndCapture && GetCapture() == _hwndCapture)
|
|
ReleaseCapture();
|
|
_hwndCapture = hNil;
|
|
#endif //WIN
|
|
_pgobTrack = pvNil;
|
|
}
|
|
if (_cmdCur.pcmh == pcmh)
|
|
_cmdCur.pcmh = pvNil;
|
|
if (_cmd.pcmh == pcmh)
|
|
{
|
|
_cmd.pcmh = pvNil;
|
|
_cact = 0;
|
|
}
|
|
|
|
for (icmhe = _pglcmhe->IvMac(); icmhe-- != 0; )
|
|
{
|
|
_pglcmhe->Get(icmhe, &cmhe);
|
|
if (cmhe.pcmh == pcmh)
|
|
{
|
|
_pglcmhe->Delete(icmhe);
|
|
if (icmhe < _icmheNext)
|
|
_icmheNext--;
|
|
}
|
|
}
|
|
|
|
for (icmd = _pglcmd->IvMac(); icmd-- != 0; )
|
|
{
|
|
_pglcmd->Get(icmd, &cmd);
|
|
if (cmd.pcmh == pcmh)
|
|
{
|
|
_pglcmd->Delete(icmd);
|
|
ReleasePpo(&cmd.pgg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Finds the first item with the given cmhl in the handler list. If there
|
|
aren't any, still sets *picmhe to where they would be.
|
|
***************************************************************************/
|
|
bool CEX::_FFindCmhl(long cmhl, long *picmhe)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(picmhe);
|
|
long icmhe, icmheMin, icmheLim;
|
|
CMHE *qrgcmhe;
|
|
|
|
qrgcmhe = (CMHE *)_pglcmhe->QvGet(0);
|
|
for (icmheMin = 0, icmheLim = _pglcmhe->IvMac(); icmheMin < icmheLim; )
|
|
{
|
|
icmhe = (icmheMin + icmheLim) / 2;
|
|
if (qrgcmhe[icmhe].cmhl < cmhl)
|
|
icmheMin = icmhe + 1;
|
|
else
|
|
icmheLim = icmhe;
|
|
}
|
|
|
|
*picmhe = icmheMin;
|
|
return icmheMin < _pglcmhe->IvMac() && qrgcmhe[icmheMin].cmhl == cmhl;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Adds a command to the tail of the queue.
|
|
***************************************************************************/
|
|
void CEX::EnqueueCid(long cid, PCMH pcmh, PGG pgg,
|
|
long lw0, long lw1, long lw2, long lw3)
|
|
{
|
|
Assert(cid != cidNil, 0);
|
|
AssertNilOrPo(pcmh, 0);
|
|
AssertNilOrPo(pgg, 0);
|
|
CMD cmd;
|
|
|
|
cmd.cid = cid;
|
|
cmd.pcmh = pcmh;
|
|
cmd.pgg = pgg;
|
|
cmd.rglw[0] = lw0;
|
|
cmd.rglw[1] = lw1;
|
|
cmd.rglw[2] = lw2;
|
|
cmd.rglw[3] = lw3;
|
|
EnqueueCmd(&cmd);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Pushes a command onto the head of the queue.
|
|
***************************************************************************/
|
|
void CEX::PushCid(long cid, PCMH pcmh, PGG pgg,
|
|
long lw0, long lw1, long lw2, long lw3)
|
|
{
|
|
Assert(cid != cidNil, 0);
|
|
AssertNilOrPo(pcmh, 0);
|
|
AssertNilOrPo(pgg, 0);
|
|
CMD cmd;
|
|
|
|
cmd.cid = cid;
|
|
cmd.pcmh = pcmh;
|
|
cmd.pgg = pgg;
|
|
cmd.rglw[0] = lw0;
|
|
cmd.rglw[1] = lw1;
|
|
cmd.rglw[2] = lw2;
|
|
cmd.rglw[3] = lw3;
|
|
PushCmd(&cmd);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Adds a command to the tail of the queue. This asserts if it can't add
|
|
it to the queue. Clients should make sure that the value of ccmdInit
|
|
passed to PcexNew is large enough to handle the busiest session.
|
|
***************************************************************************/
|
|
void CEX::EnqueueCmd(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmd, 0);
|
|
Assert(pcmd->cid != cidNil, "why enqueue a nil command?");
|
|
|
|
if (!_pglcmd->FEnqueue(pcmd))
|
|
{
|
|
Bug("event queue not big enough");
|
|
ReleasePpo(&pcmd->pgg);
|
|
}
|
|
#ifdef DEBUG
|
|
if (_ccmdMax < _pglcmd->IvMac())
|
|
_ccmdMax = _pglcmd->IvMac();
|
|
#endif //DEBUG
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Pushes a command onto the head of the queue. This asserts if it can't
|
|
add it to the queue. Clients should make sure that the value of ccmdInit
|
|
passed to PcexNew is large enough to handle the busiest session.
|
|
***************************************************************************/
|
|
void CEX::PushCmd(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmd, 0);
|
|
Assert(pcmd->cid != cidNil, "why enqueue a nil command?");
|
|
|
|
if (!_pglcmd->FPush(pcmd))
|
|
{
|
|
Bug("event queue not big enough");
|
|
ReleasePpo(&pcmd->pgg);
|
|
}
|
|
#ifdef DEBUG
|
|
if (_ccmdMax < _pglcmd->IvMac())
|
|
_ccmdMax = _pglcmd->IvMac();
|
|
#endif //DEBUG
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Checks if a cid is in the queue.
|
|
***************************************************************************/
|
|
bool CEX::FCidIn(long cid)
|
|
{
|
|
AssertThis(0);
|
|
Assert(cid != cidNil, "why check for a nil command?");
|
|
|
|
long icmd;
|
|
CMD cmd;
|
|
|
|
for (icmd = _pglcmd->IvMac(); icmd-- > 0; )
|
|
{
|
|
_pglcmd->Get(icmd, &cmd);
|
|
if (cmd.cid == cid)
|
|
return fTrue;
|
|
}
|
|
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Flushes all instances of a cid in the queue.
|
|
***************************************************************************/
|
|
void CEX::FlushCid(long cid)
|
|
{
|
|
AssertThis(0);
|
|
Assert(cid != cidNil, "why flush a nil command?");
|
|
|
|
long icmd;
|
|
CMD cmd;
|
|
|
|
for (icmd = _pglcmd->IvMac(); icmd-- > 0; )
|
|
{
|
|
_pglcmd->Get(icmd, &cmd);
|
|
if (cmd.cid == cid)
|
|
{
|
|
_pglcmd->Delete(icmd);
|
|
ReleasePpo(&cmd.pgg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get the next command to be dispatched (put it in _cmdCur). Return tYes
|
|
if there was a command and it should be dispatched. Return tNo if there
|
|
wasn't a command (if the system queue should be checked). Return tMaybe
|
|
if the command shouldn't be dispatched, but we shouldn't check the
|
|
system queue.
|
|
***************************************************************************/
|
|
bool CEX::_TGetNextCmd(void)
|
|
{
|
|
AssertThis(0);
|
|
|
|
// get the next command from the command stream
|
|
if (!_pglcmd->FPop(&_cmdCur))
|
|
{
|
|
ClearPb(&_cmdCur, size(_cmdCur));
|
|
if (pvNil == _pgobTrack)
|
|
return tNo;
|
|
|
|
AssertPo(_pgobTrack, 0);
|
|
PT pt;
|
|
PCMD_MOUSE pcmd = (PCMD_MOUSE)&_cmdCur;
|
|
|
|
_cmdCur.pcmh = _pgobTrack;
|
|
_cmdCur.cid = cidTrackMouse;
|
|
vpappb->TrackMouse(_pgobTrack, &pt);
|
|
pcmd->xp = pt.xp;
|
|
pcmd->yp = pt.yp;
|
|
pcmd->grfcust = vpappb->GrfcustCur();
|
|
|
|
if (!vpappb->FForeground())
|
|
{
|
|
// if we're not in the foreground, toggle the state of
|
|
// fcustMouse repeatedly. Most of the time, this will cause
|
|
// the client to stop tracking the mouse. Clients
|
|
// whose tracking state depends on something other than
|
|
// the mouse state should call vpappb->FForeground() to
|
|
// determine if tracking should be aborted.
|
|
static bool _fDown;
|
|
|
|
_fDown = !_fDown;
|
|
if (!_fDown)
|
|
pcmd->grfcust ^= fcustMouse;
|
|
else
|
|
pcmd->grfcust &= ~fcustMouse;
|
|
}
|
|
}
|
|
AssertPo(&_cmdCur, 0);
|
|
|
|
// handle playing and recording
|
|
if (rsPlaying == _rs)
|
|
{
|
|
// We're playing back. Throw away the incoming command and play
|
|
// one from the stream.
|
|
ReleasePpo(&_cmdCur.pgg);
|
|
|
|
// let a cidCexStopPlay go through and handle it at the end.
|
|
// this is so a cmh can intercept it.
|
|
if (_cmdCur.cid != cidCexStopPlay && !_FReadCmd(&_cmdCur))
|
|
return tMaybe;
|
|
}
|
|
|
|
if (!_FCmhOk(_cmdCur.pcmh))
|
|
{
|
|
vpappb->BadModalCmd(&_cmdCur);
|
|
ReleasePpo(&_cmdCur.pgg);
|
|
return tMaybe;
|
|
}
|
|
|
|
return tYes;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Send the command (_cmdCur) to the given command handler.
|
|
***************************************************************************/
|
|
bool CEX::_FSendCmd(PCMH pcmh)
|
|
{
|
|
AssertPo(pcmh, 0);
|
|
|
|
if (!_FCmhOk(pcmh))
|
|
return fFalse;
|
|
|
|
return pcmh->FDoCmd(&_cmdCur);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Handle post processing on the command - record it if we're recording,
|
|
free the pgg, etc.
|
|
***************************************************************************/
|
|
void CEX::_CleanUpCmd(void)
|
|
{
|
|
// If the handler went away during command dispatching, we should
|
|
// have heard about it (via BuryCmh) and should have set _cmdCur.pcmh
|
|
// to nil.
|
|
AssertNilOrPo(_cmdCur.pcmh, 0);
|
|
|
|
// record the command after dispatching, in case arguments got added
|
|
// or the cid was set to nil.
|
|
if (rsRecording == _rs && !FIn(_cmdCur.cid, cidMinNoRecord, cidLimNoRecord)
|
|
&& cidNil != _cmdCur.cid && cidCexStopRec != _cmdCur.cid)
|
|
{
|
|
RecordCmd(&_cmdCur);
|
|
}
|
|
|
|
// check for a stop record or stop play command
|
|
if (rsNormal != _rs)
|
|
{
|
|
if (cidCexStopPlay == _cmdCur.cid && rsPlaying == _rs)
|
|
StopPlaying();
|
|
else if (cidCexStopRec == _cmdCur.cid && rsRecording == _rs)
|
|
StopRecording();
|
|
}
|
|
|
|
ReleasePpo(&_cmdCur.pgg);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
If there is a command in the queue, this dispatches it and returns
|
|
true. If there aren't any commands in the queue, it simply returns
|
|
false. If a gob is tracking the mouse and the queue is empty, a
|
|
cidTrackMouse command is generated and dispatched to the gob.
|
|
|
|
NOTE: care has to be taken here because a CMH may go away while
|
|
dispatching the command. That's why _cmdCur and _icmheNext are
|
|
member variables - so BuryCmh can adjust them if needed.
|
|
***************************************************************************/
|
|
bool CEX::FDispatchNextCmd(void)
|
|
{
|
|
AssertThis(0);
|
|
CMHE cmhe;
|
|
bool fHandled;
|
|
bool tRet;
|
|
|
|
if (_fDispatching)
|
|
{
|
|
Bug("recursing into FDispatchNextCmd!");
|
|
return fFalse;
|
|
}
|
|
_fDispatching = fTrue;
|
|
|
|
tRet = _TGetNextCmd();
|
|
if (tYes != tRet)
|
|
{
|
|
_fDispatching = fFalse;
|
|
return tRet != tNo;
|
|
}
|
|
|
|
// pipe it through the command handlers, then to the target
|
|
fHandled = fFalse;
|
|
for (_icmheNext = 0; _icmheNext < _pglcmhe->IvMac() && !fHandled; )
|
|
{
|
|
_pglcmhe->Get(_icmheNext++, &cmhe);
|
|
if (pvNil == _cmdCur.pcmh)
|
|
{
|
|
if (!(cmhe.grfcmm & fcmmNobody))
|
|
continue;
|
|
}
|
|
else if (cmhe.pcmh == _cmdCur.pcmh)
|
|
{
|
|
if (!(cmhe.grfcmm & fcmmThis))
|
|
continue;
|
|
}
|
|
else if (!(cmhe.grfcmm & fcmmOthers))
|
|
continue;
|
|
|
|
fHandled = _FSendCmd(cmhe.pcmh);
|
|
}
|
|
|
|
if (!fHandled && pvNil != _cmdCur.pcmh)
|
|
{
|
|
AssertPo(_cmdCur.pcmh, 0);
|
|
fHandled = _FSendCmd(_cmdCur.pcmh);
|
|
}
|
|
|
|
_CleanUpCmd();
|
|
|
|
_fDispatching = fFalse;
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Give the handler a crack at enabling/disabling the command.
|
|
***************************************************************************/
|
|
bool CEX::_FEnableCmd(PCMH pcmh, PCMD pcmd, ulong *pgrfeds)
|
|
{
|
|
AssertPo(pcmh, 0);
|
|
AssertPo(pcmd, 0);
|
|
AssertVarMem(pgrfeds);
|
|
|
|
if (!_FCmhOk(pcmh))
|
|
return fFalse;
|
|
|
|
return pcmh->FEnableCmd(pcmd, pgrfeds);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Determines whether the given command is currently enabled. This is
|
|
normally used for menu graying/checking etc and toolbar enabling/status.
|
|
***************************************************************************/
|
|
bool CEX::GrfedsForCmd(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pcmd, 0);
|
|
long icmhe, ccmhe;
|
|
CMHE cmhe;
|
|
ulong grfeds;
|
|
|
|
// pipe it through the command handlers, then to the target
|
|
for (icmhe = 0, ccmhe = _pglcmhe->IvMac(); icmhe < ccmhe; icmhe++)
|
|
{
|
|
_pglcmhe->Get(icmhe, &cmhe);
|
|
grfeds = fedsNil;
|
|
if (_FEnableCmd(cmhe.pcmh, pcmd, &grfeds))
|
|
goto LDone;
|
|
}
|
|
if (pcmd->pcmh != pvNil)
|
|
{
|
|
AssertPo(pcmd->pcmh, 0);
|
|
grfeds = fedsNil;
|
|
if (_FEnableCmd(pcmd->pcmh, pcmd, &grfeds))
|
|
goto LDone;
|
|
}
|
|
|
|
// handle the CEX commands
|
|
switch (pcmd->cid)
|
|
{
|
|
case cidCexStopRec:
|
|
grfeds = rsRecording == _rs ? fedsEnable : fedsDisable;
|
|
break;
|
|
|
|
case cidCexStopPlay:
|
|
grfeds = rsPlaying == _rs ? fedsEnable : fedsDisable;
|
|
break;
|
|
|
|
default:
|
|
grfeds = fedsDisable;
|
|
break;
|
|
}
|
|
|
|
LDone:
|
|
return grfeds;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Determines whether the given command is currently enabled. This is
|
|
normally used for menu graying/checking etc and toolbar enabling/status.
|
|
***************************************************************************/
|
|
bool CEX::GrfedsForCid(long cid, PCMH pcmh, PGG pgg,
|
|
long lw0, long lw1, long lw2, long lw3)
|
|
{
|
|
AssertThis(0);
|
|
Assert(cid != cidNil, 0);
|
|
AssertNilOrPo(pcmh, 0);
|
|
AssertNilOrPo(pgg, 0);
|
|
CMD cmd;
|
|
|
|
cmd.cid = cid;
|
|
cmd.pcmh = pcmh;
|
|
cmd.pgg = pgg;
|
|
cmd.rglw[0] = lw0;
|
|
cmd.rglw[1] = lw1;
|
|
cmd.rglw[2] = lw2;
|
|
cmd.rglw[3] = lw3;
|
|
return GrfedsForCmd(&cmd);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
See if the next command is a key command and if so, put it in *pcmd
|
|
(and remove it from the queue).
|
|
***************************************************************************/
|
|
bool CEX::FGetNextKey(PCMD pcmd)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(pcmd);
|
|
long iv;
|
|
|
|
if (_rs != rsNormal)
|
|
goto LFail;
|
|
if ((iv = _pglcmd->IvMac()) > 0)
|
|
{
|
|
//get next cmd
|
|
_pglcmd->Get(iv - 1, pcmd);
|
|
if (pcmd->cid == cidKey)
|
|
{
|
|
AssertDo(_pglcmd->FPop(pvNil), 0);
|
|
return fTrue;
|
|
}
|
|
LFail:
|
|
TrashVar(pcmd);
|
|
return fFalse;
|
|
}
|
|
|
|
return vpappb->FGetNextKeyFromOsQueue((PCMD_KEY)pcmd);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
The given GOB wants to track the mouse.
|
|
***************************************************************************/
|
|
void CEX::TrackMouse(PGOB pgob)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pgob, 0);
|
|
Assert(_pgobTrack == pvNil, "some other gob is already tracking the mouse");
|
|
|
|
_pgobTrack = pgob;
|
|
#ifdef WIN
|
|
_hwndCapture = pgob->HwndContainer();
|
|
SetCapture(_hwndCapture);
|
|
#endif //WIN
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Stop tracking the mouse.
|
|
***************************************************************************/
|
|
void CEX::EndMouseTracking(void)
|
|
{
|
|
AssertThis(0);
|
|
|
|
#ifdef WIN
|
|
if (pvNil != _pgobTrack)
|
|
{
|
|
if (hNil != _hwndCapture && GetCapture() == _hwndCapture)
|
|
ReleaseCapture();
|
|
_hwndCapture = hNil;
|
|
}
|
|
#endif //WIN
|
|
_pgobTrack = pvNil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return the gob that is tracking the mouse.
|
|
***************************************************************************/
|
|
PGOB CEX::PgobTracking(void)
|
|
{
|
|
AssertThis(0);
|
|
return _pgobTrack;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Suspend or resume the command dispatcher. All this does is
|
|
release (capture) the mouse if we're current tracking the mouse and
|
|
we're being suspended (resumed).
|
|
***************************************************************************/
|
|
void CEX::Suspend(bool fSuspend)
|
|
{
|
|
AssertThis(0);
|
|
|
|
#ifdef WIN
|
|
if (pvNil == _pgobTrack || hNil == _hwndCapture)
|
|
return;
|
|
|
|
if (fSuspend && GetCapture() == _hwndCapture)
|
|
ReleaseCapture();
|
|
else if (!fSuspend && GetCapture() != _hwndCapture)
|
|
SetCapture(_hwndCapture);
|
|
#endif //WIN
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the modal GOB.
|
|
***************************************************************************/
|
|
void CEX::SetModalGob(PGOB pgob)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrPo(pgob, 0);
|
|
|
|
_pgobModal = pgob;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of the command dispatcher
|
|
***************************************************************************/
|
|
void CEX::AssertValid(ulong grf)
|
|
{
|
|
CEX_PAR::AssertValid(fobjAllocated);
|
|
AssertPo(_pglcmhe, 0);
|
|
AssertPo(_pglcmd, 0);
|
|
AssertNilOrPo(_pglcmdf, 0);
|
|
AssertNilOrPo(_pcfl, 0);
|
|
AssertNilOrPo(_cmdCur.pgg, 0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Mark the memory associated with the command dispatcher.
|
|
***************************************************************************/
|
|
void CEX::MarkMem(void)
|
|
{
|
|
AssertThis(0);
|
|
CMD cmd;
|
|
long icmd;
|
|
|
|
CEX_PAR::MarkMem();
|
|
MarkMemObj(_pglcmhe);
|
|
MarkMemObj(_pglcmd);
|
|
MarkMemObj(_pglcmdf);
|
|
MarkMemObj(_cmdCur.pgg);
|
|
|
|
for (icmd = _pglcmd->IvMac(); icmd-- != 0; )
|
|
{
|
|
_pglcmd->Get(icmd, &cmd);
|
|
if (cmd.pgg != pvNil)
|
|
MarkMemObj(cmd.pgg);
|
|
}
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|