/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** Author: ShonK Project: Kauai Reviewed: Copyright (c) Microsoft Corporation Rich text document and associated DDG, continued ***************************************************************************/ #include "frame.h" ASSERTNAME RTCLASS(TRUL) const long kdxpMax = 0x01000000; /*************************************************************************** Character run data. ***************************************************************************/ typedef struct CHRD *PCHRD; struct CHRD { long cpLim; long cpLimDraw; long xpLim; long xpLimDraw; }; /*************************************************************************** Character run class. This is used to format a line, draw a line, map between cp and xp on a line, etc. ***************************************************************************/ const long kcchMaxChr = 128; class CHR { ASSERT private: CHP _chp; PAP _pap; PTXTB _ptxtb; PGNV _pgnv; bool _fMustAdvance: 1; bool _fBreak: 1; bool _fObject: 1; long _cpMin; long _cpLim; long _cpLimFetch; long _xpMin; long _xpBreak; CHRD _chrd; CHRD _chrdBop; long _dypAscent; long _dypDescent; achar _rgch[kcchMaxChr]; bool _FFit(void); void _SetToBop(void); void _SkipIgnores(void); void _DoTab(void); public: void Init(CHP *pchp, PAP *ppap, PTXTB ptxtb, PGNV pgnv, long cpMin, long cpLim, long xpBase, long xpLimLine, long xpBreak); void GetNextRun(bool fMustAdvance = fFalse); bool FBreak(void) { return _fBreak; } void GetChrd(PCHRD pchrd, PCHRD pchrdBop = pvNil) { AssertNilOrVarMem(pchrd); AssertNilOrVarMem(pchrdBop); if (pvNil != pchrd) *pchrd = _chrd; if (pvNil != pchrdBop) *pchrdBop = _chrdBop; } long DypAscent(void) { return _dypAscent; } long DypDescent(void) { return _dypDescent; } long XpMin(void) { return _xpMin; } long XpBreak(void) { return _xpBreak; } achar *Prgch(void) { return _rgch; } long CpMin(void) { return _cpMin; } bool FObject(void) { return _fObject; } }; #ifdef DEBUG /*************************************************************************** Assert the validity of a CHR. ***************************************************************************/ void CHR::AssertValid(ulong grf) { AssertThisMem(); AssertPo(_ptxtb, 0); AssertPo(_pgnv, 0); } #endif //DEBUG /*************************************************************************** Initialize the CHR. ***************************************************************************/ void CHR::Init(CHP *pchp, PAP *ppap, PTXTB ptxtb, PGNV pgnv, long cpMin, long cpLim, long xpBase, long xpLimLine, long xpBreak) { AssertVarMem(pchp); AssertVarMem(ppap); AssertPo(ptxtb, 0); AssertPo(pgnv, 0); AssertIn(cpMin, 0, ptxtb->CpMac()); AssertIn(cpLim, cpMin + 1, ptxtb->CpMac() + 1); Assert(xpBase <= xpLimLine, "xpBase > xpLimLine"); RC rc; _chp = *pchp; _pap = *ppap; _ptxtb = ptxtb; _pgnv = pgnv; _fBreak = fFalse; _cpMin = cpMin; _cpLim = cpLim; _cpLimFetch = cpMin; _xpMin = xpBase; _xpBreak = LwMin(xpBreak, xpLimLine); // apply any indenting if (0 == xpBase) { switch (_pap.nd) { case ndFirst: if (_ptxtb->FMinPara(_cpMin)) _xpMin += _pap.dxpTab; break; case ndRest: if (!_ptxtb->FMinPara(_cpMin)) _xpMin += _pap.dxpTab; break; case ndAll: _xpMin += _pap.dxpTab; break; } } if (ndAll == _pap.nd) _xpBreak = LwMin(_xpBreak, xpLimLine - _pap.dxpTab); _chrd.cpLim = _chrd.cpLimDraw = _cpMin; _chrd.xpLim = _chrd.xpLimDraw = _xpMin; _chrdBop = _chrd; // get the vertical dimensions _pgnv->SetFont(_chp.onn, _chp.grfont, _chp.dypFont, tahLeft, tavBaseline); #ifndef SOC_BUG_1500 // REVIEW shonk: Win95 bug workaround // If we don't draw to the _pgnv before getting the metrics, the metrics // can be different than after we draw! achar ch = kchSpace; _pgnv->DrawRgch(&ch, 1, 0, 0); #endif //!REVIEW _pgnv->GetRcFromRgch(&rc, pvNil, 0); _dypAscent = LwMax(0, -rc.ypTop - _chp.dypOffset); _dypDescent = LwMax(0, rc.ypBottom + _chp.dypOffset); AssertThis(0); } /*************************************************************************** Get the next run (within the bounds we were inited with). ***************************************************************************/ void CHR::GetNextRun(bool fMustAdvance) { AssertThis(0); ulong grfch; achar ch; RC rc; // Start the next run if (FIn(_chrd.cpLim, _cpMin + 1, _cpLimFetch)) BltPb(_rgch + _chrd.cpLim - _cpMin, _rgch, _cpLimFetch - _chrd.cpLim); _cpMin = _chrd.cpLimDraw = _chrd.cpLim; _xpMin = _chrd.xpLimDraw = _chrd.xpLim; _xpBreak = LwMax(_xpBreak, _xpMin); _chrdBop = _chrd; if (_cpMin >= _cpLim) return; // Refill the buffer if (_cpLimFetch < _cpLim && _cpLimFetch < _cpMin + kcchMaxChr) { long cpFetch = LwMax(_cpMin, _cpLimFetch); _cpLimFetch = LwMin(kcchMaxChr, _cpLim - _cpMin) + _cpMin; _ptxtb->FetchRgch(cpFetch, _cpLimFetch - cpFetch, _rgch + cpFetch - _cpMin); } _fMustAdvance = FPure(fMustAdvance); _fBreak = fFalse; _fObject = fFalse; _pgnv->SetFont(_chp.onn, _chp.grfont, _chp.dypFont, tahLeft, tavBaseline); for (;;) { if (_chrd.cpLim >= _cpLimFetch || (fchIgnore & (grfch = GrfchFromCh( ch = _rgch[_chrd.cpLim - _cpMin])))) { // we're out of characters or this is an ignoreable character - // return the run if (!_FFit()) _SetToBop(); else _SkipIgnores(); break; } if (grfch & fchTab) { // handle the string of tabs, then return the run _DoTab(); break; } if (grfch & fchBreak) { // This line must break after this character - return the run. if (!_FFit()) { _SetToBop(); break; } _chrd.cpLim++; _SkipIgnores(); // this is a BOP _chrdBop = _chrd; _fBreak = fTrue; break; } if (grfch & fchMayBreak) { _chrd.cpLimDraw = ++_chrd.cpLim; if (_FFit()) { // this is a BOP _chrdBop = _chrd; continue; } _fBreak = fTrue; if (grfch & fchWhiteOverhang) { // see if everything but this character fits long xp = _chrd.xpLim; _chrd.cpLimDraw--; if (_FFit()) { // fits with the overhang _chrd.xpLim = xp; _chrdBop = _chrd; _SkipIgnores(); break; } } _SetToBop(); break; } if (kchObject == ch) { // this is an object character if (_chrd.cpLim > _cpMin) { // return the run before processing the object - objects // go in their own run. if (!_FFit()) _SetToBop(); else _chrdBop = _chrd; break; } // return just the object (if it really is an object) if (!_ptxtb->FGetObjectRc(_chrd.cpLim, _pgnv, &_chp, &rc)) { // treat as a normal character _chrd.cpLimDraw = ++_chrd.cpLim; continue; } rc.Offset(0, _chp.dypOffset); Assert(!rc.FEmpty() && rc.xpRight >= 0, "bad rectangle for the object"); if (fMustAdvance || _xpMin + rc.xpRight <= _xpBreak) { _fObject = fTrue; _chrd.cpLimDraw = ++_chrd.cpLim; _chrd.xpLim = _chrd.xpLimDraw = _xpMin + rc.xpRight; _chrdBop = _chrd; _dypAscent = LwMax(_dypAscent, -rc.ypTop); _dypDescent = LwMax(_dypDescent, rc.ypBottom); if (_xpMin + rc.xpRight >= _xpBreak) _fBreak = fTrue; _SkipIgnores(); } break; } // normal character _chrd.cpLimDraw = ++_chrd.cpLim; } AssertThis(0); } /*************************************************************************** Test whether everything from _cpMin to _chrd.cpLimDraw fits. Assumes the font is set in the _pgnv. ***************************************************************************/ bool CHR::_FFit(void) { AssertThis(0); RC rc; if (_chrd.cpLimDraw == _cpMin) _chrd.xpLim = _chrd.xpLimDraw = _xpMin; else { _pgnv->GetRcFromRgch(&rc, _rgch, _chrd.cpLimDraw - _cpMin); _chrd.xpLim = _chrd.xpLimDraw = _xpMin + rc.Dxp(); } return _chrd.xpLimDraw <= _xpBreak; } /*************************************************************************** Set the CHR to the last break opportunity. If there wasn't one and _fMustAdvance is true, gobble as many characters as we can, but at least one. ***************************************************************************/ void CHR::_SetToBop(void) { AssertThis(0); _fBreak = fTrue; if (_chrdBop.cpLim <= _cpMin && _fMustAdvance) { // no break opportunity seen - gobble as many characters as we can, // but at least one. // do a binary search for the character to break at Assert(_chrd.cpLimDraw > _cpMin, "why is _chrd.cpLimDraw == _cpMin?"); RC rc; long ivMin, ivLim, iv; long dxp = _xpBreak - _xpMin; for (ivMin = 0, ivLim = _chrd.cpLimDraw - _cpMin; ivMin < ivLim; ) { iv = (ivMin + ivLim) / 2 + 1; AssertIn(iv, ivMin + 1, ivLim + 1); _pgnv->GetRcFromRgch(&rc, _rgch, iv); if (rc.Dxp() <= dxp) ivMin = iv; else ivLim = iv - 1; } AssertIn(ivMin, 0, _chrd.cpLimDraw - _cpMin + 1); if (ivMin == 0) { // nothing fits - use one character ivMin = 1; } // set _chrd.cpLim and _chrd.cpLimDraw, then set the _xp values _chrd.cpLim = _chrd.cpLimDraw = _cpMin + ivMin; _FFit(); } else _chrd = _chrdBop; _SkipIgnores(); } /*************************************************************************** Skip any trailing ignore characters. Just changes _chrd.cpLim. ***************************************************************************/ void CHR::_SkipIgnores(void) { AssertThis(0); while (_chrd.cpLim < _cpLimFetch && (fchIgnore & GrfchFromCh(_rgch[_chrd.cpLim - _cpMin]))) { _chrd.cpLim++; } if (_chrd.cpLim == _cpLimFetch) { achar ch; long cpMac = _ptxtb->CpMac(); while (_chrd.cpLim < cpMac) { _ptxtb->FetchRgch(_chrd.cpLim, 1, &ch); if (!(fchIgnore & GrfchFromCh(ch))) return; _chrd.cpLim++; } } } /*************************************************************************** Swallow as many tabs as possible. ***************************************************************************/ void CHR::_DoTab(void) { AssertThis(0); if (!_FFit()) { _SetToBop(); return; } while (_chrd.cpLim < _cpLimFetch && (fchTab & GrfchFromCh(_rgch[_chrd.cpLim - _cpMin]))) { _chrd.cpLim++; _chrd.xpLim = LwRoundAway(_chrd.xpLim + 1, _pap.dxpTab); if (_chrd.xpLim > _xpBreak) { // this tab would carry us over the edge. if (_chrd.cpLim == _cpMin + 1 && _fMustAdvance) { // the line is empty, so we have to force the tab onto the line _SkipIgnores(); _fBreak = fTrue; } else _SetToBop(); return; } // this is a BOP _chrd.xpLimDraw = _chrd.xpLim; _chrdBop = _chrd; } _SkipIgnores(); } /*************************************************************************** Constructor for the text document display GOB. ***************************************************************************/ TXTG::TXTG(PTXTB ptxtb, PGCB pgcb) : TXTG_PAR(ptxtb, pgcb) { AssertBaseThis(0); _ptxtb = ptxtb; _fMark = (kginMark == pgcb->_gin || kginDefault == pgcb->_gin && kginMark == GOB::GinDefault()); _pgnv = pvNil; } /*************************************************************************** Destructor for TXTG. ***************************************************************************/ TXTG::~TXTG(void) { AssertBaseThis(0); ReleasePpo(&_pgllin); ReleasePpo(&_pgnv); } #ifdef DEBUG /*************************************************************************** Assert the validity of a TXTG. ***************************************************************************/ void TXTG::AssertValid(ulong grf) { TXTG_PAR::AssertValid(0); AssertPo(_pgllin, 0); AssertIn(_ilinInval, 0, _pgllin->IvMac() + 1); AssertPo(_pgnv, 0); AssertNilOrPo(_ptrul, 0); //REVIEW shonk: TXTG::AssertValid: fill out. } /*************************************************************************** Mark memory for the TXTG. ***************************************************************************/ void TXTG::MarkMem(void) { AssertValid(0); TXTG_PAR::MarkMem(); MarkMemObj(_pgllin); MarkMemObj(_pgnv); } #endif //DEBUG /*************************************************************************** Initialize the text document display gob. ***************************************************************************/ bool TXTG::_FInit(void) { AssertBaseThis(0); PGPT pgpt; if (!TXTG_PAR::_FInit()) return fFalse; if (pvNil == (_pgllin = GL::PglNew(size(LIN)))) return fFalse; // Allocate the GNV for formatting. Use an offscreen one iff _fMark // is set. if (_fMark) { RC rc(0, 0, 1, 1); if (pvNil == (pgpt = GPT::PgptNewOffscreen(&rc, 8))) return fFalse; } else { pgpt = Pgpt(); pgpt->AddRef(); } _pgnv = NewObj GNV(this, pgpt); ReleasePpo(&pgpt); if (pvNil == _pgnv) return fFalse; _pgllin->SetMinGrow(20); _ilinDisp = 0; _cpDisp = 0; _dypDisp = 0; _ilinInval = 0; if (_DxpDoc() > 0) { long cpMac = _ptxtb->CpMac(); _Reformat(0, cpMac, cpMac); } AssertThis(0); return fTrue; } /*************************************************************************** Deactivate the TXTG - turn off the selection. ***************************************************************************/ void TXTG::_Activate(bool fActive) { AssertThis(0); TXTG_PAR::_Activate(fActive); if (!fActive) _SwitchSel(fFalse, kginSysInval); } /*************************************************************************** Get the LIN for the given ilin. If ilin is past the end of _pgllin, _CalcLine is called repeatedly and new lines are added to _pgllin. The actual index of the returned line is put in *pilinActual (if not nil). ***************************************************************************/ void TXTG::_FetchLin(long ilin, LIN *plin, long *pilinActual) { AssertThis(0); AssertIn(ilin, 0, kcbMax); AssertVarMem(plin); AssertNilOrVarMem(pilinActual); long cpLim, cpMac; long dypTot; LIN *qlin; bool fAdd; long ilinLim = LwMin(_pgllin->IvMac(), ilin + 1); if (pvNil != pilinActual) *pilinActual = ilin; if (_ilinInval < ilinLim) { qlin = (LIN *)_pgllin->QvGet(_ilinInval); if (_ilinInval == 0) { cpLim = 0; dypTot = 0; } else { qlin--; cpLim = qlin->cpMin + qlin->ccp; dypTot = qlin->dypTot + qlin->dyp; qlin++; } // adjust LINs up to ilinLim for ( ; _ilinInval < ilinLim; _ilinInval++, qlin++) { qlin->cpMin = cpLim; qlin->dypTot = dypTot; cpLim += qlin->ccp; dypTot += qlin->dyp; } } Assert(_ilinInval >= ilinLim, 0); if (ilin < _ilinInval) { *plin = *(LIN *)_pgllin->QvGet(ilin); return; } Assert(ilinLim == _pgllin->IvMac(), 0); if (ilinLim == 0) { cpLim = 0; dypTot = 0; ClearPb(plin, size(LIN)); } else { // get the LIN in case we don't actuall calc any lines below *plin = *(LIN *)_pgllin->QvGet(ilinLim - 1); cpLim = plin->cpMin + plin->ccp; dypTot = plin->dypTot + plin->dyp; } cpMac = _ptxtb->CpMac(); fAdd = fTrue; for ( ; ilinLim <= ilin && cpLim < cpMac; ilinLim++) { _CalcLine(cpLim, dypTot, plin); fAdd = fAdd && _pgllin->FAdd(plin); cpLim += plin->ccp; dypTot += plin->dyp; } _ilinInval = _pgllin->IvMac(); if (pvNil != pilinActual) *pilinActual = ilinLim - 1; } /*************************************************************************** Find the LIN that contains the given cpFind. pilin and/or plin can be nil. If fCalcLines is false, we won't calculate any new lines and the returned LIN may be before cpFind. ***************************************************************************/ void TXTG::_FindCp(long cpFind, LIN *plin, long *pilin, bool fCalcLines) { AssertThis(0); AssertIn(cpFind, 0, _ptxtb->CpMac()); AssertNilOrVarMem(pilin); AssertNilOrVarMem(plin); LIN *qlin; LIN lin; long dypTot; long cpLim; long ilinMac; bool fAdd; // get the starting cp and dypTot values for _ilinInval qlin = (LIN *)_pgllin->QvGet(_ilinInval); if (_ilinInval == 0) { cpLim = 0; dypTot = 0; } else { qlin--; cpLim = qlin->cpMin + qlin->ccp; dypTot = qlin->dypTot + qlin->dyp; qlin++; } if (cpFind < cpLim) { // do a binary search to find the LIN containing cpFind long ivMin, ivLim, iv; for (ivMin = 0, ivLim = _ilinInval; ivMin < ivLim; ) { iv = (ivMin + ivLim) / 2; qlin = (LIN *)_pgllin->QvGet(iv); if (cpFind < qlin->cpMin) ivLim = iv; else if (cpFind >= qlin->cpMin + qlin->ccp) ivMin = iv + 1; else { if (pvNil != pilin) *pilin = iv; if (pvNil != plin) *plin = *qlin; return; } } Bug("Invalid LINs"); cpLim = 0; dypTot = 0; _ilinInval = 0; } Assert(cpFind >= cpLim, "why isn't cpFind >= cpLim?"); if (_ilinInval < (ilinMac = _pgllin->IvMac())) { // adjust LINs up to cpFind qlin = (LIN *)_pgllin->QvGet(_ilinInval); for ( ; _ilinInval < ilinMac && cpFind >= cpLim; _ilinInval++, qlin++) { qlin->cpMin = cpLim; qlin->dypTot = dypTot; cpLim += qlin->ccp; dypTot += qlin->dyp; } if (cpFind < cpLim) { AssertIn(cpFind, qlin[-1].cpMin, cpLim); if (pvNil != pilin) *pilin = _ilinInval - 1; if (pvNil != plin) *plin = qlin[-1]; return; } } Assert(_ilinInval == ilinMac, "why isn't _ilinInval == ilinMac?"); Assert(cpFind >= cpLim, "why isn't cpFind >= cpLim?"); if (!fCalcLines) { if (pvNil != pilin) *pilin = ilinMac - (ilinMac > 0); if (pvNil != plin) { if (ilinMac > 0) *plin = *(LIN *)_pgllin->QvGet(ilinMac - 1); else ClearPb(plin, size(LIN)); } return; } // have to calculate some lines for (fAdd = fTrue; cpFind >= cpLim; ilinMac++) { _CalcLine(cpLim, dypTot, &lin); fAdd = fAdd && _pgllin->FAdd(&lin); cpLim += lin.ccp; dypTot += lin.dyp; } _ilinInval = _pgllin->IvMac(); if (pvNil != pilin) *pilin = ilinMac - 1; if (pvNil != plin) *plin = lin; } /*************************************************************************** Find the LIN that contains the given dypFind value (measured from the top of the document). pilin and/or plin can be nil. If fCalcLines is false, we won't calculate any new lines and the returned LIN may be before dypFind. ***************************************************************************/ void TXTG::_FindDyp(long dypFind, LIN *plin, long *pilin, bool fCalcLines) { AssertThis(0); AssertIn(dypFind, 0, kcbMax); AssertNilOrVarMem(pilin); AssertNilOrVarMem(plin); LIN *qlin; LIN lin; long dypTot; long cpLim, cpMac; long ilinMac; bool fAdd; // get the starting cp and dypTot values for _ilinInval qlin = (LIN *)_pgllin->QvGet(_ilinInval); if (_ilinInval == 0) { cpLim = 0; dypTot = 0; } else { qlin--; cpLim = qlin->cpMin + qlin->ccp; dypTot = qlin->dypTot + qlin->dyp; qlin++; } if (dypFind < dypTot) { // do a binary search to find the LIN containing dypFind long ivMin, ivLim, iv; for (ivMin = 0, ivLim = _ilinInval; ivMin < ivLim; ) { iv = (ivMin + ivLim) / 2; qlin = (LIN *)_pgllin->QvGet(iv); if (dypFind < qlin->dypTot) ivLim = iv; else if (dypFind >= qlin->dypTot + qlin->dyp) ivMin = iv + 1; else { if (pvNil != pilin) *pilin = iv; if (pvNil != plin) *plin = *qlin; return; } } Bug("Invalid LINs"); cpLim = 0; dypTot = 0; _ilinInval = 0; } Assert(dypFind >= dypTot, "why isn't dypFind >= dypTot?"); if (_ilinInval < (ilinMac = _pgllin->IvMac())) { // adjust LINs up to cp qlin = (LIN *)_pgllin->QvGet(_ilinInval); for ( ; _ilinInval < ilinMac && dypFind >= dypTot; _ilinInval++, qlin++) { qlin->cpMin = cpLim; qlin->dypTot = dypTot; cpLim += qlin->ccp; dypTot += qlin->dyp; } if (dypFind < dypTot) { AssertIn(dypFind, qlin[-1].dypTot, dypTot); if (pvNil != pilin) *pilin = _ilinInval - 1; if (pvNil != plin) *plin = qlin[-1]; return; } } Assert(_ilinInval == ilinMac, "why isn't _ilinInval == ilinMac?"); Assert(dypFind >= dypTot, "why isn't dypFind >= dypTot?"); if (!fCalcLines) { if (pvNil != pilin) *pilin = ilinMac - (ilinMac > 0); if (pvNil != plin) { if (ilinMac > 0) *plin = *(LIN *)_pgllin->QvGet(ilinMac - 1); else ClearPb(plin, size(LIN)); } return; } cpMac = _ptxtb->CpMac(); if (cpLim >= cpMac) { if (pvNil != plin) { // Get a valid lin. if (ilinMac > 0) _pgllin->Get(ilinMac - 1, &lin); else ClearPb(&lin, size(LIN)); } _ilinInval = ilinMac; } else { Assert(dypFind >= dypTot && cpLim < cpMac, 0); for (fAdd = fTrue; dypFind >= dypTot && cpLim < cpMac; ilinMac++) { _CalcLine(cpLim, dypTot, &lin); fAdd = fAdd && _pgllin->FAdd(&lin); cpLim += lin.ccp; dypTot += lin.dyp; } _ilinInval = _pgllin->IvMac(); } if (pvNil != pilin) *pilin = ilinMac - 1; if (pvNil != plin) *plin = lin; } /*************************************************************************** Recalculate the _pgllin after an edit. Sets *pyp, *pdypIns, *pdypDel to indicate the vertical display space that was affected. ***************************************************************************/ void TXTG::_Reformat(long cp, long ccpIns, long ccpDel, long *pyp, long *pdypIns, long *pdypDel) { AssertThis(0); AssertIn(cp, 0, _ptxtb->CpMac()); AssertIn(ccpIns, 0, _ptxtb->CpMac() - cp + 1); AssertIn(ccpDel, 0, kcbMax); AssertNilOrVarMem(pyp); AssertNilOrVarMem(pdypIns); AssertNilOrVarMem(pdypDel); long ypDel, dypDel, dypIns, dypCur; long ccp, cpCur, cpNext, cpMac; long ilin, ilinOld; LIN linOld, lin, linT; _fClear = _ptxtb->AcrBack() == kacrClear; _fXpValid = fFalse; cpMac = _ptxtb->CpMac(); // Find the LIN that contains cp (if there is one) - don't calc any lines // to get the LIN. _FindCp(cp, &linOld, &ilinOld, fFalse); if (cp >= linOld.cpMin + linOld.ccp) { // the LIN for this cp was not cached - recalc from the beginning of // the last lin. ccpIns += cp - linOld.cpMin; ccpDel += cp - linOld.cpMin; cp = linOld.cpMin; } AssertIn(cp, linOld.cpMin, linOld.cpMin + linOld.ccp + (linOld.ccp == 0)); // make sure the previous line is formatted correctly if (ilinOld > 0 && !_ptxtb->FMinPara(linOld.cpMin)) { _FetchLin(ilinOld - 1, &lin); _CalcLine(lin.cpMin, lin.dypTot, &linT); if (linT.ccp != lin.ccp) { //edit affected previous line - so start //formatting from there ilinOld--; linOld = lin; } } AssertIn(cp, linOld.cpMin, cpMac); // remove deleted lines Assert(ilinOld <= _ilinInval, 0); _ilinInval = ilinOld; for (ccp = dypDel = 0; linOld.cpMin + ccp <= cp + ccpDel && ilinOld < _pgllin->IvMac(); ) { _pgllin->Get(ilinOld, &lin); dypDel += lin.dyp; ccp += lin.ccp; _pgllin->Delete(ilinOld); } //insert the new lines cpCur = linOld.cpMin; dypCur = linOld.dypTot; // cpNext is the cp of the next (possibly stale) LIN in _pgllin if (ilinOld < _pgllin->IvMac()) { cpNext = linOld.cpMin + ccp - ccpDel + ccpIns; AssertIn(cpNext, linOld.cpMin, cpMac + 1); } else cpNext = cpMac; AssertIn(ilinOld, 0, _pgllin->IvMac() + 1); dypIns = 0; for (ilin = ilinOld; ; ) { AssertIn(cpCur, linOld.cpMin, cpMac + 1); AssertIn(dypCur, linOld.dypTot, kcbMax); while (cpNext < cpCur && ilin < _pgllin->IvMac()) { _pgllin->Get(ilin, &lin); _pgllin->Delete(ilin); cpNext += lin.ccp; dypDel += lin.dyp; } AssertIn(cpNext, cpCur, cpMac + 1); if (ilin >= _pgllin->IvMac()) { // no more LINs that might be preservable, so we may as well // stop trying. ilin = _pgllin->IvMac(); if (cpNext < cpMac) dypDel = kswMax; if (cpCur < cpMac) dypIns = kswMax; break; } AssertIn(ilin, 0, _pgllin->IvMac()); if (cpCur == cpNext) { // everything from here on should be correct - we still need to // set _ilinDisp break; } _CalcLine(cpCur, dypCur, &lin); if (!_pgllin->FInsert(ilin, &lin)) { AssertIn(ilin, 0, _pgllin->IvMac()); _pgllin->Get(ilin, &linT); cpNext += linT.ccp; dypDel += linT.dyp; _pgllin->Put(ilin, &lin); } dypIns += lin.dyp; cpCur += lin.ccp; dypCur += lin.dyp; ilin++; } _ilinInval = ilin; if (_cpDisp <= linOld.cpMin) ypDel = linOld.dypTot - _dypDisp; else { if (_cpDisp > cp) { if (_cpDisp >= cp + ccpDel) _cpDisp += ccpIns - ccpDel; else if (_cpDisp >= cp + ccpIns) _cpDisp = cp + ccpIns; } ypDel = 0; _FindCp(_cpDisp, &lin, &_ilinDisp); _cpDisp = lin.cpMin; dypDel = LwMax(0, linOld.dypTot + dypDel - _dypDisp); _dypDisp = lin.dypTot; dypIns = LwMax(0, linOld.dypTot + dypIns - _dypDisp); } if (pvNil != pyp) *pyp = ypDel; if (pvNil != pdypIns) *pdypIns = dypIns; if (pvNil != pdypDel) *pdypDel = dypDel; } /*************************************************************************** Calculate the end of the line, the left position of the line, the height of the line and the ascent of the line. ***************************************************************************/ void TXTG::_CalcLine(long cpMin, long dypBase, LIN *plin) { AssertThis(0); AssertIn(cpMin, 0, _ptxtb->CpMac()); AssertVarMem(plin); struct RUN { long cpLim; long xpLim; long dypAscent; long dypDescent; }; long dxpDoc; PAP pap; CHP chp; long cpLimPap, cpLimChp; CHR chr; CHRD chrd, chrdBop; RUN run, runSure, runT; dxpDoc = _DxpDoc(); _FetchPap(cpMin, &pap, pvNil, &cpLimPap); cpLimChp = cpMin; run.cpLim = cpMin; run.xpLim = 0; run.dypAscent = run.dypDescent = 0; runSure = run; for (;;) { // make sure the chp is valid if (run.cpLim >= cpLimChp) { _FetchChp(run.cpLim, &chp, pvNil, &cpLimChp); cpLimChp = LwMin(cpLimChp, cpLimPap); if (cpLimChp <= run.cpLim) { Bug("why is run.cpLim >= cpLimChp?"); break; } chr.Init(&chp, &pap, _ptxtb, _pgnv, run.cpLim, cpLimChp, run.xpLim, dxpDoc, dxpDoc); } chr.GetNextRun(runSure.cpLim == cpMin); chr.GetChrd(&chrd, &chrdBop); if (chrd.cpLim == run.cpLim) { // didn't move forward - use runSure Assert(runSure.cpLim > cpMin, "why don't we have a BOP?"); run = runSure; break; } runT = run; run.cpLim = chrd.cpLim; run.xpLim = chrd.xpLimDraw; run.dypAscent = LwMax(run.dypAscent, chr.DypAscent()); run.dypDescent = LwMax(run.dypDescent, chr.DypDescent()); if (chr.FBreak()) { // we know that this is the end of the line - use run or runT if (run.xpLim > chr.XpBreak() && runT.cpLim > cpMin) run = runT; break; } if (chrdBop.cpLim > runT.cpLim) { // put chrdBop info into runSure runSure.cpLim = chrdBop.cpLim; runSure.xpLim = chrdBop.xpLimDraw; runSure.dypAscent = LwMax(runSure.dypAscent, chr.DypAscent()); runSure.dypDescent = LwMax(runSure.dypDescent, chr.DypDescent()); } } Assert(run.dypAscent > 0 || run.dypDescent > 0, "bad run"); Assert(run.cpLim > cpMin, "why is cch zero?"); switch (pap.jc) { default: plin->xpLeft = 0; break; case jcRight: plin->xpLeft = (short)(dxpDoc - run.xpLim); break; case jcCenter: plin->xpLeft = (dxpDoc - run.xpLim) / 2; break; } plin->ccp = (short)(run.cpLim - cpMin); plin->dyp = (short)(run.dypAscent + run.dypDescent); if (pap.numLine != kdenLine) plin->dyp = (short)LwMulDiv(plin->dyp, pap.numLine, kdenLine); plin->dyp += pap.dypExtraLine; if (_ptxtb->FMinPara(run.cpLim) || run.cpLim == _ptxtb->CpMac()) { if (pap.numAfter != kdenAfter) plin->dyp = (short)LwMulDiv(plin->dyp, pap.numAfter, kdenAfter); plin->dyp += pap.dypExtraAfter; } if (plin->dyp <= 0) plin->dyp = 1; plin->dypAscent = (short)run.dypAscent; if (plin->dypAscent <= 0) plin->dypAscent = 1; plin->cpMin = cpMin; plin->dypTot = dypBase; } /*************************************************************************** Get the cp that the point is in. If fClosest is true, this finds the cp boundary that the point is closest to (for traditional selection). If fClosest is false, it finds the character that the point is over. ***************************************************************************/ bool TXTG::_FGetCpFromPt(long xp, long yp, long *pcp, bool fClosest) { AssertThis(0); AssertVarMem(pcp); LIN lin; RC rc; GetRc(&rc, cooLocal); if (!FIn(yp, 0, rc.ypBottom)) return fFalse; _FindDyp(_dypDisp + yp, &lin); if (_dypDisp + yp >= lin.dypTot + lin.dyp) { *pcp = _ptxtb->CpMac() - 1; return fTrue; } //we've found the line, now get the cp on the line return _FGetCpFromXp(xp, &lin, pcp, fClosest); } /*************************************************************************** Get the cp on the line given by *plin that the xp is in. If fClosest is true, this finds the cp boundary that the point is closest to (for traditional selection). If fClosest is false, it finds the character that the xp is over. This only returns false if fClosest is false and the xp is before the beginning of the line. ***************************************************************************/ bool TXTG::_FGetCpFromXp(long xp, LIN *plin, long *pcp, bool fClosest) { AssertThis(0); AssertVarMem(plin); AssertIn(plin->cpMin, 0, _ptxtb->CpMac()); AssertVarMem(plin); AssertIn(plin->ccp, 1, _ptxtb->CpMac() + 1 - plin->cpMin); AssertVarMem(pcp); CHP chp; PAP pap; CHR chr; long cpCur, cpLimChp, cpLim; long xpCur, dxpDoc; CHRD chrd; xp -= plin->xpLeft + kdxpIndentTxtg - _scvHorz; if (xp <= 0) { *pcp = plin->cpMin; return FPure(fClosest); } if (xp >= (dxpDoc = _DxpDoc())) { *pcp = _ptxtb->CpPrev(plin->cpMin + plin->ccp); return fTrue; } cpLimChp = cpCur = plin->cpMin; cpLim = cpCur + plin->ccp; xpCur = 0; _FetchPap(cpCur, &pap); for (;;) { // make sure the chp is valid if (cpCur >= cpLimChp) { if (cpCur >= cpLim) { // everything fit *pcp = LwMax(plin->cpMin, _ptxtb->CpPrev(cpLim)); return fTrue; } _FetchChp(cpCur, &chp, pvNil, &cpLimChp); cpLimChp = LwMin(cpLimChp, cpLim); Assert(cpLimChp > cpCur, "why is cpCur >= cpLimChp?"); chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpLimChp, xpCur, dxpDoc, xp); } chr.GetNextRun(fTrue); chr.GetChrd(&chrd); if (chr.FBreak() && (chrd.xpLim >= chr.XpBreak() || chrd.cpLim >= cpLim)) { long cpPrev = _ptxtb->CpPrev(chrd.cpLim); if (!fClosest || chrd.cpLim >= cpLim) goto LPrev; if (cpPrev > cpCur) { CHRD chrdT; // get the length from cpCur to cpPrev chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpPrev, xpCur, dxpDoc, xp); chr.GetNextRun(fTrue); chr.GetChrd(&chrdT); cpCur = chrdT.cpLim; xpCur = chrdT.xpLim; } Assert(xp >= xpCur && chrd.xpLim >= xp, "what?"); if (xp - xpCur > chrd.xpLim - xp) { *pcp = chrd.cpLim; return fTrue; } LPrev: *pcp = LwMax(plin->cpMin, cpPrev); return fTrue; } xpCur = chrd.xpLim; cpCur = chrd.cpLim; } } /*************************************************************************** Get the vertical bounds of the line containing cp and the horizontal position of the cp on the line. If fView is true, the values are in view coordinates. If fView is false, the values are in logical values (independent of the current scrolling of the view). ***************************************************************************/ void TXTG::_GetXpYpFromCp(long cp, long *pypMin, long *pypLim, long *pxp, long *pypBaseLine, bool fView) { AssertThis(0); AssertIn(cp, 0, _ptxtb->CpMac()); AssertNilOrVarMem(pypMin); AssertNilOrVarMem(pypLim); AssertNilOrVarMem(pxp); AssertNilOrVarMem(pypBaseLine); LIN lin; long xp; _FindCp(cp, &lin); xp = lin.xpLeft; if (fView) { lin.dypTot -= _dypDisp; xp -= _scvHorz; } if (pvNil != pypMin) *pypMin = lin.dypTot; if (pvNil != pypLim) *pypLim = lin.dypTot + lin.dyp; if (pvNil != pxp) *pxp = xp + _DxpFromCp(lin.cpMin, cp); if (pvNil != pypBaseLine) *pypBaseLine = lin.dypTot + lin.dypAscent; } /*************************************************************************** Find the xp location of the given cp. Assumes that cpLine is the start of the line containing cp. This includes a buffer on the left of kdxpIndentTxtg, but doesn't include centering or right justification correction. ***************************************************************************/ long TXTG::_DxpFromCp(long cpLine, long cp) { AssertThis(0); AssertIn(cpLine, 0, _ptxtb->CpMac()); AssertIn(cp, cpLine, _ptxtb->CpMac()); CHP chp; PAP pap; CHR chr; long cpCur, cpLimChp, cpLim; long xpCur; CHRD chrd; cpLimChp = cpCur = cpLine; cpLim = cp + (cp == cpLine); xpCur = 0; _FetchPap(cpCur, &pap); for (;;) { // make sure the chp is valid if (cpCur >= cpLimChp) { _FetchChp(cpCur, &chp, pvNil, &cpLimChp); cpLimChp = LwMin(cpLimChp, cpLim); Assert(cpLimChp > cpCur, "why is cpCur >= cpLimChp?"); chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpLimChp, xpCur, kdxpMax, kdxpMax); } chr.GetNextRun(); chr.GetChrd(&chrd); if (chrd.cpLim >= cp || chr.FBreak()) { if (cp == cpLine) return chr.XpMin() + kdxpIndentTxtg; else return chrd.xpLim + kdxpIndentTxtg; } cpCur = chrd.cpLim; xpCur = chrd.xpLim; } } /*************************************************************************** Replaces the characters between cp1 and cp2 with the given ones. ***************************************************************************/ bool TXTG::FReplace(achar *prgch, long cch, long cp1, long cp2) { AssertThis(0); AssertIn(cch, 0, kcbMax); AssertPvCb(prgch, cch); AssertIn(cp1, 0, _ptxtb->CpMac()); AssertIn(cp2, 0, _ptxtb->CpMac()); HideSel(); SortLw(&cp1, &cp2); if (!_ptxtb->FReplaceRgch(prgch, cch, cp1, cp2 - cp1)) return fFalse; cp1 += cch; SetSel(cp1, cp1); ShowSel(); return fTrue; } /*************************************************************************** Invalidate the display from cp. If we're the active TXTG, also redraw. ***************************************************************************/ void TXTG::InvalCp(long cp, long ccpIns, long ccpDel) { AssertThis(0); AssertIn(cp, 0, _ptxtb->CpMac() + 1); AssertIn(ccpIns, 0, _ptxtb->CpMac() + 1 - cp); AssertIn(ccpDel, 0, kcbMax); Assert(!_fSelOn, "selection is on in InvalCp!"); long cpAnchor, cpOther; //adjust the sel cpAnchor = _cpAnchor; cpOther = _cpOther; FAdjustIv(&cpAnchor, cp, ccpIns, ccpDel); FAdjustIv(&cpOther, cp, ccpIns, ccpDel); if (cpAnchor != _cpAnchor || cpOther != _cpOther) SetSel(cpAnchor, cpOther, ginNil); _ReformatAndDraw(cp, ccpIns, ccpDel); if (pvNil != _ptrul) { PAP pap; _FetchPap(LwMin(_cpAnchor, _cpOther), &pap); _ptrul->SetDxpTab(pap.dxpTab); _ptrul->SetDxpDoc(_DxpDoc()); } } /*************************************************************************** Reformat the TXTG and update the display. If this TXTG is not the active one, the display is invalidated instead of updated. ***************************************************************************/ void TXTG::_ReformatAndDraw(long cp, long ccpIns, long ccpDel) { RC rcLoc, rc; long yp, dypIns, dypDel; RC rcUpdate(0, 0, 0, 0); //reformat _Reformat(cp, ccpIns, ccpDel, &yp, &dypIns, &dypDel); //determine the dirty rectangles and if we're active, update them GetRc(&rcLoc, cooLocal); if (!_fActive) { rc = rcLoc; rc.ypTop = yp; if (dypIns == dypDel) rc.ypBottom = yp + dypIns; InvalRc(&rc); return; } rc = rcLoc; rc.ypTop = yp; rc.ypBottom = yp + dypIns; if (dypIns != dypDel) { // Have some bits to blt vertically. If the background isn't clear, // but _fMark is set, still do the scroll, since _fMark is intended // to avoid flashing (allowing offscreen drawing) and scrolling doesn't // flash anyway. if (_fClear) rc.ypBottom = rcLoc.ypBottom; else { rc = rcLoc; rc.ypTop = LwMax(rc.ypTop, yp + LwMin(dypIns, dypDel)); Scroll(&rc, 0, dypIns - dypDel, _fMark ? kginMark : kginDraw); rc.ypBottom = rc.ypTop; rc.ypTop = yp; } } if (!rc.FEmpty()) InvalRc(&rc, _fClear || _fMark ? kginMark : kginDraw); _fXpValid = fFalse; } /*************************************************************************** Perform a scroll according to scaHorz and scaVert. ***************************************************************************/ void TXTG::_Scroll(long scaHorz, long scaVert, long scvHorz, long scvVert) { RC rc; long dxp, dyp; GetRc(&rc, cooLocal); dxp = 0; switch (scaHorz) { case scaPageUp: dxp = -LwMulDiv(rc.Dxp(), 9, 10); goto LHorz; case scaPageDown: dxp = LwMulDiv(rc.Dxp(), 9, 10); goto LHorz; case scaLineUp: dxp = -rc.Dxp() / 10; goto LHorz; case scaLineDown: dxp = rc.Dxp() / 10; goto LHorz; case scaToVal: dxp = scvHorz - _scvHorz; LHorz: dxp = LwBound(_scvHorz + dxp, 0, _ScvMax(fFalse) + 1) - _scvHorz; _scvHorz += dxp; if (pvNil != _ptrul) _ptrul->SetXpLeft(kdxpIndentTxtg - _scvHorz); break; } dyp = 0; if (scaVert != scaNil) { long cpT; LIN lin, linDisp; long ilin; RC rc; switch (scaVert) { case scaToVal: cpT = LwBound(scvVert, 0, _ptxtb->CpMac()); _FindCp(cpT, &lin, &ilin); dyp = lin.dypTot - _dypDisp; _ilinDisp = ilin; _cpDisp = lin.cpMin; _dypDisp = lin.dypTot; break; case scaPageDown: // scroll down a page GetRc(&rc, cooLocal); _FindDyp(rc.Dyp() + _dypDisp, &lin, &ilin); if (lin.cpMin <= _cpDisp) { // we didn't go anywhere so force going down a line _FetchLin(_ilinDisp + 1, &lin, &ilin); } else if (lin.dypTot + lin.dyp > _dypDisp + rc.Dyp() && ilin > _ilinDisp + 1) { // the line crosses the bottom of the ddg so back up one Assert(ilin > 0, 0); _FetchLin(ilin - 1, &lin, &ilin); } dyp = lin.dypTot - _dypDisp; _ilinDisp = ilin; _cpDisp = lin.cpMin; _dypDisp = lin.dypTot; break; case scaLineDown: // scroll down a line _FetchLin(_ilinDisp + 1, &lin, &_ilinDisp); dyp = lin.dypTot - _dypDisp; _cpDisp = lin.cpMin; _dypDisp = lin.dypTot; break; case scaPageUp: // scroll down a page if (_ilinDisp <= 0) break; GetRc(&rc, cooLocal); _FetchLin(_ilinDisp, &linDisp); Assert(linDisp.dypTot == _dypDisp, 0); // determine where to scroll to - try to keep the top line // visible, but scroll up at least one line dyp = LwMax(0, linDisp.dypTot + linDisp.dyp - rc.Dyp()); _FindDyp(dyp, &lin, &ilin); if (lin.cpMin >= linDisp.cpMin) { // we didn't go anywhere so force going up a line _FetchLin(_ilinDisp - 1, &lin, &ilin); } else if (linDisp.dypTot + linDisp.dyp > lin.dypTot + rc.Dyp() && ilin < _ilinDisp - 1) { // the previous disp line crosses the bottom of the ddg, so move // down one line _FetchLin(ilin + 1, &lin, &ilin); } dyp = lin.dypTot - _dypDisp; _ilinDisp = ilin; _cpDisp = lin.cpMin; _dypDisp = lin.dypTot; break; case scaLineUp: // scroll up a line if (_ilinDisp <= 0) break; _FetchLin(_ilinDisp - 1, &lin, &_ilinDisp); dyp = lin.dypTot - _dypDisp; _cpDisp = lin.cpMin; _dypDisp = lin.dypTot; break; } AssertIn(_cpDisp, 0, _ptxtb->CpMac()); _scvVert = _cpDisp; } _SetScrollValues(); if (dxp != 0 || dyp != 0) _ScrollDxpDyp(dxp, dyp); } /*************************************************************************** Move the bits in the window. ***************************************************************************/ void TXTG::_ScrollDxpDyp(long dxp, long dyp) { AssertThis(0); RC rcLoc, rcBad1, rcBad2; // determine the dirty rectangles and update them GetRc(&rcLoc, cooLocal); if (_fClear) { InvalRc(&rcLoc, kginMark); vpappb->UpdateMarked(); return; } Scroll(&rcLoc, -dxp, -dyp, _fMark ? kginMark : kginDraw); if (_fMark) vpappb->UpdateMarked(); } /*************************************************************************** Update the display of the document. ***************************************************************************/ void TXTG::Draw(PGNV pgnv, RC *prcClip) { AssertPo(pgnv, 0); AssertVarMem(prcClip); DrawLines(pgnv, prcClip, kdxpIndentTxtg - _scvHorz, 0, _ilinDisp); if (_fSelOn) _InvertSel(pgnv); _SetScrollValues(); } /*************************************************************************** Draws some lines of the document. ***************************************************************************/ void TXTG::DrawLines(PGNV pgnv, RC *prcClip, long dxp, long dyp, long ilinMin, long ilinLim, ulong grftxtg) { AssertPo(pgnv, 0); AssertVarMem(prcClip); CHP chp; PAP pap; CHR chr; CHRD chrd; RC rc; long xpBase, xp, yp, xpChr, dxpDoc; long cpLine, cpCur, cpLimChp, cpLim, cpLimLine; LIN lin; long ilin; cpLim = _ptxtb->CpMac(); dxpDoc = _DxpDoc(); _FetchLin(ilinMin, &lin); cpLine = lin.cpMin; pgnv->FillRc(prcClip, _ptxtb->AcrBack()); yp = dyp; for (ilin = ilinMin; ilin < ilinLim; ilin++) { if (yp >= prcClip->ypBottom || cpLine >= cpLim) break; _FetchLin(ilin, &lin); if (yp + lin.dyp <= prcClip->ypTop) { yp += lin.dyp; cpLine += lin.ccp; continue; } _FetchPap(cpLine, &pap); xpBase = lin.xpLeft + dxp; cpLimChp = cpCur = cpLine; cpLimLine = cpLine + lin.ccp; xpChr = 0; // draw the line for (;;) { // make sure the chp is valid if (cpCur >= cpLimChp) { _FetchChp(cpCur, &chp, pvNil, &cpLimChp); cpLimChp = LwMin(cpLimChp, cpLimLine); Assert(cpLimChp > cpCur, "why is cpCur >= cpLimChp?"); chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpLimChp, xpChr, dxpDoc, dxpDoc); } chr.GetNextRun(fTrue); chr.GetChrd(&chrd); if (chrd.cpLimDraw > cpCur) { // draw some text xp = xpBase + chr.XpMin(); if (chr.FObject()) { // draw the object _ptxtb->FDrawObject(chr.CpMin(), pgnv, &xp, yp + lin.dypAscent + chp.dypOffset, &chp, prcClip); } else { pgnv->SetFont(chp.onn, chp.grfont, chp.dypFont, tahLeft, tavBaseline); pgnv->DrawRgch(chr.Prgch(), chrd.cpLimDraw - chr.CpMin(), xp, yp + lin.dypAscent + chp.dypOffset, chp.acrFore, chp.acrBack); } } if (chrd.cpLim >= cpLimLine || chr.FBreak()) break; cpCur = chrd.cpLim; xpChr = chrd.xpLim; } _DrawLinExtra(pgnv, prcClip, &lin, dxp, yp, grftxtg); yp += lin.dyp; cpLine = cpLimLine; } } /*************************************************************************** Gives a subclass an opportunity to draw extra stuff associated with the line. Default does nothing. ***************************************************************************/ void TXTG::_DrawLinExtra(PGNV pgnv, PRC prcClip, LIN *plin, long dxp, long yp, ulong grftxtg) { } /*************************************************************************** Handle a mousedown in the TXTG. ***************************************************************************/ bool TXTG::FCmdTrackMouse(PCMD_MOUSE pcmd) { AssertThis(0); AssertVarMem(pcmd); RC rc; long cp; long scaHorz, scaVert; long xp = pcmd->xp; long yp = pcmd->yp; if (pcmd->cid == cidMouseDown) { Assert(vpcex->PgobTracking() == pvNil, "mouse already being tracked!"); _fSelByWord = (pcmd->cact > 1) && !(pcmd->grfcust & fcustShift); vpcex->TrackMouse(this); } else { Assert(vpcex->PgobTracking() == this, "not tracking mouse!"); Assert(pcmd->cid == cidTrackMouse, 0); } //do autoscrolling GetRc(&rc, cooLocal); if (!FIn(xp, rc.xpLeft, rc.xpRight)) { scaHorz = (xp < rc.xpLeft) ? scaLineUp : scaLineDown; xp = LwBound(xp, rc.xpLeft, rc.xpRight); } else scaHorz = scaNil; if (!FIn(yp, rc.ypTop, rc.ypBottom)) { scaVert = (yp < rc.ypTop) ? scaLineUp : scaLineDown; yp = LwBound(yp, rc.ypTop, rc.ypBottom); } else scaVert = scaNil; if (scaHorz != scaNil || scaVert != scaNil) _Scroll(scaHorz, scaVert); //set the selection if (_FGetCpFromPt(xp, yp, &cp, !_fSelByWord)) { if (pcmd->cid != cidMouseDown || (pcmd->grfcust & fcustShift)) { if (_fSelByWord) { cp = (cp < _cpAnchor) ? _ptxtb->CpPrev(cp + 1, fTrue) : _ptxtb->CpNext(cp, fTrue); } SetSel(_cpAnchor, cp); } else { if (_fSelByWord) cp = _ptxtb->CpPrev(cp + 1, fTrue); SetSel(cp, _fSelByWord ? _ptxtb->CpNext(cp, fTrue) : cp); } _fXpValid = fFalse; } _SwitchSel(fTrue); //make sure the selection is on if (!(pcmd->grfcust & fcustMouse)) vpcex->EndMouseTracking(); return fTrue; } /*************************************************************************** Do idle processing. If this handler has the active selection, 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 TXTG::FCmdSelIdle(PCMD pcmd) { AssertThis(0); // if rglw[1] is this one's hid, don't change the sel state. if (pcmd->rglw[1] != Hid()) { if (!pcmd->rglw[0]) _SwitchSel(fFalse, kginDefault); else if (_cpAnchor != _cpOther || _tsSel == 0) _SwitchSel(fTrue); else if (DtsCaret() < TsCurrent() - _tsSel) _SwitchSel(!_fSelOn); } pcmd->rglw[0] = fFalse; return fFalse; } /*************************************************************************** Get the current selection. ***************************************************************************/ void TXTG::GetSel(long *pcpAnchor, long *pcpOther) { AssertThis(0); AssertVarMem(pcpAnchor); AssertVarMem(pcpOther); *pcpAnchor = _cpAnchor; *pcpOther = _cpOther; } /*************************************************************************** Set the selection. ***************************************************************************/ void TXTG::SetSel(long cpAnchor, long cpOther, long gin) { AssertThis(0); long cpMac = _ptxtb->CpMac(); cpAnchor = LwBound(cpAnchor, 0, cpMac); cpOther = LwBound(cpOther, 0, cpMac); if (cpAnchor == _cpAnchor && cpOther == _cpOther) return; if (_fSelOn) { if ((_fMark || _fClear) && gin == kginDraw) gin = kginMark; _pgnv->SetGobRc(this); if (_cpAnchor != cpAnchor || _cpAnchor == _cpOther || cpAnchor == cpOther) { _InvertSel(_pgnv, gin); _cpAnchor = cpAnchor; _cpOther = cpOther; _InvertSel(_pgnv, gin); _tsSel = TsCurrent(); } else { //they have the same anchor and neither is an insertion _InvertCpRange(_pgnv, _cpOther, cpOther, gin); _cpOther = cpOther; } } else { _cpAnchor = cpAnchor; _cpOther = cpOther; _tsSel = 0L; } } /*************************************************************************** Make sure the selection is visible (at least the _cpOther end of it). ***************************************************************************/ void TXTG::ShowSel(void) { AssertThis(0); long cpScroll; long ilinOther, ilinAnchor, ilin; long dxpScroll, dypSel; long xpMin, xpLim; RC rc; LIN linOther, linAnchor, lin; long cpAnchor = _cpAnchor; //find the lines we want to show _FindCp(_cpOther, &linOther, &ilinOther); _FindCp(_cpAnchor, &linAnchor, &ilinAnchor); linOther.dypTot -= _dypDisp; linAnchor.dypTot -= _dypDisp; GetRc(&rc, cooLocal); cpScroll = _cpDisp; if (!FIn(linOther.dypTot, 0, rc.Dyp() - linOther.dyp) || !FIn(linAnchor.dypTot, 0, rc.Dyp() - linAnchor.dyp)) { if (_cpOther < cpAnchor) dypSel = linAnchor.dypTot - linOther.dypTot + linAnchor.dyp; else dypSel = linOther.dypTot - linAnchor.dypTot + linOther.dyp; if (dypSel > rc.Dyp()) { //just show _cpOther cpAnchor = _cpOther; ilinAnchor = ilinOther; linAnchor = linOther; dypSel = linOther.dyp; } if (linOther.dypTot < 0 || linAnchor.dypTot < 0) { //scroll up cpScroll = LwMin(linOther.cpMin, linAnchor.cpMin); } else if (linOther.dypTot + linOther.dyp >= rc.Dyp() || linAnchor.dypTot + linAnchor.dyp >= rc.Dyp()) { //scroll down ilin = LwMin(ilinOther, ilinAnchor); cpScroll = LwMin(linOther.cpMin, linAnchor.cpMin); dypSel = rc.Dyp() - dypSel; for (;;) { if (--ilin < 0) break; _FetchLin(ilin, &lin); if (lin.dyp > dypSel) break; cpScroll -= lin.ccp; dypSel -= lin.dyp; } } } //now do the horizontal stuff xpMin = _DxpFromCp(linOther.cpMin, _cpOther) + linOther.xpLeft - _scvHorz; xpLim = _DxpFromCp(linAnchor.cpMin, cpAnchor) + linAnchor.xpLeft - _scvHorz; if (LwAbs(xpLim - xpMin) > rc.Dxp()) { //can't show both if (xpMin > xpLim) { xpLim = xpMin; xpMin = xpLim - rc.Dxp(); } else xpLim = xpMin + rc.Dxp(); } else SortLw(&xpMin, &xpLim); dxpScroll = LwMax(LwMin(0, rc.xpRight - xpLim), rc.xpLeft - xpMin); if (dxpScroll != 0 || cpScroll != _cpDisp) _Scroll(scaToVal, scaToVal, _scvHorz - dxpScroll, cpScroll); } /*************************************************************************** Turn the selection off. ***************************************************************************/ void TXTG::HideSel(void) { AssertThis(0); if (!_fSelOn) return; _SwitchSel(fFalse); _tsSel = 0; } /*************************************************************************** Turn the sel on or off according to fOn. ***************************************************************************/ void TXTG::_SwitchSel(bool fOn, long gin) { AssertThis(0); if (FPure(fOn) != FPure(_fSelOn)) { if ((_fMark || _fClear) && gin == kginDraw) gin = kginMark; _pgnv->SetGobRc(this); _InvertSel(_pgnv, gin); _fSelOn = FPure(fOn); _tsSel = TsCurrent(); } } /*************************************************************************** Invert the current selection. ***************************************************************************/ void TXTG::_InvertSel(PGNV pgnv, long gin) { AssertThis(0); AssertPo(pgnv, 0); RC rc, rcT; GetRc(&rc, cooLocal); if (_cpAnchor == _cpOther) { //insertion bar _GetXpYpFromCp(_cpAnchor, &rcT.ypTop, &rcT.ypBottom, &rcT.xpLeft); rcT.xpRight = --rcT.xpLeft + 2; if (rcT.FIntersect(&rc)) { if (kginDraw == gin) pgnv->FillRc(&rcT, kacrInvert); else InvalRc(&rcT, gin); } } else _InvertCpRange(pgnv, _cpAnchor, _cpOther, gin); } /*************************************************************************** Invert a range. ***************************************************************************/ void TXTG::_InvertCpRange(PGNV pgnv, long cp1, long cp2, long gin) { AssertThis(0); AssertPo(pgnv, 0); AssertIn(cp1, 0, _ptxtb->CpMac()); AssertIn(cp2, 0, _ptxtb->CpMac()); RC rc1, rc2, rcClip, rcT, rcDoc; if (cp1 == cp2) return; SortLw(&cp1, &cp2); _GetXpYpFromCp(cp1, &rc1.ypTop, &rc1.ypBottom, &rc1.xpLeft); _GetXpYpFromCp(cp2, &rc2.ypTop, &rc2.ypBottom, &rc2.xpRight); GetRc(&rcClip, cooLocal); if (rc1.ypTop >= rcClip.ypBottom || rc2.ypBottom <= rcClip.ypTop) return; if (rc1.ypTop == rc2.ypTop && rc1.ypBottom == rc2.ypBottom) { // only one line involved rc1.xpRight = rc2.xpRight; if (rcT.FIntersect(&rc1, &rcClip)) { if (kginDraw == gin) pgnv->HiliteRc(&rcT, kacrWhite); else InvalRc(&rcT, gin); } return; } // invert the sel on the first line rc1.xpRight = kdxpIndentTxtg - _scvHorz + _DxpDoc(); if (rcT.FIntersect(&rc1, &rcClip)) { if (kginDraw == gin) pgnv->HiliteRc(&rcT, kacrWhite); else InvalRc(&rcT, gin); } //invert the main rectangular block rc1.xpLeft = kdxpIndentTxtg - _scvHorz; rc1.ypTop = rc1.ypBottom; rc1.ypBottom = rc2.ypTop; if (rcT.FIntersect(&rc1, &rcClip)) { if (kginDraw == gin) pgnv->HiliteRc(&rcT, kacrWhite); else InvalRc(&rcT, gin); } //invert the last line rc2.xpLeft = rc1.xpLeft; if (rcT.FIntersect(&rc2, &rcClip)) { if (kginDraw == gin) pgnv->HiliteRc(&rcT, kacrWhite); else InvalRc(&rcT, gin); } } /*************************************************************************** Handle a key down. ***************************************************************************/ bool TXTG::FCmdKey(PCMD_KEY pcmd) { const long kcchInsBuf = 64; AssertThis(0); AssertVarMem(pcmd); ulong grfcust; long vkDone; long ichLim; long cact; long dcp, cpT; CMD cmd; LIN lin, linT; long ilin, ilinT; RC rc; achar rgch[kcchInsBuf + 1]; // keep fetching characters until we get a cursor key, delete key or // until the buffer is full. vkDone = vkNil; ichLim = 0; do { grfcust = pcmd->grfcust; switch (pcmd->vk) { //these keys all terminate the key fetching loop case kvkHome: case kvkEnd: case kvkLeft: case kvkRight: case kvkUp: case kvkDown: case kvkPageUp: case kvkPageDown: case kvkDelete: case kvkBack: vkDone = pcmd->vk; goto LInsert; default: if (chNil == pcmd->ch) break; for (cact = 0; cact < pcmd->cact && ichLim < kcchInsBuf; cact++) { rgch[ichLim++] = (achar)pcmd->ch; #ifdef WIN if ((achar)pcmd->ch == kchReturn) rgch[ichLim++] = kchLineFeed; #endif //WIN } break; } pcmd = (PCMD_KEY)&cmd; } while (ichLim < kcchInsBuf && vpcex->FGetNextKey(&cmd)); LInsert: if (ichLim > 0) { //have some characters to insert FReplace(rgch, ichLim, _cpAnchor, _cpOther); } dcp = 0; switch (vkDone) { case kvkHome: if (grfcust & fcustCmd) dcp = -_cpOther; else { _FindCp(_cpOther, &lin); dcp = lin.cpMin - _cpOther; } _fXpValid = fFalse; goto LSetSel; case kvkEnd: if (grfcust & fcustCmd) dcp = _ptxtb->CpMac() - _cpOther; else { _FindCp(_cpOther, &lin); cpT = _ptxtb->CpPrev(lin.cpMin + lin.ccp); AssertIn(cpT, _cpOther, _ptxtb->CpMac()); dcp = cpT - _cpOther; } _fXpValid = fFalse; goto LSetSel; case kvkLeft: dcp = _ptxtb->CpPrev(_cpOther, FPure(grfcust & fcustCmd)) - _cpOther; _fXpValid = fFalse; goto LSetSel; case kvkRight: dcp = _ptxtb->CpNext(_cpOther, FPure(grfcust & fcustCmd)) - _cpOther; _fXpValid = fFalse; goto LSetSel; case kvkUp: case kvkPageUp: case kvkDown: case kvkPageDown: // get the LIN for _cpOther and make sure _xpSel is up to date _FindCp(_cpOther, &lin, &ilin); if (!_fXpValid) { // get the xp of _cpOther _xpSel = lin.xpLeft + _DxpFromCp(lin.cpMin, _cpOther); _fXpValid = fTrue; } switch (vkDone) { case kvkUp: if (ilin == 0) { dcp = -_cpOther; goto LSetSel; } _FetchLin(ilin - 1, &lin); break; case kvkPageUp: if (ilin == 0) { dcp = -_cpOther; goto LSetSel; } GetRc(&rc, cooLocal); _FindDyp(LwMax(0, lin.dypTot - rc.Dyp() + lin.dyp), &linT, &ilinT); if (linT.cpMin >= lin.cpMin) { // we didn't go anywhere so force going up a line _FetchLin(ilin - 1, &lin); } else if (lin.dypTot + lin.dyp > linT.dypTot + rc.Dyp() && ilinT < ilin - 1) { // the previous line crosses the bottom of the ddg, so move // down one line _FetchLin(ilinT + 1, &lin); } else lin = linT; break; case kvkDown: if (lin.cpMin + lin.ccp >= _ptxtb->CpMac()) { dcp = _ptxtb->CpMac() - _cpOther; goto LSetSel; } _FetchLin(ilin + 1, &lin); break; case kvkPageDown: if (lin.cpMin + lin.ccp >= _ptxtb->CpMac()) { dcp = _ptxtb->CpMac() - _cpOther; goto LSetSel; } GetRc(&rc, cooLocal); _FindDyp(lin.dypTot + rc.Dyp(), &linT, &ilinT); if (linT.cpMin <= lin.cpMin) { // we didn't go anywhere so force going down a line _FetchLin(ilin + 1, &lin); } else if (linT.dypTot + linT.dyp > lin.dypTot + rc.Dyp() && ilinT > ilin + 1) { // the line crosses the bottom of the ddg so back up one line Assert(ilinT > 0, 0); _FetchLin(ilinT - 1, &lin); } else lin = linT; break; } // we have the line, now find the position on the line _FGetCpFromXp(_xpSel - _scvHorz, &lin, &dcp); dcp -= _cpOther; LSetSel: //move the selection if (grfcust & fcustShift) { //extend selection SetSel(_cpAnchor, _cpOther + dcp); ShowSel(); } else { cpT = _cpOther; if (cpT == _cpAnchor || (grfcust & fcustCmd)) cpT += dcp; else if ((dcp > 0) != (cpT > _cpAnchor)) cpT = _cpAnchor; SetSel(cpT, cpT); ShowSel(); } break; case kvkDelete: case kvkBack: if (_cpAnchor != _cpOther) dcp = _cpOther - _cpAnchor; else if (vkDone == kvkDelete) { dcp = _ptxtb->CpNext(_cpAnchor) - _cpAnchor; if (_cpAnchor + dcp >= _ptxtb->CpMac()) dcp = 0; } else dcp = _ptxtb->CpPrev(_cpAnchor) - _cpAnchor; if (dcp != 0) FReplace(pvNil, 0, _cpAnchor, _cpAnchor + dcp); break; } return fTrue; } /*************************************************************************** Return the maximum scroll value for this view of the doc. ***************************************************************************/ long TXTG::_ScvMax(bool fVert) { RC rc; long dxp; if (fVert) return _ptxtb->CpMac() - 1; dxp = _DxpDoc() + 2 * kdxpIndentTxtg; GetRc(&rc, cooLocal); return LwMax(0, dxp - rc.Dxp()); } /*************************************************************************** Return the logical width of the text "page". ***************************************************************************/ long TXTG::_DxpDoc(void) { return _ptxtb->DxpDef(); } /*************************************************************************** Set the tab width. Default does nothing. ***************************************************************************/ void TXTG::SetDxpTab(long dxp) { AssertThis(0); } /*************************************************************************** Set the document width. Default calls SetDxpDef on the TXTB. ***************************************************************************/ void TXTG::SetDxpDoc(long dxp) { AssertThis(0); dxp = LwBound(dxp, 1, kcbMax); _ptxtb->SetDxpDef(dxp); } /*************************************************************************** Show or hide the ruler. ***************************************************************************/ void TXTG::ShowRuler(bool fShow) { AssertThis(0); RC rcAbs, rcRel; long dyp; if (FPure(fShow) == (_ptrul != pvNil)) return; dyp = _DypTrul(); if (fShow) { PGOB pgob; GCB gcb; pgob = PgobPar(); if (pvNil == pgob || !pgob->FIs(kclsDSG)) return; GetPos(&rcAbs, &rcRel); rcAbs.ypTop += dyp; SetPos(&rcAbs, &rcRel); rcRel.ypBottom = rcRel.ypTop; rcAbs.ypBottom = rcAbs.ypTop; rcAbs.ypTop -= dyp; gcb.Set(HidUnique(), pgob, fgobNil, kginDefault, &rcAbs, &rcRel); if (pvNil == (_ptrul = _PtrulNew(&gcb))) goto LFail; } else { ReleasePpo(&_ptrul); LFail: GetPos(&rcAbs, &rcRel); rcAbs.ypTop -= dyp; SetPos(&rcAbs, &rcRel); } } /*************************************************************************** Return the height of the ruler. ***************************************************************************/ long TXTG::_DypTrul(void) { AssertThis(0); return 0; } /*************************************************************************** Create the ruler. ***************************************************************************/ PTRUL TXTG::_PtrulNew(PGCB pgcb) { AssertThis(0); return pvNil; } /*************************************************************************** Get the natural width and height of the view on the document. ***************************************************************************/ void TXTG::GetNaturalSize(long *pdxp, long *pdyp) { AssertThis(0); AssertNilOrVarMem(pdxp); AssertNilOrVarMem(pdyp); LIN lin; if (pvNil != pdxp) *pdxp = _DxpDoc() + 2 * kdxpIndentTxtg; if (pvNil == pdyp) return; _FetchLin(kcbMax - 1, &lin); *pdyp = lin.dypTot + lin.dyp; } /*************************************************************************** Constructor for the plain line text document display gob. ***************************************************************************/ TXLG::TXLG(PTXTB ptxtb, PGCB pgcb, long onn, ulong grfont, long dypFont, long cchTab) : TXLG_PAR(ptxtb, pgcb) { RC rc; achar ch = kchSpace; GNV gnv(this); gnv.SetFont(onn, fontNil, dypFont); gnv.GetRcFromRgch(&rc, &ch, 1); _dxpChar = rc.Dxp(); _onn = onn; _grfont = grfont; _dypFont = dypFont; _cchTab = LwBound(cchTab, 1, kcbMax); } /*************************************************************************** Static method to create a new plain line text doc display gob. ***************************************************************************/ PTXLG TXLG::PtxlgNew(PTXTB ptxtb, PGCB pgcb, long onn, ulong grfont, long dypFont, long cchTab) { PTXLG ptxlg; if (pvNil == (ptxlg = NewObj TXLG(ptxtb, pgcb, onn, grfont, dypFont, cchTab))) return pvNil; if (!ptxlg->_FInit()) { ReleasePpo(&ptxlg); return pvNil; } ptxlg->Activate(fTrue); return ptxlg; } /*************************************************************************** Get the width of the logical "page". For a TXLG, this is some big value, so we do no word wrap. ***************************************************************************/ long TXLG::_DxpDoc(void) { return kswMax; } /*************************************************************************** Set the tab width. ***************************************************************************/ void TXLG::SetDxpTab(long dxp) { AssertThis(0); long cch; cch = LwBound(dxp / _dxpChar, 1, kswMax / _dxpChar); if (cch != _cchTab) { _cchTab = cch; InvalCp(0, _ptxtb->CpMac(), _ptxtb->CpMac()); } } /*************************************************************************** Set the document width. Does nothing. ***************************************************************************/ void TXLG::SetDxpDoc(long dxp) { AssertThis(0); } /*************************************************************************** Get the character properties for display. These are the same for all characters in the TXLG. ***************************************************************************/ void TXLG::_FetchChp(long cp, PCHP pchp, long *pcpMin, long *pcpLim) { AssertIn(cp, 0, _ptxtb->CpMac()); AssertVarMem(pchp); AssertNilOrVarMem(pcpMin); AssertNilOrVarMem(pcpLim); pchp->Clear(); pchp->grfont = _grfont; pchp->onn = _onn; pchp->dypFont = _dypFont; pchp->acrFore = kacrBlack; pchp->acrBack = kacrWhite; if (pvNil != pcpMin) *pcpMin = 0; if (pvNil != pcpLim) *pcpLim = _ptxtb->CpMac(); } /*************************************************************************** Get the paragraph properties for disply. These are constant for all characters in the TXLG. ***************************************************************************/ void TXLG::_FetchPap(long cp, PPAP ppap, long *pcpMin, long *pcpLim) { AssertIn(cp, 0, _ptxtb->CpMac()); AssertVarMem(ppap); AssertNilOrVarMem(pcpMin); AssertNilOrVarMem(pcpLim); ClearPb(ppap, size(PAP)); ppap->dxpTab = (short)LwMul(_cchTab, _dxpChar); ppap->numLine = kdenLine; ppap->numAfter = kdenAfter; if (pvNil != pcpMin) *pcpMin = 0; if (pvNil != pcpLim) *pcpLim = _ptxtb->CpMac(); } /*************************************************************************** Copy the selection. ***************************************************************************/ bool TXLG::_FCopySel(PDOCB *ppdocb) { AssertThis(0); AssertNilOrVarMem(ppdocb); PTXPD ptxpd; if (_cpAnchor == _cpOther) return fFalse; if (pvNil == ppdocb) return fTrue; if (pvNil != (ptxpd = TXPD::PtxpdNew(pvNil))) { long cpMin = LwMin(_cpAnchor, _cpOther); long cpLim = LwMax(_cpAnchor, _cpOther); ptxpd->SuspendUndo(); if (!ptxpd->FReplaceBsf(_ptxtb->Pbsf(), cpMin, cpLim - cpMin, 0, 0, fdocNil)) { ReleasePpo(&ptxpd); } else ptxpd->ResumeUndo(); } *ppdocb = ptxpd; return pvNil != *ppdocb; } /*************************************************************************** Delete the selection. ***************************************************************************/ void TXLG::_ClearSel(void) { AssertThis(0); FReplace(pvNil, 0, _cpAnchor, _cpOther); ShowSel(); } /*************************************************************************** Paste the selection. ***************************************************************************/ bool TXLG::_FPaste(PCLIP pclip, bool fDoIt, long cid) { AssertThis(0); AssertPo(pclip, 0); long cp1, cp2; long ccp; PTXTB ptxtb; if (cid != cidPaste || !pclip->FGetFormat(kclsTXTB)) return fFalse; if (!fDoIt) return fTrue; if (!pclip->FGetFormat(kclsTXTB, (PDOCB *)&ptxtb)) return fFalse; AssertPo(ptxtb, 0); if ((ccp = ptxtb->CpMac() - 1) <= 0) { ReleasePpo(&ptxtb); return fTrue; } HideSel(); cp1 = LwMin(_cpAnchor, _cpOther); cp2 = LwMax(_cpAnchor, _cpOther); if (!_ptxtb->FReplaceTxtb(ptxtb, 0, ccp, cp1, cp2 - cp1)) { ReleasePpo(&ptxtb); return fFalse; } ReleasePpo(&ptxtb); cp1 += ccp; SetSel(cp1, cp1); ShowSel(); return fTrue; } /*************************************************************************** Constructor for a rich text document display gob. ***************************************************************************/ TXRG::TXRG(PTXRD ptxrd, PGCB pgcb) : TXRG_PAR(ptxrd, pgcb) { } /*************************************************************************** Create a new rich text document display GOB. ***************************************************************************/ PTXRG TXRG::PtxrgNew(PTXRD ptxrd, PGCB pgcb) { PTXRG ptxrg; if (pvNil == (ptxrg = NewObj TXRG(ptxrd, pgcb))) return pvNil; if (!ptxrg->_FInit()) { ReleasePpo(&ptxrg); return pvNil; } ptxrg->Activate(fTrue); return ptxrg; } #ifdef DEBUG /*************************************************************************** Assert the validity of a TXRG. ***************************************************************************/ void TXRG::AssertValid(ulong grf) { TXRG_PAR::AssertValid(0); AssertNilOrPo(_ptrul, 0); } #endif //DEBUG /*************************************************************************** Get the character properties for displaying the given cp. ***************************************************************************/ void TXRG::_FetchChp(long cp, PCHP pchp, long *pcpMin, long *pcpLim) { ((PTXRD)_ptxtb)->FetchChp(cp, pchp, pcpMin, pcpLim); } /*************************************************************************** Get the paragraph properties for displaying the given cp. ***************************************************************************/ void TXRG::_FetchPap(long cp, PPAP ppap, long *pcpMin, long *pcpLim) { ((PTXRD)_ptxtb)->FetchPap(cp, ppap, pcpMin, pcpLim); } /*************************************************************************** Set the tab width for the currently selected paragraph(s). ***************************************************************************/ void TXRG::SetDxpTab(long dxp) { AssertThis(0); long cpMin, cpLim, cpAnchor, cpOther; PAP papOld, papNew; dxp = LwRoundClosest(dxp, kdzpInch / 8); dxp = LwBound(dxp, kdzpInch / 8, 100 * kdzpInch); ClearPb(&papOld, size(PAP)); papNew = papOld; cpMin = LwMin(cpAnchor = _cpAnchor, cpOther = _cpOther); cpLim = LwMax(_cpAnchor, _cpOther); papNew.dxpTab = (short)dxp; _SwitchSel(fFalse, ginNil); if (!((PTXRD)_ptxtb)->FApplyPap(cpMin, cpLim - cpMin, &papNew, &papOld, &cpMin, &cpLim)) { _SwitchSel(fTrue, kginMark); } SetSel(cpAnchor, cpOther); ShowSel(); } /*************************************************************************** Set the selection for the TXRG. Invalidates _chpIns if the selection changes. ***************************************************************************/ void TXRG::SetSel(long cpAnchor, long cpOther, long gin) { AssertThis(0); long cpMac = _ptxtb->CpMac(); cpAnchor = LwBound(cpAnchor, 0, cpMac); cpOther = LwBound(cpOther, 0, cpMac); if (cpAnchor == _cpAnchor && cpOther == _cpOther) return; _fValidChp = fFalse; TXRG_PAR::SetSel(cpAnchor, cpOther, gin); if (pvNil != _ptrul) { PAP pap; _FetchPap(LwMin(_cpAnchor, _cpOther), &pap); _ptrul->SetDxpTab(pap.dxpTab); } } /*************************************************************************** Get the chp to use to replace the given range of characters. If the range is empty and not at the beginning of a paragraph, gets the chp of the previous character. Otherwise gets the chp of the character at LwMin(cp1, cp2). ***************************************************************************/ void TXRG::_FetchChpSel(long cp1, long cp2, PCHP pchp) { AssertThis(0); AssertVarMem(pchp); long cp = LwMin(cp1, cp2); if (cp1 == cp2 && cp1 > 0) { if (!_ptxtb->FMinPara(cp) || _ptxtb->FMinPara(cp2 = _ptxtb->CpNext(cp)) || cp2 >= _ptxtb->CpMac()) { cp = _ptxtb->CpPrev(cp); } } // NOTE: don't just call _FetchChp here. _FetchChp allows derived // classes to modify the chp used for display without affecting the // chp used for editing. This chp is an editing chp. Same note // applies several other places in this file. ((PTXRD)_ptxtb)->FetchChp(cp, pchp); } /*************************************************************************** Make sure the _chpIns is valid. ***************************************************************************/ void TXRG::_EnsureChpIns(void) { AssertThis(0); if (!_fValidChp) { _FetchChpSel(_cpAnchor, _cpOther, &_chpIns); _fValidChp = fTrue; } } /*************************************************************************** Replaces the characters between cp1 and cp2 with the given ones. ***************************************************************************/ bool TXRG::FReplace(achar *prgch, long cch, long cp1, long cp2) { AssertIn(cch, 0, kcbMax); AssertPvCb(prgch, cch); AssertIn(cp1, 0, _ptxtb->CpMac()); AssertIn(cp2, 0, _ptxtb->CpMac()); CHP chp; bool fSetChpIns = fFalse; HideSel(); SortLw(&cp1, &cp2); if (cp1 == LwMin(_cpAnchor, _cpOther) && cp2 == LwMax(_cpAnchor, _cpOther)) { _EnsureChpIns(); chp = _chpIns; fSetChpIns = fTrue; } else _FetchChpSel(cp1, cp2, &chp); if (!((PTXRD)_ptxtb)->FReplaceRgch(prgch, cch, cp1, cp2 - cp1, &chp)) return fFalse; cp1 += cch; SetSel(cp1, cp1); ShowSel(); if (fSetChpIns) { // preserve the _chpIns _chpIns = chp; _fValidChp = fTrue; } return fTrue; } /*************************************************************************** Copy the selection. ***************************************************************************/ bool TXRG::_FCopySel(PDOCB *ppdocb) { AssertNilOrVarMem(ppdocb); PTXRD ptxrd; if (_cpAnchor == _cpOther) return fFalse; if (pvNil == ppdocb) return fTrue; if (pvNil != (ptxrd = TXRD::PtxrdNew(pvNil))) { long cpMin = LwMin(_cpAnchor, _cpOther); long cpLim = LwMax(_cpAnchor, _cpOther); ptxrd->SuspendUndo(); if (!ptxrd->FReplaceTxrd((PTXRD)_ptxtb, cpMin, cpLim - cpMin, 0, 0, fdocNil)) { ReleasePpo(&ptxrd); } else ptxrd->ResumeUndo(); } *ppdocb = ptxrd; return pvNil != *ppdocb; } /*************************************************************************** Delete the selection. ***************************************************************************/ void TXRG::_ClearSel(void) { FReplace(pvNil, 0, _cpAnchor, _cpOther); ShowSel(); } /*************************************************************************** Paste the selection. ***************************************************************************/ bool TXRG::_FPaste(PCLIP pclip, bool fDoIt, long cid) { AssertThis(0); long cp1, cp2; long ccp; PTXTB ptxtb; bool fRet; if (cid != cidPaste || !pclip->FGetFormat(kclsTXTB)) return fFalse; if (!fDoIt) return fTrue; if (!pclip->FGetFormat(kclsTXRD, (PDOCB *)&ptxtb) && !pclip->FGetFormat(kclsTXTB, (PDOCB *)&ptxtb)) { return fFalse; } AssertPo(ptxtb, 0); if ((ccp = ptxtb->CpMac() - 1) <= 0) { ReleasePpo(&ptxtb); return fTrue; } HideSel(); cp1 = LwMin(_cpAnchor, _cpOther); cp2 = LwMax(_cpAnchor, _cpOther); _ptxtb->BumpCombineUndo(); if (ptxtb->FIs(kclsTXRD)) { fRet = ((PTXRD)_ptxtb)->FReplaceTxrd((PTXRD)ptxtb, 0, ccp, cp1, cp2 - cp1); } else { _EnsureChpIns(); fRet = ((PTXRD)_ptxtb)->FReplaceTxtb(ptxtb, 0, ccp, cp1, cp2 - cp1, &_chpIns); } ReleasePpo(&ptxtb); if (fRet) { _ptxtb->BumpCombineUndo(); cp1 += ccp; SetSel(cp1, cp1); ShowSel(); } return fRet; } /*************************************************************************** Apply the given character properties to the current selection. ***************************************************************************/ bool TXRG::FApplyChp(PCHP pchp, PCHP pchpDiff) { AssertThis(0); AssertVarMem(pchp); AssertNilOrVarMem(pchpDiff); long cpMin, cpLim, cpAnchor, cpOther; ulong grfont; cpMin = LwMin(cpAnchor = _cpAnchor, cpOther = _cpOther); cpLim = LwMax(_cpAnchor, _cpOther); if (cpMin == cpLim) { if (pvNil == pchpDiff) { _chpIns = *pchp; _fValidChp = fTrue; return fTrue; } _EnsureChpIns(); if (fontNil != (grfont = pchp->grfont ^ pchpDiff->grfont)) _chpIns.grfont = (_chpIns.grfont & ~grfont) | (pchp->grfont & grfont); if (pchp->onn != pchpDiff->onn) _chpIns.onn = pchp->onn; if (pchp->dypFont != pchpDiff->dypFont) _chpIns.dypFont = pchp->dypFont; if (pchp->dypOffset != pchpDiff->dypOffset) _chpIns.dypOffset = pchp->dypOffset; if (pchp->acrFore != pchpDiff->acrFore) _chpIns.acrFore = pchp->acrFore; if (pchp->acrBack != pchpDiff->acrBack) _chpIns.acrBack = pchp->acrBack; AssertThis(0); return fTrue; } _SwitchSel(fFalse, ginNil); if (!((PTXRD)_ptxtb)->FApplyChp(cpMin, cpLim - cpMin, pchp, pchpDiff)) { _SwitchSel(fTrue, kginMark); return fFalse; } _fValidChp = fFalse; SetSel(cpAnchor, cpOther); ShowSel(); return fTrue; } /*************************************************************************** Apply the given paragraph properties to the current selection. ***************************************************************************/ bool TXRG::FApplyPap(PPAP ppap, PPAP ppapDiff, bool fExpand) { AssertThis(0); AssertVarMem(ppap); AssertNilOrVarMem(ppapDiff); long cpMin, cpLim, cpAnchor, cpOther; cpMin = LwMin(cpAnchor = _cpAnchor, cpOther = _cpOther); cpLim = LwMax(_cpAnchor, _cpOther); _SwitchSel(fFalse, ginNil); if (!((PTXRD)_ptxtb)->FApplyPap(cpMin, cpLim - cpMin, ppap, ppapDiff, pvNil, pvNil, fExpand)) { _SwitchSel(fTrue, kginMark); return fFalse; } SetSel(cpAnchor, cpOther); ShowSel(); return fTrue; } /*************************************************************************** Apply a character or paragraph property ***************************************************************************/ bool TXRG::FCmdApplyProperty(PCMD pcmd) { AssertThis(0); AssertVarMem(pcmd); CHP chpOld, chpNew; PAP papOld, papNew; long onn; STN stn; ClearPb(&papOld, size(PAP)); papNew = papOld; _EnsureChpIns(); chpOld = chpNew = _chpIns; switch (pcmd->cid) { case cidPlain: chpNew.grfont = fontNil; chpOld.grfont = (ulong)~fontNil; goto LApplyChp; case cidBold: chpNew.grfont ^= fontBold; goto LApplyChp; case cidItalic: chpNew.grfont ^= fontItalic; goto LApplyChp; case cidUnderline: chpNew.grfont ^= fontUnderline; goto LApplyChp; case cidChooseFont: if (pvNil == pcmd->pgg || pcmd->pgg->IvMac() != 1 || !stn.FSetData(pcmd->pgg->QvGet(0), pcmd->pgg->Cb(0))) { break; } if (!vntl.FGetOnn(&stn, &onn)) break; chpNew.onn = onn; chpOld.onn = ~onn; goto LApplyChp; case cidChooseSubSuper: // the amount to offset by is pcmd->rglw[0] ^ (1L << 31), so 0 can be // used to indicate that we're to ask the user. if (pcmd->rglw[0] == 0) { long dyp = _chpIns.dypOffset; // ask the user for the amount to sub/superscript by if (!_FGetOtherSubSuper(&dyp)) return fTrue; pcmd->rglw[0] = dyp ^ (1L << 31); } chpNew.dypOffset = pcmd->rglw[0] ^ (1L << 31); chpOld.dypOffset = ~chpNew.dypOffset; goto LApplyChp; case cidChooseFontSize: if (pcmd->rglw[0] == 0) { long dyp = _chpIns.dypFont; // ask the user for the font size if (!_FGetOtherSize(&dyp)) return fTrue; pcmd->rglw[0] = dyp; } if (!FIn(pcmd->rglw[0], 6, 256)) return fTrue; chpNew.dypFont = pcmd->rglw[0]; chpOld.dypFont = ~pcmd->rglw[0]; LApplyChp: FApplyChp(&chpNew, &chpOld); break; case cidJustifyLeft: papNew.jc = jcLeft; papOld.jc = jcLim; goto LApplyPap; case cidJustifyCenter: papNew.jc = jcCenter; papOld.jc = jcLim; goto LApplyPap; case cidJustifyRight: papNew.jc = jcRight; papOld.jc = jcLim; goto LApplyPap; case cidIndentNone: papNew.nd = ndNone; papOld.nd = ndLim; goto LApplyPap; case cidIndentFirst: papNew.nd = ndFirst; papOld.nd = ndLim; goto LApplyPap; case cidIndentRest: papNew.nd = ndRest; papOld.nd = ndLim; goto LApplyPap; case cidIndentAll: papNew.nd = ndAll; papOld.nd = ndLim; LApplyPap: FApplyPap(&papNew, &papOld); break; } return fTrue; } /*************************************************************************** Get a font size from the user. ***************************************************************************/ bool TXRG::_FGetOtherSize(long *pdypFont) { AssertThis(0); AssertVarMem(pdypFont); TrashVar(pdypFont); return fFalse; } /*************************************************************************** Get the amount to sub/superscript from the user. ***************************************************************************/ bool TXRG::_FGetOtherSubSuper(long *pdypOffset) { AssertThis(0); AssertVarMem(pdypOffset); TrashVar(pdypOffset); return fFalse; } /*************************************************************************** Apply a character or paragraph property ***************************************************************************/ bool TXRG::FSetColor(ACR *pacrFore, ACR *pacrBack) { AssertThis(0); AssertNilOrPo(pacrFore, 0); AssertNilOrPo(pacrBack, 0); CHP chp, chpDiff; chp.Clear(); chpDiff.Clear(); if (pvNil != pacrFore) { chp.acrFore = *pacrFore; chpDiff.acrFore.SetToInvert(); if (chpDiff.acrFore == chp.acrFore) chpDiff.acrFore.SetToClear(); } if (pvNil != pacrBack) { chp.acrBack = *pacrBack; chpDiff.acrBack.SetToInvert(); if (chpDiff. acrBack == chp.acrBack) chpDiff.acrBack.SetToClear(); } return FApplyChp(&chp, &chpDiff); } /*************************************************************************** Enable, check/uncheck property commands. ***************************************************************************/ bool TXRG::FEnablePropCmd(PCMD pcmd, ulong *pgrfeds) { PAP pap; bool fCheck; STN stn, stnT; _EnsureChpIns(); ((PTXRD)_ptxtb)->FetchPap(LwMin(_cpAnchor, _cpOther), &pap); switch (pcmd->cid) { case cidPlain: fCheck = (_chpIns.grfont == fontNil); break; case cidBold: fCheck = FPure(_chpIns.grfont & fontBold); break; case cidItalic: fCheck = FPure(_chpIns.grfont & fontItalic); break; case cidUnderline: fCheck = FPure(_chpIns.grfont & fontUnderline); break; case cidChooseFont: if (pvNil == pcmd->pgg || pcmd->pgg->IvMac() != 1) break; if (!stnT.FSetData(pcmd->pgg->PvLock(0), pcmd->pgg->Cb(0))) { pcmd->pgg->Unlock(); break; } pcmd->pgg->Unlock(); vntl.GetStn(_chpIns.onn, &stn); fCheck = stn.FEqual(&stnT); break; case cidChooseFontSize: fCheck = (_chpIns.dypFont == pcmd->rglw[0]); break; case cidChooseSubSuper: fCheck = (_chpIns.dypOffset == (pcmd->rglw[0] ^ (1L << 31))); break; case cidJustifyLeft: fCheck = (pap.jc == jcLeft); break; case cidJustifyCenter: fCheck = (pap.jc == jcCenter); break; case cidJustifyRight: fCheck = (pap.jc == jcRight); break; case cidIndentNone: fCheck = (pap.nd == ndNone); break; case cidIndentFirst: fCheck = (pap.nd == ndFirst); break; case cidIndentRest: fCheck = (pap.nd == ndRest); break; case cidIndentAll: fCheck = (pap.nd == ndAll); break; } *pgrfeds = fCheck ? fedsEnable | fedsCheck : fedsEnable | fedsUncheck; return fTrue; }