/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** Author: ShonK Project: Kauai Reviewed: Copyright (c) Microsoft Corporation Graphics object code. ***************************************************************************/ #include "frame.h" ASSERTNAME BEGIN_CMD_MAP(GOB, CMH) ON_CID_GEN(cidKey, &GOB::FCmdKeyCore, pvNil) ON_CID_GEN(cidSelIdle, &GOB::FCmdSelIdle, pvNil) ON_CID_ME(cidActivateSel, &GOB::FCmdActivateSel, pvNil) ON_CID_ME(cidBadKey, &GOB::FCmdBadKeyCore, pvNil) ON_CID_ME(cidCloseWnd, &GOB::FCmdCloseWnd, pvNil) ON_CID_ME(cidMouseDown, &GOB::FCmdTrackMouseCore, pvNil) ON_CID_ME(cidTrackMouse, &GOB::FCmdTrackMouseCore, pvNil) ON_CID_ME(cidMouseMove, &GOB::FCmdMouseMoveCore, pvNil) END_CMD_MAP_NIL() RTCLASS(GOB) RTCLASS(GTE) long GOB::_ginDefGob = kginSysInval; long GOB::_gridLast; /*************************************************************************** Fill in the elements of the GCB. ***************************************************************************/ void GCB::Set(long hid, PGOB pgob, ulong grfgob, long gin, RC *prcAbs, RC *prcRel) { Assert(hidNil != hid, "bad hid"); AssertNilOrPo(pgob, 0); _hid = hid; _pgob = pgob; _grfgob = grfgob; _gin = gin; if (pvNil == prcAbs) _rcAbs.Zero(); else _rcAbs = *prcAbs; if (pvNil == prcRel) _rcRel.Zero(); else _rcRel = *prcRel; } /*************************************************************************** Static method to shut down all GOBs. ***************************************************************************/ void GOB::ShutDown(void) { while (pvNil != _pgobScreen) { _pgobScreen->FAttachHwnd(hNil); // freeing the _pgobScreen also updates _pgobScreen to its sibling. // _pgobScreen is really the root of the forest. _pgobScreen->Release(); } } /*************************************************************************** Constructor for a graphics object. pgob is either the parent of the new gob or a sibling, according to (grfgob & fgobSibling). ***************************************************************************/ GOB::GOB(PGCB pgcb) : CMH(pgcb->_hid) { _Init(pgcb); } /*************************************************************************** Initialize the gob. ***************************************************************************/ void GOB::_Init(PGCB pgcb) { AssertVarMem(pgcb); AssertNilOrPo(pgcb->_pgob, 0); _grid = ++_gridLast; _ginDefault = pgcb->_gin; _fCreating = fTrue; if (pvNil == pgcb->_pgob) { Assert(pvNil == _pgobScreen, "screen gob already created"); _pgobScreen = this; } else if (pgcb->_grfgob & fgobSibling) { AssertPo(pgcb->_pgob, 0); _pgobPar = pgcb->_pgob->_pgobPar; _pgobSib = pgcb->_pgob->_pgobSib; pgcb->_pgob->_pgobSib = this; } else { AssertPo(pgcb->_pgob, 0); _pgobPar = pgcb->_pgob; _pgobSib = pgcb->_pgob->_pgobChd; pgcb->_pgob->_pgobChd = this; } if (pvNil != _pgobPar) _pgpt = _pgobPar->_pgpt; SetPos(&pgcb->_rcAbs, &pgcb->_rcRel); AssertThis(0); _fCreating = fFalse; } /*************************************************************************** Constructor for GOB. ***************************************************************************/ GOB::GOB(long hid) : CMH(hid) { GCB gcb(hid, GOB::PgobScreen()); _Init(&gcb); } /*************************************************************************** First tells the app that the gob is dying; then calls Release on all direct child gobs of this GOB; then calls delete on itself. ***************************************************************************/ void GOB::Release(void) { AssertThis(0); PGOB pgob; if (--_cactRef > 0) return; //Mark this gob as being freed (may already be marked) _fFreeing = fTrue; //invalidate if (pvNil == _pgobPar || !_pgobPar->_fFreeing) InvalRc(pvNil); while (pvNil != (pgob = _pgobChd)) pgob->Release(); delete this; } /*************************************************************************** Destructor for the graphics object class. ***************************************************************************/ GOB::~GOB(void) { AssertThis(0); PGOB *ppgob; //remove it from the sibling list Assert(pvNil == _pgobChd, "gob still has children"); for (ppgob = pvNil != _pgobPar ? &_pgobPar->_pgobChd : &_pgobScreen; *ppgob != this && pvNil != *ppgob; ppgob = &(*ppgob)->_pgobSib) { } if (*ppgob == this) *ppgob = _pgobSib; else Bug("corrupt gob tree"); //nuke its port and hwnd if (pvNil != _pgpt && (pvNil == _pgobPar || _pgpt != _pgobPar->_pgpt)) ReleasePpo(&_pgpt); if (_hwnd != hNil) _DestroyHwnd(_hwnd); ReleasePpo(&_pglrtvm); ReleasePpo(&_pcurs); } /*************************************************************************** Called by OS specific code when an hwnd is activated or deactivated. We inform the entire gob subtree for the hwnd so individual elements can do whatever is necessary. This is a static member function. ***************************************************************************/ void GOB::ActivateHwnd(HWND hwnd, bool fActive) { PGOB pgob; if (pvNil == (pgob = PgobFromHwnd(hwnd))) return; //if it's becoming active, bring it to the front in our gob tree. if (fActive) pgob->SendBehind(pvNil); GTE gte; ulong grfgte; gte.Init(pgob, fgteBackToFront); while (gte.FNextGob(&pgob, &grfgte, fgteNil)) { if (grfgte & fgtePre) pgob->_ActivateHwnd(fActive); } } /*************************************************************************** Make this the first child of its parent. Doesn't invalidate anything. ***************************************************************************/ void GOB::BringToFront(void) { AssertThis(0); SendBehind(pvNil); } /*************************************************************************** Put this GOB behind the given sibling. If pgobBehind is nil, does the equivalent of a BringToFront. Asserts that pgobBehind and this gob have the same parent. Does no invalidation. ***************************************************************************/ void GOB::SendBehind(PGOB pgobBehind) { AssertThis(0); AssertNilOrPo(pgobBehind, 0); PGOB pgob; if (pvNil != pgobBehind && pgobBehind->_pgobPar != _pgobPar) { Bug("don't have the same parent"); return; } pgob = PgobPrevSib(); if (pgob == pgobBehind) return; //nothing to do //take this gob out of the sibling list if (pvNil == pgob) { Assert(_pgobPar->_pgobChd == this, "corrupt GOB tree"); _pgobPar->_pgobChd = _pgobSib; } else { Assert(pgob->_pgobSib == this, "corrupt GOB tree"); pgob->_pgobSib = _pgobSib; } //now insert it after pgobBehind if (pvNil == pgobBehind) { _pgobSib = _pgobPar->_pgobChd; _pgobPar->_pgobChd = this; } else { _pgobSib = pgobBehind->_pgobSib; pgobBehind->_pgobSib = this; AssertPo(pgobBehind, 0); } AssertThis(0); } /*************************************************************************** Invalidate the given rc in this gob. If gin is ginNil, nothing is done. If gin is kginRedraw, the area is redraw. If gin is kginMark, the area is marked dirty at the framework level. If gin is kginSysInval, the area is marked dirty at the operating system level. In all cases, passing pvNil for prc affects the whole gob. ***************************************************************************/ void GOB::InvalRc(RC *prc, long gin) { AssertThis(0); AssertNilOrVarMem(prc); PT dpt; RC rc; PGOB pgob; RCS rcs; if (kginDefault == gin) { gin = _ginDefault; if (kginDefault == gin) gin = _ginDefGob; } if (ginNil == gin) return; GetRcVis(&rc, cooLocal); if (pvNil != prc) rc.FIntersect(prc); if (rc.FEmpty()) return; for (pgob = this; pvNil != pgob && pgob->_hwnd == hNil; pgob = pgob->_pgobPar) { rc.Offset(pgob->_rcCur.xpLeft, pgob->_rcCur.ypTop); } if (pvNil == pgob) return; switch (gin) { default: Bug("bad gin value"); break; case kginDraw: //do this so we do whatever the app does during a normal draw, such //as drawing offscreen.... vpappb->UpdateHwnd(pgob->_hwnd, &rc); break; case kginMark: vpappb->MarkRc(&rc, pgob); break; case kginSysInval: rcs = RCS(rc); InvalHwndRcs(pgob->_hwnd, &rcs); break; } } /*************************************************************************** Validate the given rc in this gob. If gin is ginNil, nothing is done. If gin is kginRedraw, the area is validated at both the framework level and the system level. If gin is kginMark or kginSysInval, the area is validated only at the given level. In any case, passing pvNil for prc affects the whole gob. ***************************************************************************/ void GOB::ValidRc(RC *prc, long gin) { AssertThis(0); RC rc; PT dpt; PGOB pgob; if (kginDefault == gin) { gin = _ginDefault; if (kginDefault == gin) gin = _ginDefGob; } if (ginNil == gin) return; GetRcVis(&rc, cooLocal); if (pvNil != prc) rc.FIntersect(prc); if (rc.FEmpty()) return; for (pgob = this; pvNil != pgob && pgob->_hwnd == hNil; pgob = pgob->_pgobPar) { rc.Offset(pgob->_rcCur.xpLeft, pgob->_rcCur.ypTop); } if (pvNil == pgob) return; if (gin != kginSysInval) { // do a framework level validation vpappb->UnmarkRc(&rc, pgob); } if (gin != kginMark) { // do a system level validation RCS rcs; rcs = RCS(rc); ValidHwndRcs(pgob->_hwnd, &rcs); } } /*************************************************************************** Get the dirty portion of this gob. Return true iff the dirty rectangle is non-empty. If gin is kginDraw, gets the union of the marked area and system-invalidated area. ***************************************************************************/ bool GOB::FGetRcInval(RC *prc, long gin) { AssertThis(0); AssertVarMem(prc); RC rc; PGOB pgob; PT dpt(0, 0); prc->Zero(); if (kginDefault == gin) { gin = _ginDefault; if (kginDefault == gin) gin = _ginDefGob; } GetRcVis(&rc, cooLocal); if (rc.FEmpty() || ginNil == gin) return fFalse; for (pgob = this; pvNil != pgob && pgob->_hwnd == hNil; pgob = pgob->_pgobPar) { dpt.Offset(pgob->_rcCur.xpLeft, pgob->_rcCur.ypTop); } rc.Offset(dpt.xp, dpt.yp); if (pvNil == pgob) return fFalse; if (kginSysInval != gin) { //get any marked area vpappb->FGetMarkedRc(pgob->_hwnd, prc); } if (kginMark != gin) { //get any system invalidated area RCS rcs; RC rcT; #ifdef WIN GetUpdateRect(pgob->_hwnd, &rcs, fFalse); #endif //WIN #ifdef MAC PPRT pprt; rcs = (*pgob->_hwnd->updateRgn)->rgnBBox; GetPort(&pprt); SetPort(&pgob->_hwnd->port); GlobalToLocal((PTS *)&rcs); GlobalToLocal((PTS *)&rcs + 1); SetPort(pprt); #endif //MAC rcT = RC(rcs); if (rcT.FIntersect(&rc)) prc->Union(&rcT); } prc->Offset(-dpt.xp, -dpt.yp); return !prc->FEmpty(); } /*************************************************************************** Scrolls the given rectangle in the GOB. Translates any invalid portion. Handles this being covered by any GOBs or system windows. If prc is nil, the entire content rectangle is used. ***************************************************************************/ void GOB::Scroll(RC *prc, long dxp, long dyp, long gin, RC *prcBad1, RC *prcBad2) { AssertThis(0); AssertNilOrVarMem(prc); AssertNilOrVarMem(prcBad1); AssertNilOrVarMem(prcBad2); RC rc, rcBad1, rcBad2, rcInval, rcT; PT dpt(0, 0); PGOB pgob, pgobT; GTE gte; ulong grfgte, grfgteIn; bool fFound; if (kginDefault == gin) { gin = _ginDefault; if (kginDefault == gin) gin = _ginDefGob; } if (pvNil != prcBad1) prcBad1->Zero(); if (pvNil != prcBad2) prcBad2->Zero(); if (dxp == 0 && dyp == 0) return; GetRcVis(&rc, cooLocal); if (pvNil != prc && !rc.FIntersect(prc)) return; for (pgob = this; pvNil != pgob && pgob->_hwnd == hNil; pgob = pgob->_pgobPar) { dpt.Offset(pgob->_rcCur.xpLeft, pgob->_rcCur.ypTop); } rc.Offset(dpt.xp, dpt.yp); if (pvNil == pgob) return; //check for GOBs on top of this one. gte.Init(pgob, fgteBackToFront); fFound = fFalse; grfgteIn = fgteNil; while (gte.FNextGob(&pgobT, &grfgte, grfgteIn)) { if (!(grfgte & fgtePre)) continue; if (!fFound) { fFound = pgobT == this; continue; } pgobT->GetRc(&rcT, cooHwnd); if (rcT.FIntersect(&rc)) { //there is a GOB on top of this one, just invalidate the //rectangle to be scrolled pgob->ValidRc(&rc, kginDraw); pgob->InvalRc(&rc, gin); if (pvNil != prcBad1) prcBad1->OffsetCopy(&rc, -dpt.xp, -dpt.yp); return; } grfgteIn = fgteSkipToSib; } //translate any marked area if (FGetRcInval(&rcT, kginMark)) { //something is marked rcT.Offset(dpt.xp, dpt.yp); if (rcT.FIntersect(&rc)) { pgob->ValidRc(&rcT, kginMark); rcT.Offset(dxp, dyp); if (rcT.FIntersect(&rc)) pgob->InvalRc(&rcT, kginMark); } } #ifdef WIN // SW_INVALIDATE invalidates any uncovered stuff and translates any // previously invalid stuff RCS rcs = RCS(rc); ScrollWindowEx(pgob->_hwnd, dxp, dyp, pvNil, &rcs, hNil, pvNil, SW_INVALIDATE); // compute the bad rectangles GNV::GetBadRcForScroll(&rc, dxp, dyp, &rcBad1, &rcBad2); if (pvNil != prcBad1) prcBad1->OffsetCopy(&rcBad1, -dpt.xp, -dpt.yp); if (pvNil != prcBad2) prcBad2->OffsetCopy(&rcBad2, -dpt.xp, -dpt.yp); switch (gin) { default: Bug("bad gin"); break; case kginDraw: UpdateWindow(pgob->_hwnd); break; case kginSysInval: break; case kginMark: vpappb->MarkRc(&rcBad1, pgob); vpappb->MarkRc(&rcBad2, pgob); // fall through case ginNil: if (!rcBad1.FEmpty()) { rcs = RCS(rcBad1); ValidateRect(pgob->_hwnd, &rcs); } if (!rcBad2.FEmpty()) { rcs = RCS(rcBad2); ValidateRect(pgob->_hwnd, &rcs); } break; } #endif //WIN #ifdef MAC HRGN hrgn; //Make sure the vis region intersected with the rectangle to scroll is //a rectangle if (!FCreateRgn(&hrgn, &rc) || !FIntersectRgn(hrgn, pgob->_hwnd->port.visRgn, hrgn) || !FRectRgn(hrgn, &rc)) { //there is something on top of this one, just invalidate the //rectangle to be scrolled FreePhrgn(&hrgn); pgob->ValidRc(&rc, kginDraw); pgob->InvalRc(&rc, gin); if (pvNil != prcBad1) prcBad1->OffsetCopy(&rc, -dpt.xp, -dpt.yp); return; } FreePhrgn(&hrgn); GNV gnv(pgob); gnv.ScrollRc(&rc, dxp, dyp, &rcBad1, &rcBad2); //translate any invalid area if (FGetRcInval(&rcT, kginSysInval)) { //something is invalid rcT.Offset(dpt.xp, dpt.yp); if (rcT.FIntersect(&rc)) { pgob->ValidRc(&rcT, kginSysInval); rcT.Offset(dxp, dyp); if (rcT.FIntersect(&rc)) pgob->InvalRc(&rcT, kginSysInval); } } if (pvNil != prcBad1) prcBad1->OffsetCopy(&rcBad1, -dpt.xp, -dpt.yp); if (pvNil != prcBad2) prcBad2->OffsetCopy(&rcBad2, -dpt.xp, -dpt.yp); switch (gin) { default: Bug("bad gin"); //fall through case ginNil: break; case kginDraw: vpappb->MarkRc(&rcBad1, pgob); vpappb->MarkRc(&rcBad2, pgob); vpappb->UpdateMarked(); break; case kginSysInval: pgob->InvalRc(&rcBad1); pgob->InvalRc(&rcBad2); break; case kginMark: vpappb->MarkRc(&rcBad1, pgob); vpappb->MarkRc(&rcBad2, pgob); break; } #endif //MAC } /*************************************************************************** Draw the gob and its children into the given port. If the pgpt is nil, use the GOB's UI (natural) port. If the prc is pvNil, use the GOB's rectangle based at (0, 0). If prcClip is not nil, only GOB's that intersect prcClip will be drawn. prcClip is in the GOB's local coordinates. ***************************************************************************/ void GOB::DrawTree(PGPT pgpt, RC *prc, RC *prcClip, ulong grfgob) { AssertThis(0); AssertNilOrPo(pgpt, 0); AssertNilOrVarMem(prc); AssertNilOrVarMem(prcClip); RC rcSrc, rcClip, rcSrcGob, rcClipGob, rcVis, rc; //to translate from this->local to pgob->local coordinates add dpt PT dpt; if (pgpt == pvNil && (pgpt = _pgpt) == pvNil) { Bug("no port to draw to"); return; } if (pvNil != prc && prc->FEmpty()) return; dpt = _rcCur.PtTopLeft(); //get the source and clip rectangles in local (this) coordinates rcSrc = _rcCur; if (rcSrc.FEmpty()) return; rcSrc.OffsetToOrigin(); if (pvNil == prcClip) rcClip = rcSrc; else if (!rcClip.FIntersect(prcClip, &rcSrc)) return; GNV gnv(pgpt); GTE gte; ulong grfgte, grfgteIn; PGOB pgob; gte.Init(this, fgteBackToFront); grfgteIn = fgteNil; while (gte.FNextGob(&pgob, &grfgte, grfgteIn)) { if (pgob->_pgpt != _pgpt || pgob->_rcCur.FEmpty()) goto LNextSib; grfgteIn = fgteNil; if (grfgte & fgtePre) { if (grfgob & (fgobAutoVis | fgobUseVis)) { pgob->GetRcVis(&rcVis, cooLocal); if (rcVis.FEmpty()) goto LNextSib; } //get the source and clip rectangles in local (pgob) coordinates rcSrcGob = pgob->_rcCur; dpt.xp -= rcSrcGob.xpLeft; dpt.yp -= rcSrcGob.ypTop; rcSrcGob.OffsetToOrigin(); rcClipGob = rcClip + dpt; if (!rcClipGob.FIntersect(&rcSrcGob)) goto LOffsetNextSib; // set the source rectangle gnv.SetRcSrc(&rcSrcGob); //set the dest rc rc = rcSrcGob - dpt; if (pvNil != prc) rc.Map(&rcSrc, prc); gnv.SetRcDst(&rc); //set the vis rectangle if (grfgob & (fgobAutoVis | fgobUseVis)) { if (!rcVis.FIntersect(&rcClipGob)) { LOffsetNextSib: dpt.xp += pgob->_rcCur.xpLeft; dpt.yp += pgob->_rcCur.ypTop; LNextSib: grfgteIn = fgteSkipToSib; continue; } if (!(grfgob & fgobUseVis) && rcSrcGob == rcVis) gnv.SetRcVis(pvNil); else gnv.SetRcVis(&rcVis); rcClipGob = rcVis; } //draw the gob pgob->Draw(&gnv, &rcClipGob); } if (grfgte & fgtePost) { dpt.xp += pgob->_rcCur.xpLeft; dpt.yp += pgob->_rcCur.ypTop; } } } /*************************************************************************** Draw the gob and its children into the given port. If the pgpt is nil, use the GOB's UI (natural) port. If the prc is pvNil, use the GOB's rectangle based at (0, 0). Only GOB's that intersect pregn will be drawn. pregn is in the GOB's local coordinates. ***************************************************************************/ void GOB::DrawTreeRgn(PGPT pgpt, RC *prc, REGN *pregn, ulong grfgob) { AssertThis(0); AssertNilOrPo(pgpt, 0); AssertNilOrVarMem(prc); AssertPo(pregn, 0); RC rcSrc, rcSrcGob, rcClipGob, rcVis, rc; //to translate from this->local to pgob->local coordinates add dpt PT dpt; if (pgpt == pvNil && (pgpt = _pgpt) == pvNil) { Bug("no port to draw to"); return; } if (pvNil != prc && prc->FEmpty()) return; if (pregn->FEmpty()) return; dpt = _rcCur.PtTopLeft(); //get the source rectangle and clip region in local (this) coordinates rcSrc = _rcCur; rcSrc.OffsetToOrigin(); if (rcSrc.FEmpty()) return; GNV gnv(pgpt); GTE gte; ulong grfgte, grfgteIn; PGOB pgob; PREGN pregnClip; PREGN pregnClipGob = pvNil; if (pvNil == (pregnClip = REGN::PregnNew(&rcSrc)) || pvNil == (pregnClipGob = REGN::PregnNew()) || !pregnClip->FIntersect(pregn)) { goto LFail; } if (pregnClip->FEmpty()) goto LDone; gte.Init(this, fgteBackToFront); grfgteIn = fgteNil; while (gte.FNextGob(&pgob, &grfgte, grfgteIn)) { if (pgob->_pgpt != _pgpt || pgob->_rcCur.FEmpty()) goto LNextSib; grfgteIn = fgteNil; if (grfgte & fgtePre) { if (grfgob & (fgobAutoVis | fgobUseVis)) { pgob->GetRcVis(&rcVis, cooLocal); if (rcVis.FEmpty()) goto LNextSib; } //get the source and clip rectangles in local (pgob) coordinates rcSrcGob = pgob->_rcCur; dpt.xp -= rcSrcGob.xpLeft; dpt.yp -= rcSrcGob.ypTop; rcSrcGob.OffsetToOrigin(); pregnClipGob->SetRc(&rcSrcGob); pregnClipGob->Offset(-dpt.xp, -dpt.yp); if (!pregnClipGob->FIntersect(pregnClip)) goto LFail; if (pregnClipGob->FEmpty(&rcClipGob)) goto LOffsetNextSib; rcClipGob.Offset(dpt.xp, dpt.yp); // set the source rectangle gnv.SetRcSrc(&rcSrcGob); //set the dest rc rc = rcSrcGob - dpt; if (pvNil != prc) rc.Map(&rcSrc, prc); gnv.SetRcDst(&rc); //set the vis rectangle if (grfgob & (fgobAutoVis | fgobUseVis)) { if (!rcVis.FIntersect(&rcClipGob)) { LOffsetNextSib: dpt.xp += pgob->_rcCur.xpLeft; dpt.yp += pgob->_rcCur.ypTop; LNextSib: grfgteIn = fgteSkipToSib; continue; } if (!(grfgob & fgobUseVis) && rcSrcGob == rcVis) gnv.SetRcVis(pvNil); else gnv.SetRcVis(&rcVis); } // draw the gob // NOTE: we use pregn and not pregnClip or pregnClipGob for speed. // Using pregn, the cached hrgn stuff kicks in to only require // one hrgn creation. If we use pregnClip we only have one hrgn // creation here, but another one when the offscreen bitmap is // copied to the screen. Using pregnClipGob would cause lots // of hregn creations. pgpt->ClipToRegn(&pregn); pgob->Draw(&gnv, &rcClipGob); pgpt->ClipToRegn(&pregn); } if (grfgte & fgtePost) { dpt.xp += pgob->_rcCur.xpLeft; dpt.yp += pgob->_rcCur.ypTop; } } LDone: ReleasePpo(&pregnClip); ReleasePpo(&pregnClipGob); return; LFail: pregn->FEmpty(&rc); DrawTree(pgpt, prc, &rc, grfgob); } /*************************************************************************** Draw the GOB into the given graphics environment. On entry, the source rectangle of the GNV is set to (0, 0, dxp, dyp), where dxp and dyp are the width and height of the gob. The gob is free to change the source rectangle, but should not touch the destination rectangle. ***************************************************************************/ void GOB::Draw(PGNV pgnv, RC *prcClip) { AssertThis(0); } /*************************************************************************** Make this gob fill up its parent's interior. ***************************************************************************/ void GOB::Maximize(void) { AssertThis(0); _rcAbs.Zero(); _rcRel.xpLeft = _rcRel.ypTop = krelZero; _rcRel.xpRight = _rcRel.ypBottom = krelOne; _SetRcCur(); } /*************************************************************************** Set the gob's position. Invalidates both the old and new position. ***************************************************************************/ void GOB::SetPos(RC *prcAbs, RC *prcRel) { AssertThis(0); AssertNilOrVarMem(prcAbs); AssertNilOrVarMem(prcRel); if (prcAbs == pvNil) _rcAbs.Zero(); else _rcAbs = *prcAbs; if (prcRel == pvNil) _rcRel.Zero(); else _rcRel = *prcRel; _SetRcCur(); } /*************************************************************************** Get the gob's position. ***************************************************************************/ void GOB::GetPos(RC *prcAbs, RC *prcRel) { AssertThis(0); AssertNilOrVarMem(prcAbs); AssertNilOrVarMem(prcRel); if (pvNil != prcAbs) *prcAbs = _rcAbs; if (pvNil != prcRel) *prcRel = _rcRel; } /*************************************************************************** Set the gob's rectangle from its hwnd. ***************************************************************************/ void GOB::SetRcFromHwnd(void) { AssertThis(0); Assert(_hwnd != hNil, "no hwnd"); _SetRcCur(); } /*************************************************************************** Get the bounding rectangle of the gob in the given coordinates. ***************************************************************************/ void GOB::GetRc(RC *prc, long coo) { AssertThis(0); AssertVarMem(prc); PT dpt; *prc = _rcCur; _HwndGetDptFromCoo(&dpt, coo); prc->Offset(dpt.xp - _rcCur.xpLeft, dpt.yp - _rcCur.ypTop); } /*************************************************************************** Get the visible rectangle of the gob in the given coordinates. ***************************************************************************/ void GOB::GetRcVis(RC *prc, long coo) { AssertThis(0); AssertVarMem(prc); PT dpt; *prc = _rcVis; _HwndGetDptFromCoo(&dpt, coo); prc->Offset(dpt.xp - _rcCur.xpLeft, dpt.yp - _rcCur.ypTop); } /*************************************************************************** Get the rectangle for the gob in cooHwnd coordinates and return the enclosing hwnd (if there is one). This is a protected API. ***************************************************************************/ HWND GOB::_HwndGetRc(RC *prc) { PT dpt; HWND hwnd; *prc = _rcCur; hwnd = _HwndGetDptFromCoo(&dpt, cooHwnd); prc->Offset(dpt.xp - _rcCur.xpLeft, dpt.yp - _rcCur.ypTop); return hwnd; } /*************************************************************************** Return the hwnd that contains this GOB. ***************************************************************************/ HWND GOB::HwndContainer(void) { AssertThis(0); PGOB pgob = this; while (pvNil != pgob) { if (hNil != pgob->_hwnd) return pgob->_hwnd; pgob = pgob->_pgobPar; } return hNil; } /*************************************************************************** Map a point from cooSrc coordinates to cooDst coordinates (relative to the gob). ***************************************************************************/ void GOB::MapPt(PT *ppt, long cooSrc, long cooDst) { AssertThis(0); AssertVarMem(ppt); PT dpt; _HwndGetDptFromCoo(&dpt, cooSrc); ppt->xp -= dpt.xp; ppt->yp -= dpt.yp; _HwndGetDptFromCoo(&dpt, cooDst); ppt->xp += dpt.xp; ppt->yp += dpt.yp; } /*************************************************************************** Map an rc from cooSrc coordinates to cooDst coordinates (relative to the gob). ***************************************************************************/ void GOB::MapRc(RC *prc, long cooSrc, long cooDst) { AssertThis(0); AssertVarMem(prc); PT dpt; _HwndGetDptFromCoo(&dpt, cooSrc); prc->Offset(-dpt.xp, -dpt.yp); _HwndGetDptFromCoo(&dpt, cooDst); prc->Offset(dpt.xp, dpt.yp); } /*************************************************************************** Get the dxp and dyp to map from local coordinates to coo coordinates. If coo is cooHwnd or cooGlobal, also return the containing hwnd (otherwise return hNil). ***************************************************************************/ HWND GOB::_HwndGetDptFromCoo(PT *pdpt, long coo) { PGOB pgob, pgobT; HWND hwnd = hNil; switch (coo) { default: Assert(coo == cooLocal, "bad coo"); pdpt->xp = pdpt->yp = 0; break; case cooParent: pdpt->xp = _rcCur.xpLeft; pdpt->yp = _rcCur.ypTop; break; case cooGpt: pdpt->xp = pdpt->yp = 0; for (pgob = this; (pgobT = pgob->_pgobPar) != pvNil && pgobT->_pgpt == _pgpt; pgob = pgobT) { pdpt->xp += pgob->_rcCur.xpLeft; pdpt->yp += pgob->_rcCur.ypTop; } break; case cooHwnd: case cooGlobal: pdpt->xp = pdpt->yp = 0; for (pgob = this; pgob != pvNil && pgob->_hwnd == hNil; pgob = pgob->_pgobPar) { pdpt->xp += pgob->_rcCur.xpLeft; pdpt->yp += pgob->_rcCur.ypTop; } if (pvNil != pgob) hwnd = pgob->_hwnd; if (cooGlobal == coo && hNil != hwnd) { //Map from Hwnd to screen PTS pts; pts = PTS(*pdpt); #ifdef WIN ClientToScreen(hwnd, &pts); #endif //WIN #ifdef MAC PPRT pprt; GetPort(&pprt); SetPort(&hwnd->port); LocalToGlobal(&pts); SetPort(pprt); #endif //MAC *pdpt = PT(pts); } break; } return hwnd; } /*************************************************************************** Get the minimum and maximum size for a gob. ***************************************************************************/ void GOB::GetMinMax(RC *prcMinMax) { prcMinMax->xpLeft = prcMinMax->ypTop = 0; //yes kswMax for safety prcMinMax->xpRight = prcMinMax->ypBottom = kswMax; } /*************************************************************************** Static method to find the gob containing the given point (in global coordinates). If the mouse isn't over a GOB, this returns pvNil and sets *pptLocal to the passed in (xp, yp). ***************************************************************************/ PGOB GOB::PgobFromPtGlobal(long xp, long yp, PT *pptLocal) { AssertNilOrVarMem(pptLocal); HWND hwnd; PTS pts; PGOB pgob; #ifdef MAC PPRT pprt; pts.h = (short)xp; pts.v = (short)yp; if (inContent != FindWindow(pts, (WindowPtr *)&hwnd) || hNil == hwnd || pvNil == (pgob = PgobFromHwnd(hwnd))) { if (pvNil != pptLocal) { pptLocal->xp = xp; pptLocal->yp = yp; } return pvNil; } GetPort(&pprt); SetPort(&hwnd->port); GlobalToLocal(&pts); SetPort(pprt); return pgob->PgobFromPt(pts.h, pts.v, pptLocal); #endif //MAC #ifdef WIN pts.x = xp; pts.y = yp; if (hNil == (hwnd = WindowFromPoint(pts)) || pvNil == (pgob = PgobFromHwnd(hwnd))) { if (pvNil != pptLocal) { pptLocal->xp = xp; pptLocal->yp = yp; } return pvNil; } ScreenToClient(hwnd, &pts); return pgob->PgobFromPt(pts.x, pts.y, pptLocal); #endif //WIN } /*************************************************************************** Determine which gob in the tree starting with this GOB the given point is in. This may return pvNil if no gob claims to contain the given point. xp, yp is assumed to be in this gob's parent's coordinates. This is recursive, so a GOB can build it's own world and hit testing method. ***************************************************************************/ PGOB GOB::PgobFromPt(long xp, long yp, PT *pptLocal) { AssertThis(0); xp -= _rcCur.xpLeft; yp -= _rcCur.ypTop; if (FPtInBounds(xp, yp)) { // the point is in our bounding rectangle, so give the children // a whack at it PGOB pgob, pgobT; for (pgob = _pgobChd; pvNil != pgob; pgob = pgob->_pgobSib) { if (pvNil != (pgobT = pgob->PgobFromPt(xp, yp, pptLocal))) return pgobT; } } // call FPtIn whether or not FInBounds returned true so a parent can will some // extra space to a child if (FPtIn(xp, yp)) { if (pptLocal != pvNil) { pptLocal->xp = xp; pptLocal->yp = yp; } return this; } return pvNil; } /*************************************************************************** Determine whether the given point (in this gob's local coordinates) is in this gob. This will be subclassed by all non-rectangular gobs (including ones that don't want to respond to the mouse at all). We handle tool tips here to avoid bugs of omission and for convenience. ***************************************************************************/ bool GOB::FPtIn(long xp, long yp) { AssertThis(0); RC rc; // tool tips and their children are "invisible". if (khidToolTip == Hid()) return fFalse; GetRc(&rc, cooLocal); return rc.FPtIn(xp, yp); } /*************************************************************************** Determine whether the given point (in this gob's local coordinates) is in this gob's bounding rectangle. This indicates whether it's OK to ask the GOB's children whether the point is in them. This will be subclassed by all GOBs that don't want to respond to the mouse. We handle tool tips here to avoid bugs of omission and for convenience. If this returns false, PgobFromPt will still call FPtIn. ***************************************************************************/ bool GOB::FPtInBounds(long xp, long yp) { AssertThis(0); RC rc; // tool tips and their children are "invisible". if (khidToolTip == Hid()) return fFalse; GetRc(&rc, cooLocal); return rc.FPtIn(xp, yp); } /*************************************************************************** Default mouse down handler just enqueues a cidActivateSel, cidSelIdle and a cidTrackMouse command. ***************************************************************************/ void GOB::MouseDown(long xp, long yp, long cact, ulong grfcust) { AssertThis(0); Assert(grfcust & fcustMouse, "grfcust wrong"); CMD_MOUSE cmd; vpcex->EnqueueCid(cidActivateSel, this); vpcex->EnqueueCid(cidSelIdle, pvNil, pvNil, fTrue, Hid()); cmd.cid = cidMouseDown; cmd.pcmh = this; cmd.pgg = pvNil; cmd.xp = xp; cmd.yp = yp; cmd.grfcust = grfcust; cmd.cact = cact; vpcex->EnqueueCmd((PCMD)&cmd); } /*************************************************************************** Set the _rcCur values based on _rcAbs and _rcRel. If there is an OS window associated with this GOB, set _rcCur based on the hwnd. Invalidates the old and new rectangles. ***************************************************************************/ void GOB::_SetRcCur(void) { PGOB pgob; GTE gte; ulong grfgte; RC rc, rcVis; //invalidate the original rc InvalRc(pvNil); gte.Init(this, fgteNil); while (gte.FNextGob(&pgob, &grfgte, fgteNil)) { if (!(grfgte & fgtePre)) continue; //get the new rc and the rcVis of the parent (in the parent's local //coordinates) if (pgob->_hwnd != hNil) { RCS rcs; GetClientRect(pgob->_hwnd, &rcs); rc = rcs; rcVis.Max(); } else if (pgob->_pgobPar != pvNil) { long dxp; long dyp; dxp = pgob->_pgobPar->_rcCur.Dxp(); dyp = pgob->_pgobPar->_rcCur.Dyp(); rc.xpLeft = pgob->_rcAbs.xpLeft + LwMulDiv(dxp, pgob->_rcRel.xpLeft, krelOne); rc.ypTop = pgob->_rcAbs.ypTop + LwMulDiv(dyp, pgob->_rcRel.ypTop, krelOne); rc.xpRight = pgob->_rcAbs.xpRight + LwMulDiv(dxp, pgob->_rcRel.xpRight, krelOne); rc.ypBottom = pgob->_rcAbs.ypBottom + LwMulDiv(dyp, pgob->_rcRel.ypBottom, krelOne); pgob->_pgobPar->GetRcVis(&rcVis, cooLocal); } else { rc = pgob->_rcAbs; rcVis.Max(); } //intersect the parents visible portion with the new rc to get //this gob's visible portion rcVis.FIntersect(&rc); pgob->_rcCur = rc; pgob->_rcVis = rcVis; if (grfgte & fgteRoot) { //invalidate the new rectangle - we do it here so children //can draw and validate themselves if they want InvalRc(pvNil); } //tell the gob that it has a new rectangle pgob->_NewRc(); } } /*************************************************************************** Return the previous sibling for the gob. ***************************************************************************/ PGOB GOB::PgobPrevSib(void) { PGOB pgob; pgob = _pgobPar == pvNil ? _pgobScreen : _pgobPar->_pgobChd; if (pgob == this) return pvNil; for ( ; pgob != pvNil && pgob->_pgobSib != this; pgob = pgob->_pgobSib) ; if (pgob == pvNil) { Bug("corrupt gob tree"); return pvNil; } Assert(pgob->_pgobSib == this, "wrong logic"); return pgob; } /*************************************************************************** Return the last child of the gob. ***************************************************************************/ PGOB GOB::PgobLastChild(void) { PGOB pgob; if ((pgob = _pgobChd) == pvNil) return pvNil; for ( ; pgob->_pgobSib != pvNil; pgob = pgob->_pgobSib) ; Assert(pgob->_pgobSib == pvNil, "wrong logic"); return pgob; } /*************************************************************************** Create a new MDI window and attach it to the gob. ***************************************************************************/ bool GOB::FCreateAndAttachMdi(PSTN pstnTitle) { AssertThis(0); AssertPo(pstnTitle, 0); HWND hwnd; if ((hwnd = _HwndNewMdi(pstnTitle)) == hNil) return fFalse; if (!FAttachHwnd(hwnd)) { _DestroyHwnd(hwnd); return fFalse; } AssertThis(0); return fTrue; } /*************************************************************************** Static method: find the currently active MDI gob. ***************************************************************************/ PGOB GOB::PgobMdiActive(void) { HWND hwnd; if (hNil == (hwnd = HwndMdiActive())) return pvNil; return PgobFromHwnd(hwnd); } /*************************************************************************** Static method: find the first gob of the given class in the screen's gob tree. ***************************************************************************/ PGOB GOB::PgobFromClsScr(long cls) { if (pvNil == _pgobScreen) return pvNil; return _pgobScreen->PgobFromCls(cls); } /*************************************************************************** Find a gob in this gob's subtree that is of the given class. ***************************************************************************/ PGOB GOB::PgobFromCls(long cls) { AssertThis(0); GTE gte; ulong grfgte; PGOB pgob; gte.Init(this, fgteNil); while (gte.FNextGob(&pgob, &grfgte, fgteNil)) { if (pgob->FIs(cls)) return pgob; } return pvNil; } /*************************************************************************** Find a direct child of this gob of the given class. ***************************************************************************/ PGOB GOB::PgobChildFromCls(long cls) { AssertThis(0); PGOB pgob; for (pgob = _pgobChd; pvNil != pgob; pgob = pgob->_pgobSib) { if (pgob->FIs(cls)) return pgob; } return pvNil; } /*************************************************************************** Find a gob of the given class in the parent chain of this gob. ***************************************************************************/ PGOB GOB::PgobParFromCls(long cls) { AssertThis(0); PGOB pgob; for (pgob = _pgobPar; pvNil != pgob; pgob = pgob->_pgobPar) { if (pgob->FIs(cls)) return pgob; } return pvNil; } /*************************************************************************** Static method: find the first gob with the given hid in the screen's gob tree. ***************************************************************************/ PGOB GOB::PgobFromHidScr(long hid) { Assert(hid != hidNil, "nil hid"); if (pvNil == _pgobScreen) return pvNil; return _pgobScreen->PgobFromHid(hid); } /*************************************************************************** Find a gob in this gobs subtree having the given hid. ***************************************************************************/ PGOB GOB::PgobFromHid(long hid) { AssertThis(0); GTE gte; ulong grfgte; PGOB pgob; gte.Init(this, fgteNil); while (gte.FNextGob(&pgob, &grfgte, fgteNil)) { if (pgob->Hid() == hid) return pgob; } return pvNil; } /*************************************************************************** Find a direct child of this gob having the given hid. ***************************************************************************/ PGOB GOB::PgobChildFromHid(long hid) { AssertThis(0); PGOB pgob; for (pgob = _pgobChd; pvNil != pgob; pgob = pgob->_pgobSib) { if (pgob->Hid() == hid) return pgob; } return pvNil; } /*************************************************************************** Find a gob with the given hid in the parent chain of this gob. ***************************************************************************/ PGOB GOB::PgobParFromHid(long hid) { AssertThis(0); PGOB pgob; for (pgob = _pgobPar; pvNil != pgob; pgob = pgob->_pgobPar) { if (pgob->Hid() == hid) return pgob; } return pvNil; } /*************************************************************************** Find a gob in this gobs subtree having the given gob run-time id. ***************************************************************************/ PGOB GOB::PgobFromGrid(long grid) { AssertThis(0); GTE gte; ulong grfgte; PGOB pgob; gte.Init(this, fgteNil); while (gte.FNextGob(&pgob, &grfgte, fgteNil)) { if (pgob->Grid() == grid) return pgob; } return pvNil; } /*************************************************************************** Handles a close command. ***************************************************************************/ bool GOB::FCmdCloseWnd(PCMD pcmd) { AssertThis(0); Release(); return fTrue; } /*************************************************************************** Handles a mouse track command. ***************************************************************************/ bool GOB::FCmdTrackMouse(PCMD_MOUSE pcmd) { AssertThis(0); return fTrue; } /*************************************************************************** Command function to handle a key stroke. ***************************************************************************/ bool GOB::FCmdKey(PCMD_KEY pcmd) { return fFalse; } /*************************************************************************** Command function to handle a bad key command (sent by a child to its parent). ***************************************************************************/ bool GOB::FCmdBadKey(PCMD_BADKEY pcmd) { return fFalse; } /*************************************************************************** Do selection idle processing. Make sure the selection is on or off according to rglw[0] (non-zero means on) and set rglw[0] to false. Always return false. ***************************************************************************/ bool GOB::FCmdSelIdle(PCMD pcmd) { AssertThis(0); AssertVarMem(pcmd); return fFalse; } /*************************************************************************** Activate the selection. Default does nothing. ***************************************************************************/ bool GOB::FCmdActivateSel(PCMD pcmd) { AssertThis(0); AssertVarMem(pcmd); return fFalse; } /*************************************************************************** The mouse moved in this GOB, set the cursor. ***************************************************************************/ bool GOB::FCmdMouseMove(PCMD_MOUSE pcmd) { AssertThis(0); AssertVarMem(pcmd); vpappb->SetCurs(_pcurs); return fTrue; } /*************************************************************************** Drag the rectangle, restricting to [zpMin, zpLim). While zp is in [zpMinActive, zpLimActive), the bar is filled with solid invert, otherwise with patterned (50%) invert. ***************************************************************************/ long GOB::ZpDragRc(RC *prc, bool fVert, long zp, long zpMin, long zpLim, long zpMinActive, long zpLimActive) { RC rcBound, rcActive; PT pt, dpt; bool fActive, fActiveNew, fDown; GNV gnv(this); if (fVert) { pt.xp = 0; pt.yp = zp; rcBound.Set(0, zpMin, 1, zpLim); rcActive.Set(0, zpMinActive, 1, zpLimActive); } else { pt.xp = zp; pt.yp = 0; rcBound.Set(zpMin, 0, zpLim, 1); rcActive.Set(zpMinActive, 0, zpLimActive, 1); } //draw the initial bar fActive = rcActive.FPtIn(pt.xp, pt.yp); if (fActive) gnv.FillRc(prc, kacrInvert); else gnv.FillRcApt(prc, &vaptGray, kacrInvert, kacrClear); for (;;) { GetPtMouse(&dpt, &fDown); if (!fDown) break; //pin the pt to rcBound rcBound.PinPt(&dpt); Assert(dpt.xp == 0 || dpt.yp == 0, "bad pinned point"); if (pt == dpt) continue; //move the bar fActiveNew = rcActive.FPtIn(dpt.xp, dpt.yp); dpt -= pt; if (FPure(fActive) == FPure(fActiveNew)) { //invert the two pieces of the difference between //the new and old rectangles RC rc1, rc2; long dzp; rc1 = *prc; if (fVert) rc1.Transform(fptTranspose); rc2 = rc1; Assert(dpt.xp == 0 || dpt.yp == 0, "bad pinned point"); dzp = dpt.xp + dpt.yp; rc1.Offset(dzp, 0); if (dzp < 0) SortLw(&rc1.xpRight, &rc2.xpLeft); else SortLw(&rc2.xpRight, &rc1.xpLeft); if (fVert) { rc1.Transform(fptTranspose); rc2.Transform(fptTranspose); } if (fActive) { gnv.FillRc(&rc1, kacrInvert); gnv.FillRc(&rc2, kacrInvert); } else { gnv.FillRcApt(&rc1, &vaptGray, kacrInvert, kacrClear); gnv.FillRcApt(&rc2, &vaptGray, kacrInvert, kacrClear); } *prc += dpt; } else if (fActive) { //just draw the two gnv.FillRc(prc, kacrInvert); *prc += dpt; gnv.FillRcApt(prc, &vaptGray, kacrInvert, kacrClear); } else { //just draw the two gnv.FillRcApt(prc, &vaptGray, kacrInvert, kacrClear); *prc += dpt; gnv.FillRc(prc, kacrInvert); } fActive = fActiveNew; pt += dpt; } //erase the current bar if (fActive) gnv.FillRc(prc, kacrInvert); else gnv.FillRcApt(prc, &vaptGray, kacrInvert, kacrClear); return fVert ? pt.yp : pt.xp; } /*************************************************************************** Set the cursor for this GOB to pcurs. ***************************************************************************/ void GOB::SetCurs(PCURS pcurs) { AssertThis(0); AssertNilOrPo(pcurs, 0); SwapVars(&pcurs, &_pcurs); if (pvNil != _pcurs) _pcurs->AddRef(); ReleasePpo(&pcurs); } /*************************************************************************** Set the cursor for this GOB as indicated. ***************************************************************************/ void GOB::SetCursCno(PRCA prca, CNO cno) { AssertPo(prca, 0); PCURS pcurs; if (pvNil == (pcurs = (PCURS)prca->PbacoFetch(kctgCursor, cno, CURS::FReadCurs))) { Warn("cursor not found"); return; } SetCurs(pcurs); ReleasePpo(&pcurs); } /*************************************************************************** Return the address of the variable list belonging to this gob. When the gob is freed, the pointer is no longer valid. ***************************************************************************/ PGL *GOB::Ppglrtvm(void) { AssertThis(0); return &_pglrtvm; } /*************************************************************************** Put up a tool tip if this GOB has one. ***************************************************************************/ bool GOB::FEnsureToolTip(PGOB *ppgobCurTip, long xpMouse, long ypMouse) { AssertThis(0); AssertVarMem(ppgobCurTip); AssertNilOrPo(*ppgobCurTip, 0); return fFalse; } /*************************************************************************** Return the state of the GOB. Must be non-zero. ***************************************************************************/ long GOB::LwState(void) { AssertThis(0); return 1; } #ifdef DEBUG /*************************************************************************** Assert the validity of the GOB. ***************************************************************************/ void GOB::AssertValid(ulong grf) { GOB_PAR::AssertValid(0); if (hNil != _hwnd) { Assert(0 == _rcCur.xpLeft && 0 == _rcCur.ypTop, "_hwnd based gob not at (0, 0)"); } if (pvNil != _pgpt) { AssertPo(_pgpt, 0); } } /*************************************************************************** Mark memory referenced by the gob. ***************************************************************************/ void GOB::MarkMem(void) { AssertValid(0); GOB_PAR::MarkMem(); MarkMemObj(_pgpt); MarkMemObj(_pglrtvm); } /*************************************************************************** Mark memory for this gob and all descendent gobs. ***************************************************************************/ void GOB::MarkGobTree(void) { GTE gte; PGOB pgob; ulong grfgte; gte.Init(this, fgteNil); while (gte.FNextGob(&pgob, &grfgte, fgteNil)) { if (grfgte & fgtePre) pgob->MarkMem(); } } #endif //DEBUG /*************************************************************************** Constructor for a GOB tree enumerator. ***************************************************************************/ GTE::GTE(void) { _es = esDone; } /*************************************************************************** Initialize a GOB tree enumerator. ***************************************************************************/ void GTE::Init(PGOB pgob, ulong grfgte) { _pgobRoot = pgob; _pgobCur = pvNil; _fBackWards = FPure(grfgte & fgteBackToFront); _es = pgob == pvNil ? esDone : esStart; } /*************************************************************************** Goes to the next node in the sub tree being enumerated. Returns false iff the enumeration is done. ***************************************************************************/ bool GTE::FNextGob(PGOB *ppgob, ulong *pgrfgteOut, ulong grfgte) { PGOB pgobT; *pgrfgteOut = fgteNil; switch (_es) { case esStart: _pgobCur = _pgobRoot; *pgrfgteOut |= fgteRoot; goto LCheckForKids; case esGoDown: if (!(grfgte & fgteSkipToSib)) { pgobT = _fBackWards ? _pgobCur->PgobLastChild() : _pgobCur->_pgobChd; if (pgobT != pvNil) { _pgobCur = pgobT; goto LCheckForKids; } } //fall through case esGoRight: //go to the sibling (if there is one) or parent if (_pgobCur == _pgobRoot) { _es = esDone; return fFalse; } pgobT = _fBackWards ? _pgobCur->PgobPrevSib() : _pgobCur->_pgobSib; if (pgobT != pvNil) { _pgobCur = pgobT; LCheckForKids: *pgrfgteOut |= fgtePre; if (_pgobCur->_pgobChd == pvNil) { *pgrfgteOut |= fgtePost; _es = esGoRight; } else _es = esGoDown; } else { //no more siblings, go to parent _pgobCur = _pgobCur->_pgobPar; *pgrfgteOut |= fgtePost; if (_pgobCur == _pgobRoot) { _es = esDone; *pgrfgteOut |= fgteRoot; } else _es = esGoRight; } break; case esDone: return fFalse; } *ppgob = _pgobCur; return fTrue; }