Windows2003-3790/windows/richedit/re30/select.cpp
2020-09-30 16:53:55 +02:00

4296 lines
143 KiB
C++

/*
* @doc INTERNAL
*
* @module SELECT.CPP -- Implement the CTxtSelection class |
*
* This module implements the internal CTxtSelection methods.
* See select2.c and range2.c for the ITextSelection methods
*
* Authors: <nl>
* RichEdit 1.0 code: David R. Fulmer
* Christian Fortini (initial conversion to C++)
* Murray Sargent <nl>
*
* @devnote
* The selection UI is one of the more intricate parts of an editor.
* One common area of confusion is the "ambiguous cp", that is,
* a cp at the beginning of one line, which is also the cp at the
* end of the previous line. We control which location to use by
* the _fCaretNotAtBOL flag. Specifically, the caret is OK at the
* beginning of the line (BOL) (_fCaretNotAtBOL = FALSE) except in
* three cases:
*
* 1) the user clicked at or past the end of a wrapped line,
* 2) the user typed End key on a wrapped line,
* 3) the active end of a nondegenerate selection is at the EOL.
*
* Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_select.h"
#include "_edit.h"
#include "_disp.h"
#include "_measure.h"
#include "_font.h"
#include "_rtfconv.h"
#include "_antievt.h"
#ifdef LINESERVICES
#include "_ols.h"
#endif
ASSERTDATA
// ======================= Invariant stuff and Constructors ======================================================
#define DEBUG_CLASSNAME CTxtSelection
#include "_invar.h"
#ifdef DEBUG
BOOL
CTxtSelection::Invariant() const
{
// FUTURE: maybe add some thoughtful asserts...
static LONG numTests = 0;
numTests++; // how many times we've been called
if(IsInOutlineView() && _cch)
{
LONG cpMin, cpMost;
GetRange(cpMin, cpMost);
CTxtPtr tp(_rpTX); // Scan range for an EOP
tp.SetCp(cpMin);
// _fSelHasEop flag may be off when last cr selected so don't
// assert in that case.
if (GetPed()->GetAdjustedTextLength() != cpMost)
{
AssertSz((unsigned)(tp.FindEOP(cpMost - cpMin) != 0) == _fSelHasEOP,
"Incorrect CTxtSelection::_fSelHasEOP");
}
}
return CTxtRange::Invariant();
}
#endif
CTxtSelection::CTxtSelection(CDisplay * const pdp) :
CTxtRange(pdp->GetPed())
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::CTxtSelection");
Assert(pdp);
Assert(GetPed());
_fSel = TRUE; // This range is a selection
_pdp = pdp;
_hbmpCaret = NULL;
_fEOP = FALSE; // Haven't typed a CR
// Set show-selection flag to inverse of hide-selection flag in ped
_fShowSelection = !GetPed()->fHideSelection();
// When we are initialized we don't have a selection therefore,
// we do want to show caret.
_fShowCaret = TRUE;
}
void SelectionNull(CTxtEdit *ped)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "SelectionNull");
if(ped)
ped->SetSelectionToNull();
}
CTxtSelection::~CTxtSelection()
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::~CTxtSelection");
DeleteCaretBitmap(FALSE);
// Notify edit object that we are gone (if there's a nonNULL ped, i.e.,
// if the selection isn't a zombie).
SelectionNull(GetPed());
}
//////////////////////////////// Assignments /////////////////////////////////////////
CRchTxtPtr& CTxtSelection::operator =(const CRchTxtPtr& rtp)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::operator =");
_TEST_INVARIANT_
return CTxtRange::operator =(rtp);
}
CTxtRange& CTxtSelection::operator =(const CTxtRange &rg)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::operator =");
_TEST_INVARIANT_
return CTxtRange::operator =(rg);
}
////////////////////// Update caret & selection mechanism ///////////////////////////////
/*
* CTxtSelection::Update(fScrollIntoView)
*
* @mfunc
* Update selection and/or caret on screen. As a side
* effect, this methods ends deferring updates.
*
* @rdesc
* TRUE if success, FALSE otherwise
*/
BOOL CTxtSelection::Update (
BOOL fScrollIntoView) //@parm TRUE if should scroll caret into view
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Update");
LONG cch;
LONG cchSave = _cch;
LONG cchText = GetTextLength();
LONG cp, cpMin, cpMost;
BOOL fMoveBack = _fMoveBack;
CTxtEdit *ped = GetPed();
if(!_cch) // Update _cpAnchor, etc.
UpdateForAutoWord();
if(!ped->fInplaceActive() || ped->IsStreaming())
{
// Nothing to do while inactive or streaming in text or RTF data
return TRUE;
}
if(_cch && (_fSelHasEOP || _fSelHasCell))
{
BOOL fInTable = GetPF()->InTable();
if(!fInTable)
{
CFormatRunPtr rp(_rpPF);
rp.AdvanceCp(-_cch);
fInTable = (ped->GetParaFormat(rp.GetFormat()))->InTable();
}
if(fInTable)
Expander(_fSelHasEOP ? tomParagraph : tomCell,
TRUE, NULL, &cpMin, &cpMost);
}
if(IsInOutlineView() && !ped->IsMouseDown() && _rpPF.IsValid())
{
CPFRunPtr rp(*this);
cp = GetCp();
GetRange(cpMin, cpMost);
if(_cch && (cpMin || cpMost < cchText))
{
LONG *pcpMin = &cpMin;
LONG *pcpMost = &cpMost;
// If selection contains an EOP, expand to para boundaries
if(_fSelHasEOP)
{
if(_fMoveBack ^ (_cch < 0)) // Decreasing selection
{ // size: move active end
if(_fMoveBack)
pcpMost = NULL; // to StartOf para
else
pcpMin = NULL; // to EndOf para
}
Expander(tomParagraph, TRUE, NULL, pcpMin, pcpMost);
}
LONG cpMinSave = cpMin; // Save initial cp's to see if
LONG cpMostSave = cpMost; // we need to Set() below
// The following handles selection expansion correctly, but
// not compression; need logic like that preceding Expander()
rp.AdvanceCp(cpMin - cp); // Start at cpMin
if(rp.IsCollapsed())
cpMin += rp.FindExpandedBackward();
rp.AdjustForward();
BOOL fCpMinCollapsed = rp.IsCollapsed();
rp.AdvanceCp(cpMost - cpMin); // Go to cpMost
Assert(cpMost == rp.CalculateCp());
if(rp.IsCollapsed())
cpMost += rp.FindExpandedForward();
if(fCpMinCollapsed || rp.IsCollapsed() && cpMost < cchText)
{
if(rp.IsCollapsed())
{
rp.AdvanceCp(cpMin - cpMost);
rp.AdjustForward();
cpMost = cpMin;
}
else
cpMin = cpMost;
}
if(cpMin != cpMinSave || cpMost != cpMostSave)
Set(cpMost, cpMost - cpMin);
}
if(!_cch && rp.IsCollapsed()) // Note: above may have collapsed
{ // selection...
cch = fMoveBack ? rp.FindExpandedBackward() : 0;
if(rp.IsCollapsed())
cch = rp.FindExpanded();
_fExtend = FALSE;
Advance(cch);
rp.AdjustForward();
if(cch <= 0 && rp.IsCollapsed() && _rpTX.IsAfterEOP())
BackupCRLF();
_fCaretNotAtBOL = FALSE;
}
}
// Don't let active end be in hidden text
CCFRunPtr rp(*this);
cp = GetCp();
GetRange(cpMin, cpMost);
if(_cch && (cpMin || cpMost < cchText))
{
rp.AdvanceCp(cpMin - cp); // Start at cpMin
BOOL fHidden = rp.IsInHidden();
rp.AdvanceCp(cpMost - cpMin); // Go to cpMost
if(fHidden) // It's hidden, so collapse
Collapser(tomEnd); // selection at End for treatment
else if(rp.IsInHidden() && // cpMin OK, how about cpMost?
cpMost < cchText)
{ // Check both sides of edge
Collapser(tomEnd); // collapse selection at end
}
}
if(!_cch && rp.IsInHidden()) // Note: above may have collapsed
{ // selection...
cch = fMoveBack ? rp.FindUnhiddenBackward() : 0;
if(!fMoveBack || rp.IsHidden())
cch = rp.FindUnhidden();
_fExtend = FALSE;
Advance(cch);
_fCaretNotAtBOL = FALSE;
}
if((cchSave ^ _cch) < 0) // Don't change active end
FlipRange();
if(!_cch && cchSave) // Fixups changed nondegenerate
{ // selection to IP. Update
Update_iFormat(-1); // _iFormat and _fCaretNotAtBOL
_fCaretNotAtBOL = FALSE;
}
_TEST_INVARIANT_
// Recalc up to active end (caret)
if(!_pdp->WaitForRecalc(GetCp(), -1)) // Line recalc failure
Set(0, 0); // Put caret at start of text
ShowCaret(ped->_fFocus);
UpdateCaret(fScrollIntoView); // Update Caret position, possibly
// scrolling it into view
ped->TxShowCaret(FALSE);
UpdateSelection(); // Show new selection
ped->TxShowCaret(TRUE);
return TRUE;
}
/*
* CTxtSelection::CheckSynchCharSet(dwCharFlag)
*
* @mfunc
* Check if the current keyboard matches the current font's charset;
* if not, call CheckChangeFont to find the right font
*
* @rdesc
* Current keyboard codepage
*/
UINT CTxtSelection::CheckSynchCharSet(
DWORD dwCharFlag)
{
CTxtEdit *ped = GetPed();
LONG iFormat = GetiFormat();
const CCharFormat *pCF = ped->GetCharFormat(iFormat);
BYTE bCharSet = pCF->_bCharSet;
HKL hkl = GetKeyboardLayout(0xFFFFFFFF); // Force refresh
WORD wlidKbd = LOWORD(hkl);
UINT uKbdCodePage = ConvertLanguageIDtoCodePage(wlidKbd);
// If current font is not set correctly,
// change to a font preferred by current keyboard.
// To summarize the logic below:
// Check that lcidKbd is valid
// Check that current charset differs from current keyboard
// Check that current keyboard is legit in a single codepage control
// Check that current charset isn't SYMBOL, DEFAULT, or OEM
if (wlidKbd &&
(UINT)GetCodePage(bCharSet) != uKbdCodePage &&
(!ped->_fSingleCodePage ||
uKbdCodePage == 1252 ||
uKbdCodePage == (ped->_pDocInfo ?
ped->_pDocInfo->wCpg :
GetSystemDefaultCodePage())) &&
bCharSet != SYMBOL_CHARSET &&
bCharSet != OEM_CHARSET &&
!(W32->IsFECodePage(uKbdCodePage) && bCharSet == ANSI_CHARSET))
{
CheckChangeFont(hkl, uKbdCodePage, iFormat, dwCharFlag);
}
return uKbdCodePage;
}
/*
* CTxtSelection::MatchKeyboardToPara()
*
* @mfunc
* Match the keyboard to the current paragraph direction. If the paragraph
* is an RTL paragraph then the keyboard will be switched to an RTL
* keyboard, and vice versa.
*
* @rdesc
* TRUE iff a keyboard was changed
*
* @devnote
* We use the following tests when trying to find a keyboard to match the
* paragraph direction:
*
* See if the current keyboard matches the direction of the paragraph.
*
* Search backward from rtp looking for a charset that matches the
* direction of the paragraph.
*
* Search forward from rtp looking for a charset that matches the
* direction of the paragraph.
*
* See if the default charformat charset matches the direction of the
* paragraph.
*
* See if there's only a single keyboard that matches the paragraph
* direction.
*
* If all this fails, just leave the keyboard alone.
*/
BOOL CTxtSelection::MatchKeyboardToPara()
{
CTxtEdit *ped = GetPed();
if(!ped->IsBiDi() || !GetPed()->_fFocus || GetPed()->_fIMEInProgress)
return FALSE;
BOOL fRTLPara = IsParaRTL(); // Get paragraph direction
if(W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0))) == fRTLPara)
return FALSE;
// Current keyboard direction didn't match paragraph direction...
BYTE bCharSet;
HKL hkl = 0;
const CCharFormat * pCF;
CFormatRunPtr rpCF(_rpCF);
// Look backward in text, trying to find a CharSet that matches
// the paragraph direction.
do
{
pCF = ped->GetCharFormat(rpCF.GetFormat());
bCharSet = pCF->_bCharSet;
if(IsRTLCharSet(bCharSet) == fRTLPara)
hkl = W32->CheckChangeKeyboardLayout(bCharSet);
} while (!hkl && rpCF.PrevRun());
if(!hkl)
{
// Didn't find an appropriate charformat so reset run pointer
// and look forward instead
rpCF = _rpCF;
while (!hkl && rpCF.NextRun())
{
pCF = ped->GetCharFormat(rpCF.GetFormat());
bCharSet = pCF->_bCharSet;
if(IsRTLCharSet(bCharSet) == fRTLPara)
hkl = W32->CheckChangeKeyboardLayout(bCharSet);
}
if(!hkl)
{
// Still didn't find an appropriate charformat so see if
// default charformat matches paragraph direction.
pCF = ped->GetCharFormat(rpCF.GetFormat());
bCharSet = pCF->_bCharSet;
if(IsRTLCharSet(bCharSet) == fRTLPara)
hkl = W32->CheckChangeKeyboardLayout(bCharSet);
if(!hkl)
{
// If even that didn't work, walk through the list of
// keyboards and grab the first one we come to that matches
// the paragraph direction.
pCF = NULL;
hkl = W32->FindDirectionalKeyboard(fRTLPara);
}
}
}
if (hkl && ped->_fFocus && IsCaretShown())
{
CreateCaret();
ped->TxShowCaret(TRUE);
}
return hkl ? TRUE : FALSE;
}
/*
* CTxtSelection::GetCaretPoint(&rcClient, pt, &rp)
*
* @mfunc
* This routine determines where the caret should be positioned
* on screen.
* This routine is trivial, except for the Bidi case. In that case
* if we are told to retrieve formatting from the forward CP, we draw
* the caret at the logical left edge of the CP. Else, we draw it at
* the logical right edge of the previous CP.
*
* @rdesc
* TRUE if we didn't OOM.
*/
BOOL CTxtSelection::GetCaretPoint(
RECT & rcClient,
POINT & pt,
CLinePtr *prp,
BOOL fBeforeCp)
{
CDispDim dispdim;
CRchTxtPtr rtp(*this);
UINT taMode = TA_BASELINE | TA_LOGICAL;
if(GetPed()->IsBiDi() && _rpCF.IsValid())
{
if(_fHomeOrEnd) // Home/End
taMode |= _fCaretNotAtBOL ? TA_ENDOFLINE : TA_STARTOFLINE;
else if(!GetIchRunCF() || !GetCchLeftRunCF())
{
// In a Bidi context on a run boundary where the reverse level
// changes, then we should respect the fBeforeCp flag.
BYTE bLevelBwd, bLevelFwd;
BOOL fStart = FALSE;
LONG cp = rtp._rpTX.GetCp();
CBiDiLevel level;
bLevelBwd = bLevelFwd = rtp.IsParaRTL() ? 1 : 0;
rtp._rpCF.AdjustBackward();
if (cp)
bLevelBwd = rtp._rpCF.GetLevel();
rtp._rpCF.AdjustForward();
if (cp != rtp._rpTX.GetTextLength())
{
bLevelFwd = rtp._rpCF.GetLevel(&level);
fStart = level._fStart;
}
if((bLevelBwd != bLevelFwd || fStart) && !fBeforeCp && rtp.Advance(-1))
{
// Direction change at cp, caret in prev CF run, and can
// backspace to previous char: then get to the right of
// previous char
taMode |= TA_RIGHT;
_fCaretNotAtBOL = !rtp._rpTX.IsAfterEOP();
}
}
}
if (_pdp->PointFromTp(rtp, &rcClient, _fCaretNotAtBOL, pt, prp, taMode, &dispdim) < 0)
return FALSE;
return TRUE;
}
/*
* CTxtSelection::UpdateCaret(fScrollIntoView, bForceCaret)
*
* @mfunc
* This routine updates caret/selection active end on screen.
* It figures its position, size, clipping, etc. It can optionally
* scroll the caret into view.
*
* @rdesc
* TRUE if view was scrolled, FALSE otherwise
*
* @devnote
* The caret is actually shown on screen only if _fShowCaret is TRUE.
*/
BOOL CTxtSelection::UpdateCaret (
BOOL fScrollIntoView, //@parm If TRUE, scroll caret into view if we have
BOOL bForceCaret) // focus or if not and selection isn't hidden
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::UpdateCaret");
_TEST_INVARIANT_
if(_pdp->IsFrozen()) // If display is currently frozen
{ // save call for another time
_pdp->SaveUpdateCaret(fScrollIntoView);
return FALSE;
}
CTxtEdit *ped = GetPed();
if(ped->IsStreaming()) // Don't bother doing anything if we
return FALSE; // are loading in text or RTF data
if(!ped->fInplaceActive()) // If not inplace active, set up
{ // for when focus is regained
if(fScrollIntoView)
ped->_fScrollCaretOnFocus = TRUE;
return FALSE;
}
DWORD dwScrollBars = ped->TxGetScrollBars();
BOOL fAutoVScroll = FALSE;
BOOL fAutoHScroll = FALSE;
BOOL fBeforeCp = _rpTX.IsAfterEOP();
POINT pt;
CLinePtr rp(_pdp);
RECT rcClient;
RECT rcView;
LONG xWidthView, yHeightView;
LONG xScroll = _pdp->GetXScroll();
LONG yScroll = _pdp->GetYScroll();
INT yAbove = 0; // Ascent of line above & beyond IP
INT yAscent; // Ascent of IP
INT yAscentLine;
LONG yBase; // Base of IP & line
INT yBelow = 0; // Descent of line below & beyond IP
INT yDescent; // Descent of IP
INT yDescentLine;
INT ySum;
LONG yViewTop, yViewBottom;
if(ped->_fFocus && (_fShowCaret || bForceCaret))
{
_fShowCaret = TRUE; // We're trying to force caret to display so set this flag to true
if(!_fDualFontMode && !_fNoKeyboardUpdate && !_fIsChar && !_fHomeOrEnd)
{
// Avoid re-entrance to CheckChangeKeyboardLayout
_fNoKeyboardUpdate = TRUE;
// If we're in "dual font" mode, charset change is only
// temporary and we don't want to change keyboard layout
CheckChangeKeyboardLayout();
if(!fBeforeCp && ped->IsBiDi() && _rpCF.IsValid() &&
(!_rpCF.GetIch() || !_rpCF.GetCchLeft()))
{
_rpCF.AdjustBackward();
BOOL fRTLPrevRun = IsRTLCharSet(GetCF()->_bCharSet);
_rpCF.AdjustForward();
if (fRTLPrevRun != IsRTLCharSet(GetCF()->_bCharSet) &&
fRTLPrevRun != W32->IsBiDiLcid(GetKeyboardLCID()))
{
fBeforeCp = TRUE;
}
}
_fNoKeyboardUpdate = FALSE;
}
}
// Get client rectangle once to save various callers getting it
ped->TxGetClientRect(&rcClient);
_pdp->GetViewRect(rcView, &rcClient);
// View can be bigger than client rect because insets can be negative.
// We don't want the caret to be any bigger than the client view otherwise
// the caret will leave pixel dust on other windows.
yViewTop = max(rcView.top, rcClient.top);
yViewBottom = min(rcView.bottom, rcClient.bottom);
xWidthView = rcView.right - rcView.left;
yHeightView = yViewBottom - yViewTop;
if(fScrollIntoView)
{
fAutoVScroll = (dwScrollBars & ES_AUTOVSCROLL) != 0;
fAutoHScroll = (dwScrollBars & ES_AUTOHSCROLL) != 0;
// If we're not forcing a scroll, only scroll if window has focus
// or selection isn't hidden
if (!ped->Get10Mode() || !GetForceScrollCaret())
fScrollIntoView = ped->_fFocus || !ped->fHideSelection();
}
if(!fScrollIntoView && (fAutoVScroll || fAutoHScroll))
{ // Would scroll but don't have
ped->_fScrollCaretOnFocus = TRUE; // focus. Signal to scroll
if (!ped->Get10Mode() || !GetAutoVScroll())
fAutoVScroll = fAutoHScroll = FALSE; // when we do get focus
}
SetAutoVScroll(FALSE);
if (!_cch && IsInOutlineView() && IsCollapsed())
goto not_visible;
if (!GetCaretPoint(rcClient, pt, &rp, fBeforeCp))
goto not_visible;
// HACK ALERT - Because plain-text multiline controls do not have the
// automatic EOP, we need to special case their processing here because
// if you are at the end of the document and last character is an EOP,
// you need to be on the next line in the display not the current line.
if(CheckPlainTextFinalEOP()) // Terminated by an EOP
{
LONG Align = GetPF()->_bAlignment;
pt.x = rcView.left; // Default left
if(Align == PFA_CENTER)
pt.x = (rcView.left + rcView.right)/2;
else if(Align == PFA_RIGHT)
pt.x = rcView.right;
pt.x -= xScroll; // Absolute coordinate
// Bump the y up a line. We get away with the calculation because
// the document is plain text so all lines have the same height.
// Also, note that the rp below is used only for height
// calculations, so it is perfectly valid for the same reason
// even though it is not actually pointing to the correct line.
// (I told you this is a hack.)
pt.y += rp->_yHeight;
}
_xCaret = (LONG) pt.x;
yBase = (LONG) pt.y;
// Compute caret height, ascent, and descent
yAscent = GetCaretHeight(&yDescent);
yAscent -= yDescent;
// Default to line empty case. Use what came back from the default
// calculation above.
yDescentLine = yDescent;
yAscentLine = yAscent;
if(rp.IsValid())
{
if(rp->_yDescent != -1)
{
// Line has been measured so we can use the line's values
yDescentLine = rp->_yDescent;
yAscentLine = rp->_yHeight - yDescentLine;
}
}
if(yAscent + yDescent == 0)
{
yAscent = yAscentLine;
yDescent = yDescentLine;
}
else
{
// This is a bit counter-intuitive at first. Basically, even if
// the caret should be large (e.g., due to a large font at the
// insertion point), we can only make it as big as the line. If
// a character is inserted, then the line becomes bigger, and we
// can make the caret the correct size.
yAscent = min(yAscent, yAscentLine);
yDescent = min(yDescent, yDescentLine);
}
if(fAutoVScroll)
{
Assert(yDescentLine >= yDescent);
Assert(yAscentLine >= yAscent);
yBelow = yDescentLine - yDescent;
yAbove = yAscentLine - yAscent;
ySum = yAscent;
// Scroll as much as possible into view, giving priorities
// primarily to IP and secondarily ascents
if(ySum > yHeightView)
{
yAscent = yHeightView;
yDescent = 0;
yAbove = 0;
yBelow = 0;
}
else if((ySum += yDescent) > yHeightView)
{
yDescent = yHeightView - yAscent;
yAbove = 0;
yBelow = 0;
}
else if((ySum += yAbove) > yHeightView)
{
yAbove = yHeightView - (ySum - yAbove);
yBelow = 0;
}
else if((ySum += yBelow) > yHeightView)
yBelow = yHeightView - (ySum - yBelow);
}
else
{
AssertSz(yAbove == 0, "yAbove non-zero");
AssertSz(yBelow == 0, "yBelow non-zero");
}
// Update real caret x pos (constant during vertical moves)
_xCaretReally = _xCaret - rcView.left + xScroll;
if (!(dwScrollBars & ES_AUTOHSCROLL) && // Not auto hscrolling
!_pdp->IsHScrollEnabled()) // and no scrollbar
{
if (_xCaret < rcView.left) // Caret off left edge
_xCaret = rcView.left;
else if(_xCaret + GetCaretDelta() > rcView.right)// Caret off right edge
_xCaret = rcView.right - dxCaret; // Back caret up to
} // exactly the right edge
// From this point on we need a new caret
_fCaretCreated = FALSE;
if(ped->_fFocus)
ped->TxShowCaret(FALSE); // Hide old caret before
// making a new one
if(yBase + yDescent + yBelow > yViewTop &&
yBase - yAscent - yAbove < yViewBottom)
{
if(yBase - yAscent - yAbove < yViewTop) // Caret is partially
{ // visible
if(fAutoVScroll) // Top isn't visible
goto scrollit;
Assert(yAbove == 0);
yAscent = yBase - yViewTop; // Change ascent to amount
if(yBase < yViewTop) // visible
{ // Move base to top
yDescent += yAscent;
yAscent = 0;
yBase = yViewTop;
}
}
if(yBase + yDescent + yBelow > yViewBottom)
{
if(fAutoVScroll) // Bottom isn't visible
goto scrollit;
Assert(yBelow == 0);
yDescent = yViewBottom - yBase; // Change descent to amount
if(yBase > yViewBottom) // visible
{ // Move base to bottom
yAscent += yDescent;
yDescent = 0;
yBase = yViewBottom;
}
}
// Anything still visible?
if(yAscent <= 0 && yDescent <= 0)
goto not_visible;
// If left or right isn't visible, scroll or set non_visible
if (_xCaret < rcView.left || // Left isn't visible
_xCaret + GetCaretDelta() > rcView.right)// Right isn't visible
{
if(fAutoHScroll)
goto scrollit;
goto not_visible;
}
_yCaret = yBase - yAscent;
_yHeightCaret = (INT) yAscent + yDescent;
}
else if(fAutoHScroll || fAutoVScroll) // Caret isn't visible
goto scrollit; // scroll it into view
else
{
not_visible:
// Caret isn't visible, don't show it
_xCaret = -32000;
_yCaret = -32000;
_yHeightCaret = 1;
}
// Now update caret for real on screen. We only want to show the caret
// if it is in the view and there is no selection.
if(ped->_fFocus && _fShowCaret)
{
CreateCaret();
ped->TxShowCaret(TRUE);
}
return FALSE;
scrollit:
if(fAutoVScroll)
{
// Scroll to top for cp = 0. This is important if the first line
// contains object(s) taller than the client area is high. The
// resulting behavior agrees with the Word UI in all ways except in
// Backspacing (deleting) the char at cp = 0 when it is followed by
// other chars that preceed the large object.
if(!GetCp())
yScroll = 0;
else if(yBase - yAscent - yAbove < yViewTop) // Top invisible
yScroll -= yViewTop - (yBase - yAscent - yAbove); // Make it so
else if(yBase + yDescent + yBelow > yViewBottom) // Bottom invisible
{
yScroll += yBase + yDescent + yBelow - yViewBottom; // Make it so
// Don't do following special adjust if the current line is bigger
// than the client area
if(rp->_yHeight < yViewBottom - yViewTop)
{
yScroll = _pdp->AdjustToDisplayLastLine(yBase + rp->_yHeight,
yScroll);
}
}
}
if(fAutoHScroll)
{
if(_xCaret < rcView.left) // Left invisible
{
xScroll -= rcView.left - _xCaret; // Make it visible
if(xScroll > 0) // Scroll left in
{ // chunks to make
xScroll -= xWidthView / 3; // typing faster
xScroll = max(0, xScroll);
}
}
else if(_xCaret + GetCaretDelta() > rcView.right) // right invisible
{ // Make it visible
xScroll += _xCaret + dxCaret - rcView.left // We don't scroll
- xWidthView; // in chunks because
} // this more edit
} // control like.
if(yScroll != _pdp->GetYScroll() || xScroll != _pdp->GetXScroll())
{
if (_pdp->ScrollView(xScroll, yScroll, FALSE, FALSE) == FALSE)
{
if(ped->_fFocus && _fShowCaret)
{
CreateCaret();
ped->TxShowCaret(TRUE);
}
return FALSE;
}
return TRUE;
}
return FALSE;
}
/*
* CTxtSelection::GetCaretHeight(pyDescent)
*
* @mfunc
* Add a given amount to _xCaret (to make special case of inserting
* a character nice and fast)
*
* @rdesc
* Caret height, <lt> 0 if failed
*/
INT CTxtSelection::GetCaretHeight (
INT *pyDescent) const //@parm Out parm to receive caret descent
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::GetCaretHeight");
// (undefined if the return value is <lt> 0)
_TEST_INVARIANT_
CLock lock; // Uses global (shared) FontCache
CTxtEdit *ped = GetPed();
const CCharFormat *pCF = ped->GetCharFormat(_iFormat);
const CDevDesc *pdd = _pdp->GetDdRender();
HDC hdc = pdd->GetDC();
if(!hdc)
return -1;
LONG yHeight = -1;
LONG dypInch = MulDiv(GetDeviceCaps(hdc, LOGPIXELSY), _pdp->GetZoomNumerator(), _pdp->GetZoomDenominator());
CCcs *pccs = fc().GetCcs(pCF, dypInch);
if(!pccs)
goto ret;
LONG yOffset, yAdjust;
pccs->GetOffset(pCF, dypInch, &yOffset, &yAdjust);
SHORT yAdjustFE;
yAdjustFE = pccs->AdjustFEHeight(!fUseUIFont() && ped->_pdp->IsMultiLine());
if(pyDescent)
*pyDescent = pccs->_yDescent + yAdjustFE - yAdjust - yOffset;
yHeight = pccs->_yHeight + (yAdjustFE << 1);
pccs->Release();
ret:
pdd->ReleaseDC(hdc);
return yHeight;
}
/*
* CTxtSelection::ShowCaret(fShow)
*
* @mfunc
* Hide or show caret
*
* @rdesc
* TRUE if caret was previously shown, FALSE if it was hidden
*/
BOOL CTxtSelection::ShowCaret (
BOOL fShow) //@parm TRUE for showing, FALSE for hiding
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::ShowCaret");
_TEST_INVARIANT_
const BOOL fRet = _fShowCaret;
if(fRet != fShow)
{
_fShowCaret = fShow;
if(GetPed()->_fFocus || GetPed()->fInOurHost())
{
if(fShow && !_fCaretCreated)
CreateCaret();
GetPed()->TxShowCaret(fShow);
}
}
return fRet;
}
/*
* CTxtSelection::IsCaretInView()
*
* @mfunc
* Returns TRUE iff caret is inside visible view
*/
BOOL CTxtSelection::IsCaretInView() const
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::IsCaretInView");
_TEST_INVARIANT_
RECT rc;
_pdp->GetViewRect(rc);
return (_xCaret + dxCaret > rc.left) &&
(_xCaret < rc.right) &&
(_yCaret + _yHeightCaret > rc.top) &&
(_yCaret < rc.bottom);
}
/*
* CTxtSelection::CaretNotAtBOL()
*
* @mfunc
* Returns TRUE iff caret is not allowed at BOL
*/
BOOL CTxtSelection::CaretNotAtBOL() const
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::CaretNotAtBOL");
_TEST_INVARIANT_
return _cch ? (_cch > 0) : _fCaretNotAtBOL;
}
/*
* CTxtSelection::LineLength(pcch)
*
* @mfunc
* get # unselected chars on lines touched by current selection
*
* @rdesc
* said number of chars
*/
LONG CTxtSelection::LineLength(
LONG *pcp) const
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::LineLength");
_TEST_INVARIANT_
LONG cch;
CLinePtr rp(_pdp);
if(!_cch) // Insertion point
{
rp.RpSetCp(GetCp(), _fCaretNotAtBOL);
cch = rp.GetAdjustedLineLength();
*pcp = GetCp() - rp.RpGetIch();
}
else
{
LONG cpMin, cpMost, cchLast;
GetRange(cpMin, cpMost);
rp.RpSetCp(cpMin, FALSE); // Selections can't start at EOL
cch = rp.RpGetIch();
*pcp = cpMin - cch;
rp.RpSetCp(cpMost, TRUE); // Selections can't end at BOL
// Remove trailing EOP, if it exists and isn't already selected
cchLast = rp.GetAdjustedLineLength() - rp.RpGetIch();
if(cchLast > 0)
cch += cchLast;
}
return cch;
}
/*
* CTxtSelection::ShowSelection(fShow)
*
* @mfunc
* Update, hide or show selection on screen
*
* @rdesc
* TRUE iff selection was previously shown
*/
BOOL CTxtSelection::ShowSelection (
BOOL fShow) //@parm TRUE for showing, FALSE for hiding
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::ShowSelection");
_TEST_INVARIANT_
const BOOL fShowPrev = _fShowSelection;
const BOOL fInplaceActive = GetPed()->fInplaceActive();
LONG cpSelSave = _cpSel;
LONG cchSelSave = _cchSel;
// Sleep(1000);
_fShowSelection = fShow;
if(fShowPrev && !fShow)
{
if(cchSelSave) // Hide old selection
{
// Set up selection before telling the display to update
_cpSel = 0;
_cchSel = 0;
if(fInplaceActive)
_pdp->InvertRange(cpSelSave, cchSelSave, selSetNormal);
}
}
else if(!fShowPrev && fShow)
{
if(_cch) // Show new selection
{
// Set up selection before telling the display to update
_cpSel = GetCp();
_cchSel = _cch;
if(fInplaceActive)
_pdp->InvertRange(GetCp(), _cch, selSetHiLite);
}
}
return fShowPrev;
}
/*
* CTxtSelection::UpdateSelection()
*
* @mfunc
* Updates selection on screen
*
* Note:
* This method inverts the delta between old and new selections
*/
void CTxtSelection::UpdateSelection()
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::UpdateSelection");
_TEST_INVARIANT_
LONG cp = GetCp();
LONG cpNA = cp - _cch;
LONG cpSelNA = _cpSel - _cchSel;
LONG cpMin, cpMost;
LONG cpMinSel = 0;
LONG cpMostSel = 0;
CObjectMgr* pobjmgr = NULL;
LONG NumObjInSel = 0, NumObjInOldSel = 0;
LONG cpSelSave = _cpSel;
LONG cchSelSave = _cchSel;
GetRange(cpMin, cpMost);
//We need to know if there were objects is the previous and current
//selections to determine how they should be selected.
if(GetPed()->HasObjects())
{
pobjmgr = GetPed()->GetObjectMgr();
if(pobjmgr)
{
CTxtRange tr(GetPed(), _cpSel, _cchSel);
tr.GetRange(cpMinSel, cpMostSel);
NumObjInSel = pobjmgr->CountObjectsInRange(cpMin, cpMost);
NumObjInOldSel = pobjmgr->CountObjectsInRange(cpMinSel, cpMostSel);
}
}
//If the old selection contained a single object and nothing else
//we need to notify the object manager that this is no longer the
//case if the selection is changing.
if (NumObjInOldSel && (abs(_cchSel) == 1) &&
!(cpMin == cpMinSel && cpMost == cpMostSel))
{
if(pobjmgr)
pobjmgr->HandleSingleSelect(GetPed(), cpMinSel, /* fHilite */ FALSE);
}
// Update selection data before the invert so the selection can be
// painted by the render
_cpSel = GetCp();
_cchSel = _cch;
if(_fShowSelection)
{
if(!_cch || !cchSelSave || // Old/new selection missing,
cpMost < min(cpSelSave, cpSelNA) || // or new preceeds old,
cpMin > max(cpSelSave, cpSelNA)) // or new follows old, so
{ // they don't intersect
if(_cch)
_pdp->InvertRange(cp, _cch, selSetHiLite);
if(cchSelSave)
_pdp->InvertRange(cpSelSave, cchSelSave, selSetNormal);
}
else
{
if(cpNA != cpSelNA) // Old & new dead ends differ
{ // Invert text between them
_pdp->InvertRange(cpNA, cpNA - cpSelNA, selUpdateNormal);
}
if(cp != cpSelSave) // Old & new active ends differ
{ // Invert text between them
_pdp->InvertRange(cp, cp - cpSelSave, selUpdateHiLite);
}
}
}
// If new selection contains a single object and nothing else, we need
// to notify object manager as long as it's not the same object.
if (NumObjInSel && abs(_cch) == 1 &&
(cpMin != cpMinSel || cpMost != cpMostSel))
{
if(pobjmgr)
pobjmgr->HandleSingleSelect(GetPed(), cpMin, /* fHiLite */ TRUE);
}
}
/*
* CTxtSelection::SetSelection(cpFirst, cpMost)
*
* @mfunc
* Set selection between two cp's
*
* @devnote
* <p cpFirst> and <p cpMost> must be greater than 0, but may extend
* past the current max cp. In that case, the cp will be truncated to
* the max cp (at the end of the text).
*/
void CTxtSelection::SetSelection (
LONG cpMin, //@parm Start of selection and dead end
LONG cpMost) //@parm End of selection and active end
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SetSelection");
_TEST_INVARIANT_
CTxtEdit *ped = GetPed();
StopGroupTyping();
if(ped->HasObjects())
{
CObjectMgr* pobjmgr = GetPed()->GetObjectMgr();
if(pobjmgr)
{
COleObject *pobjactive = pobjmgr->GetInPlaceActiveObject();
if (pobjactive)
{
if (pobjactive != pobjmgr->GetObjectFromCp(cpMin) || cpMost - cpMin > 1)
pobjactive->DeActivateObj();
}
}
}
_fCaretNotAtBOL = FALSE; // Put caret for ambiguous cp at BOL
Set(cpMost, cpMost - cpMin); // Set() validates cpMin, cpMost
if(GetPed()->fInplaceActive()) // Inplace active:
Update(!ped->Get10Mode() ? TRUE : !ped->fHideSelection()); // update selection now
else
{
// Update selection data used for screen display so whenever we
// get displayed the selection will be displayed.
_cpSel = GetCp();
_cchSel = _cch;
if(!ped->fHideSelection())
{
// Selection isn't hidden so tell container to update display
// when it feels like.
ped->TxInvalidateRect(NULL, FALSE);
ped->TxUpdateWindow();
}
}
CancelModes(); // Cancel word selection mode
}
/*
* CTxtSelection::PointInSel(pt, prcClient, Hit)
*
* @mfunc
* Figures whether a given point is within the selection
*
* @rdesc
* TRUE if point inside selection, FALSE otherwise
*/
BOOL CTxtSelection::PointInSel (
const POINT pt, //@parm Point in containing window client coords
const RECT *prcClient, //@parm Client rectangle can be NULL if active
HITTEST Hit) const //@parm May be computer Hit value
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::PointInSel");
_TEST_INVARIANT_
if(!_cch || Hit && Hit < HT_Text) // Degenerate range (no selection):
return FALSE; // mouse can't be in, or Hit not
// in text
LONG cpActual;
_pdp->CpFromPoint(pt, prcClient, NULL, NULL, FALSE, &Hit, NULL, &cpActual);
if(Hit < HT_Text)
return FALSE;
LONG cpMin, cpMost;
GetRange(cpMin, cpMost);
return cpActual >= cpMin && cpActual < cpMost;
}
////////////////////////////////// Selection with the mouse ///////////////////////////////////
/*
* CTxtSelection::SetCaret(pt, fUpdate)
*
* @mfunc
* Sets caret at a given point
*
* @devnote
* In the plain-text case, placing the caret at the beginning of the
* line following the final EOP requires some extra code, since the
* underlying rich-text engine doesn't assign a line to a final EOP
* (plain-text doesn't currently have the rich-text final EOP). We
* handle this by checking to see if the count of lines times the
* plain-text line height is below the actual y position. If so, we
* move the cp to the end of the story.
*/
void CTxtSelection::SetCaret(
const POINT pt, //@parm Point of click
BOOL fUpdate) //@parm If TRUE, update the selection/caret
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SetCaret");
_TEST_INVARIANT_
LONG cp, cpActual;
CDispDim dispdim;
HITTEST Hit;
RECT rcView;
CLinePtr rp(_pdp);
CRchTxtPtr rtp(GetPed());
LONG y;
StopGroupTyping();
// Set caret at point
if(_pdp->CpFromPoint(pt, NULL, &rtp, &rp, FALSE, &Hit, &dispdim, &cpActual) >= 0)
{
cp = rtp.GetCp();
// If the resolved CP is greater than the cp we are above, then we
// want to stay backwards.
BOOL fBeforeCp = cp <= cpActual;
// Set selection to the correct location. If plain-text
// multiline control, we need to check to see if pt.y is below
// the last line of text. If so and if the text ends with an EOP,
// we need to set the cp at the end of the story and set up to
// display the caret at the beginning of the line below the last
// line of text
if(!IsRich() && _pdp->IsMultiLine()) // Plain-text,
{ // multiline control
_pdp->GetViewRect(rcView, NULL);
y = pt.y + _pdp->GetYScroll() - rcView.top;
if(y > rp.Count()*rp->_yHeight) // Below last line of
{ // text
rtp.Advance(tomForward); // Move rtp to end of text
if(rtp._rpTX.IsAfterEOP()) // If text ends with an
{ // EOP, set up to move
cp = rtp.GetCp(); // selection there
rp.AdvanceCp(-rp.GetIch()); // Set rp._ich = 0 to
} // set _fCaretNotAtBOL
} // = FALSE to display
} // caret at next BOL
Set(cp, 0);
if(GetPed()->IsBiDi())
{
if(!fBeforeCp)
_rpCF.AdjustBackward();
else
_rpCF.AdjustForward();
Set_iCF(_rpCF.GetFormat());
}
_fCaretNotAtBOL = rp.RpGetIch() != 0; // Caret OK at BOL if click
if(fUpdate)
Update(TRUE);
else
UpdateForAutoWord();
_SelMode = smNone; // Cancel word selection mode
}
}
/*
* CTxtSelection::SelectWord(pt)
*
* @mfunc
* Select word around a given point
*/
void CTxtSelection::SelectWord (
const POINT pt) //@parm Point of click
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SelectWord");
_TEST_INVARIANT_
// Get rp where the hit is
if(_pdp->CpFromPoint(pt, NULL, this, NULL, FALSE) >= 0)
{
// Extend both active and dead ends on word boundaries
_cch = 0; // Start with IP at pt
SetExtend(FALSE);
FindWordBreak(WB_MOVEWORDRIGHT); // Go to end of word
SetExtend(TRUE);
FindWordBreak(WB_MOVEWORDLEFT); // Extend to start of word
GetRange(_cpAnchorMin, _cpAnchorMost);
GetRange(_cpWordMin, _cpWordMost);
if(!_fInAutoWordSel)
_SelMode = smWord;
// cpMost needs to be the active end
if(_cch < 0)
FlipRange();
Update(FALSE);
}
}
/*
* CTxtSelection::SelectUnit(pt, Unit)
*
* @mfunc
* Select line/paragraph around a given point and enter
* line/paragraph selection mode. In Outline View, convert
* SelectLine to SelectPara, and SelectPara to SelectPara
* along with all subordinates
*/
void CTxtSelection::SelectUnit (
const POINT pt, //@parm Point of click
LONG Unit) //@parm tomLine or tomParagraph
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SelectPara");
_TEST_INVARIANT_
AssertSz(Unit == tomLine || Unit == tomParagraph,
"CTxtSelection::SelectPara: Unit must equal tomLine/tomParagraph");
LONG nHeading;
CLinePtr rp(_pdp);
// Get rp and selection active end where the hit is
if(_pdp->CpFromPoint(pt, NULL, this, &rp, FALSE) >= 0)
{
LONG cchBackward, cchForward;
BOOL fOutline = IsInOutlineView();
if(Unit == tomLine && !fOutline) // SelectLine
{
_cch = 0; // Start with insertion
cchBackward = -rp.RpGetIch(); // point at pt
cchForward = rp->_cch;
_SelMode = smLine;
}
else // SelectParagraph
{
cchBackward = rp.FindParagraph(FALSE); // Go to start of para
cchForward = rp.FindParagraph(TRUE); // Extend to end of para
_SelMode = smPara;
}
SetExtend(FALSE);
Advance(cchBackward);
if(Unit == tomParagraph && fOutline) // Move para in outline
{ // view
rp.AdjustBackward(); // If heading, include
nHeading = rp.GetHeading(); // subordinate paras
if(nHeading)
{
for(; rp.NextRun(); cchForward += rp->_cch)
{
LONG n = rp.GetHeading();
if(n && n <= nHeading)
break;
}
}
}
SetExtend(TRUE);
Advance(cchForward);
GetRange(_cpAnchorMin, _cpAnchorMost);
Update(FALSE);
}
}
/*
* CTxtSelection::SelectAll()
*
* @mfunc
* Select all text in story
*/
void CTxtSelection::SelectAll()
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SelectAll");
_TEST_INVARIANT_
StopGroupTyping();
LONG cchText = GetTextLength();
Set(cchText, cchText);
Update(FALSE);
}
/*
* CTxtSelection::ExtendSelection(pt)
*
* @mfunc
* Extend/Shrink selection (moves active end) to given point
*/
void CTxtSelection::ExtendSelection (
const POINT pt) //@parm Point to extend to
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::ExtendSelection");
_TEST_INVARIANT_
LONG cch;
LONG cchPrev = _cch;
LONG cp;
LONG cpMin, cpMost;
BOOL fAfterEOP;
const BOOL fWasInAutoWordSel = _fInAutoWordSel;
INT iDir = 0;
CTxtEdit * ped = GetPed();
CLinePtr rp(_pdp);
CRchTxtPtr rtp(ped);
StopGroupTyping();
// Get rp and rtp at the point pt
if(_pdp->CpFromPoint(pt, NULL, &rtp, &rp, TRUE) < 0)
return;
// If we are in word, line, or paragraph select mode, we need to make
// sure the active end is correct. If we are extending backward from
// the first Unit selected, we want the active end to be at cpMin. If
// we are extending forward from the first Unit selected, we want the
// active end to be at cpMost.
if(_SelMode != smNone)
{
cch = _cpAnchorMost - _cpAnchorMin;
GetRange(cpMin, cpMost);
cp = rtp.GetCp();
if(cp <= cpMin && _cch > 0) // If active end changes,
Set(_cpAnchorMin, -cch); // select the original
// Unit (will be extended
if(cp >= cpMost && _cch < 0) // below)
Set(_cpAnchorMost, cch);
}
SetExtend(TRUE);
cch = rp.RpGetIch();
if(_SelMode > smWord && cch == rp->_cch) // If in line or para select
{ // modes and pt at EOL,
rtp.Advance(-cch); // make sure we stay on that
rp.RpAdvanceCp(-cch); // line
cch = 0;
}
SetCp(rtp.GetCp()); // Move active end to pt
// Caret OK at BOL _unless_
_fCaretNotAtBOL = _cch > 0; // forward selection
// Now adjust selection
if(_SelMode == smLine) // depending on mode
{ // Extend selection by line
if(_cch >= 0) // Active end at cpMost
cch -= rp->_cch; // Setup to add chars to EOL
Advance(-cch);
}
else if(_SelMode == smPara)
Advance(rp.FindParagraph(_cch >= 0)); // Extend selection by para
else
{
// If the sign of _cch has changed this means that the direction
// of the selection is changing and we want to reset the auto
// selection information.
if((_cch ^ cchPrev) < 0)
{
_fAutoSelectAborted = FALSE;
_cpWordMin = _cpAnchorMin;
_cpWordMost = _cpAnchorMost;
}
cp = rtp.GetCp();
fAfterEOP = rtp._rpTX.IsAfterEOP();
_fInAutoWordSel = _SelMode != smWord && GetPed()->TxGetAutoWordSel()
&& !_fAutoSelectAborted
&& (cp < _cpWordMin || cp > _cpWordMost);
if(_fInAutoWordSel && !fWasInAutoWordSel)
{
CTxtPtr txtptr(GetPed(), _cpAnchor);
// Extend both ends dead to word boundaries
ExtendToWordBreak(fAfterEOP,
_cch < 0 ? WB_MOVEWORDLEFT : WB_MOVEWORDRIGHT);
if(_cch < 0)
{
// Direction is left so update word border on left
_cpWordPrev = _cpWordMin;
_cpWordMin = GetCp();
}
else
{
// Direction is right so update word border on right
_cpWordPrev = _cpWordMost;
_cpWordMost = GetCp();
}
// If we are at start of a word already, we don't need to extend
// selection in other direction
if(!txtptr.IsAtBOWord() && txtptr.GetChar() != ' ')
{
FlipRange();
Advance(_cpAnchor - GetCp()); // Extend from anchor
FindWordBreak(_cch < 0 ? WB_MOVEWORDLEFT : WB_MOVEWORDRIGHT);
if(_cch > 0) // Direction is right so
_cpWordMost = GetCp(); // update word border on right
else // Direction is left so
_cpWordMin = GetCp(); // update word border on left
FlipRange();
}
}
else if(_fInAutoWordSel || _SelMode == smWord)
{
// Save direction
iDir = cp <= _cpWordMin ? WB_MOVEWORDLEFT : WB_MOVEWORDRIGHT;
if(_SelMode == smWord) // Extend selection by word
{
if(cp > _cpAnchorMost || cp < _cpAnchorMin)
FindWordBreak(iDir);
else if(_cch <= 0) // Maintain current active end
Set(_cpAnchorMin, _cpAnchorMin - _cpAnchorMost);
else
Set(_cpAnchorMost, _cpAnchorMost - _cpAnchorMin);
}
else
ExtendToWordBreak(fAfterEOP, iDir);
if(_fInAutoWordSel)
{
if(WB_MOVEWORDLEFT == iDir)
{
// Direction is left so update word border on left
_cpWordPrev = _cpWordMin;
_cpWordMin = GetCp();
}
else
{
// Direction is right so update word border on right
_cpWordPrev = _cpWordMost;
_cpWordMost = GetCp();
}
}
}
else if(fWasInAutoWordSel)
{
// If we are in between where the previous word ended and
// the cp we auto selected to, then we want to stay in
// auto select mode.
if(_cch < 0)
{
if(cp >= _cpWordMin && cp < _cpWordPrev)
{
// Set direction for end of word search
iDir = WB_MOVEWORDLEFT;
// Mark that we are still in auto select mode
_fInAutoWordSel = TRUE;
}
}
else if(cp <= _cpWordMost && cp >= _cpWordPrev)
{
// Mark that we are still in auto select mode
_fInAutoWordSel = TRUE;
// Set direction for end of word search
iDir = WB_MOVEWORDRIGHT;
}
//We have to check to see if we are on the boundary between
//words because we don't want to extend the selection until
//we are actually beyond the current word.
if(cp != _cpWordMost && cp != _cpWordMin)
{
if(_fInAutoWordSel)
{
// Auto selection still on so make sure we have the
// entire word we are on selected
ExtendToWordBreak(fAfterEOP, iDir);
}
else
{
// FUTURE: Word has a behavior where it extends the
// selection one word at a time unless you back up
// and then start extending the selection again, in
// which case it extends one char at a time. We
// follow this behavior. However, Word will resume
// extending a word at a time if you continue extending
// for several words. We just keep extending on char
// at a time. We might want to change this sometime.
_fAutoSelectAborted = TRUE;
}
}
}
if(_fAutoSelectAborted)
{
// If we are in the range of a word we previously selected
// we want to leave that selected. If we have moved back
// a word we want to pop back an entire word. Otherwise,
// leave the cp were it is.
if(_cch < 0)
{
if(cp > _cpWordMin && cp < _cpWordPrev)
{
// In the range leave the range at the beginning of the word
ExtendToWordBreak(fAfterEOP, WB_MOVEWORDLEFT);
}
else if(cp >= _cpWordPrev)
{
AutoSelGoBackWord(&_cpWordMin,
WB_MOVEWORDRIGHT, WB_MOVEWORDLEFT);
}
}
else if(cp < _cpWordMost && cp >= _cpWordPrev)
{
// In the range leave the range at the beginning of the word
ExtendToWordBreak(fAfterEOP, WB_MOVEWORDRIGHT);
}
else if(cp < _cpWordPrev)
{
AutoSelGoBackWord(&_cpWordMost,
WB_MOVEWORDLEFT, WB_MOVEWORDRIGHT);
}
}
}
// An OLE object cannot have an anchor point <b> inside </b> it,
// but sometimes we'd like it to behave like a word. So, if
// the direction changed, the object has to stay selected --
// this is the "right thing" (kind of word selection mode)
// If we had something selected and the direction changed
if(cchPrev && (_cch ^ cchPrev) < 0)
{
FlipRange();
// See if an object was selected on the other end
BOOL fObjectWasSelected = (_cch > 0 ? _rpTX.GetChar() : GetPrevChar())
== WCH_EMBEDDING;
// If it was, we want it to stay selected
if(fObjectWasSelected)
Advance(_cch > 0 ? 1 : -1);
FlipRange();
}
Update(TRUE);
}
/*
* CTxtSelection::ExtendToWordBreak (fAfterEOP, iAction)
*
* @mfunc
* Moves active end of selection to the word break in the direction
* given by iDir unless fAfterEOP = TRUE. When this is TRUE, the
* cursor just follows an EOP marker and selection should be suppressed.
* Otherwise moving the cursor to the left of the left margin would
* select the EOP on the line above, and moving the cursor to the
* right of the right margin would select the first word in the line
* below.
*/
void CTxtSelection::ExtendToWordBreak (
BOOL fAfterEOP, //@parm Cursor is after an EOP
INT iAction) //@parm Word break action (WB_MOVEWORDRIGHT/LEFT)
{
if(!fAfterEOP)
FindWordBreak(iAction);
}
/*
* CTxtSelection::CancelModes(fAutoWordSel)
*
* @mfunc
* Cancel either all modes or Auto Select Word mode only
*/
void CTxtSelection::CancelModes (
BOOL fAutoWordSel) //@parm TRUE cancels Auto Select Word mode only
{ // FALSE cancels word, line and para sel mode
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::CancelModes");
_TEST_INVARIANT_
if(fAutoWordSel)
{
if(_fInAutoWordSel)
{
_fInAutoWordSel = FALSE;
_fAutoSelectAborted = FALSE;
}
}
else
_SelMode = smNone;
}
/////////////////////////////////// Keyboard movements ////////////////////////////////////
/*
* CTxtSelection::Left(fCtrl)
*
* @mfunc
* do what cursor-keypad left-arrow key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @comm
* Left/Right-arrow IPs can go to within one character (treating CRLF
* as a character) of EOL. They can never be at the actual EOL, so
* _fCaretNotAtBOL is always FALSE for these cases. This includes
* the case with a right-arrow collapsing a selection that goes to
* the EOL, i.e, the caret ends up at the next BOL. Furthermore,
* these cases don't care whether the initial caret position is at
* the EOL or the BOL of the next line. All other cursor keypad
* commands may care.
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::Left (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Left");
_TEST_INVARIANT_
LONG cp;
CancelModes();
StopGroupTyping();
if(!_fExtend && _cch) // Collapse selection to
{ // nearest whole Unit before
if(fCtrl) // cpMin
Expander(tomWord, FALSE, NULL, &cp, NULL);
Collapser(tomStart); // Collapse to cpMin
}
else // Not collapsing selection
{
if (!GetCp() || // Already at beginning of
!BypassHiddenText(tomBackward)) // story
{
Beep();
return FALSE;
}
if(IsInOutlineView() && (_fSelHasEOP || // If outline view with EOP
_fExtend && _rpTX.IsAfterEOP())) // now or will have after
{ // this command,
return Up(FALSE); // treat as up arrow
}
if(fCtrl) // WordLeft
FindWordBreak(WB_MOVEWORDLEFT);
else // CharLeft
{
BackupCRLF();
SnapToCluster(-1);
}
}
_fCaretNotAtBOL = FALSE; // Caret always OK at BOL
Update(TRUE);
return TRUE;
}
/*
* CTxtSelection::Right(fCtrl)
*
* @mfunc
* do what cursor-keypad right-arrow key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @comm
* Right-arrow selection can go to the EOL, but the cp of the other
* end identifies whether the selection ends at the EOL or starts at
* the beginning of the next line. Hence here and in general for
* selections, _fCaretNotAtBOL is not needed to resolve EOL/BOL
* ambiguities. It should be set to FALSE to get the correct
* collapse character. See also comments for Left() above.
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::Right (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Right");
_TEST_INVARIANT_
LONG cchText;
LONG cp;
CancelModes();
StopGroupTyping();
if(!_fExtend && _cch) // Collapse selection to
{ // nearest whole Unit after
if(fCtrl) // cpMost
Expander(tomWord, FALSE, NULL, NULL, &cp);
Collapser(tomEnd);
}
else // Not collapsing selection
{
cchText = _fExtend ? GetTextLength() : GetAdjustedTextLength();
if (GetCp() >= cchText || // Already at end of story
!BypassHiddenText(tomForward))
{
Beep(); // Tell the user
return FALSE;
}
if(IsInOutlineView() && _fSelHasEOP) // If outline view with EOP
return Down(FALSE); // Treat as down arrow
if(fCtrl) // WordRight
FindWordBreak(WB_MOVEWORDRIGHT);
else // CharRight
{
AdvanceCRLF();
SnapToCluster();
}
}
_fCaretNotAtBOL = _fExtend; // If extending to EOL, need
Update(TRUE); // TRUE to get _xCaretReally
return TRUE; // at EOL
}
/*
* CTxtSelection::Up(fCtrl)
*
* @mfunc
* do what cursor-keypad up-arrow key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @comm
* Up arrow doesn't go to EOL regardless of _xCaretPosition (stays
* to left of EOL break character), so _fCaretNotAtBOL is always FALSE
* for Up arrow. Ctrl-Up/Down arrows always end up at BOPs or the EOD.
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::Up (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Up");
_TEST_INVARIANT_
LONG cchSave = _cch; // Save starting position for
LONG cpSave = GetCp(); // change check
BOOL fCollapse = _cch && !_fExtend; // Collapse nondegenerate sel
BOOL fPTNotAtEnd;
CLinePtr rp(_pdp);
LONG xCaretReally = _xCaretReally; // Save desired caret x pos
CancelModes();
StopGroupTyping();
if(fCollapse) // Collapse selection at cpMin
{
Collapser(tomTrue);
_fCaretNotAtBOL = FALSE; // Selections can't begin at
} // EOL
rp.RpSetCp(GetCp(), _fCaretNotAtBOL); // Initialize line ptr
if(fCtrl) // Move to beginning of para
{
if(!fCollapse && // If no selection collapsed
rp > 0 && !rp.RpGetIch()) // and are at BOL,
{ // backup to prev BOL to make
rp--; // sure we move to prev. para
Advance(-rp->_cch);
}
Advance(rp.FindParagraph(FALSE)); // Go to beginning of para
_fCaretNotAtBOL = FALSE; // Caret always OK at BOL
}
else // Move up a line
{ // If on first line, can't go
fPTNotAtEnd = !CheckPlainTextFinalEOP();// up
if(rp <= 0 && fPTNotAtEnd)
{
//if(!_fExtend)// &&_pdp->GetYScroll())
UpdateCaret(TRUE); // Be sure caret in view
}
else
{
LONG cch;
BOOL fSelHasEOPInOV = IsInOutlineView() && _fSelHasEOP;
if(fSelHasEOPInOV && _cch > 0)
{
rp.AdjustBackward();
cch = rp->_cch;
rp.AdvanceCp(-cch); // Go to start of line
Assert(!rp.GetIch());
cch -= rp.FindParagraph(FALSE); // Ensure start of para in
} // case of word wrap
else
{
cch = 0;
if(fPTNotAtEnd)
{
cch = rp.RpGetIch();
rp--;
}
cch += rp->_cch;
}
Advance(-cch); // Move to previous BOL
if(fSelHasEOPInOV && !_fSelHasEOP) // If sel had EOP but doesn't
{ // after Advance, must be IP
Assert(!_cch); // Suppress restore of
xCaretReally = -1; // _xCaretReally
}
else if(!SetXPosition(xCaretReally, rp))// Set this cp corresponding
Set(cpSave, cchSave); // to xCaretReally here, but
} // agree on Down()
}
if(GetCp() == cpSave && _cch == cchSave)
{
// Continue to select to the beginning of the first line
// This is what 1.0 is doing
if(_fExtend)
return Home(fCtrl);
Beep(); // Nothing changed, so beep
return FALSE;
}
Update(TRUE); // Update and then restore
if(!_cch && !fCtrl && xCaretReally >= 0) // _xCaretReally conditionally
_xCaretReally = xCaretReally; // Need to use _cch instead of
// cchSave in case of collapse
return TRUE;
}
/*
* CTxtSelection::Down(fCtrl)
*
* @mfunc
* do what cursor-keypad down-arrow key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @comm
* Down arrow can go to the EOL if the _xCaretPosition (set by
* horizontal motions) is past the end of the line, so
* _fCaretNotAtBOL needs to be TRUE for this case.
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::Down (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Down");
_TEST_INVARIANT_
LONG cch;
LONG cchSave = _cch; // Save starting position for
LONG cpSave = GetCp(); // change check
BOOL fCollapse = _cch && !_fExtend; // Collapse nondegenerate sel
CLinePtr rp(_pdp);
LONG xCaretReally = _xCaretReally; // Save _xCaretReally
CancelModes();
StopGroupTyping();
if(fCollapse) // Collapse at cpMost
{
Collapser(tomEnd);
_fCaretNotAtBOL = TRUE; // Selections can't end at BOL
}
rp.RpSetCp(GetCp(), _fCaretNotAtBOL);
if(fCtrl) // Move to next para
{
Advance(rp.FindParagraph(TRUE)); // Go to end of para
if(IsInOutlineView() && !BypassHiddenText(tomForward))
SetCp(cpSave);
else
_fCaretNotAtBOL = FALSE; // Next para is never at EOL
}
else if(_pdp->WaitForRecalcIli(rp + 1)) // Go to next line
{
LONG cch;
BOOL fSelHasEOPInOV = IsInOutlineView() && _fSelHasEOP;
if(fSelHasEOPInOV && _cch < 0)
cch = rp.FindParagraph(TRUE);
else
{
cch = rp.GetCchLeft(); // Advance selection to end
rp++; // of current line
}
Advance(cch);
if(fSelHasEOPInOV && !_fSelHasEOP) // If sel had EOP but doesn't
{ // after Advance, must be IP
Assert(!_cch); // Suppress restore of
xCaretReally = -1; // _xCaretReally
}
else if(!SetXPosition(xCaretReally, rp))// Set *this to cp <--> x
Set(cpSave, cchSave); // If failed, restore sel
}
else if(!_fExtend) // No more lines to pass
// && _pdp->GetYScroll() + _pdp->GetViewHeight() < _pdp->GetHeight())
{
if (!IsRich() && _pdp->IsMultiLine() && // Plain-text, multiline
!_fCaretNotAtBOL) // control with caret OK
{ // at BOL
cch = Advance(rp.GetCchLeft()); // Advance selection to end
if(!_rpTX.IsAfterEOP()) // If control doesn't end
Advance(-cch); // with EOP, go back
}
UpdateCaret(TRUE); // Be sure caret in view
}
if(GetCp() == cpSave && _cch == cchSave)
{
// Continue to select to the end of the lastline
// This is what 1.0 is doing.
if(_fExtend)
return End(fCtrl);
Beep(); // Nothing changed, so beep
return FALSE;
}
Update(TRUE); // Update and then
if(!_cch && !fCtrl && xCaretReally >= 0) // restore _xCaretReally
_xCaretReally = xCaretReally; // Need to use _cch instead of
return TRUE; // cchSave in case of collapse
}
/*
* CTxtSelection::SetXPosition(xCaret, rp)
*
* @mfunc
* Put this text ptr at cp nearest to xCaret. If xCaret is in right
* margin, we put caret either at EOL (for lines with no para mark),
* or just before para mark
*
* @rdesc
* TRUE iff could create measurer
*/
BOOL CTxtSelection::SetXPosition (
LONG xCaret, //@parm Desired horizontal coordinate
CLinePtr& rp) //@parm Line ptr identifying line to check
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SetXPosition");
_TEST_INVARIANT_
LONG cch = 0;
CMeasurer me(_pdp, *this);
if(IsInOutlineView())
{
BOOL fSelHasEOP = _fSelHasEOP;
rp.AdjustForward();
_fCaretNotAtBOL = FALSE; // Leave at start of line
while(rp->_fCollapsed)
{
if(_fMoveBack)
{
if(!rp.PrevRun()) // No more uncollapsed text
return FALSE; // before current cp
cch -= rp->_cch;
}
else
{
cch += rp->_cch;
if(!rp.NextRun()) // No more uncollapsed text
return FALSE; // after current cp
if(_fExtend && _cch > 0)
_fCaretNotAtBOL = TRUE; // Leave at end of line
}
}
if(cch)
Advance(cch);
if(fSelHasEOP)
return TRUE;
if(cch)
me.Advance(cch);
}
POINT pt = {xCaret, 0};
CDispDim dispdim;
HITTEST hit;
cch = rp->CchFromXpos(me, pt, &dispdim, &hit);// Move out from start of line
if(!_fExtend && cch == rp->_cch && // Not extending, at EOL,
rp->_cchEOP) // and have EOP:
{ // backup before EOP
cch += me._rpTX.BackupCpCRLF(); // Note: me._rpCF/_rpPF
} // are now inconsistent
SetCp(me.GetCp()); // but doesn't matter since
_fCaretNotAtBOL = cch != 0; // me.GetCp() doesn't care
return TRUE;
}
/*
* CTxtSelection::GetXCaretReally()
*
* @mfunc
* Get _xCaretReally - horizontal scrolling + left margin
*
* @rdesc
* x caret really
*/
LONG CTxtSelection::GetXCaretReally()
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::GetXCaretReally");
_TEST_INVARIANT_
RECT rcView;
_pdp->GetViewRect(rcView);
return _xCaretReally - _pdp->GetXScroll() + rcView.left;
}
/*
* CTxtSelection::Home(fCtrl)
*
* @mfunc
* do what cursor-keypad Home key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::Home (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Home");
_TEST_INVARIANT_
const LONG cchSave = _cch;
const LONG cpSave = GetCp();
CancelModes();
StopGroupTyping();
if(fCtrl) // Move to start of document
SetCp(0);
else
{
CLinePtr rp(_pdp);
if(_cch && !_fExtend) // Collapse at cpMin
{
Collapser(tomStart);
_fCaretNotAtBOL = FALSE; // Selections can't start at
} // EOL
rp.RpSetCp(GetCp(), _fCaretNotAtBOL); // Define line ptr for
Advance(-rp.RpGetIch()); // current state. Now BOL
}
_fCaretNotAtBOL = FALSE; // Caret always goes to BOL
_fHomeOrEnd = TRUE;
if(!MatchKeyboardToPara() && GetCp() == cpSave && _cch == cchSave)
{
Beep(); // No change, so beep
_fHomeOrEnd = FALSE;
return FALSE;
}
Update(TRUE);
_fHomeOrEnd = FALSE;
return TRUE;
}
/*
* CTxtSelection::End(fCtrl)
*
* @mfunc
* do what cursor-keypad End key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @comm
* On lines without paragraph marks (EOP), End can go all the way
* to the EOL. Since this character position (cp) is the same as
* that for the start of the next line, we need _fCaretNotAtBOL to
* distinguish between the two possible caret positions.
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::End (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::End");
_TEST_INVARIANT_
LONG cch;
const LONG cchSave = _cch;
const LONG cpSave = GetCp();
CLinePtr rp(_pdp);
CancelModes();
StopGroupTyping();
if(fCtrl) // Move to end of document
{
SetCp(GetTextLength());
_fCaretNotAtBOL = FALSE;
goto Exit;
}
else if(!_fExtend && _cch) // Collapse at cpMost
{
Collapser(tomEnd);
_fCaretNotAtBOL = TRUE; // Selections can't end at BOL
}
rp.RpSetCp(GetCp(), _fCaretNotAtBOL); // Initialize line ptr
cch = rp->_cch; // Default target pos in line
Advance(cch - rp.RpGetIch()); // Move active end to EOL
if(!_fExtend && rp->_cchEOP && _rpTX.IsAfterEOP())// Not extending and have EOP:
cch += BackupCRLF(); // backup before EOP
_fCaretNotAtBOL = cch != 0; // Decide ambiguous caret pos by whether at BOL
Exit:
if(!MatchKeyboardToPara() && GetCp() == cpSave && _cch == cchSave)
{
Beep(); // No change, so Beep
return FALSE;
}
_fHomeOrEnd = TRUE;
Update(TRUE);
_fHomeOrEnd = FALSE;
return TRUE;
}
/*
* CTxtSelection::PageUp(fCtrl)
*
* @mfunc
* do what cursor-keypad PgUp key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::PageUp (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::PageUp");
_TEST_INVARIANT_
const LONG cchSave = _cch;
const LONG cpSave = GetCp();
LONG xCaretReally = _xCaretReally;
CancelModes();
StopGroupTyping();
if(_cch && !_fExtend) // Collapse selection
{
Collapser(tomStart);
_fCaretNotAtBOL = FALSE;
}
if(fCtrl) // Ctrl-PgUp: move to top
{ // of visible view for
SetCp(_pdp->IsMultiLine() // multiline but top of
? _pdp->GetFirstVisibleCp() : 0); // text for SL
_fCaretNotAtBOL = FALSE;
}
else if(_pdp->GetFirstVisibleCp() == 0) // PgUp in top Pg: move to
{ // start of document
SetCp(0);
_fCaretNotAtBOL = FALSE;
}
else // PgUp with scrolling to go
{ // Scroll up one windowful
ScrollWindowful(SB_PAGEUP); // leaving caret at same
} // position in window
if(GetCp() == cpSave && _cch == cchSave) // Beep if no change
{
Beep();
return FALSE;
}
Update(TRUE);
if(GetCp()) // Maintain x offset on page
_xCaretReally = xCaretReally; // up/down
return TRUE;
}
/*
* CTxtSelection::PageDown(fCtrl)
*
* @mfunc
* do what cursor-keypad PgDn key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::PageDown (
BOOL fCtrl) //@parm TRUE iff Ctrl key is pressed (or being simulated)
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::PageDown");
_TEST_INVARIANT_
const LONG cchSave = _cch;
LONG cpMostVisible;
const LONG cpSave = GetCp();
POINT pt;
CLinePtr rp(_pdp);
LONG xCaretReally = _xCaretReally;
CancelModes();
StopGroupTyping();
if(_cch && !_fExtend) // Collapse selection
{
Collapser(tomStart);
_fCaretNotAtBOL = TRUE;
}
_pdp->GetCliVisible(&cpMostVisible, fCtrl);
if(fCtrl) // Move to end of last
{ // entirely visible line
RECT rcView;
SetCp(cpMostVisible);
if(_pdp->PointFromTp(*this, NULL, TRUE, pt, &rp, TA_TOP) < 0)
return FALSE;
_fCaretNotAtBOL = TRUE;
_pdp->GetViewRect(rcView);
if(rp > 0 && pt.y + rp->_yHeight > rcView.bottom)
{
Advance(-rp->_cch);
rp--;
}
if(!_fExtend && !rp.GetCchLeft() && rp->_cchEOP)
{
BackupCRLF(); // After backing up over EOP,
_fCaretNotAtBOL = FALSE; // caret can't be at EOL
}
}
else if(cpMostVisible == GetTextLength())
{ // Move to end of text
SetCp(GetTextLength());
_fCaretNotAtBOL = !_rpTX.IsAfterEOP();
}
else
{
if(!ScrollWindowful(SB_PAGEDOWN)) // Scroll down 1 windowful
return FALSE;
}
if(GetCp() == cpSave && _cch == cchSave) // Beep if no change
{
Beep();
return FALSE;
}
Update(TRUE);
_xCaretReally = xCaretReally;
return TRUE;
}
/*
* CTxtSelection::ScrollWindowful(wparam)
*
* @mfunc
* Sroll up or down a windowful
*
* @rdesc
* TRUE iff movement occurred
*/
BOOL CTxtSelection::ScrollWindowful (
WPARAM wparam) //@parm SB_PAGEDOWN or SB_PAGEUP
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::ScrollWindowful");
// Scroll windowful
_TEST_INVARIANT_
POINT pt; // leaving caret at same
CLinePtr rp(_pdp); // point on screen
LONG cpFirstVisible = _pdp->GetFirstVisibleCp();
LONG cpLastVisible;
LONG cpMin;
LONG cpMost;
GetRange(cpMin, cpMost);
// Get last character in the view
_pdp->GetCliVisible(&cpLastVisible, TRUE);
// Is active end in visible area of control?
if((cpMin < cpFirstVisible && _cch <= 0) || (cpMost > cpLastVisible && _cch >= 0))
{
// Not in view - we need to calculate a new range for selection
SetCp(cpFirstVisible);
// Real caret postion is now at beginning of line
_xCaretReally = 0;
}
if(_pdp->PointFromTp(*this, NULL, _fCaretNotAtBOL, pt, &rp, TA_TOP) < 0)
return FALSE;
// The point is visible so use that
pt.x = _xCaretReally;
pt.y += rp->_yHeight / 2;
_pdp->VScroll(wparam, 0);
if(_fExtend)
{
// Disable auto word select -- if we have to use ExtendSelection()
// for non-mouse operations, let's try to get rid of its side-effects
BOOL fInAutoWordSel = _fInAutoWordSel;
_fInAutoWordSel = FALSE;
ExtendSelection(pt);
_fInAutoWordSel = fInAutoWordSel;
}
else
SetCaret(pt, FALSE);
return TRUE;
}
//////////////////////////// Keyboard support /////////////////////////////////
/*
* CTxtSelection::CheckChangeKeyboardLayout (BOOL fChangedFont)
*
* @mfunc
* Change keyboard for new font, or font at new character position.
*
* @comm
* Using only the currently loaded KBs, locate one that will support
* the insertion points font. This is called anytime a character format
* change occurs, or the insert font (caret position) changes.
*
* @devnote
* The current KB is preferred. If a previous association
* was made, see if the KB is still loaded in the system and if so use
* it. Otherwise, locate a suitable KB, preferring KB's that have
* the same charset ID as their default, preferred charset. If no match
* can be made then nothing changes.
*/
void CTxtSelection::CheckChangeKeyboardLayout ()
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::CheckChangeKeyboardLayout");
CTxtEdit * const ped = GetPed(); // Document context
if (ped && ped->_fFocus && !ped->fUseUIFont() && // If ped, focus, not UIFont, & auto
ped->IsAutoKeyboard() && // kbd, check kbd change
!ped->_fIMEInProgress && // not in IME composition and
ped->GetAdjustedTextLength() && // not empty control and
_rpTX.GetPrevChar() != WCH_EMBEDDING) // not an object, then
{ // check kbd change
LONG iFormat = GetiFormat();
const CCharFormat *pCF = ped->GetCharFormat(iFormat);
BYTE bCharSet = pCF->_bCharSet;
if (!IsFECharSet(bCharSet) &&
(bCharSet != ANSI_CHARSET || !IsFELCID((WORD)GetKeyboardLayout(0))) &&
!fc().GetInfoFlags(pCF->_iFont).fNonBiDiAscii)
{
// Don't do auto-kbd inside FE or single-codepage ASCII font.
W32->CheckChangeKeyboardLayout(bCharSet);
}
}
}
/*
* CTxtSelection::CheckChangeFont (hkl, cpg, iSelFormat, dwCharFlag)
*
* @mfunc
* Change font for new keyboard layout.
*
* @comm
* If no previous preferred font has been associated with this KB, then
* locate a font in the document suitable for this KB.
*
* @rdesc
* TRUE iff suitable font is found
*
* @devnote
* This routine is called via WM_INPUTLANGCHANGEREQUEST message
* (a keyboard layout switch). This routine can also be called
* from WM_INPUTLANGCHANGE, but we are called more, and so this
* is less efficient.
*
* Exact match is done via charset ID bitmask. If a match was previously
* made, use it. A user can force the insertion font to be associated
* to a keyboard if the control key is held through the KB changing
* process. The association is broken when another KB associates to
* the font. If no match can be made then nothing changes.
*/
bool CTxtSelection::CheckChangeFont (
const HKL hkl, //@parm Keyboard Layout to go
UINT cpg, //@parm code page to use (could be ANSI for FE with IME off)
LONG iSelFormat, //@parm Format to use for selection case
DWORD dwCharFlag) //@parm 0 if called from WM_INPUTLANGCHANGE/WM_INPUTLANGCHANGEREQUEST
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::CheckChangeFont");
CTxtEdit * const ped = GetPed();
if (!ped->IsAutoFont() || // EXIT if auto font is turned off
_cch && !dwCharFlag) // or if kbd change with nondegenerate
return true; // selection (WM_INPUTLANGCHANGEREQUEST)
// Set new format using current format and new KB info.
LONG iCurrentFormat = _cch ? iSelFormat : _iFormat;
const CCharFormat *pCF = ped->GetCharFormat(iCurrentFormat);
CCharFormat CF = *pCF;
WORD wLangID = LOWORD(hkl);
CF._lcid = wLangID;
CF._bCharSet = GetCharSet(cpg);
if (pCF->_lcid == wLangID && CF._bCharSet == pCF->_bCharSet)
{
if (ped->_fFocus && IsCaretShown())
{
CreateCaret();
ped->TxShowCaret(TRUE);
}
return true;
}
CCFRunPtr rp(*this);
int iMatchFont = MATCH_FONT_SIG;
// If current is a primary US or UK kbd. We allow matching ASCII fonts
if ((!dwCharFlag || dwCharFlag & fASCII) &&
PRIMARYLANGID(wLangID) == LANG_ENGLISH &&
IN_RANGE (SUBLANG_ENGLISH_US, SUBLANGID(wLangID), SUBLANG_ENGLISH_UK) &&
wLangID == HIWORD((DWORD_PTR)hkl))
{
iMatchFont |= MATCH_ASCII;
}
if (rp.GetPreferredFontInfo(
cpg,
CF._bCharSet,
CF._iFont,
CF._yHeight,
CF._bPitchAndFamily,
iCurrentFormat,
iMatchFont))
{
if (W32->IsFECodePage(cpg) || cpg == CP_THAI)
ped->OrCharFlags(GetFontSig((WORD)cpg) << 8);
// FUTURE: turn current fBIDI into fRTL and make
// fBIDI = (fRTL | fARABIC | fHEBREW). Then can combine following if
// with preceding if
else if (W32->IsBiDiCodePage(cpg))
ped->OrCharFlags(fBIDI | (GetFontSig((WORD)cpg) << 8));
if (!_cch)
{
SetCharFormat(&CF, SCF_NOKBUPDATE, NULL, CFM_FACE | CFM_CHARSET | CFM_LCID | CFM_SIZE, CFM2_NOCHARSETCHECK);
if(ped->IsComplexScript())
UpdateCaret(FALSE);
}
else
{
// Create a format and use it for the selection
LONG iCF;
ICharFormatCache *pf = GetCharFormatCache();
pf->Cache(&CF, &iCF);
#ifdef LINESERVICES
if (g_pols)
g_pols->DestroyLine(NULL);
#endif
Set_iCF(iCF);
pf->Release(iCF); // pf->Cache AddRef it
_fUseiFormat = TRUE;
}
return true;
}
return false;
}
//////////////////////////// PutChar, Delete, Replace //////////////////////////////////
/*
* CTxtSelection::PutChar(ch, dwFlags, publdr)
*
* @mfunc
* Insert or overtype a character
*
* @rdesc
* TRUE if successful
*/
BOOL CTxtSelection::PutChar (
TCHAR ch, //@parm Char to put
DWORD dwFlags, //@parm Overtype mode and whether keyboard input
IUndoBuilder *publdr) //@parm If non-NULL, where to put anti-events
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::PutChar");
_TEST_INVARIANT_
BOOL fOver = dwFlags & 1;
CTxtEdit *ped = GetPed();
SetExtend(FALSE);
if(ch == TAB && GetPF()->InTable())
{
LONG cpMin, cpMost;
LONG cch = GetRange(cpMin, cpMost);
LONG cch0 = 0;
LONG iDir = GetKeyboardFlags() & SHIFT ? -1 : 1;
if(_fSelHasEOP) // If selection has an EOP
{ // collapse to cpMin and
Collapser(tomStart); // go forward
iDir = 1;
if(!GetPF()->InTable())
{
Update(TRUE);
return TRUE;
}
}
if((_cch ^ iDir) < 0) // If at cpMost going back or
FlipRange(); // at cpMin going forward,
// switch active end
CRchTxtPtr rtp(*this);
CancelModes();
StopGroupTyping();
if(iDir < 0 || _cch)
rtp.Advance(-1);
// Scan for start/end of next/prev cell
do
{
ch = rtp.GetChar();
} while(rtp.Advance(iDir) && ch != CELL && rtp.GetPF()->InTable());
if(ch != CELL)
{
if(iDir < 0)
return FALSE;
insertRow: rtp.BackupCRLF(); // Tabbed past end of table:
Set(rtp.GetCp(), 0); // insert new row
return InsertEOP(publdr);
}
if(iDir > 0) // Check for IP between CELL
{ // and CR at row end
if(rtp.GetChar() == CR)
rtp.AdvanceCRLF(); // Bypass row end
}
for(cch = 0; // Determine cchSel
(ch = rtp.GetChar()) != CELL && rtp.GetPF()->InTable();
cch += iDir)
{
cch0 = rtp.Advance(iDir);
if(!cch0)
break;
}
if(iDir > 0) // Tabbing forward
{
if(ch != CELL) // Went past end of table
goto insertRow; // so go insert new row
}
else if(cch) // Tabbing backward with
{ // nondegenerate selection
if(cch0) // Didn't run into start of story
{
rtp.Advance(1); // Advance over CELL. Then if at
if(rtp.GetChar() == CR) // end of row, advance over EOP
cch += rtp.AdvanceCRLF();
}
else // Ran into start of story
cch -= 1; // Include another char since
} // broke out of for loop
else if(cpMin > 1) // Handles tabbing back over
rtp.Advance(1); // adjacent null cells
Set(rtp.GetCp(), cch);
_fCaretNotAtBOL = FALSE;
Update(TRUE);
return TRUE;
}
// EOPs might be entered by ITextSelection::TypeText()
if(IsEOP(ch))
return _pdp->IsMultiLine() // EOP isn't allowed in
? InsertEOP(publdr, ch) : FALSE;// single line controls
if(publdr)
{
publdr->SetNameID(UID_TYPING);
publdr->StartGroupTyping();
}
// FUTURE: a Unicode lead surrogate needs to have a trail surrogate, i.e.,
// two 16-bit chars. A more thorough check would worry about this.
if ((DWORD)GetTextLength() >= ped->TxGetMaxLength() &&
((_cch == -1 || !_cch && fOver) && _rpTX.IsAtEOP() ||
_cch == 1 && _rpTX.IsAfterEOP()))
{
// Can't overtype a CR, so need to insert new char but no room
ped->GetCallMgr()->SetMaxText();
return FALSE;
}
if((!fOver || !_cch && GetCp() == GetTextLength()) &&
!CheckTextLength(1)) // Return if we can't
{ // add even 1 more char
return FALSE;
}
// The following if statement implements Word95's "Smart Quote" feature.
// To build this in, we still need an API to turn it on and off. This
// could be EM_SETSMARTQUOTES with wparam turning the feature on or off.
// murrays. NB: this needs localization for French, German, and many
// other languages (unless system can provide open/close chars given
// an LCID).
if((ch == '\'' || ch == '"') && // Smart quotes
SmartQuotesEnabled() &&
PRIMARYLANGID(GetKeyboardLayout(0)) == LANG_ENGLISH)
{
LONG cp = GetCpMin(); // Open vs close depends
CTxtPtr tp(ped, cp - 1); // on char preceding
// selection cpMin
ch = (ch == '"') ? RDBLQUOTE : RQUOTE; // Default close quote
// or apostrophe. If at
WCHAR chp = tp.GetChar();
if(!cp || IsWhiteSpace(chp) || chp == '(') // BOStory or preceded
ch--; // by whitespace, use
} // open quote/apos
// Some languages, e.g., Thai, Vietnamese require verifying the input
// sequence order before submitting it to the backing store.
BOOL fBaseChar = TRUE; // Assume ch is a base consonant
if(!IsInputSequenceValid(&ch, 1, fOver, &fBaseChar))
{
SetDualFontMode(FALSE); // Ignore bad sequence
return FALSE;
}
DWORD bCharSetDefault = ped->GetCharFormat(-1)->_bCharSet;
DWORD dw = GetCharFlags(ch, bCharSetDefault);
ped->OrCharFlags(dw, publdr);
// BEFORE we do "dual-font", we sync the keyboard and current font's
// (_iFormat) charset if it hasn't been done.
const CCharFormat *pCFCurrent = NULL;
CCharFormat CF = *ped->GetCharFormat(GetiFormat());
BYTE bCharSet = CF._bCharSet;
BOOL fRestoreCF = FALSE;
if(ped->IsAutoFont())
{
UINT uKbdcpg = 0;
BOOL fFEKbd = FALSE;
if(!(ped->_fIMEInProgress))
uKbdcpg = CheckSynchCharSet(dw);
if (fUseUIFont() && ch <= 0x0FF)
{
// For UIFont, we need to format ANSI characters
// so we will not have different formats between typing and
// WM_SETTEXT.
if (!ped->_fIMEInProgress && dw == fHILATIN1)
{
// Use Ansi font if based font or current font is FE.
if(IsFECharSet(bCharSetDefault) || IsFECharSet(bCharSet))
SetupDualFont(); // Use Ansi font for HiAnsi
}
else if (dw & fASCII && (GetCharSetMask(TRUE) & fASCII) == fASCII)
{
CCharFormat CFDefault = *ped->GetCharFormat(-1);
if (IsRich() && IsBiDiCharSet(CFDefault._bCharSet)
&& !W32->IsBiDiCodePage(uKbdcpg))
CFDefault._bCharSet = ANSI_CHARSET;
SetCharFormat(&CFDefault, SCF_NOKBUPDATE, publdr, CFM_CHARSET | CFM_FACE | CFM_SIZE,
CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK | CFM2_HOLDITEMIZE);
_fUseiFormat = FALSE;
pCFCurrent = &CF;
fRestoreCF = ped->_fIMEInProgress;
}
}
else if(!fUseUIFont() && bCharSet != ANSI_CHARSET &&
(ped->_fDualFont && bCharSet != SYMBOL_CHARSET &&
(((fFEKbd = (ped->_fIMEInProgress || W32->IsFECodePage(uKbdcpg))) && ch < 127 && IsAlpha(ch)) ||
(!fFEKbd && IsFECharSet(ped->GetCharFormat(GetiFormat())->_bCharSet) && ch < 127))
|| ped->_fHbrCaps))
{
SetupDualFont();
pCFCurrent = &CF;
fRestoreCF = ped->_fIMEInProgress;
}
}
// = Indic/Thai overtyping convention =
//
// The deal is that we will overwrite the cluster if ch is a cluster-start char
// otherwise we just insert. This new convention was proposed by SA Office2000.
//
// Abc.Def.Ghi
// Typing X at D Abc.X.Ghi
// Typing y and z Abc.Xyz.Ghi
SetExtend(TRUE); // Tell Advance() to
if(fOver && fBaseChar) // select chars
{ // If nothing selected and
if(!_cch && !_rpTX.IsAtEOP()) // not at EOP char, try
{ // to select char at IP
LONG iFormatSave = Get_iCF(); // Remember char's format
AdvanceCRLF();
SnapToCluster();
ReplaceRange(0, NULL, publdr,
SELRR_REMEMBERENDIP); // Delete this character.
ReleaseFormats(_iFormat, -1);
_iFormat = iFormatSave; // Restore char's format.
}
}
else if(_SelMode == smWord && ch != TAB && _cch)// Replace word selection
{
// The code below wants the active end to be at the end of the
// word. Make sure this is so.
// FUTURE: (alexgo, andreib), _cch will only be less than zero
// in certain weird timing situations where we get a mouse move
// message in between the double click and mouse up messages.
// we should rethink how we process messages && the ordering thereof.
if(_cch < 0)
FlipRange();
// Leave word break chars
CTxtPtr tp(_rpTX); // at end of selection
Assert(_cch > 0);
tp.AdvanceCp(-1);
if(tp.GetCp() && tp.FindWordBreak(WB_ISDELIMITER))// Delimeter at sel end
FindWordBreak(WB_LEFTBREAK); // Backspace over it, etc.
}
_fIsChar = TRUE; // Tell CDisplay::UpdateView
_fDontUpdateFmt = TRUE; // we're PuttingChar()
LONG iFormat = GetiFormat(); // Save current value
AdjustEndEOP(NEWCHARS);
if(!_cch)
Set_iCF(iFormat);
_fDontUpdateFmt = FALSE;
if(ped->_fUpperCase)
CharUpperBuff(&ch, 1);
else if(ped->_fLowerCase)
CharLowerBuff(&ch, 1);
{
CFreezeDisplay fd(GetPed()->_pdp);
if(!_cch)
{
if(bCharSet == DEFAULT_CHARSET)
{
CCharFormat CFTemp;
if (dw & fFE) // Find a better charset for FE char
CFTemp._bCharSet = MatchFECharSet(dw, GetFontSignatureFromFace(CF._iFont));
else
CFTemp._bCharSet = GetCharSet(W32->ScriptIndexFromFontSig(dw >> 8), NULL);
SetCharFormat(&CFTemp, SCF_NOKBUPDATE, NULL, CFM_CHARSET, CFM2_NOCHARSETCHECK);
}
else if(bCharSet == SYMBOL_CHARSET && dwFlags & KBD_CHAR && ch > 255)
{
UINT cpg = GetKeyboardCodePage(0); // If 125x, convert char
if(IN_RANGE(1250, cpg, 1257)) // back to ANSI for storing
{ // SYMBOL_CHARSET chars
BYTE ach;
WCTMB(cpg, 0, &ch, 1, (char *)&ach, 1, NULL, NULL, NULL);
ch = ach;
}
}
}
if(dwFlags & KBD_CHAR || ped->_fIMEInProgress || bCharSet == SYMBOL_CHARSET)
ReplaceRange(1, &ch, publdr, SELRR_REMEMBERRANGE);
else
CleanseAndReplaceRange(1, &ch, TRUE, publdr, NULL);
}
_fIsChar = FALSE;
// Restore font for Hebrew CAPS. Note that FE font is not restored
// (is handled by IME).
if(pCFCurrent && (W32->UsingHebrewKeyboard() || fRestoreCF))
SetCharFormat(pCFCurrent, SCF_NOKBUPDATE, NULL, CFM_FACE | CFM_CHARSET | CFM_SIZE, CFM2_NOCHARSETCHECK);
else if(iFormat != Get_iFormat())
CheckChangeKeyboardLayout();
SetDualFontMode(FALSE);
if (!_pdp->IsFrozen())
CheckUpdateWindow(); // Need to update display
// for pending chars.
return TRUE;
}
/*
* CTxtSelection::CheckUpdateWindow()
*
* @mfunc
* If it's time to update the window, after pending-typed characters,
* do so now. This is needed because WM_PAINT has a lower priority
* than WM_CHAR.
*/
void CTxtSelection::CheckUpdateWindow()
{
DWORD ticks = GetTickCount();
DWORD delta = ticks - _ticksPending;
if(!_ticksPending)
_ticksPending = ticks;
else if(delta >= ticksPendingUpdate)
GetPed()->TxUpdateWindow();
}
/*
* CTxtSelection::BypassHiddenText(iDir)
*
* @mfunc
* Bypass hidden text forward/backward for iDir positive/negative
*
* @rdesc
* TRUE if succeeded or no hidden text. FALSE if at document limit
* (end/start for Direction positive/negative) or if hidden text between
* cp and that limit.
*/
BOOL CTxtSelection::BypassHiddenText(
LONG iDir)
{
if(iDir > 0)
_rpCF.AdjustForward();
else
_rpCF.AdjustBackward();
if(!(GetPed()->GetCharFormat(_rpCF.GetFormat())->_dwEffects & CFE_HIDDEN))
return TRUE;
BOOL fExtendSave = _fExtend;
SetExtend(FALSE);
CCFRunPtr rp(*this);
LONG cch = (iDir > 0)
? rp.FindUnhiddenForward() : rp.FindUnhiddenBackward();
BOOL bRet = !rp.IsHidden(); // Note whether still hidden
if(bRet) // It isn't:
Advance(cch); // bypass hidden text
SetExtend(fExtendSave);
return bRet;
}
/*
* CTxtSelection::InsertEOP(publdr)
*
* @mfunc
* Insert EOP character(s)
*
* @rdesc
* TRUE if successful
*/
BOOL CTxtSelection::InsertEOP (
IUndoBuilder *publdr, //@parm If non-NULL, where to put anti-events
WCHAR ch) //@parm Possible EOP char
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::InsertEOP");
_TEST_INVARIANT_
LONG cchEOP = GetPed()->fUseCRLF() ? 2 : 1;
BOOL fResult = FALSE;
LONG i, iFormatSave;
WCHAR szBlankRow[MAX_TAB_STOPS + 1] = {CR, LF, 0};
WCHAR * pch = szBlankRow;
BOOL fPFInTable;
WORD wPFNumbering;
BYTE bPFTabCount;
BOOL fPrevPFInTable = TRUE;
{
const CParaFormat *pPF = GetPF(); // Get paragraph format
// pPF may become invalid after SetParaFormat. So, we need
// to save up necessary data from pPF.
fPFInTable = pPF->InTable();
wPFNumbering = pPF->_wNumbering;
bPFTabCount = pPF->_bTabCount;
}
if(ch && (GetPed()->fUseCRLF() || IN_RANGE(VT, ch, FF)))
{
szBlankRow[0] = ch;
cchEOP = 1;
}
_fEOP = TRUE;
if(publdr)
{
publdr->StartGroupTyping();
publdr->SetNameID(UID_TYPING);
}
if(fPFInTable)
{
SetExtend(FALSE); // Don't want extended selection
if(!_cch && !_rpPF.GetIch())
{
const CParaFormat *pPFPrev; // Get previous paragraph format
_rpPF.AdjustBackward();
pPFPrev = GetPed()->GetParaFormat(_rpPF.GetFormat());
fPrevPFInTable = (pPFPrev->_wEffects & PFE_TABLE) ? TRUE : FALSE;
_rpPF.AdjustForward();
}
if(fPrevPFInTable)
{
if(GetCp() && !_rpTX.IsAfterEOP())
{
while(!_rpTX.IsAtEOP() && Advance(1))
;
*pch++ = CR;
}
for(i = bPFTabCount; i--; *pch++ = CELL)
;
*pch++ = CR;
pch = szBlankRow;
cchEOP = bPFTabCount + 1;
}
}
if(!GetCch() && wPFNumbering && _rpTX.IsAfterEOP())
{
// Two enters in a row turn off numbering
CParaFormat PF;
PF._wNumbering = 0;
PF._dxOffset = 0;
SetParaFormat(&PF, publdr, PFM_NUMBERING | PFM_OFFSET);
}
if(CheckTextLength(cchEOP)) // If cchEOP chars can fit...
{
CFreezeDisplay fd(GetPed()->_pdp);
iFormatSave = Get_iCF(); // Save CharFormat before EOP
// Get_iCF() does AddRefFormat()
if(wPFNumbering) // Bullet paragraph: EOP has
{ // desired bullet CharFormat
CFormatRunPtr rpCF(_rpCF); // Get run pointers for locating
CTxtPtr rpTX(_rpTX); // EOP CharFormat
rpCF.AdvanceCp(rpTX.FindEOP(tomForward));
rpCF.AdjustBackward();
Set_iCF(rpCF.GetFormat()); // Set _iFormat to EOP CharFormat
}
// Put in appropriate EOP mark
fResult = ReplaceRange(cchEOP, pch, // If Shift-Enter, insert VT
publdr, SELRR_REMEMBERRANGE, NULL, RR_NO_EOR_CHECK);
Set_iCF(iFormatSave); // Restore _iFormat if changed
ReleaseFormats(iFormatSave, -1); // Release iFormatSave
if(fPFInTable)
{
if(!fPrevPFInTable) // Turn off PFE_TABLE bit of
{ // EOP just inserted
CParaFormat PF;
_cch = cchEOP; // Select EOP just inserted
PF._wEffects = 0;
SetParaFormat(&PF, publdr, PFM_TABLE);
_cch = 0; // Back to insertion point
}
else if(cchEOP > 1)
{
if(*pch == CR)
cchEOP--;
Advance(-cchEOP);
_fCaretNotAtBOL = FALSE;
Update(FALSE);
}
}
}
return fResult;
}
/*
* CTxtSelection::Delete(fCtrl, publdr)
*
* @mfunc
* Delete the selection. If fCtrl is true, this method deletes from
* min of selection to end of word
*
* @rdesc
* TRUE if successful
*/
BOOL CTxtSelection::Delete (
DWORD fCtrl, //@parm If TRUE, Ctrl key depressed
IUndoBuilder *publdr) //@parm if non-NULL, where to put anti-events
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Delete");
_TEST_INVARIANT_
SELRR mode = SELRR_REMEMBERRANGE;
AssertSz(!GetPed()->TxGetReadOnly(), "CTxtSelection::Delete(): read only");
if(!_cch)
BypassHiddenText(tomForward);
if(publdr)
{
publdr->StopGroupTyping();
publdr->SetNameID(UID_DELETE);
}
SetExtend(TRUE); // Setup to change selection
if(fCtrl)
{ // Delete to word end from cpMin
Collapser(tomStart); // (won't necessarily repaint,
FindWordBreak(WB_MOVEWORDRIGHT); // since won't delete it)
}
if(!_cch) // No selection
{
mode = SELRR_REMEMBERCPMIN;
if(!AdvanceCRLF()) // Try to select char at IP
{ // End of text, nothing to delete
Beep(); // Only executed in plain text,
return FALSE; // since else there's always
} // a final EOP to select
SnapToCluster();
_fMoveBack = TRUE; // Convince Update_iFormat() to
_fUseiFormat = TRUE; // use forward format
}
AdjustEndEOP(NONEWCHARS);
ReplaceRange(0, NULL, publdr, mode); // Delete selection
return TRUE;
}
/*
* CTxtSelection::BackSpace(fCtrl, publdr)
*
* @mfunc
* do what keyboard BackSpace key is supposed to do
*
* @rdesc
* TRUE iff movement occurred
*
* @comm
* This routine should probably use the Move methods, i.e., it's
* logical, not directional
*
* @devnote
* _fExtend is TRUE iff Shift key is pressed or being simulated
*/
BOOL CTxtSelection::Backspace (
BOOL fCtrl, //@parm TRUE iff Ctrl key is pressed (or being simulated)
IUndoBuilder *publdr) //@parm If not-NULL, where to put the antievents
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::Backspace");
_TEST_INVARIANT_
SELRR mode = SELRR_REMEMBERRANGE;
AssertSz(!GetPed()->TxGetReadOnly(),
"CTxtSelection::Backspace(): read only");
_fCaretNotAtBOL = FALSE;
if(publdr)
{
publdr->SetNameID(UID_TYPING);
if(_cch || fCtrl)
publdr->StopGroupTyping();
}
SetExtend(TRUE); // Set up to extend range
if(fCtrl) // Delete word left
{
if(!GetCpMin()) // Beginning of story: no word
{ // to delete
Beep();
return FALSE;
}
Collapser(tomStart); // First collapse to cpMin
if(!BypassHiddenText(tomBackward))
goto beep;
FindWordBreak(WB_MOVEWORDLEFT); // Extend word left
}
else if(!_cch) // Empty selection
{ // Try to select previous char
if (!BypassHiddenText(tomBackward) ||
!BackupCRLF(FALSE))
{ // Nothing to delete
beep: Beep();
return FALSE;
}
mode = SELRR_REMEMBERENDIP;
if(publdr)
publdr->StartGroupTyping();
}
ReplaceRange(0, NULL, publdr, mode); // Delete selection
return TRUE;
}
/*
* CTxtSelection::ReplaceRange(cchNew, pch, publdr, SELRRMode)
*
* @mfunc
* Replace selected text by new given text and update screen according
* to _fShowCaret and _fShowSelection
*
* @rdesc
* length of text inserted
*/
LONG CTxtSelection::ReplaceRange (
LONG cchNew, //@parm Length of replacing text or -1 to request
// <p pch> sz length
const TCHAR *pch, //@parm Replacing text
IUndoBuilder *publdr, //@parm If non-NULL, where to put anti-events
SELRR SELRRMode, //@parm what to do about selection anti-events.
LONG* pcchMove, //@parm number of chars moved after replacing
DWORD dwFlags) //@parm Special flags
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::ReplaceRange");
_TEST_INVARIANT_
LONG cchNewSave;
LONG cchOld;
LONG cchText = GetTextLength();
LONG cpFirstRecalc;
LONG cpMin, cpMost;
LONG cpSave;
BOOL fDeleteAll = FALSE;
BOOL fHeading = FALSE;
const BOOL fUpdateView = _fShowSelection;
CancelModes();
if(cchNew < 0)
cchNew = wcslen(pch);
if(!_cch && !cchNew) // Nothing to do
return 0;
if (!GetPed()->IsStreaming() && // If not pasting,
(!_cch && *pch != CR && // Don't insert bet CELL & CR
CRchTxtPtr::GetChar() == CR && GetPrevChar() == CELL && GetPF()->InTable() ||
_cch != cchText && (IsInOutlineView() && IsCollapsed() ||
IsHidden())))
{ // Don't insert into collapsed
Beep(); // or hidden region (should
return 0; // only happen if whole story
} // collapsed or hidden)
GetPed()->GetCallMgr()->SetSelectionChanged();
CheckTableSelection();
cchOld = GetRange(cpMin, cpMost);
if (cpMin > min(_cpSel, _cpSel + _cchSel) ||// If new sel doesn't
cpMost < max(_cpSel, _cpSel + _cchSel)) // contain all of old
{ // sel, remove old sel
ShowSelection(FALSE);
_fShowCaret = TRUE;
}
_fCaretNotAtBOL = FALSE;
_fShowSelection = FALSE; // Suppress the flashies
// If we are streaming in text or RTF data, don't bother with incremental
// recalcs. The data transfer engine will take care of a final recalc
if(!GetPed()->IsStreaming())
{
// Do this before calling ReplaceRange() so that UpdateView() works
// AROO !!! Do this before replacing the text or the format ranges!!!
if(!_pdp->WaitForRecalc(cpMin, -1))
{
Tracef(TRCSEVERR, "WaitForRecalc(%ld) failed", cpMin);
cchNew = 0; // Nothing inserted
goto err;
}
}
if(publdr)
{
Assert(SELRRMode != SELRR_IGNORE);
// Use selection AntiEvent mode to determine what to do for undo
LONG cp = cpMin;
LONG cch = 0;
if(SELRRMode == SELRR_REMEMBERRANGE)
{
cp = GetCp();
cch = _cch;
}
else if(SELRRMode == SELRR_REMEMBERENDIP)
{
cp = cpMost;
}
else
{
Assert(SELRRMode == SELRR_REMEMBERCPMIN);
}
HandleSelectionAEInfo(GetPed(), publdr, cp, cch, cpMin + cchNew,
0, SELAE_MERGE);
}
if(_cch == cchText && !cchNew) // For delete all, set
{ // up to choose Normal
fDeleteAll = TRUE; // or Heading 1
FlipRange();
fHeading = IsInOutlineView() && IsHeadingStyle(GetPF()->_sStyle);
}
cpSave = cpMin;
cpFirstRecalc = cpSave;
cchNewSave = cchNew;
cchNew = CTxtRange::ReplaceRange(cchNew, pch, publdr, SELRR_IGNORE, pcchMove, dwFlags);
_cchSel = 0; // No displayed selection
_cpSel = GetCp();
cchText = GetTextLength(); // Update total text length
if(cchNew != cchNewSave)
{
Tracef(TRCSEVERR, "CRchTxtPtr::ReplaceRange(%ld, %ld, %ld) failed", GetCp(), cchOld, cchNew);
_fShowSelection = fUpdateView;
goto err;
}
// The cp should be at *end* (cpMost) of replaced range (it starts
// at cpMin of the prior range).
AssertSz(_cpSel == cpSave + cchNew && _cpSel <= cchText,
"CTxtSelection::ReplaceRange() - Wrong cp after replacement");
_fShowSelection = fUpdateView;
if(fDeleteAll) // When all text is deleted
{ // use Normal style unless
CParaFormat PF; // in Outline View and first
PF._sStyle = fHeading ? STYLE_HEADING_1 : STYLE_NORMAL;
SetParaStyle(&PF, NULL, PFM_STYLE); // para was a heading
if(GetPed()->IsBiDi())
{
if(GetPed()->_fFocus && !GetPed()->_fIMEInProgress)
{
MatchKeyboardToPara();
CheckSynchCharSet(0);
}
}
else
Update_iFormat(-1);
}
// Only update caret if inplace active
if(GetPed()->fInplaceActive())
UpdateCaret(fUpdateView); // May need to scroll
else // Update caret when we get
GetPed()->_fScrollCaretOnFocus = TRUE; // focus again
return cchNew;
err:
TRACEERRSZSC("CTxtSelection::ReplaceRange()", E_FAIL);
Tracef(TRCSEVERR, "CTxtSelection::ReplaceRange(%ld, %ld)", cchOld, cchNew);
Tracef(TRCSEVERR, "cchText %ld", cchText);
return cchNew;
}
/*
* CTxtSelection::GetPF()
*
* @mfunc
* Return ptr to CParaFormat at active end of this selection. If no PF
* runs are allocated, then return ptr to default format. If active
* end is at cpMost of a nondegenerate selection, use the PF of the
* previous char (last char in selection).
*
* @rdesc
* Ptr to CParaFormat at active end of this selection
*/
const CParaFormat* CTxtSelection::GetPF()
{
TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtSelection::GetPF");
if(_cch > 0)
_rpPF.AdjustBackward();
const CParaFormat* pPF = GetPed()->GetParaFormat(_rpPF.GetFormat());
if(_cch > 0)
_rpPF.AdjustForward();
return pPF;
}
/*
* CTxtSelection::CheckTableSelection()
*
* @mfunc
* Select whole cells if one or more CELLs are selected
*/
void CTxtSelection::CheckTableSelection ()
{
if(!_fSelHasEOP && GetPF()->InTable()) // For now, don't let
{ // table CELLs be
LONG cpMin, cpMost; // deleted, unless in
CTxtPtr tp(_rpTX); // complete rows
GetRange(cpMin, cpMost);
if(_cch > 0)
tp.AdvanceCp(-_cch); // Start at cpMin
while(tp.GetCp() < cpMost)
{
if(tp.GetChar() == CELL) // Stop selection at CELL
{
Set(cpMin, cpMin - tp.GetCp());
UpdateSelection();
return;
}
tp.AdvanceCp(1);
}
}
}
/*
* CTxtSelection::SetCharFormat(pCF, fApplyToWord, publdr, dwMask, dwMask2)
*
* @mfunc
* apply CCharFormat *pCF to this selection. If range is an IP
* and fApplyToWord is TRUE, then apply CCharFormat to word surrounding
* this insertion point
*
* @rdesc
* HRESULT = NOERROR if no error
*/
HRESULT CTxtSelection::SetCharFormat (
const CCharFormat *pCF, //@parm Ptr to CCharFormat to fill with results
DWORD flags, //@parm If SCF_WORD and selection is an IP,
// use enclosing word
IUndoBuilder *publdr, //@parm Undo context
DWORD dwMask, //@parm CHARFORMAT2 mask
DWORD dwMask2) //@parm Second mask
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SetCharFormat");
HRESULT hr = 0;
LONG iFormat = _iFormat;
if(publdr)
publdr->StopGroupTyping();
/*
* The code below applies character formatting to a double-clicked
* selection the way Word does it, that is, not applying the formatting to
* the last character in the selection if that character is a blank.
*
* See also the corresponding code in CTxtRange::GetCharFormat().
*/
LONG cpMin, cpMost;
LONG cch = GetRange(cpMin, cpMost);;
BOOL fCheckKeyboard = (flags & SCF_NOKBUPDATE) == 0;
CTxtRange rg(GetPed());
CCharFormat CF;
if(_SelMode == smWord && (flags & SCF_USEUIRULES) && cch)
{
// In word select mode, don't include final blank in SetCharFormat
CTxtPtr tpLast(GetPed(), cpMost - 1);
if(tpLast.GetChar() == ' ') // Selection ends with a blank:
{
cpMost--; // Setup end point to end at last
cch--; // char in selection
fCheckKeyboard = FALSE;
flags &= ~SCF_WORD;
}
}
BYTE bCharSet = pCF->_bCharSet;
// Smart SB/DB Font Apply Feature
if (cch && IsRich() && // > 0 chars in rich text
!GetPed()->_fSingleCodePage && // Not in single cp mode
(dwMask & CFM_FACE)) // font change
{
if (!(dwMask & CFM_CHARSET) || bCharSet == DEFAULT_CHARSET)
{
// Setup charset for DEFAULT_CHARSET or when the client only specifies
// facename
CF = *pCF;
CF._bCharSet = GetFirstAvailCharSet(GetFontSignatureFromFace(CF._iFont));
pCF = &CF;
}
dwMask2 |= CFM2_MATCHFONT; // Signal to match font charsets
#if 0
// Single byte 125x CharSet
CFreezeDisplay fd(_pdp); // Speed this up
CTxtPtr tp(GetPed(), cpMin);
CCharFormat CF = *pCF;
while(cpMin < cpMost)
{
BOOL fInCharSet = In125x(tp.GetChar(), pCF->_bCharSet);
while(fInCharSet == In125x(tp.GetChar(), pCF->_bCharSet) &&
tp.GetCp() < cpMost)
{
tp.AdvanceCp(1);
}
dwMask &= ~(CFM_FACE | CFM_CHARSET);
if(fInCharSet)
dwMask |= (CFM_FACE | CFM_CHARSET);
rg.SetRange(cpMin, tp.GetCp());
HRESULT hr1 = dwMask
? rg.SetCharFormat(&CF, flags | SCF_IGNORESELAE, publdr, dwMask, dwMask2)
: NOERROR;
hr = FAILED(hr) ? hr : hr1;
cpMin = tp.GetCp();
}
#endif
}
if(_cch)
{
// Selection is being set to a charformat
if (IsRich())
{
GetPed()->SetfSelChangeCharFormat();
}
// REVIEW (murrays): can _iFormat change if _cch != 0?
// It is OK to not update _iFormat for a non degenerate selection
// even if UI rules and word select have reduced it.
rg.SetRange(cpMin, cpMost);
hr = rg.SetCharFormat(pCF, flags, publdr, dwMask, dwMask2);
}
else
{
// But for a degenerate selection, _iFormat must be updated
hr = CTxtRange::SetCharFormat(pCF, flags, publdr, dwMask, dwMask2);
}
if(fCheckKeyboard && (dwMask & CFM_CHARSET) && _iFormat != iFormat)
CheckChangeKeyboardLayout();
_fIsChar = TRUE;
UpdateCaret(!GetPed()->fHideSelection());
_fIsChar = FALSE;
return hr;
}
/*
* CTxtSelection::SetParaFormat(pPF, publdr)
*
* @mfunc
* apply CParaFormat *pPF to this selection.
*
* @rdesc
* HRESULT = NOERROR if no error
*/
HRESULT CTxtSelection::SetParaFormat (
const CParaFormat* pPF, //@parm ptr to CParaFormat to apply
IUndoBuilder *publdr, //@parm Undo context for this operation
DWORD dwMask) //@parm Mask to use
{
TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtSelection::SetParaFormat");
CFreezeDisplay fd(GetPed()->_pdp);
HRESULT hr;
if(publdr)
publdr->StopGroupTyping();
// Apply the format
hr = CTxtRange::SetParaFormat(pPF, publdr, dwMask);
UpdateCaret(!GetPed()->Get10Mode() || IsCaretInView());
return hr;
}
/*
* CTxtSelection::SetSelectionInfo (pselchg)
*
* @mfunc Fills out data members in a SELCHANGE structure
*/
void CTxtSelection::SetSelectionInfo(
SELCHANGE *pselchg) //@parm SELCHANGE structure to use
{
LONG cpMin, cpMost;
LONG cch = GetRange(cpMin, cpMost);;
pselchg->chrg.cpMin = cpMin;
pselchg->chrg.cpMost = cpMost;
pselchg->seltyp = SEL_EMPTY;
// OR in the following selection type flags if active:
//
// SEL_EMPTY: insertion point
// SEL_TEXT: at least one character selected
// SEL_MULTICHAR: more than one character selected
// SEL_OBJECT: at least one object selected
// SEL_MULTIOJBECT: more than one object selected
//
// Note that the flags are OR'ed together.
if(cch)
{
LONG cObjects = GetObjectCount(); // Total object count
if(cObjects) // There are objects:
{ // get count in range
CObjectMgr *pobjmgr = GetPed()->GetObjectMgr();
Assert(pobjmgr);
cObjects = pobjmgr->CountObjectsInRange(cpMin, cpMost);
if(cObjects > 0)
{
pselchg->seltyp |= SEL_OBJECT;
if(cObjects > 1)
pselchg->seltyp |= SEL_MULTIOBJECT;
}
}
cch -= cObjects;
AssertSz(cch >= 0, "objects are overruning the selection");
if(cch > 0)
{
pselchg->seltyp |= SEL_TEXT;
if(cch > 1)
pselchg->seltyp |= SEL_MULTICHAR;
}
}
}
/*
* CTxtSelection::UpdateForAutoWord ()
*
* @mfunc Update state to prepare for auto word selection
*
* @rdesc void
*/
void CTxtSelection::UpdateForAutoWord()
{
AssertSz(!_cch,
"CTxtSelection::UpdateForAutoWord: Selection isn't degenerate");
// If enabled, prepare Auto Word Sel
if(GetPed()->TxGetAutoWordSel())
{
CTxtPtr tp(_rpTX);
// Move anchor to new location
_cpAnchor = GetCp();
// Remember that FindWordBreak moves tp's cp
// (aren't side effects wonderful?
tp.FindWordBreak(WB_MOVEWORDRIGHT);
_cpAnchorMost =_cpWordMost = tp.GetCp();
tp.FindWordBreak(WB_MOVEWORDLEFT);
_cpAnchorMin = _cpWordMin = tp.GetCp();
_fAutoSelectAborted = FALSE;
}
}
/*
* CTxtSelection::AutoSelGoBackWord(pcpToUpdate, iDirToPrevWord, iDirToNextWord)
*
* @mfunc Backup a word in auto word selection
*/
void CTxtSelection::AutoSelGoBackWord(
LONG * pcpToUpdate, //@parm end of word selection to update
int iDirToPrevWord, //@parm direction to next word
int iDirToNextWord) //@parm direction to previous word
{
if (GetCp() >= _cpAnchorMin &&
GetCp() <= _cpAnchorMost)
{
// We are back in the first word. Here we want to pop
// back to a selection anchored by the original selection
Set(GetCp(), GetCp() - _cpAnchor);
_fAutoSelectAborted = FALSE;
_cpWordMin = _cpAnchorMin;
_cpWordMost = _cpAnchorMost;
}
else
{
// pop back a word
*pcpToUpdate = _cpWordPrev;
CTxtPtr tp(_rpTX);
_cpWordPrev = GetCp() + tp.FindWordBreak(iDirToPrevWord);
FindWordBreak(iDirToNextWord);
}
}
/*
* CTxtSelection::InitClickForAutWordSel (pt)
*
* @mfunc Init auto selection for click with shift key down
*
* @rdesc void
*/
void CTxtSelection::InitClickForAutWordSel(
const POINT pt) //@parm Point of click
{
// If enabled, prepare Auto Word Sel
if(GetPed()->TxGetAutoWordSel())
{
// If auto word selection is occuring we want to pretend
// that the click is really part of extending the selection.
// Therefore, we want the auto word select data to look as
// if the user had been extending the selection via the
// mouse all along. So we set the word borders to the
// word that would have been previously selected.
// Need this for finding word breaks
CRchTxtPtr rtp(GetPed());
LONG cpClick = _pdp->CpFromPoint(pt, NULL, &rtp, NULL, TRUE);
int iDir = -1;
if(cpClick < 0)
{
// If this fails what can we do? Prentend it didn't happen!
// We can do this because it will only make the UI act a
// little funny and chances are the user won't even notice.
return;
}
// Assume click is within anchor word
_cpWordMost = _cpAnchorMost;
_cpWordMin = _cpAnchorMin;
if(cpClick > _cpAnchorMost)
{
// Click is after anchor word, so set cpMost appropriately
iDir = WB_MOVEWORDLEFT;
rtp.FindWordBreak(WB_MOVEWORDLEFT);
_cpWordMost = rtp.GetCp();
}
// Click is before the anchor word
else if(cpClick < _cpAnchorMost)
{
// Click is before anchor word, so set cpMin appropriately.
iDir = WB_MOVEWORDRIGHT;
rtp.FindWordBreak(WB_MOVEWORDRIGHT);
_cpWordMin = rtp.GetCp();
}
if(iDir != -1)
{
rtp.FindWordBreak(iDir);
_cpWordPrev = rtp.GetCp();
}
}
}
/*
* CTxtSelection::CreateCaret ()
*
* @mfunc Create a caret
*
* @devnote
* The caret is characterized by a height (_yHeightCaret), a keyboard
* direction (if BiDi), a width (1 to 8, since OS can't handle carets
* larger than 8 pixels), and an italic state. One could cache this
* info thereby avoiding computing the caret on every keystroke.
*/
void CTxtSelection::CreateCaret()
{
CTxtEdit * ped = GetPed();
const CCharFormat *pCF = ped->GetCharFormat(_iFormat);
DWORD dwCaretType = 0;
BOOL fItalic;
LONG y = min(_yHeightCaret, 512);
y = max(0, y);
// Caret shape reflects current charset
if(IsComplexKbdInstalled())
{
// Custom carets aren't italicized
fItalic = 0;
LCID lcid = GetKeyboardLCID();
dwCaretType = CARET_CUSTOM;
if (W32->IsBiDiLcid(lcid))
dwCaretType = CARET_CUSTOM | CARET_BIDI;
else if (PRIMARYLANGID(lcid) == LANG_THAI)
dwCaretType = CARET_CUSTOM | CARET_THAI;
else if (W32->IsIndicLcid(lcid))
dwCaretType = CARET_CUSTOM | CARET_INDIC;
}
else
fItalic = pCF->_dwEffects & CFE_ITALIC && _yHeightCaret > 15; //9 pt/15 pixels looks bad
INT dx = dxCaret;
DWORD dwCaretInfo = (_yHeightCaret << 16) | (dx << 8) | (dwCaretType << 4) |
(fItalic << 1) | !_cch;
if (ped->_fKoreanBlockCaret)
{
// Support Korean block caret during Kor IME composition
// Basically, we want to create a caret using the width and height of the
// character at current cp.
CDisplay *pdp = ped->_pdp;
LONG cpMin, cpMost;
POINT ptStart, ptEnd;
GetRange(cpMin, cpMost);
CRchTxtPtr tp(ped, cpMin);
if (pdp->PointFromTp(tp, NULL, FALSE, ptStart, NULL, TA_TOP+TA_LEFT) != -1 &&
pdp->PointFromTp(tp, NULL, FALSE, ptEnd, NULL, TA_BOTTOM+TA_RIGHT) != -1)
{
// Destroy whatever caret bitmap we had previously
DeleteCaretBitmap(TRUE);
LONG iCharWidth = ptEnd.x - ptStart.x;
if (!ped->fUseLineServices())
{
const CCharFormat *pCF = tp.GetCF();
CLock lock;
HDC hdc = W32->GetScreenDC();
if(hdc)
{
LONG dypInch = MulDiv(GetDeviceCaps(hdc, LOGPIXELSY), _pdp->GetZoomNumerator(), _pdp->GetZoomDenominator());
CCcs *pccs = fc().GetCcs(pCF, dypInch);
if(pccs)
{
LONG iKorCharWidth;
if (pccs->Include(0xAC00, iKorCharWidth))
iCharWidth = iKorCharWidth;
pccs->Release();
}
}
}
ped->TxCreateCaret(0, iCharWidth, ptEnd.y - ptStart.y);
_fCaretCreated = TRUE;
ped->TxSetCaretPos(ptStart.x, ptStart.y);
}
return;
}
// We always create the caret bitmap on the fly since it
// may be of arbitrary size
if (dwCaretInfo != _dwCaretInfo)
{
_dwCaretInfo = dwCaretInfo; // Update caret info
// Destroy whatever caret bitmap we had previously
DeleteCaretBitmap(FALSE);
if (y && y == _yHeightCaret && (_cch || fItalic || dwCaretType))
{
LONG dy = 4; // Assign value to suppress
LONG i; // compiler warning
WORD rgCaretBitMap[512];
WORD wBits = 0x0020;
if(_cch) // Create blank bitmap if
{ // selection is nondegenerate
y = 1; // (allows others to query
wBits = 0; // OS where caret is)
fItalic = FALSE;
}
if(fItalic)
{
i = (5*y)/16 - 1; // System caret can't be wider
i = min(i, 7); // than 8 bits
wBits = 1 << i; // Make larger italic carets
dy = y/7; // more vertical. Ideal is
dy = max(dy, 4); // usually up 4 over 1, but
} // if bigger go up 5 over 1...
for(i = y; i--; )
{
rgCaretBitMap[i] = wBits;
if(fItalic && !(i % dy))
wBits /= 2;
}
if(!fItalic && !_cch && dwCaretType)
{
dwCaretType &= ~CARET_CUSTOM;
// Create an appropriate shape
switch (dwCaretType)
{
case CARET_BIDI:
// BiDi is a caret with a little triangle on top (flag shape pointing left)
rgCaretBitMap[0] = 0x00E0;
rgCaretBitMap[1] = 0x0060;
break;
case CARET_THAI:
// Thai is an L-like shape (same as system edit control)
rgCaretBitMap[y-2] = 0x0030;
rgCaretBitMap[y-1] = 0x0038;
break;
case CARET_INDIC:
// Indic is a T-like shape
rgCaretBitMap[0] = 0x00F8;
rgCaretBitMap[1] = 0x0070;
break;
default:
if (ped->IsBiDi())
{
// Non-BiDi caret in BiDi document (flag shape pointing right)
rgCaretBitMap[0] = 0x0038;
rgCaretBitMap[1] = 0x0030;
}
}
}
_hbmpCaret = (HBITMAP)CreateBitmap(8, y, 1, 1, rgCaretBitMap);
}
}
ped->TxCreateCaret(_hbmpCaret, dx, (INT)_yHeightCaret);
_fCaretCreated = TRUE;
LONG xShift = _hbmpCaret ? 2 : 0;
if(fItalic)
{
// TODO: figure out better shift algorithm. Use CCcs::_xOverhang?
if(pCF->_iFont == IFONT_TMSNEWRMN)
xShift = 4;
xShift += y/16;
}
xShift = _xCaret - xShift;
#ifdef Boustrophedon
//if(_pPF->_wEffects & PFE_BOUSTROPHEDON)
{
RECT rcView;
_pdp->GetViewRect(rcView, NULL);
xShift = rcView.right - xShift;
}
#endif
ped->TxSetCaretPos(xShift, (INT)_yCaret);
}
/*
* CTxtSelection::DeleteCaretBitmap (fReset)
*
* @mfunc DeleteCaretBitmap
*/
void CTxtSelection::DeleteCaretBitmap(
BOOL fReset)
{
if(_hbmpCaret)
{
DestroyCaret();
DeleteObject((void *)_hbmpCaret);
_hbmpCaret = NULL;
}
if(fReset)
_dwCaretInfo = 0;
}
/*
* CTxtSelection::SetDelayedSelectionRange (cp, cch)
*
* @mfunc sets the selection range such that it won't take effect until
* the control is "stable"
*/
void CTxtSelection::SetDelayedSelectionRange(
LONG cp, //@parm Active end
LONG cch) //@parm Signed extension
{
CSelPhaseAdjuster *pspa;
pspa = (CSelPhaseAdjuster *)GetPed()->GetCallMgr()->GetComponent(
COMP_SELPHASEADJUSTER);
Assert(pspa);
pspa->CacheRange(cp, cch);
}
/*
* CTxtSelection::CheckPlainTextFinalEOP ()
*
* @mfunc
* returns TRUE if this is a plain-text, multiline control with caret
* allowed at BOL and the selection at the end of the story following
* and EOP
*
* @rdesc
* TRUE if all of the conditions above are met
*/
BOOL CTxtSelection::CheckPlainTextFinalEOP()
{
return !IsRich() && _pdp->IsMultiLine() && // Plain-text, multiline
!_fCaretNotAtBOL && // with caret OK at BOL,
GetCp() == GetTextLength() && // & cp at end of story
_rpTX.IsAfterEOP();
}
/*
* CTxtSelection::StopGroupTyping()
*
* @mfunc
* Tell undo manager to stop group typing
*/
void CTxtSelection::StopGroupTyping()
{
IUndoMgr * pundo = GetPed()->GetUndoMgr();
if(pundo)
pundo->StopGroupTyping();
}
/*
* CTxtSelection::SetupDualFont(ch)
*
* @mfunc checks to see if dual font support is necessary; in this case,
* switching to an English font if English text is entered into
* an FE run
* @rdesc
* A pointer to the current CharFormat if the font has to be changed.
*/
void CTxtSelection::SetupDualFont()
{
CTxtEdit *ped = GetPed();
CCharFormat CF;
CF._bCharSet = ANSI_CHARSET;
CCFRunPtr rp(*this);
if (rp.GetPreferredFontInfo(
1252,
CF._bCharSet,
CF._iFont,
CF._yHeight,
CF._bPitchAndFamily,
_iFormat,
IGNORE_CURRENT_FONT))
{
if (!_cch)
SetCharFormat(&CF, SCF_NOKBUPDATE, NULL, CFM_FACE | CFM_CHARSET | CFM_SIZE, 0);
else
{
// For selection, we need to set the character format at cpMin+1
// and use the format for the selection.
CTxtRange rg(ped, GetCpMin() + 1, 0);
rg.SetCharFormat(&CF, SCF_NOKBUPDATE, NULL, CFM_FACE | CFM_CHARSET | CFM_SIZE, 0);
Set_iCF(rg.Get_iCF());
GetCharFormatCache()->Release(_iFormat); // rg.Get_iCF() AddRefs it
_fUseiFormat = TRUE;
}
SetDualFontMode(TRUE);
}
}
//
// CSelPhaseAdjuster methods
//
/*
* CSelPhaseAdjuster::CSelPhaseAdjuster
*
* @mfunc constructor
*/
CSelPhaseAdjuster::CSelPhaseAdjuster(
CTxtEdit *ped) //@parm the edit context
{
_cp = _cch = -1;
_ped = ped;
_ped->GetCallMgr()->RegisterComponent((IReEntrantComponent *)this,
COMP_SELPHASEADJUSTER);
}
/*
* CSelPhaseAdjuster::~CSelPhaseAdjuster
*
* @mfunc destructor
*/
CSelPhaseAdjuster::~CSelPhaseAdjuster()
{
// Save some indirections
CTxtEdit *ped = _ped;
if(_cp != -1)
{
ped->GetSel()->SetSelection(_cp - _cch, _cp);
// If the selection is updated, then we invalidate the
// entire display because the old selection can still
// appear othewise because the part of the screen that
// it was on is not updated.
if(ped->fInplaceActive())
{
// Tell entire client rectangle to update.
// FUTURE: The smaller we make this the better.
ped->TxInvalidateRect(NULL, FALSE);
}
}
ped->GetCallMgr()->RevokeComponent((IReEntrantComponent *)this);
}
/*
* CSelPhaseAdjuster::CacheRange(cp, cch)
*
* @mfunc tells this class the selection range to remember
*/
void CSelPhaseAdjuster::CacheRange(
LONG cp, //@parm Active end to remember
LONG cch) //@parm Signed extension to remember
{
_cp = cp;
_cch = cch;
}