|
|
/*
* @doc INTERNAL * * @module EDIT.C - main part of CTxtEdit | * * See also textserv.cpp (ITextServices and SendMessage interfaces) * and tomDoc.cpp (ITextDocument interface) * * Authors: <nl> * Original RichEdit code: David R. Fulmer <nl> * Christian Fortini, Murray Sargent, Alex Gounares, Rick Sailor, * Jon Matousek * * History: <nl> * 12/28/95 jonmat-Added support of Magellan mouse and smooth scrolling. * * @devnote * Be sure to set tabs at every four (4) columns. In fact, don't even * think of doing anything else! * * Copyright (c) 1995-1998 Microsoft Corporation. All rights reserved. */
#include "_common.h"
#include "_edit.h"
#include "_dispprt.h"
#include "_dispml.h"
#include "_dispsl.h"
#include "_select.h"
#include "_text.h"
#include "_runptr.h"
#include "_font.h"
#include "_measure.h"
#include "_render.h"
#include "_m_undo.h"
#include "_antievt.h"
#include "_rtext.h"
#include "_uspi.h"
#include "_urlsup.h"
#ifdef LINESERVICES
#include "_ols.h"
#endif
#include "_txtbrk.h"
#include "_clasfyc.h"
#define CONTROL(_ch) (_ch - 'A' + 1)
ASSERTDATA
// This is not public because we don't really want folks using it.
// ITextServices is a private interface.
EXTERN_C const IID IID_ITextServices = { // 8d33f740-cf58-11ce-a89d-00aa006cadc5
0x8d33f740, 0xcf58, 0x11ce, {0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5} };
// {13E670F4-1A5A-11cf-ABEB-00AA00B65EA1}
EXTERN_C const GUID IID_ITextHost = { 0x13e670f4, 0x1a5a, 0x11cf, { 0xab, 0xeb, 0x0, 0xaa, 0x0, 0xb6, 0x5e, 0xa1 } };
// {13E670F5-1A5A-11cf-ABEB-00AA00B65EA1}
EXTERN_C const GUID IID_ITextHost2 = { 0x13e670f5, 0x1a5a, 0x11cf, { 0xab, 0xeb, 0x0, 0xaa, 0x0, 0xb6, 0x5e, 0xa1 } };
// this is used internally do tell if a data object is one of our own.
EXTERN_C const GUID IID_IRichEditDO = { /* 21bc3b20-e5d5-11cf-93e1-00aa00b65ea1 */ 0x21bc3b20, 0xe5d5, 0x11cf, {0x93, 0xe1, 0x00, 0xaa, 0x00, 0xb6, 0x5e, 0xa1} };
// Static data members
DWORD CTxtEdit::_dwTickDblClick; // time of last double-click
POINT CTxtEdit::_ptDblClick; // position of last double-click
//HCURSOR CTxtEdit::_hcurCross = 0; // We don't implement outline drag move
HCURSOR CTxtEdit::_hcurArrow = 0; HCURSOR CTxtEdit::_hcurHand = 0; HCURSOR CTxtEdit::_hcurIBeam = 0; HCURSOR CTxtEdit::_hcurItalic = 0; HCURSOR CTxtEdit::_hcurSelBar = 0;
const TCHAR szCRLF[]= TEXT("\r\n"); const TCHAR szCR[] = TEXT("\r");
WORD g_wFlags = 0; // Keyboard controlled flags
/*
* GetKbdFlags(vkey, dwFlags) * * @func * return bit mask (RSHIFT, LSHIFT, RCTRL, LCTRL, RALT, or LALT) * corresponding to vkey = VK_SHIFT, VK_CONTROL, or VK_MENU and * dwFlags * * @rdesc * Bit mask corresponding to vkey and dwFlags */ DWORD GetKbdFlags( WORD vkey, //@parm Virtual key code
DWORD dwFlags) //@parm lparam of WM_KEYDOWN msg
{ if(vkey == VK_SHIFT) return (LOBYTE(HIWORD(dwFlags)) == 0x36) ? RSHIFT : LSHIFT;
if(vkey == VK_CONTROL) return (HIWORD(dwFlags) & KF_EXTENDED) ? RCTRL : LCTRL;
Assert(vkey == VK_MENU);
return (HIWORD(dwFlags) & KF_EXTENDED) ? RALT : LALT; }
///////////////// CTxtEdit Creation, Initialization, Destruction ///////////////////////////////////////
/*
* CTxtEdit::CTxtEdit() * * @mfunc * constructor */ CTxtEdit::CTxtEdit( ITextHost2 *phost, IUnknown * punk) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CTxtEdit");
_unk.Init(); _punk = (punk) ? punk : &_unk; _ldte.Init(this); _phost = phost; _cpAccelerator = -1; // Default to no accelerator
// Initialize _iCF and _iPF to something bogus
Set_iCF(-1); Set_iPF(-1);
// Initialize local maximum text size to window default
_cchTextMost = cInitTextMax;
// This actually counts the number of active ped
W32->AddRef(); }
/*
* CTxtEdit::~CTxtEdit() * * @mfunc * Destructor */ CTxtEdit::~CTxtEdit () { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::~CTxtEdit");
Assert(!_fMButtonCapture); // Need to properly transition
// Magellan mouse if asserts!
_fSelfDestruct = TRUE; // Tell the Call Mgr not to
// call this any more
// Flush clipboard first
_ldte.FlushClipboard();
if(_pDocInfo) // Do this before closing
{ // down internal structures
CloseFile(TRUE); // Close any open file
delete _pDocInfo; // Delete document info
_pDocInfo = NULL; }
if(_pdetecturl) delete _pdetecturl;
if (_pbrk) delete _pbrk;
if(_pobjmgr) delete _pobjmgr;
// Release our reference to selection object
if(_psel) _psel->Release();
// Delete undo and redo managers
if(_pundo) _pundo->Destroy();
// Release message filter.
// Note that the attached message filter must have released this document
// Otherwise we will never get here.
if (_pMsgFilter) _pMsgFilter->Release();
if(_predo) _predo->Destroy();
ReleaseFormats(Get_iCF(), Get_iPF()); // Release default formats
delete _pdp; // Delete displays
delete _pdpPrinter; _pdp = NULL; // Break any further attempts to
// use display
if (_fHost2) { // We are in a windows host - need to deal with the shutdown
// problem where the window can be destroyed before text
// services is.
if (!_fReleaseHost) { ((ITextHost2*)_phost)->TxFreeTextServicesNotification(); } else { // Had to keep host alive so tell it we are done with it.
_phost->Release(); } }
W32->Release(); }
/*
* CTxtEdit::Init (prcClient) * * @mfunc * Initializes this CTxtEdit. Called by CreateTextServices() * * @rdesc * Return TRUE if successful */
BOOL CTxtEdit::Init ( const RECT *prcClient) //@parm Client RECT
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::Init");
CCharFormat CF; DWORD dwBits = 0; DWORD dwMask; LONG iCF, iPF; CParaFormat PF; CCallMgr callmgr(this);
static BOOL fOnce = FALSE; if (!fOnce) { CLock lock; fOnce = TRUE; _fnpPropChg[ 0] = &CTxtEdit::OnRichEditChange; // TXTBIT_RICHTEXT
_fnpPropChg[ 1] = &CTxtEdit::OnTxMultiLineChange; // TXTBIT_MULTILINE
_fnpPropChg[ 2] = &CTxtEdit::OnTxReadOnlyChange; // TXTBIT_READONLY
_fnpPropChg[ 3] = &CTxtEdit::OnShowAccelerator; // TXTBIT_SHOWACCELERATOR
_fnpPropChg[ 4] = &CTxtEdit::OnUsePassword; // TXTBIT_USEPASSWORD
_fnpPropChg[ 5] = &CTxtEdit::OnTxHideSelectionChange; // TXTBIT_HIDESELECTION
_fnpPropChg[ 6] = &CTxtEdit::OnSaveSelection; // TXTBIT_SAVESELECTION
_fnpPropChg[ 7] = &CTxtEdit::OnAutoWordSel; // TXTBIT_AUTOWORDSEL
_fnpPropChg[ 8] = &CTxtEdit::OnTxVerticalChange; // TXTBIT_VERTICAL
_fnpPropChg[ 9] = &CTxtEdit::NeedViewUpdate; // TXTBIT_SELECTIONBAR
_fnpPropChg[10] = &CTxtEdit::OnWordWrapChange; // TXTBIT_WORDWRAP
_fnpPropChg[11] = &CTxtEdit::OnAllowBeep; // TXTBIT_ALLOWBEEP
_fnpPropChg[12] = &CTxtEdit::OnDisableDrag; // TXTBIT_DISABLEDRAG
_fnpPropChg[13] = &CTxtEdit::NeedViewUpdate; // TXTBIT_VIEWINSETCHANGE
_fnpPropChg[14] = &CTxtEdit::OnTxBackStyleChange; // TXTBIT_BACKSTYLECHANGE
_fnpPropChg[15] = &CTxtEdit::OnMaxLengthChange; // TXTBIT_MAXLENGTHCHANGE
_fnpPropChg[16] = &CTxtEdit::OnScrollChange; // TXTBIT_SCROLLBARCHANGE
_fnpPropChg[17] = &CTxtEdit::OnCharFormatChange; // TXTBIT_CHARFORMATCHANGE
_fnpPropChg[18] = &CTxtEdit::OnParaFormatChange; // TXTBIT_PARAFORMATCHANGE
_fnpPropChg[19] = &CTxtEdit::NeedViewUpdate; // TXTBIT_EXTENTCHANGE
_fnpPropChg[20] = &CTxtEdit::OnClientRectChange; // TXTBIT_CLIENTRECTCHANGE
}
// Set up default CCharFormat and CParaFormat
if (TxGetDefaultCharFormat(&CF, dwMask) != NOERROR || TxGetDefaultParaFormat(&PF) != NOERROR || FAILED(GetCharFormatCache()->Cache(&CF, &iCF)) || FAILED(GetParaFormatCache()->Cache(&PF, &iPF))) { return FALSE; }
GetTabsCache()->Release(PF._iTabs); Set_iCF(iCF); // Save format indices
Set_iPF(iPF);
// Load mouse cursors (but only for first instance)
if(!_hcurArrow) { _hcurArrow = LoadCursor(0, IDC_ARROW); if(!_hcurHand) { if (dwMajorVersion < 5) _hcurHand = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_HAND)); else _hcurHand = LoadCursor(0, IDC_HAND); } if(!_hcurIBeam) // Load cursor
_hcurIBeam = LoadCursor(0, IDC_IBEAM); if(!_hcurItalic) _hcurItalic = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_ITALIC)); if(!_hcurSelBar) _hcurSelBar = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_SELBAR)); }
#ifdef DEBUG
// The host is going to do some checking on richtext vs. plain text.
_fRich = TRUE; #endif // DEBUG
if(_phost->TxGetPropertyBits (TXTBITS | // Get host state flags
TXTBIT_MULTILINE | TXTBIT_SHOWACCELERATOR, // that we cache or need
&dwBits) != NOERROR) // for display setup
{ return FALSE; } // Cache bits defined by
_dwFlags = dwBits & TXTBITS; // TXTBITS mask
if ((dwBits & TXTBIT_SHOWACCELERATOR) && // They want accelerator,
FAILED(UpdateAccelerator())) // so let's get it
{ return FALSE; }
_fTransparent = TxGetBackStyle() == TXTBACK_TRANSPARENT; if(dwBits & TXTBIT_MULTILINE) // Create and initialize
_pdp = new CDisplayML(this); // display
else _pdp = new CDisplaySL(this); Assert(_pdp);
if(!_pdp || !_pdp->Init()) return FALSE;
_fUseUndo = TRUE; _fAutoFont = TRUE; _fDualFont = TRUE; _f10DeferChangeNotify = 0;
// Set whether we are in our host or not
ITextHost2 *phost2; if(_phost->QueryInterface(IID_ITextHost2, (void **)&phost2) == NOERROR) { // We assume that ITextHost2 means this is our host
phost2->Release(); _fHost2 = TRUE; } else // Get maximum from our host
_phost->TxGetMaxLength(&_cchTextMost);
// Add EOP iff Rich Text
if(IsRich()) { // We should _not_ be in 10 compatibility mode yet.
// If we transition into 1.0 mode, we'll add a CRLF
// at the end of the document.
SetRichDocEndEOP(0); }
// Allow for win.ini control over use of line services
if (W32->fUseLs()) { OnSetTypographyOptions(TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY); }
if (W32->GetDigitSubstitutionMode() != DIGITS_NOTIMPL) OrCharFlags(fDIGITSHAPE); // digit substitution presents
// Initialize the BiDi property
// It is set to true if OS is BiDi (the system default LCID is a BiDi language)
// or if the current keyboard code page is a BiDi code page
// or if system.ini says we should do it.
if (W32->OnBiDiOS() || W32->IsBiDiCodePage(GetKeyboardCodePage(0xFFFFFFFF)) || W32->fUseBiDi()) OrCharFlags(fBIDI);
_fAutoKeyboard = IsBiDi() && IsBiDiKbdInstalled(); return TRUE; }
///////////////////////////// CTxtEdit IUnknown ////////////////////////////////
/*
* CTxtEdit::QueryInterface (riid, ppv) * * @mfunc * IUnknown method * * @rdesc * HRESULT = (if success) ? NOERROR : E_NOINTERFACE * * @devnote * This interface is aggregated. See textserv.cpp for discussion. */ HRESULT CTxtEdit::QueryInterface( REFIID riid, void **ppv) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::QueryInterface");
return _punk->QueryInterface(riid, ppv); }
/*
* CTxtEdit::AddRef() * * @mfunc * IUnknown method * * @rdesc * ULONG - incremented reference count */ ULONG CTxtEdit::AddRef(void) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::AddRef");
return _punk->AddRef(); }
/*
* CTxtEdit::Release() * * @mfunc * IUnknown method * * @rdesc * ULONG - decremented reference count */ ULONG CTxtEdit::Release(void) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::Release");
return _punk->Release(); }
////////////////////////// Undo Management //////////////////////////////
/*
* CTxtEdit::CreateUndoMgr (dwLim, flags) * * @mfunc * Creates an undo stack * * @rdesc * Ptr to new IUndoMgr */ IUndoMgr *CTxtEdit::CreateUndoMgr( DWORD dwLim, //@parm Size limit
USFlags flags) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CreateUndoMgr");
IUndoMgr *pmgr = NULL;
if(_fUseUndo) { pmgr = new CUndoStack(this, dwLim, flags); if(!pmgr) return NULL;
if(!pmgr->GetUndoLimit()) { // The undo stack failed to initialize properly (probably
// lack of memory). Trash it and return NULL.
pmgr->Destroy(); return NULL; } // We may be asked to create a new undo/redo manager
// before we are completely done with initialization.
// We need to clean up memory we have already allocated.
if(flags & US_REDO) { if(_predo) _predo->Destroy(); _predo = pmgr; } else { if(_pundo) _pundo->Destroy(); _pundo = pmgr; } } return pmgr; }
/*
* CTxtEdit::HandleUndoLimit (dwLim) * * @mfunc * Handles the EM_SETUNDOLIMIT message * * @rdesc * Actual limit to which things were set. */ LRESULT CTxtEdit::HandleSetUndoLimit( LONG Count) //@parm Requested limit size
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::HandleSetUndoLimit");
if (Count == tomSuspend || // This option really just
Count == tomResume) // suspends undo, i.e.,
{ // doesn't discard existing
_fUseUndo = (Count == tomResume); // antievents
return _pundo ? _pundo->GetUndoLimit() : 0; }
if(Count < 0) Count = DEFAULT_UNDO_SIZE;
if(!Count) { _fUseUndo = FALSE; if(_pundo) { _pundo->Destroy(); _pundo = NULL; } if(_predo) { _predo->Destroy(); _predo = NULL; } } else if(!_pundo) { _fUseUndo = TRUE; // Don't worry about return value; if it's NULL, we're
// in the same boat as if the API wasn't called (so later
// on, we might try to allocate the default).
CreateUndoMgr(Count, US_UNDO); } else { Count = _pundo->SetUndoLimit(Count);
// Setting the undo limit on the undo stack will return to
// us the actual amount set. Try to set the redo stack to
// the same size. If it can't go that big, too bad.
if(_predo) _predo->SetUndoLimit(Count); } return Count; }
/*
* CTxtEdit::HandleSetTextMode(mode) * * @mfunc handles setting the text mode * * @rdesc LRESULT; 0 (NOERROR) on success, OLE failure code on failure. * * @devnote the text mode does not have to be fully specified; it * is sufficient to merely specify the specific desired behavior. * * Note that the edit control must be completely empty for this * routine to work. */ LRESULT CTxtEdit::HandleSetTextMode( DWORD mode) //@parm the desired mode
{ LRESULT lres = 0;
// First off, we must be completely empty
if (GetAdjustedTextLength() || _pundo && _pundo->CanUndo() || _predo && _predo->CanUndo()) { return E_UNEXPECTED; }
// These bits are considered one at a time; thus the absence of
// any bits does _NOT_ imply any change in behavior.
// TM_RICHTEXT && TM_PLAINTEXT are mutually exclusive; they cannot
// be both set. Same goes for TM_SINGLELEVELUNDO / TM_MULTILEVELUNDO
// and TM_SINGLECODEPAGE / TM_MULTICODEPAGE
if((mode & (TM_RICHTEXT | TM_PLAINTEXT)) == (TM_RICHTEXT | TM_PLAINTEXT) || (mode & (TM_SINGLELEVELUNDO | TM_MULTILEVELUNDO)) == (TM_SINGLELEVELUNDO | TM_MULTILEVELUNDO) || (mode & (TM_SINGLECODEPAGE | TM_MULTICODEPAGE)) == (TM_SINGLECODEPAGE | TM_MULTICODEPAGE)) { lres = E_INVALIDARG; } else if((mode & TM_PLAINTEXT) && IsRich()) lres = OnRichEditChange(FALSE);
else if((mode & TM_RICHTEXT) && !IsRich()) lres = OnRichEditChange(TRUE);
if(!lres) { if(mode & TM_SINGLELEVELUNDO) { if(!_pundo) CreateUndoMgr(1, US_UNDO);
if(_pundo) { // We can 'Enable' single level mode as many times
// as we want, so no need to check for it before hand.
lres = ((CUndoStack *)_pundo)->EnableSingleLevelMode(); } else lres = E_OUTOFMEMORY; } else if(mode & TM_MULTILEVELUNDO) { // If there's no undo stack, no need to do anything,
// we're already in multi-level mode
if(_pundo && ((CUndoStack *)_pundo)->GetSingleLevelMode()) ((CUndoStack *)_pundo)->DisableSingleLevelMode(); }
if(mode & TM_SINGLECODEPAGE) _fSingleCodePage = TRUE;
else if(mode & TM_MULTICODEPAGE) _fSingleCodePage = FALSE; }
// We don't want this marked modified after this operation to make us
// work better in dialog boxes.
_fModified = FALSE;
return lres; }
////////////////////////// Uniscribe Interface //////////////////////////////
/*
* GetUniscribe() * * @mfunc * returns a pointer to the Uniscribe interface object * * @rdesc * Ptr to Uniscribe interface */ extern BOOL g_fNoUniscribe; CUniscribe* GetUniscribe() { if (g_pusp) return g_pusp;
if (g_fNoUniscribe) return NULL;
//Attempt to create the Uniscribe object, but make sure the
//OS is valid and that we can load the uniscribe DLL.
int cScripts; //Find out if OS is valid, or if delay-load fails
if (!IsSupportedOS() || FAILED(ScriptGetProperties(NULL, &cScripts))) { g_fNoUniscribe = TRUE; return NULL; }
if (!g_pusp) g_pusp = new CUniscribe();
AssertSz(g_pusp, "GetUniscribe(): Create Uniscribe object failed"); return g_pusp; }
////////////////////////// Notification Manager //////////////////////////////
/*
* CTxtEdit::GetNotifyMgr() * * @mfunc * returns a pointer to the notification manager (creating it if necessary) * * @rdesc * Ptr to notification manager */ CNotifyMgr *CTxtEdit::GetNotifyMgr() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetNotifyMgr");
return &_nm; }
////////////////////////// Object Manager ///////////////////////////////////
/*
* CTxtEdit::GetObjectMgr() * * @mfunc * returns a pointer to the object manager (creating if necessary) * * @rdesc * pointer to the object manager */ CObjectMgr *CTxtEdit::GetObjectMgr() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetObjectMgr");
if(!_pobjmgr) _pobjmgr = new CObjectMgr();
return _pobjmgr; }
////////////////////////////// Properties - Selection ////////////////////////////////
LONG CTxtEdit::GetSelMin() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelMin");
return _psel ? _psel->GetCpMin() : 0; }
LONG CTxtEdit::GetSelMost() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelMost");
return _psel ? _psel->GetCpMost() : 0; }
////////////////////////////// Properties - Text //////////////////////////////////////
LONG CTxtEdit::GetTextRange( LONG cpFirst, LONG cch, TCHAR * pch) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetTextRange");
#ifdef DEBUG
const LONG cchAsk = cch; #endif
CTxtPtr tp(this, cpFirst); LONG cchAdj = GetAdjustedTextLength();
if(--cch < 0 || cpFirst > cchAdj) return 0;
cch = min(cch, cchAdj - cpFirst); if(cch > 0) { cch = tp.GetText(cch, pch); Assert(cch >= 0); } pch[cch] = TEXT('\0');
#ifdef DEBUG
if(cch != cchAsk - 1) Tracef(TRCSEVINFO, "CTxtEdit::GetTextRange: only got %ld out of %ld", cch, cchAsk - 1); #endif
return cch; }
/*
* CTxtEdit::GetTextEx (pgt, pch) * * @mfunc * Grabs text according to various params * * @rdesc * Count of bytes gotten */ LONG CTxtEdit::GetTextEx( GETTEXTEX *pgt, //@parm Info on what to get
TCHAR * pch) //@parm Where to put the text
{ LONG cb; LONG cch; LONG cchGet = GetAdjustedTextLength(); TCHAR * pchUse = pch; CTxtPtr tp(this, 0); CTempWcharBuf twcb;
if(pgt->flags & GT_SELECTION) // Get selected text
{ LONG cpMin, cpMost; cch = GetSel()->GetRange(cpMin, cpMost); cchGet = min(cch, cchGet - cpMin); // Don't include final EOP
tp.SetCp(cpMin); }
if(pgt->codepage == (unsigned)-1) // Use default codepage
pgt->codepage = GetDefaultCodePage(EM_GETTEXTEX);
if(pgt->cb == (unsigned)-1) // Client says its buffer is big enuf
{ pgt->cb = cchGet + 1; if(W32->IsFECodePage(pgt->codepage) || pgt->codepage == 1200) pgt->cb += cchGet; else if(pgt->codepage == CP_UTF8 && (_dwCharFlags & ~fASCII)) pgt->cb *= (_dwCharFlags & fABOVEX7FF) ? 3 : 2; }
// Allocate a big buffer; make sure that we have
// enough room for lots of CRLFs if necessary
if(pgt->flags & GT_USECRLF) cchGet *= 2;
if(pgt->codepage != 1200) { // If UNICODE, copy straight to client's buffer;
// else, copy to temp buffer and translate cases first
pchUse = twcb.GetBuf(cchGet + 1); if (pch) *((char *) pch) = '\0'; // In case something fails
} else // Be sure to leave room for NULL terminator
cchGet = min(UINT(pgt->cb/2 - 1), (UINT)cchGet);
// Now grab the text.
if(pgt->flags & GT_USECRLF) cch = tp.GetPlainText(cchGet, pchUse, tomForward, FALSE); else cch = tp.GetText(cchGet, pchUse);
pchUse[cch] = L'\0';
// If we're just doing UNICODE, return number of chars written
if(pgt->codepage == 1200) return cch;
// Oops, gotta translate to ANSI.
cb = WideCharToMultiByte(pgt->codepage, 0, pchUse, cch + 1, (char *)pch, pgt->cb, pgt->lpDefaultChar, pgt->lpUsedDefChar);
// Don't count NULL terminator for compatibility with WM_GETTEXT.
return (cb) ? cb - 1 : 0; }
/*
* CTxtEdit::GetTextLengthEx (pgtl) * * @mfunc * Calculates text length in various ways. * * @rdesc * Text length calculated in various ways * * @comm * This function returns an API cp that may differ from the * corresponding internal Unicode cp. */ LONG CTxtEdit::GetTextLengthEx( GETTEXTLENGTHEX *pgtl) //@parm Info describing how to calculate length
{ LONG cchUnicode = GetAdjustedTextLength(); LONG cEOP = 0; DWORD dwFlags = pgtl->flags; GETTEXTEX gt;
if(pgtl->codepage == (unsigned)-1) pgtl->codepage = GetDefaultCodePage(EM_GETTEXTLENGTHEX);
// Make sure the flags are defined appropriately
if ((dwFlags & GTL_CLOSE) && (dwFlags & GTL_PRECISE) || (dwFlags & GTL_NUMCHARS) && (dwFlags & GTL_NUMBYTES)) { TRACEWARNSZ("Invalid flags for EM_GETTEXTLENGTHEX"); return E_INVALIDARG; }
// Note in the following if statement, the second part of the
// and clause will always be TRUE. At some point in the future
// fUseCRLF and Get10Mode may become independent, in which case
// the code below will automatically work without change.
// NEW: 1.0 mode gets text as is, so don't add count for CRs.
// (RichEdit 1.0 only inserts Enters as CRLFs; it doesn't "cleanse"
// other text insertion strings)
if((dwFlags & GTL_USECRLF) && !fUseCRLF() && !Get10Mode()) { // Important facts for 1.0 mode (REMARK: this is out of date):
//
// (1) 1.0 mode implies that the text is stored with fUseCRLF true.
// fUseCRLF means that the EOP mark can either be a CR or a
// CRLF - see CTxtRange::CleanseAndReplaceRange for details.
//
// (2) 1.0 mode has an invariant that the count of text returned
// by this call should be enough to hold all the text returned by
// WM_GETTEXT.
//
// (3) The WM_GETEXT call for 1.0 mode will return a buffer in
// which all EOPs that consist of a CR are replaced by CRLF.
//
// Therefore, for 1.0 mode, we must count all EOPs that consist
// of only a CR and add addition return character to count the
// LF that will be added into any WM_GETEXT buffer.
// For 2.0 mode, the code is much easier, just count up all
// CRs and bump count of each one by 1.
CTxtPtr tp(this, 0); LONG Results;
while(tp.FindEOP(tomForward, &Results)) { // If EOP consists of 1 char, add 1 since is returned by a CRLF.
// If it consists of 2 chars, add 0, since it's a CRLF and is
// returned as such.
if(tp.GetCp() > cchUnicode) // Don't add correction for
break; // final CR (if any)
Results &= 3; if(Results) cEOP += 2 - Results;
AssertSz(IN_RANGE(1, Results, 2) || !Results && tp.GetCp() == cchUnicode, "CTxtEdit::GetTextLengthEx: CRCRLF found in backing store"); } cchUnicode += cEOP; }
// If we're just looking for the number of characters or if it's an
// 8-bit codepage in RE 1.0 mode, we've already got the count.
if ((dwFlags & GTL_NUMCHARS) || !dwFlags || Get10Mode() && Is8BitCodePage(pgtl->codepage)) { return cchUnicode; }
// Hmm, they're looking for number of bytes, but don't care about
// precision, just multiply by two. If neither PRECISE or CLOSE is
// specified, default to CLOSE. Note if the codepage is UNICODE and
// asking for number of bytes, we also just multiply by 2.
if((dwFlags & GTL_CLOSE) || !(dwFlags & GTL_PRECISE) || pgtl->codepage == 1200) { return cchUnicode *2; }
// In order to get a precise answer, we need to convert (which is slow!).
gt.cb = 0; gt.flags = (pgtl->flags & GT_USECRLF); gt.codepage = pgtl->codepage; gt.lpDefaultChar = NULL; gt.lpUsedDefChar = NULL;
return GetTextEx(>, NULL); }
/*
* CTxtEdit::GetDefaultCodePage (msg) * * @mfunc * Return codepage to use for converting the text in RichEdit20A text * messages. * * @rdesc * Codepage to use for converting the text in RichEdit20A text messages. */ LONG CTxtEdit::GetDefaultCodePage( UINT msg) { LONG CodePage = GetACP();
// FUTURE: For backward compatibility in Office97, We always use ACP for all these
// languages. Need review in the future when the world all moves to Unicode.
if (W32->IsBiDiCodePage(CodePage) || CodePage == CP_THAI || CodePage == CP_VIETNAMESE || W32->IsFECodePage(CodePage) || _fSingleCodePage || msg == EM_GETCHARFORMAT || msg == EM_SETCHARFORMAT) { return CodePage; }
if(Get10Mode()) return GetCodePage(GetCharFormat(-1)->_bCharSet);
return GetKeyboardCodePage(); }
////////////////////////////// Properties - Formats //////////////////////////////////
/*
* CTxtEdit::HandleStyle (pCFTarget, pCF, dwMask, dwMask2) * * @mfunc * If pCF specifies a style choice, initialize pCFTarget with the * appropriate style, apply pCF, and return NOERROR. Else return * S_FALSE or an error * * @rdesc * HRESULT = (pCF specifies a style choice) ? NOERROR : S_FALSE or error code */ HRESULT CTxtEdit::HandleStyle( CCharFormat *pCFTarget, //@parm Target CF to receive CF style content
const CCharFormat *pCF, //@parm Source CF that may specify a style
DWORD dwMask, //@parm CHARFORMAT2 mask
DWORD dwMask2) //@parm Second mask
{ if(pCF->fSetStyle(dwMask, dwMask2)) { // FUTURE: generalize to use client style if specified
*pCFTarget = *GetCharFormat(-1); pCFTarget->ApplyDefaultStyle(pCF->_sStyle); return pCFTarget->Apply(pCF, dwMask, dwMask2); } return S_FALSE; }
/*
* CTxtEdit::HandleStyle (pPFTarget, pPF) * * @mfunc * If pPF specifies a style choice, initialize pPFTarget with the * appropriate style, apply pPF, and return NOERROR. Else return * S_FALSE or an error * * @rdesc * HRESULT = (pPF specifies a style choice) ? NOERROR : S_FALSE or error code */ HRESULT CTxtEdit::HandleStyle( CParaFormat *pPFTarget, //@parm Target PF to receive PF style content
const CParaFormat *pPF, //@parm Source PF that may specify a style
DWORD dwMask) //@parm Mask to use in setting CParaFormat
{ if(pPF->fSetStyle(dwMask)) { // FUTURE: generalize to use client style if specified
*pPFTarget = *GetParaFormat(-1); pPFTarget->ApplyDefaultStyle(pPF->_sStyle); return pPFTarget->Apply(pPF, dwMask); } return S_FALSE; }
//////////////////////////// Mouse Commands /////////////////////////////////
HRESULT CTxtEdit::OnTxLButtonDblClk( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags) //@parm Mouse message wparam
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonDblClk");
BOOL fEnterParaSelMode = FALSE; HITTEST Hit; CTxtSelection * psel = GetSel(); const POINT pt = {x, y};
AssertSz(psel, "CTxtEdit::OnTxLButtonDblClk() - No selection object !");
if (StopMagellanScroll()) return S_OK;
_dwTickDblClick = GetTickCount(); _ptDblClick.x = x; _ptDblClick.y = y;
TxUpdateWindow(); // Repaint window to show any exposed portions
if(!_fFocus) { TxSetFocus(); // Create and display caret
return S_OK; }
// Find out what the cursor is pointing at
_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);
if(Hit == HT_Nothing) return S_OK;
if(Hit == HT_OutlineSymbol) { CTxtRange rg(*psel); rg.ExpandOutline(0, FALSE); return S_OK; }
if(Hit == HT_LeftOfText) fEnterParaSelMode = TRUE;
_fWantDrag = FALSE; // just to be safe
// If we are over a link, let the client have a chance to process
// the message
if(Hit == HT_Link && HandleLinkNotification(WM_LBUTTONDBLCLK, (WPARAM)dwFlags, MAKELPARAM(x, y))) { return S_OK; }
if(dwFlags & MK_CONTROL) return S_OK;
// Mark mouse down
_fMouseDown = TRUE;
if(_pobjmgr && _pobjmgr->HandleDoubleClick(this, pt, dwFlags)) { // The object subsystem handled everything
_fMouseDown = FALSE; return S_OK; }
// Update the selection
if(fEnterParaSelMode) psel->SelectUnit(pt, tomParagraph); else psel->SelectWord(pt);
return S_OK; }
HRESULT CTxtEdit::OnTxLButtonDown( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags) //@parm Mouse message wparam
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonDown");
BOOL fEnterLineSelMode = FALSE; BOOL fShift = dwFlags & MK_SHIFT; HITTEST Hit; const POINT pt = {x, y}; COleObject *pobj; BOOL fMustThaw = FALSE;
const BOOL fTripleClick = GetTickCount() < _dwTickDblClick + W32->GetDCT() && abs(x - _ptDblClick.x) <= W32->GetCxDoubleClk() && abs(y - _ptDblClick.y) <= W32->GetCyDoubleClk();
if (StopMagellanScroll()) return S_OK;
// If click isn't inside view, just activate, don't select
if(!_fFocus) // Sets focus if not already
{ // We may be removing an existing selection, so freeze
// display to avoid flicker
_pdp->Freeze(); fMustThaw = TRUE; TxSetFocus(); // creates and displays caret
}
// Grab selection object
CTxtSelection * const psel = GetSel(); AssertSz(psel,"CTxtEdit::OnTxLButtonDown - No selection object !");
// Find out what cursor is pointing at
_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);
if(Hit == HT_LeftOfText) { // Shift click in sel bar treated as normal click
if(!fShift) { // Control selbar click and triple selbar click
// are select all
if((dwFlags & MK_CONTROL) || fTripleClick) { psel->SelectAll(); goto cancel_modes; } fEnterLineSelMode = TRUE; if(!GetAdjustedTextLength() && !_pdp->IsMultiLine()) { const CParaFormat *pPF = psel->GetPF(); // Can't see selected para mark when flushed right, so
// leave selection as an insertion point
if(pPF->_bAlignment == PFA_RIGHT && !pPF->IsRtlPara()) fEnterLineSelMode = FALSE; } } } else if(Hit == HT_Nothing) goto cancel_modes;
else if(!fShift) psel->CancelModes();
// Let client have a chance to handle this message if we are over a link
if(Hit == HT_Link && HandleLinkNotification(WM_LBUTTONDOWN, (WPARAM)dwFlags, MAKELPARAM(x, y))) { goto cancel_modes; }
_fMouseDown = TRUE; // Flag mouse down
if(!fShift && _pobjmgr) { // Deactivate anybody active, etc.
ClickStatus status = _pobjmgr->HandleClick(this, pt); if(status == CLICK_OBJSELECTED) { // The object subsystem will handle resizing.
// if not a resize we will signal start of drag
pobj = _pobjmgr->GetSingleSelect();
// Because HandleClick returned true, pobj better be non-null.
Assert(pobj);
if (!pobj->HandleResize(pt)) _fWantDrag = !_fDisableDrag;
goto cancel_modes; } else if(status == CLICK_OBJDEACTIVATED) goto cancel_modes; }
_fCapture = TRUE; // Capture the mouse
TxSetCapture(TRUE);
// Check for start of drag and drop
if(!fTripleClick && !fShift && psel->PointInSel(pt, NULL, Hit) && !_fDisableDrag) { // Assume we want a drag. If we don't CmdLeftUp() needs
// this to be set anyway to change the selection
_fWantDrag = TRUE;
goto cancel_modes; }
if(fShift) // Extend selection from current
{ // active end to click
psel->InitClickForAutWordSel(pt); psel->ExtendSelection(pt); } else if(fEnterLineSelMode) // Line selection mode: select line
psel->SelectUnit(pt, tomLine); else if(fTripleClick || Hit == HT_OutlineSymbol) // paragraph selection mode
psel->SelectUnit(pt, tomParagraph); else { if (Get10Mode()) _f10DeferChangeNotify = 1; psel->SetCaret(pt); _mousePt = pt; }
if(fMustThaw) _pdp->Thaw();
return S_OK;
cancel_modes: psel->CancelModes();
if(_fWantDrag) { TxSetTimer(RETID_DRAGDROP, W32->GetDragDelay()); _mousePt = pt; _bMouseFlags = (BYTE)dwFlags; _fDragged = FALSE; }
if(fMustThaw) _pdp->Thaw();
return S_OK; }
HRESULT CTxtEdit::OnTxLButtonUp( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags, //@parm Mouse message wparam
int ffOptions) //@parm Mouse options, see _edit.h for details
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonUp");
CheckRemoveContinuousScroll();
// Remove capture before test for mouse down since we wait till
// we get the mouse button up message to release capture since Forms
// wants it that way.
if(_fCapture && (ffOptions & LB_RELEASECAPTURE)) { TxSetCapture(FALSE); _fCapture = FALSE; }
// we were delaying selection change. So send it now...
if (DelayChangeNotification() && (ffOptions & LB_FLUSHNOTIFY)) { AssertSz(Get10Mode(), "Flag should only be set in 10 mode"); _f10DeferChangeNotify = 0; GetCallMgr()->SetSelectionChanged(); }
if(!_fMouseDown) { // We noticed the mouse was no longer down earlier so we don't
// need to do anything.
return S_OK; }
const BOOL fSetSel = !!_fWantDrag; const POINT pt = {x, y};
// Cancel Auto Word Sel if on
CTxtSelection * const psel = GetSel(); AssertSz(psel,"CTxtEdit::OnLeftUp() - No selection object !");
psel->CancelModes(TRUE);
// Reset flags
_fMouseDown = FALSE; _fWantDrag = FALSE; _fDragged = FALSE; TxKillTimer(RETID_DRAGDROP); if(IsInOutlineView()) psel->Update(FALSE);
// Let the client handle this message if we are over a
// link area
if(HandleLinkNotification(WM_LBUTTONUP, (WPARAM)dwFlags, MAKELPARAM(x, y))) { return NOERROR; }
// If we were in drag & drop, put caret under mouse
if(fSetSel) { CObjectMgr* pobjmgr = GetObjectMgr();
// If we were on an object, don't deselect it by setting the caret
if(pobjmgr && !pobjmgr->GetSingleSelect()) { psel->SetCaret(pt, TRUE); if(!_fFocus) TxSetFocus(); // create and display caret
} } return S_OK; }
HRESULT CTxtEdit::OnTxRButtonUp( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags, //@parm Mouse message wparam
int ffOptions) //@parm option flag
{ const POINT pt = {x, y}; CTxtSelection * psel; SELCHANGE selchg; HMENU hmenu = NULL; IOleObject * poo = NULL; COleObject * pobj = NULL; IUnknown * pUnk = NULL; IRichEditOleCallback * precall = NULL;
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxRButtonUp");
// make sure we have the focus
if(!_fFocus) TxSetFocus();
if(_fWantDrag) { _fDragged = FALSE; _fWantDrag = FALSE; TxKillTimer(RETID_DRAGDROP); }
// Grab selection object
psel = GetSel(); psel->SetSelectionInfo(&selchg);
//We need a pointer to the first object, if any, in the selection.
if(_pobjmgr) { //If the point is in the selection we need to find out if there
//are any objects in the selection. If the point is not in a
//selection but it is on an object, we need to select the object.
if(psel->PointInSel(pt, NULL) || (ffOptions & RB_FORCEINSEL)) { pobj = _pobjmgr->GetFirstObjectInRange(selchg.chrg.cpMin, selchg.chrg.cpMost); } else { //Select the object
if(_pobjmgr->HandleClick(this, pt) == CLICK_OBJSELECTED) { pobj = _pobjmgr->GetSingleSelect(); // Because HandleClick returned true, pobj better be non-null.
Assert(pobj!=NULL); //Refresh our information about the selection
psel = GetSel(); psel->SetSelectionInfo(&selchg); } } precall = _pobjmgr->GetRECallback(); }
if(pobj) pUnk = pobj->GetIUnknown();
if(pUnk) pUnk->QueryInterface(IID_IOleObject, (void **)&poo);
if(precall) precall->GetContextMenu(selchg.seltyp, poo, &selchg.chrg, &hmenu);
if(hmenu) { HWND hwnd, hwndParent; POINT ptscr;
if(TxGetWindow(&hwnd) == NOERROR) { if(!(ffOptions & RB_NOSELCHECK) && !psel->PointInSel(pt, NULL) && !psel->GetCch() && !(ffOptions & RB_FORCEINSEL)) psel->SetCaret(pt); ptscr.x = pt.x; ptscr.y = pt.y; ClientToScreen(hwnd, &ptscr);
hwndParent = GetParent(hwnd); if(!hwndParent) hwndParent = hwnd;
TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, ptscr.x, ptscr.y, 0, hwndParent, NULL); } DestroyMenu(hmenu); }
if(poo) poo->Release();
return precall ? S_OK : S_FALSE; }
HRESULT CTxtEdit::OnTxRButtonDown( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags) //@parm Mouse message wparam
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxRButtonDown");
if (StopMagellanScroll()) return S_OK;
CTxtSelection *psel = GetSel(); const POINT pt = {x, y};
psel->CancelModes();
if(psel->PointInSel(pt, NULL) && !_fDisableDrag) { _fWantDrag = TRUE;
TxSetTimer(RETID_DRAGDROP, W32->GetDragDelay()); _mousePt = pt; _bMouseFlags = (BYTE)dwFlags; _fDragged = FALSE; return S_OK; } return S_FALSE; }
HRESULT CTxtEdit::OnTxMouseMove( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags, //@parm Mouse message wparam
IUndoBuilder *publdr) { int dx, dy; BOOL fLButtonDown = FALSE; BOOL fRButtonDown = FALSE; DWORD vkLButton, vkRButton;
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMouseMove");
CTxtSelection * const psel = GetSel();
if(!_fFocus) return S_OK;
if(_fWantDrag || _fCapture) { LONG nDragMinDist = W32->GetDragMinDist() + 3; dx = _mousePt.x > x ? _mousePt.x - x : x - _mousePt.x; dy = _mousePt.y > y ? _mousePt.y - y : y - _mousePt.y; if(dx < nDragMinDist && dy < nDragMinDist) { _bMouseFlags = (BYTE)dwFlags; return S_OK; } _fDragged = _fWantDrag; }
_mousePt.x = x; // Remember for scrolling
_mousePt.y = y; // speed, and dir calc.
// RichEdit 1.0 allows the client to process mouse moves itself if
// we are over a link (but _not_ doing drag drop).
if(HandleLinkNotification(WM_MOUSEMOVE, 0, MAKELPARAM(x, y))) return NOERROR;
// If we think the mouse is down and it really is then do special
// processing.
if(GetSystemMetrics(SM_SWAPBUTTON)) { vkLButton = VK_RBUTTON; vkRButton = VK_LBUTTON; } else { vkLButton = VK_LBUTTON; vkRButton = VK_RBUTTON; }
fLButtonDown = (GetAsyncKeyState(vkLButton) < 0); if(!fLButtonDown) fRButtonDown = (GetAsyncKeyState(vkRButton) < 0);
if(fLButtonDown || fRButtonDown) { if(_fWantDrag && !_fUsePassword && !IsProtected(_fReadOnly ? WM_COPY : WM_CUT, dwFlags, MAKELONG(x,y))) { TxKillTimer(RETID_DRAGDROP); _ldte.StartDrag(psel, publdr); // the mouse button may still be down, but drag drop is over
// so we need to _think_ of it as up.
_fMouseDown = FALSE;
// similarly, OLE should have nuked the capture for us, but
// just in case something failed, release the capture.
TxSetCapture(FALSE); _fCapture = FALSE; } else if(_fMouseDown) { POINT pt = _mousePt;
// We think mouse is down and it is
if(_ldte.fInDrag()) { // Only do drag scrolling if a drag operation is in progress.
_pdp->DragScroll(&pt); }
AssertSz(psel,"CTxtEdit::OnMouseMove: No selection object !"); psel->ExtendSelection(pt); // Extend the selection
CheckInstallContinuousScroll (); } } else if (!(GetAsyncKeyState(VK_MBUTTON) < 0) && !mouse.IsAutoScrolling()) { // Make sure we aren't autoscrolling via intellimouse
if(_fMButtonCapture) OnTxMButtonUp (x, y, dwFlags);
if(_fMouseDown) { // Although we thought the mouse was down, at this moment it
// clearly is not. Therefore, we pretend we got a mouse up
// message and clear our state to get ourselves back in sync
// with what is really happening.
OnTxLButtonUp(x, y, dwFlags, LB_RELEASECAPTURE); }
}
// Either a drag was started or the mouse button was not down. In either
// case, we want no longer to start a drag so we set the flag to false.
_fWantDrag = FALSE; return S_OK; }
/*
* OnTxMButtonDown (x, y, dwFlags) * * @mfunc * The user pressed the middle mouse button, setup to do * continuous scrolls, which may in turn initiate a timer * for smooth scrolling. * * @rdesc * HRESULT = S_OK */ HRESULT CTxtEdit::OnTxMButtonDown ( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags) //@parm Mouse message wparam
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMButtonDown");
#if !defined(NOMAGELLAN)
POINT mDownPt = {x,y};
if(!_fFocus) TxSetFocus();
if(!StopMagellanScroll() && mouse.MagellanStartMButtonScroll(*this, mDownPt)) { TxSetCapture(TRUE);
_fCapture = TRUE; // Capture the mouse
_fMouseDown = TRUE; _fMButtonCapture = TRUE; } #endif
return S_OK; }
/*
* CTxtEdit::OnTxMButtonUp (x, y, dwFlags) * * @mfunc * Remove timers and capture assoicated with a MButtonDown * message. * * @rdesc * HRESULT = S_OK */ HRESULT CTxtEdit::OnTxMButtonUp ( INT x, //@parm Mouse x coordinate
INT y, //@parm Mouse y coordinate
DWORD dwFlags) //@parm Mouse message wparam
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMButtonUp");
#if !defined(NOMAGELLAN)
if (mouse.ContinueMButtonScroll(x, y)) return S_OK;
StopMagellanScroll();
#else
if(_fCapture) TxSetCapture(FALSE);
_fCapture = FALSE; _fMouseDown = FALSE; _fMButtonCapture = FALSE;
#endif
return S_OK; }
/*
* CTxtEdit::StopMagellanScroll() * * @mfunc * Stops the intellimouse autoscrolling and returns * us back into a normal state * * BOOL = TRUE if auto scrolling was turned off : FALSE * Autoscrolling was never turned on */ BOOL CTxtEdit::StopMagellanScroll () { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::StopMagellanScroll");
#if !defined(NOMAGELLAN)
if (!mouse.IsAutoScrolling()) return FALSE;
mouse.MagellanEndMButtonScroll(*this);
if(_fCapture) TxSetCapture(FALSE);
_fCapture = FALSE; _fMouseDown = FALSE; _fMButtonCapture = FALSE; return TRUE; #else
return FALSE; #endif
}
/*
* CTxtEdit::CheckInstallContinuousScroll () * * @mfunc * There are no events that inform the app on a regular * basis that a mouse button is down. This timer notifies * the app that the button is still down, so that scrolling can * continue. */ void CTxtEdit::CheckInstallContinuousScroll () { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CheckInstallContinuousScroll");
if(!_fContinuousScroll && TxSetTimer(RETID_AUTOSCROLL, cmsecScrollInterval)) _fContinuousScroll = TRUE; }
/*
* CTxtEdit::CheckRemoveContinuousScroll () * * @mfunc * The middle mouse button, or drag button, is up * remove the continuous scroll timer. */ void CTxtEdit::CheckRemoveContinuousScroll () { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CheckRemoveContinuousScroll");
if(_fContinuousScroll) { TxKillTimer(RETID_AUTOSCROLL); _fContinuousScroll = FALSE; } }
/*
* OnTxTimer(idTimer) * * @mfunc * Handle timers for doing background recalc and scrolling. * * @rdesc * HRESULT = (idTimer valid) ? S_OK : S_FALSE */ HRESULT CTxtEdit::OnTxTimer( UINT idTimer) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxTimer");
switch (idTimer) { case RETID_BGND_RECALC: _pdp->StepBackgroundRecalc(); break;
#if !defined(NOMAGELLAN)
case RETID_MAGELLANTRACK: mouse.TrackUpdateMagellanMButtonDown(*this, _mousePt); break; #endif
case RETID_AUTOSCROLL: // Continuous scrolling.
OnTxMouseMove(_mousePt.x, _mousePt.y, // Do a select drag scroll.
0, NULL); break;
#if !defined(NOMAGELLAN)
case RETID_SMOOTHSCROLL: // Smooth scrolling
if(_fMButtonCapture) // HACK, only 1 timer!
{ // delivered on Win95
// when things get busy.
mouse.TrackUpdateMagellanMButtonDown(*this, _mousePt); } if(_pdp->IsSmoothVScolling()) // Test only because of
_pdp->SmoothVScrollUpdate(); // above HACK!!
break; #endif
case RETID_DRAGDROP: TxKillTimer(RETID_DRAGDROP); if (_fWantDrag && _fDragged && !_fUsePassword && !IsProtected(_fReadOnly ? WM_COPY : WM_CUT, _bMouseFlags, MAKELONG(_mousePt.x,_mousePt.y))) { IUndoBuilder * publdr; CGenUndoBuilder undobldr(this, UB_AUTOCOMMIT, &publdr); _ldte.StartDrag(GetSel(), publdr); _fWantDrag = FALSE; _fDragged = FALSE; TxSetCapture(FALSE); _fCapture = FALSE; } break;
default: return S_FALSE; } return S_OK; }
/////////////////////////// Keyboard Commands ////////////////////////////////
/*
* CTxtEdit::OnTxKeyDown(vkey, dwFlags, publdr) * * @mfunc * Handle WM_KEYDOWN message * * @rdesc * HRESULT with the following values: * * S_OK if key was understood and consumed * S_MSG_KEY_IGNORED if key was understood, but not consumed * S_FALSE if key was not understood or just looked at * and in any event not consumed */ HRESULT CTxtEdit::OnTxKeyDown( WORD vkey, //@parm Virtual key code
DWORD dwFlags, //@parm lparam of WM_KEYDOWN msg
IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxKeyDown");
if(IN_RANGE(VK_SHIFT, vkey, VK_MENU)) { SetKeyboardFlag(GetKbdFlags(vkey, dwFlags)); return S_FALSE; }
BOOL fAlt = GetKeyboardFlag(ALT, VK_MENU); BOOL fCtrl = GetKeyboardFlag(CTRL, VK_CONTROL); BOOL fShift = GetKeyboardFlag(SHIFT, VK_SHIFT);
BOOL fRet = FALSE; // Converted to HRESULT on return
LONG nDeadKey = 0;
if(fCtrl & fShift) // Signal NonCtrl/Shift keydown
SetKeyboardFlag(LETAFTERSHIFT); // while Ctrl&Shift are down
// Handle Hebrew caps and LRM/RLM
if (IsBiDi()) { if (W32->IsBiDiCodePage(GetKeyboardCodePage(0xFFFFFFFF))) { _fHbrCaps = FALSE; if(IsRich() && W32->UsingHebrewKeyboard()) { WORD wCapital = GetKeyState(VK_CAPITAL); _fHbrCaps = ((wCapital & 1) ^ fShift) && !(wCapital & 0x0080) && IN_RANGE('A', vkey, 'Z'); if(_fHbrCaps) W32->ActivateKeyboard(ANSI_INDEX); } }
if(vkey == VK_BACK && fShift && W32->OnWin9x()) { // Shift+Backspace generates a LRM | RLM on a BiDi keyboard.
// Consequently, we must eat the Backspace lest it delete text.
W32->_fLRMorRLM = 1; return S_OK; } }
// If dragging or Alt key down, just look for ESCAPE. Note: if Alt key is
// down, we should never come here (would generate WM_SYSKEYDOWN message).
if(_fMouseDown) { if(vkey == VK_ESCAPE) { // Turn-off autoscroll.
if (StopMagellanScroll()) return S_OK;
POINT pt; // Cancel drag select or drag & drop
GetCursorPos(&pt); OnTxLButtonUp(pt.x, pt.y, 0, LB_RELEASECAPTURE | LB_FLUSHNOTIFY); return S_OK; } return OnTxSpecialKeyDown(vkey, dwFlags, publdr); }
CTxtSelection * const psel = GetSel(); AssertSz(psel,"CTxtEdit::OnKeyDown() - No selection object !");
if(fCtrl) { if(OnTxSpecialKeyDown(vkey, dwFlags, publdr) == S_OK) return S_OK;
if(fAlt) // This following code doesn't handle
return S_FALSE; // use Ctrl+Alt, which happens for
// AltGr codes (no WM_SYSKEYDOWN)
// Shift must not be pressed for these.
if(!fShift) { switch(vkey) { case 'E': case 'J': case 'R': case 'L': { CParaFormat PF; if (vkey == 'E') PF._bAlignment = PFA_CENTER; else if (vkey == 'J') PF._bAlignment = PFA_FULL_INTERWORD; else if (vkey == 'R') PF._bAlignment = PFA_RIGHT; else PF._bAlignment = PFA_LEFT;
psel->SetParaFormat(&PF, publdr, PFM_ALIGNMENT); break; } case '1': case '2': case '5': { CParaFormat PF; PF._bLineSpacingRule = tomLineSpaceMultiple; PF._dyLineSpacing = (vkey - '0') * 20; if (vkey == '5') PF._dyLineSpacing = 30;
psel->SetParaFormat(&PF, publdr, PFM_LINESPACING); break; } default: break; } }
switch(vkey) { case VK_TAB: return OnTxChar(VK_TAB, dwFlags, publdr);
case VK_CLEAR: case VK_NUMPAD5: case 'A': // Ctrl-A => pselect all
psel->SelectAll(); break;
//Toggle Subscript
case 187: // =
{ ITextFont *pfont; psel->GetFont(&pfont); if (pfont) { pfont->SetSubscript(tomToggle); pfont->Release(); } } break;
case 'C': // Ctrl-C => copy
CtrlC: CutOrCopySelection(WM_COPY, 0, 0, NULL); break;
case 'V': // Ctrl-V => paste
CtrlV: if(IsntProtectedOrReadOnly(WM_PASTE, 0, 0)) { PasteDataObjectToRange(NULL, (CTxtRange *)psel, 0, NULL, publdr, PDOR_NONE); } break;
case 'X': // Ctrl-X => cut
CtrlX: CutOrCopySelection(WM_CUT, 0, 0, publdr); break;
case 'Z': // Ctrl-Z => undo
if (_pundo && !_fReadOnly && _fUseUndo) PopAndExecuteAntiEvent(_pundo, 0); break;
case 'Y': // Ctrl-Y => redo
if(_predo && !_fReadOnly && _fUseUndo) PopAndExecuteAntiEvent(_predo, 0); break;
#ifdef DEBUG
void RicheditDebugCentral(void); case 191: RicheditDebugCentral(); break; #endif
#if defined(DOGFOOD)
case '1': // Shift+Ctrl+1 => start Aimm
// Activate AIMM by posting a message to RE (Shift+Ctrl+; for now)
if (fShift && _fInOurHost) { HWND hWnd;
TxGetWindow( &hWnd );
if (hWnd) PostMessage(hWnd, EM_SETEDITSTYLE, SES_USEAIMM, SES_USEAIMM); } break; #endif
case VK_CONTROL: goto cont;
// English keyboard defines
#define VK_APOSTROPHE 0xDE
#define VK_GRAVE 0xC0
#define VK_SEMICOLON 0xBA
#define VK_COMMA 0xBC
case VK_APOSTROPHE: if(fShift) g_wFlags ^= KF_SMARTQUOTES; else nDeadKey = ACCENT_ACUTE; break;
case VK_GRAVE: nDeadKey = fShift ? ACCENT_TILDE : ACCENT_GRAVE; break;
case VK_SEMICOLON: nDeadKey = ACCENT_UMLAUT; break;
case '6': if(!fShift) goto cont; nDeadKey = ACCENT_CARET; break;
case VK_COMMA: nDeadKey = ACCENT_CEDILLA; break;
default: goto cont; } if(nDeadKey) { // Since deadkey choices vary a bit according to keyboard, we
// only enable them for English. French, German, Italian, and
// Spanish keyboards already have a fair amount of accent
// capability.
if(PRIMARYLANGID(GetKeyboardLayout(0)) == LANG_ENGLISH) SetDeadKey((WORD)nDeadKey); else goto cont; } return S_OK; }
cont: psel->SetExtend(fShift);
switch(vkey) { case VK_BACK: case VK_F16: if(_fReadOnly) { Beep(); fRet = TRUE; } else if(IsntProtectedOrReadOnly(WM_KEYDOWN, VK_BACK, dwFlags)) { fRet = psel->Backspace(fCtrl, publdr); } break;
case VK_INSERT: // Ins
if(fShift) // Shift-Ins
goto CtrlV; // Alias for Ctrl-V
if(fCtrl) // Ctrl-Ins
goto CtrlC; // Alias for Ctrl-C
if(!_fReadOnly) // Ins
_fOverstrike = !_fOverstrike; // Toggle Ins/Ovr
fRet = TRUE; break;
case VK_LEFT: // Left arrow
case VK_RIGHT: // Right arrow
fRet = (vkey == VK_LEFT) ^ (psel->GetPF()->IsRtlPara() != 0) ? psel->Left (fCtrl) : psel->Right(fCtrl); break;
case VK_UP: // Up arrow
fRet = psel->Up(fCtrl); break;
case VK_DOWN: // Down arrow
fRet = psel->Down(fCtrl); break;
case VK_HOME: // Home
fRet = psel->Home(fCtrl); break;
case VK_END: // End
fRet = psel->End(fCtrl); break;
case VK_PRIOR: // PgUp
// If SystemEditMode and control is single-line, do nothing
if(!_fSystemEditMode || _pdp->IsMultiLine()) fRet = psel->PageUp(fCtrl); break;
case VK_NEXT: // PgDn
// If SystemEditMode and control is single-line, do nothing
if(!_fSystemEditMode || _pdp->IsMultiLine()) fRet = psel->PageDown(fCtrl); break;
case VK_DELETE: // Del
if(fShift) // Shift-Del
goto CtrlX; // Alias for Ctrl-X
if(IsntProtectedOrReadOnly(WM_KEYDOWN, VK_DELETE, dwFlags)) psel->Delete(fCtrl, publdr); fRet = TRUE; break;
case CONTROL('J'): // Ctrl-Return gives Ctrl-J
case VK_RETURN: // (LF), treat it as return
// If we are in 1.0 mode we need to handle <CR>'s on WM_CHAR
if (!Get10Mode()) { if(!_pdp->IsMultiLine()) { Beep(); return S_FALSE; } TxSetCursor(0, NULL);
if(IsntProtectedOrReadOnly(WM_CHAR, VK_RETURN, dwFlags)) psel->InsertEOP(publdr, (fShift && IsRich() ? VT : 0));
fRet = TRUE; } break;
default: return S_FALSE; }
return fRet ? S_OK : S_MSG_KEY_IGNORED; }
/*
* CTxtEdit::CutOrCopySelection(msg, wparam, lparam, publdr) * * @mfunc * Handle WM_COPY message and its keyboard hotkey aliases * * @rdesc * HRESULT */ HRESULT CTxtEdit::CutOrCopySelection( UINT msg, //@parm Message (WM_CUT or WM_COPY)
WPARAM wparam, //@parm Message wparam for protection check
LPARAM lparam, //@parm Message lparam for protection check
IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ Assert(msg == WM_CUT || msg == WM_COPY);
if(!_fUsePassword && IsntProtectedOrReadOnly(msg, wparam, lparam)) { CTxtSelection *psel = GetSel(); psel->CheckTableSelection(); return msg == WM_COPY ? _ldte.CopyRangeToClipboard((CTxtRange *)psel) : _ldte.CutRangeToClipboard((CTxtRange *)psel, publdr); } return NOERROR; }
#define ENGLISH_UK MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK)
#define ENGLISH_EIRE MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE)
/*
* CTxtEdit::OnTxSpecialKeyDown(vkey, dwFlags, publdr) * * @mfunc * Handle WM_KEYDOWN message for outline mode * * @rdesc * HRESULT with the following values: * * S_OK if key was understood and consumed * S_MSG_KEY_IGNORED if key was understood, but not consumed * S_FALSE if key was not understood (and not consumed) */ HRESULT CTxtEdit::OnTxSpecialKeyDown( WORD vkey, //@parm Virtual key code
DWORD dwFlags, //@parm lparam of WM_KEYDOWN msg
IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxSpecialKeyDown");
HRESULT hr = S_FALSE; // Key not understood yet
DWORD dwKbdFlags = GetKeyboardFlags(); BOOL fUpdateFormat = TRUE;
if(!(dwKbdFlags & (CTRL | ALT))) // All hot keys here have at
return S_FALSE; // least Ctrl or Alt
CTxtSelection * const psel = GetSel(); if(dwKbdFlags & ALT && dwKbdFlags & CTRL) { // AltGr generates LCTRL | RALT, so don't match hot keys with
// that combination
if(dwKbdFlags & LCTRL && dwKbdFlags & RALT) return S_FALSE;
//#if 0
// First they say they want it, then they don't. Leave it ifdef'd out
// for a bit in case they want it again
if(vkey == 'E') { LANGID lid = LANGIDFROMLCID(GetKeyboardLayout(0)); static const LANGID rgLangID[] = { ENGLISH_UK, ENGLISH_EIRE, LANG_POLISH, LANG_PORTUGUESE, LANG_HUNGARIAN, LANG_VIETNAMESE }; for(LONG i = ARRAY_SIZE(rgLangID); i--; ) { // Don't insert Euro if lid matches any LIDs or PLIDs in rgLangID
if(lid == rgLangID[i] || PRIMARYLANGID(lid) == rgLangID[i]) return S_FALSE; } if(psel->PutChar(EURO, _fOverstrike, publdr)) { SetKeyboardFlag(HOTEURO); // Setup flag to eat the next WM_CHAR w/ EURO
hr = S_OK; } } else //#endif
if(dwKbdFlags & SHIFT) switch(vkey) { #ifdef ENABLE_OUTLINEVIEW
// FUTURE: OutlineView hot keys postponed (see below)
case 'N': // Alt-Ctrl-N => Normal View
hr = SetViewKind(VM_NORMAL); break; case 'O': // Alt-Ctrl-O => Outline View
hr = SetViewKind(VM_OUTLINE); break; #endif
case VK_F12: // Alt-Ctrl-F12 (in case Alt-X taken)
hr = psel->HexToUnicode(publdr); break;
#if defined(DEBUG)
case VK_F11: // Alt-Ctrl-F11
if (W32->fDebugFont()) psel->DebugFont(); break; #endif
} return hr; }
AssertSz(psel, "CTxtEdit::OnTxSpecialKeyDown() - No selection object !"); CTxtRange rg(*psel);
if(!IsRich() || !_pdp->IsMultiLine() || !(dwKbdFlags & SHIFT)) return S_FALSE;
if(dwKbdFlags & ALT) // Alt+Shift hot keys
{ // NB: Alt and Shift-Alt with _graphics_ characters generate a
// WM_SYSCHAR, which see
#ifdef ENABLE_OUTLINEVIEW
// FUTURE: These are Outline related hot keys. We will postpone these features
// since we have several bugs related to these hot keys
// Bug 5687, 5689, & 5691
switch(vkey) { case VK_LEFT: // Left arrow
case VK_RIGHT: // Right arrow
hr = rg.Promote(vkey == VK_LEFT ? 1 : -1, publdr); psel->Update_iFormat(-1); psel->Update(FALSE); break;
case VK_UP: // Up arrow
case VK_DOWN: // Down arrow
hr = MoveSelection(vkey == VK_UP ? -1 : 1, publdr); psel->Update(TRUE); break; } #endif
return hr; }
Assert(dwKbdFlags & CTRL && dwKbdFlags & SHIFT);
// Ctrl+Shift hot keys
switch(vkey) {
#ifdef ENABLE_OUTLINEVIEW
// FUTUTRE: These are Outline related hot keys. We will postpone these features
// since we have several bugs related to these hot keys
// Bug 5687, 5689, & 5691
case 'N': // Demote to Body
hr = rg.Promote(0, publdr); break; #endif
//Toggle superscript
case 187: // =
{ ITextFont *pfont; psel->GetFont(&pfont); if (pfont) { pfont->SetSuperscript(tomToggle); pfont->Release(); hr = S_OK; fUpdateFormat = FALSE; } break; }
case 'A': { ITextFont *pfont; psel->GetFont(&pfont); if (pfont) { pfont->SetAllCaps(tomToggle); pfont->Release(); hr = S_OK; fUpdateFormat = FALSE; } break; }
case 'L': // Fiddle bullet style
{ CParaFormat PF; DWORD dwMask = PFM_NUMBERING | PFM_OFFSET;
PF._wNumbering = psel->GetPF()->_wNumbering + 1; PF._wNumbering %= tomListNumberAsUCRoman + 1; PF._dxOffset = 0; if(PF._wNumbering) { dwMask |= PFM_NUMBERINGSTYLE | PFM_NUMBERINGSTART; PF._wNumberingStyle = PFNS_PERIOD; PF._wNumberingStart = 1; PF._dxOffset = 360; } hr = psel->SetParaFormat(&PF, publdr, dwMask); break; } #define VK_RANGLE 190
#define VK_LANGLE 188
case VK_RANGLE: // '>' on US keyboards
case VK_LANGLE: // '<' on US keyboards
hr = OnSetFontSize(vkey == VK_RANGLE ? 1 : -1, publdr) ? S_OK : S_FALSE; fUpdateFormat = (hr == S_FALSE); break; }
if(hr != S_FALSE) { if (fUpdateFormat) psel->Update_iFormat(-1); psel->Update(FALSE); } return hr; }
/*
* CTxtEdit::OnTxChar (vkey, dwFlags, publdr) * * @mfunc * Handle WM_CHAR message * * @rdesc * HRESULT with the following values: * * S_OK if key was understood and consumed * S_MSG_KEY_IGNORED if key was understood, but not consumed * S_FALSE if key was not understood (and not consumed) */ HRESULT CTxtEdit::OnTxChar( WORD vkey, //@parm Translated key code
DWORD dwFlags, //@parm lparam of WM_KEYDOWN msg
IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxChar");
// Reset Alt key state if needed
if (!(HIWORD(dwFlags) & KF_ALTDOWN)) ResetKeyboardFlag(ALT);
DWORD dwFlagsPutChar = _fOverstrike | KBD_CHAR; if(GetKeyboardFlags() & ALTNUMPAD) { DWORD Number = GetKeyPadNumber(); if(Number >= 256 || vkey >= 256) vkey = Number; ResetKeyboardFlag(ALTNUMPAD | ALT0); dwFlagsPutChar &= ~KBD_CHAR; // Need font binding
}
if (_fMouseDown || vkey == VK_ESCAPE || // Ctrl-Backspace generates VK_F16
vkey == VK_BACK || vkey==VK_F16) // Eat it since we process it
{ // in WM_KEYDOWN
return S_OK; }
CTxtSelection * const psel = GetSel(); AssertSz(psel, "CTxtEdit::OnChar() - No selection object !"); psel->SetExtend(FALSE); // Shift doesn't mean extend for
// WM_CHAR
if(_fReadOnly && vkey != 3) // Don't allow input if read only,
{ // but allow copy (Ctrl-C)
if(vkey >= ' ') Beep(); return S_MSG_KEY_IGNORED; }
if(vkey >= ' ' || vkey == VK_TAB) { TxSetCursor(0, NULL); if(IsntProtectedOrReadOnly(WM_CHAR, vkey, dwFlags)) { LONG nDeadKey = GetDeadKey(); if(nDeadKey) { LONG ch = vkey | 0x20; // Convert to lower case
BOOL fShift = vkey != ch; // (if ASCII letter)
// a b c d e f g h i j
const static WORD chOff[] = {0xDF, 0, 0xE7, 0, 0xE7, 0, 0, 0, 0xEB, 0, // k l m n o p q r s t u
0, 0, 0, 0xF1, 0xF1, 0, 0, 0, 0, 0, 0xF8}; SetDeadKey(0); if(!IN_RANGE('a', ch, 'u')) // Not relevant ASCII
return S_OK; // letter
vkey = chOff[ch - 'a']; // Translate to base char
if(!vkey) // No accents available
return S_OK; // in current approach
if(ch == 'n') { if(nDeadKey != ACCENT_TILDE) return S_OK; } else if(nDeadKey == ACCENT_CEDILLA) { if(ch != 'c') return S_OK; } else // aeiou
{ vkey += (WORD)nDeadKey; if (nDeadKey >= ACCENT_TILDE && // eiu with ~ or :
(vkey == 0xF0 || vkey & 8)) { if(nDeadKey != ACCENT_UMLAUT)// Only have umlauts
return S_OK; vkey--; } } if(fShift) vkey &= ~0x20; }
// need to check if character is LRM | RLM character, if so
// then convert vkey
if (W32->_fLRMorRLM && IsBiDi() && IN_RANGE(0xFD, vkey, 0xFE)) { vkey = LTRMARK + (vkey - 0xFD); }
psel->PutChar((TCHAR)vkey, dwFlagsPutChar, publdr); } } else if (Get10Mode() && (vkey == VK_RETURN || vkey == CONTROL('J'))) { // 1.0 handled <CR> on WM_CHAR
// Just make sure we are entering text into a multiline control
DWORD dwStyle; GetHost()->TxGetPropertyBits(TXTBIT_MULTILINE, &dwStyle); if(dwStyle & TXTBIT_MULTILINE) { TxSetCursor(0, NULL); if(IsntProtectedOrReadOnly(WM_CHAR, VK_RETURN, dwFlags)) psel->InsertEOP(publdr); } }
if(_fHbrCaps) { W32->ActivateKeyboard(HEBREW_INDEX); _fHbrCaps = FALSE; } return S_OK; }
/*
* CTxtEdit::OnTxSysChar (vkey, dwFlags, publdr) * * @mfunc * Handle WM_SYSCHAR message * * @rdesc * HRESULT with the following values: * * S_OK if key was understood and consumed * S_MSG_KEY_IGNORED if key was understood, but not consumed * S_FALSE if key was not understood (and not consumed) */ HRESULT CTxtEdit::OnTxSysChar( WORD vkey, //@parm Translated key code
DWORD dwFlags, //@parm lparam of WM_KEYDOWN msg
IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ if(!(HIWORD(dwFlags) & KF_ALTDOWN)) return S_FALSE;
BOOL fWholeDoc = TRUE; HRESULT hr = S_FALSE; int level = 0; CTxtSelection * const psel = GetSel();
switch(vkey) { case VK_BACK: return S_OK;
case 'x': hr = psel->HexToUnicode(publdr); break;
case 'X': hr = psel->UnicodeToHex(publdr); break;
case '+': case '-': level = vkey == VK_ADD ? 1 : -1; fWholeDoc = FALSE; /* Fall through */ case 'A': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { CTxtRange rg(*psel); if(!level) level = vkey == 'A' ? 9 : vkey - '0'; return rg.ExpandOutline(level, fWholeDoc); } } return hr; }
HRESULT CTxtEdit::OnTxSysKeyDown( WORD vkey, //@parm Virtual key code
DWORD dwFlags, //@parm lparam of WM_KEYDOWN msg
IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxSysKeyDown");
if(IN_RANGE(VK_SHIFT, vkey, VK_MENU)) { SetKeyboardFlag(GetKbdFlags(vkey, dwFlags)); SetKeyPadNumber(0); // Init keypad number to 0
return S_FALSE; }
if (StopMagellanScroll()) return S_FALSE;
HRESULT hr = OnTxSpecialKeyDown(vkey, dwFlags, publdr); if(hr != S_FALSE) return hr;
if(vkey == VK_BACK && (HIWORD(dwFlags) & KF_ALTDOWN)) { if(_pundo && _pundo->CanUndo() && _fUseUndo) { if(PopAndExecuteAntiEvent(_pundo, 0) != NOERROR) hr = S_MSG_KEY_IGNORED; } else Beep(); } else if(vkey == VK_F10 && // F10
!(HIWORD(dwFlags) & KF_REPEAT) && // Key previously up
(GetKeyboardFlags() & SHIFT)) // Shift is down
{ HandleKbdContextMenu(); }
return hr; }
/////////////////////////////// Other system events //////////////////////////////
HRESULT CTxtEdit::OnContextMenu(LPARAM lparam) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnContextMenu");
POINT pt;
pt.x = LOWORD(lparam); pt.y = HIWORD(lparam);
if(TxScreenToClient(&pt)) return OnTxRButtonUp(pt.x, pt.y, 0, RB_NOSELCHECK);
return S_FALSE; }
/*
* CTxtEdit::HandleKbdContextMenu () * * @mfunc decides where to put the context menu on the basis of where the * the selection is. Useful for shift-F10 and VK_APPS, where * we aren't given a location. */ void CTxtEdit::HandleKbdContextMenu() { POINT pt; RECT rc; const CTxtSelection * const psel = GetSel(); int RbOption = RB_DEFAULT;
// Figure out where selection ends and put context menu near it
if(_pdp->PointFromTp(*psel, NULL, FALSE, pt, NULL, TA_TOP) < 0) return;
// Due to various factors, the result of PointFromTp doesn't land
// in the selection in PointInSel. Therefore, we send in an override
// here if the selection is non-degenerate and to force the result
// and thus have the correct context menu appear.
LONG cpMin; LONG cpMost; psel->GetRange(cpMin, cpMost);
if (cpMin != cpMost) { RbOption = RB_FORCEINSEL; }
// Make sure point is still within bounds of edit control
_pdp->GetViewRect(rc);
if (pt.x < rc.left) pt.x = rc.left; if (pt.x > rc.right - 2) pt.x = rc.right - 2; if (pt.y < rc.top) pt.y = rc.top; if (pt.y > rc.bottom - 2) pt.y = rc.bottom - 2;
OnTxRButtonUp(pt.x, pt.y, 0, RbOption); }
/////////////////////////////// Format Range Commands //////////////////////////////
/*
* CTxtEdit::OnFormatRange (pfr, prtcon, hdcMeasure, * xMeasurePerInch, yMeasurePerInch) * @mfunc * Format the range given by pfr * * @comm * This function inputs API cp's that may differ from the * corresponding internal Unicode cp's. */ LRESULT CTxtEdit::OnFormatRange( FORMATRANGE * pfr, SPrintControl prtcon, BOOL fSetupDC) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnFormatRange");
LONG cpMin = 0; LONG cpMost = 0;
if(pfr) { cpMin = GetCpFromAcp(pfr->chrg.cpMin); cpMost = GetCpFromAcp(pfr->chrg.cpMost); } // Even if there is 0 text, we want to print the control so that it will
// fill the control with background color.
// Use Adjusted Text Length. Embedded objects using RichEdit will get the empty
// document they expect and will create a default size document.
if(!pfr || cpMin >= GetAdjustedTextLength() && !prtcon._fPrintFromDraw) { // We're done formatting, get rid of our printer's display context.
delete _pdpPrinter; _pdpPrinter = NULL;
return GetAcpFromCp(GetAdjustedTextLength()); }
LONG cpReturn = -1; BOOL fSetDCWorked = FALSE;
// Fix MFC Print preview in mirrored control
//
// MFC CPreviewView sends us a mirrored rendering DC. We need to disable
// this mirroring effect so our internal state remains consistent with user
// action. We also need to disable mirrored window mode in CPreviewView
// window. [wchao - 4/9/1999]
//
HDC hdcLocal = pfr->hdc; DWORD dwLayout = GetLayout(hdcLocal);
if (dwLayout & LAYOUT_RTL) { HWND hwndView = WindowFromDC(hdcLocal);
if (hwndView) { DWORD dwExStyleView = GetWindowLong(hwndView, GWL_EXSTYLE);
if (dwExStyleView & WS_EX_LAYOUTRTL) SetWindowLong(hwndView, GWL_EXSTYLE, dwExStyleView & ~WS_EX_LAYOUTRTL); }
SetLayout(hdcLocal, 0); }
// First time in with this printer, set up a new display context.
// IMPORTANT: proper completion of the printing process is required
// to dispose of this context and begin a new context.
// This is implicitly done by printing the last character, or
// sending an EM_FORMATRANGE message with pfr equal to NULL.
if(!_pdpPrinter) { _pdpPrinter = new CDisplayPrinter (this, hdcLocal, pfr->rc.right - pfr->rc.left, // x width max
pfr->rc.bottom - pfr->rc.top, // y height max
prtcon);
_pdpPrinter->Init();
_pdpPrinter->SetWordWrap(TRUE); // Future: (ricksa) This is a really yucky way to pass the draw info
// to the printer but it was quick. We want to make this better.
_pdpPrinter->ResetDrawInfo(_pdp);
// Set temporary zoom factor (if there is one).
_pdpPrinter->SetTempZoomDenominator(_pdp->GetTempZoomDenominator()); } else _pdpPrinter->SetPrintDimensions(&pfr->rc);
LONG dxpInch = 0, dypInch = 0; // We set the DC everytime because it could have changed.
if(GetDeviceCaps(hdcLocal, TECHNOLOGY) != DT_METAFILE) { // This is not a metafile so do the normal thing
fSetDCWorked = _pdpPrinter->SetDC(hdcLocal); } else { //Forms^3 draws using screen resolution, while OLE specifies HIMETRIC
dxpInch = fInOurHost() ? 2540 : W32->GetXPerInchScreenDC(); dypInch = fInOurHost() ? 2540 : W32->GetYPerInchScreenDC();
if (!fSetupDC) { RECT rc; rc.left = MulDiv(pfr->rcPage.left, dxpInch, LX_PER_INCH); rc.right = MulDiv(pfr->rcPage.right, dxpInch, LX_PER_INCH); rc.top = MulDiv(pfr->rcPage.top, dypInch, LY_PER_INCH); rc.bottom = MulDiv(pfr->rcPage.bottom, dypInch, LY_PER_INCH);
SetWindowOrgEx(hdcLocal, rc.left, rc.top, NULL); SetWindowExtEx(hdcLocal, rc.right, rc.bottom, NULL); }
_pdpPrinter->SetMetafileDC(hdcLocal, dxpInch, dypInch); fSetDCWorked = TRUE; }
if(fSetDCWorked) { //It is illogical to have the target device be the screen and the presentation
//device be a HIMETRIC metafile.
LONG dxpInchT = -1, dypInchT = -1; if (dxpInch && GetDeviceCaps(pfr->hdcTarget, TECHNOLOGY) == DT_RASDISPLAY) { dxpInchT = dxpInch; dypInchT = dypInch; }
// We set this every time because it could have changed.
if(_pdpPrinter->SetTargetDC(pfr->hdcTarget, dxpInchT, dypInchT)) {
// Format another, single page worth of text.
cpReturn = _pdpPrinter->FormatRange(cpMin, cpMost, prtcon._fDoPrint); if(!prtcon._fPrintFromDraw) { // After formatting, we know where the bottom is. But we only
// want to set this if we are writing a page rather than
// displaying a control on the printer.
pfr->rc.bottom = INT (pfr->rc.top + _pdpPrinter->DYtoLY(_pdpPrinter->GetHeight())); }
// Remember this in case the host wishes to do its own banding.
_pdpPrinter->SetPrintView(pfr->rc); // we need to save this for OnDisplayBand.
_pdpPrinter->SetPrintPage(pfr->rcPage);
// If we're asked to render, then render the entire page in one go.
if(prtcon._fDoPrint && (cpReturn > 0 || prtcon._fPrintFromDraw)) { OnDisplayBand(&pfr->rc, prtcon._fPrintFromDraw);
// Note: we can no longer call OnDisplayBand without reformating.
_pdpPrinter->Clear(AF_DELETEMEM); } } }
return cpReturn > 0 ? GetAcpFromCp(cpReturn) : cpReturn; }
BOOL CTxtEdit::OnDisplayBand( const RECT *prc, BOOL fPrintFromDraw) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDisplayBand");
HDC hdcPrinter; RECT rc, rcPrint;
// Make sure OnFormatRange was called and that it actually rendered something.
if(!_pdpPrinter || !_pdpPrinter->Count()) return FALSE;
// Review (murrays): shouldn't the following use LRtoDR()? I.e.,
// _pdpPrinter->LRtoDR(rc, *prc);
// Proportionally map to printers extents.
rc.left = (INT) _pdpPrinter->LXtoDX(prc->left); rc.right = (INT) _pdpPrinter->LXtoDX(prc->right); rc.top = (INT) _pdpPrinter->LYtoDY(prc->top); rc.bottom = (INT) _pdpPrinter->LYtoDY(prc->bottom);
rcPrint = _pdpPrinter->GetPrintView(); rcPrint.left = (INT) _pdpPrinter->LXtoDX(rcPrint.left); rcPrint.right = (INT) _pdpPrinter->LXtoDX(rcPrint.right); rcPrint.top = (INT) _pdpPrinter->LYtoDY(rcPrint.top); rcPrint.bottom = (INT) _pdpPrinter->LYtoDY(rcPrint.bottom);
// Get printer DC because we use it below.
hdcPrinter = _pdpPrinter->GetDC();
if(fPrintFromDraw) { // We need to take view inset into account
_pdpPrinter->GetViewRect(rcPrint, &rcPrint); }
// Render this band (if there's something to render)
if(rc.top < rc.bottom) _pdpPrinter->Render(rcPrint, rc);
return TRUE; }
//////////////////////////////// Protected ranges //////////////////////////////////
/*
* CTxtEdit::IsProtected (msg, wparam, lparam) * * @mfunc * Find out if selection is protected * * @rdesc * TRUE iff 1) control is read-only or 2) selection is protected and * parent query says to protect */ BOOL CTxtEdit::IsProtected( UINT msg, //@parm Message id
WPARAM wparam, //@parm WPARAM from window's message
LPARAM lparam) //@parm LPARAM from window's message
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtected");
LONG iDirection = 0; CTxtSelection *psel = GetSel();
if(!psel) return FALSE;
// There are a few special cases to consider, namely backspacing
// into a protected range, deleting into a protected range, and type
// with overstrike into a protected range.
if(msg == WM_KEYDOWN && (wparam == VK_BACK || wparam == VK_F16)) { // Check for format behind selection, if we are trying to
// backspace an insertion point.
iDirection = -1; } else if(msg == WM_KEYDOWN && wparam == VK_DELETE || _fOverstrike && msg == WM_CHAR) { iDirection = 1; }
// HACK ALERT: we don't do fIsDBCS protection checking for EM_REPLACESEL,
// EM_SETCHARFORMAT, or EM_SETPARAFORMAT. Outlook uses these APIs
// extensively and DBCS protection checking messes them up. N.B. the
// following if statement assumes that IsProtected returns a tri-value.
int iProt = psel->IsProtected(iDirection); if (iProt == CTxtRange::PROTECTED_YES && msg != EM_REPLACESEL && msg != EM_SETCHARFORMAT && msg != EM_SETPARAFORMAT || iProt == CTxtRange::PROTECTED_ASK && _dwEventMask & ENM_PROTECTED && QueryUseProtection(psel, msg, wparam, lparam)) { return TRUE; } return FALSE; }
/*
* CTxtEdit::IsntProtectedOrReadOnly (msg, wparam, lparam) * * @mfunc * Find out if selection isn't protected or read only. If it is, * ring bell. For msg = WM_COPY, only protection is checked. * * @rdesc * TRUE iff 1) control isn't read-only and 2) selection either isn't * protected or parent query says not to protect * * @devnote This function is useful for UI operations (like typing). */ BOOL CTxtEdit::IsntProtectedOrReadOnly( UINT msg, //@parm Message
WPARAM wparam, //@parm Corresponding wparam
LPARAM lparam) //@parm Corresponding lparam
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtectedOrReadOnly");
if (!IsProtected(msg, wparam, lparam) && (msg == WM_COPY || !_fReadOnly)) // WM_COPY only cares about
{ // protection
return TRUE; } Beep(); return FALSE; }
/*
* CTxtEdit::IsProtectedRange (msg, wparam, lparam, prg) * * @mfunc * Find out if range prg is protected * * @rdesc * TRUE iff control is read-only or range is protected and parent * query says to protect */ BOOL CTxtEdit::IsProtectedRange( UINT msg, //@parm Message id
WPARAM wparam, //@parm WPARAM from window's message
LPARAM lparam, //@parm LPARAM from window's message
CTxtRange * prg) //@parm Range to examine
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtectedRange");
int iProt = prg->IsProtected(0);
if (iProt == CTxtRange::PROTECTED_YES || (iProt == CTxtRange::PROTECTED_ASK && (_dwEventMask & ENM_PROTECTED) && QueryUseProtection(prg, msg, wparam, lparam))) // N.B. the preceding if statement assumes that IsProtected returns a tri-value
{ return TRUE; } return FALSE; }
/*
* RegisterTypeLibrary * * @mfunc * Auxiliary function to ensure the type library is registered if Idispatch is used. */ void RegisterTypeLibrary( void ) { HRESULT hRes = NOERROR; WCHAR szModulePath[MAX_PATH]; ITypeLib *pTypeLib = NULL;
// Obtain the path to this module's executable file
W32->GetModuleFileName( hinstRE, szModulePath, MAX_PATH );
// Load and register the type library resource
if (LoadRegTypeLib(LIBID_tom, 1, 0, LANG_NEUTRAL, &pTypeLib) != NOERROR) { hRes = W32->LoadTypeLibEx(szModulePath, REGKIND_REGISTER, &pTypeLib); }
if(SUCCEEDED(hRes) && pTypeLib) { pTypeLib->Release(); } }
/////////////////////////////// Private IUnknown //////////////////////////////
HRESULT __stdcall CTxtEdit::CUnknown::QueryInterface( REFIID riid, void **ppvObj) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::QueryInterface");
CTxtEdit *ped = (CTxtEdit *)GETPPARENT(this, CTxtEdit, _unk); *ppvObj = NULL;
if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITextServices)) *ppvObj = (ITextServices *)ped;
else if(IsEqualIID(riid, IID_IDispatch)) { *ppvObj = (IDispatch *)ped; RegisterTypeLibrary(); }
else if(IsEqualIID(riid, IID_ITextDocument)) { *ppvObj = (ITextDocument *)ped; RegisterTypeLibrary(); }
else if(IsEqualIID(riid, IID_ITextDocument2)) *ppvObj = (ITextDocument2 *)ped;
else if(IsEqualIID(riid, IID_IRichEditOle)) *ppvObj = (IRichEditOle *)ped;
else if(IsEqualIID(riid, IID_IRichEditOleCallback)) { // NB!! Returning this pointer in our QI is
// phenomenally bogus; it breaks fundamental COM
// identity rules (granted, not many understand them!).
// Anyway, RichEdit 1.0 did this, so we better.
TRACEWARNSZ("Returning IRichEditOleCallback interface, COM " "identity rules broken!");
*ppvObj = ped->GetRECallback(); }
if(*ppvObj) { ((IUnknown *) *ppvObj)->AddRef(); return S_OK; } return E_NOINTERFACE; }
ULONG __stdcall CTxtEdit::CUnknown::AddRef() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::AddRef");
return ++_cRefs; }
ULONG __stdcall CTxtEdit::CUnknown::Release() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::Release");
// the call manager will take care of deleting our instance if appropriate.
CTxtEdit *ped = GETPPARENT(this, CTxtEdit, _unk); CCallMgr callmgr(ped);
ULONG culRefs = --_cRefs;
if(culRefs == 0) { // Even though we don't delete ourselves now, dump the callback
// if we have it. This make implementation a bit easier on clients.
if(ped->_pobjmgr) ped->_pobjmgr->SetRECallback(NULL);
// Make sure our timers are gone
ped->TxKillTimer(RETID_AUTOSCROLL); ped->TxKillTimer(RETID_DRAGDROP); ped->TxKillTimer(RETID_BGND_RECALC); ped->TxKillTimer(RETID_SMOOTHSCROLL); ped->TxKillTimer(RETID_MAGELLANTRACK); } return culRefs; }
/*
* ValidateTextRange(pstrg) * * @func * Makes sure that an input text range structure makes sense. * * @rdesc * Size of the buffer required to accept copy of data or -1 if all the * data in the control is requested. * * @comm * This is used both in this file and in the RichEditANSIWndProc */ LONG ValidateTextRange( TEXTRANGE *pstrg) //@parm pointer to a text range structure
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "ValidateTextRange");
// Validate that the input structure makes sense. In the first
// place it must be big enough. Secondly, the values must sense.
// Remember that if the cpMost field is -1 and the cpMin field
// is 0 this means that the call wants the entire buffer.
if (IsBadReadPtr(pstrg, sizeof(TEXTRANGE)) || ((pstrg->chrg.cpMost < 1 || pstrg->chrg.cpMin < 0 || pstrg->chrg.cpMost <= pstrg->chrg.cpMin) && !(pstrg->chrg.cpMost == -1 && !pstrg->chrg.cpMin))) { // This isn't valid so tell the caller we didn't copy any data
return 0; } // Calculate size of buffer that we need on return
return pstrg->chrg.cpMost - pstrg->chrg.cpMin; }
//////////////////////////////////// Selection /////////////////////////////////////
CTxtSelection * CTxtEdit::GetSel() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSel");
if(!_psel) { // There is no selection object available so create it.
_psel = new CTxtSelection(_pdp); if(_psel) _psel->AddRef(); // Set reference count = 1
}
// It is caller's responsiblity to notice that an error occurred
// in allocation of selection object.
return _psel; }
void CTxtEdit::DiscardSelection() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::DiscardSelection");
if(_psel) { _psel->Release(); if(_psel) { // The text services reference is not the last reference to the
// selection. We could keep track of the fact that text services
// has released its reference and when text services gets a
// reference again, do the AddRef there so that if the last
// reference went away while we were still inactive, the selection
// object would go away. However, it is seriously doubtful that
// such a case will be very common. Therefore, just do the simplest
// thing and put our reference back.
_psel->AddRef(); } } }
void CTxtEdit::GetSelRangeForRender( LONG *pcpSelMin, LONG *pcpSelMost) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelRangeForRender");
// If we have no selection or we are not active and the selection
// has been requested to be hidden, there is no selection so we
// just return 0's.
if(!_psel || (!_fInPlaceActive && _fHideSelection)) { *pcpSelMin = 0; *pcpSelMost = 0; return; }
// Otherwise return the state of the current selection.
*pcpSelMin = _psel->GetScrSelMin(); *pcpSelMost = _psel->GetScrSelMost(); }
LRESULT CTxtEdit::OnGetSelText( TCHAR *psz) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetSelText");
LONG cpMin = GetSelMin(); // length + 1 for the null
LONG cpMost = GetSelMost(); return GetTextRange(cpMin, cpMost - cpMin + 1, psz); }
/*
* CTxtEdit::OnExGetSel (pcrSel) * * @mfunc * Get the current selection acpMin, acpMost packaged in a CHARRANGE. * * @comm * This function outputs API cp's that may differ from the * corresponding internal Unicode cp's. */ void CTxtEdit::OnExGetSel( CHARRANGE *pcrSel) //@parm Output parm to receive acpMin, acpMost
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnExGetSel");
pcrSel->cpMin = GetAcpFromCp(GetSelMin()); pcrSel->cpMost = GetAcpFromCp(GetSelMost()); }
/*
* CTxtEdit::OnGetSel (pacpMin, pacpMost) * * @mfunc * Get the current selection acpMin, acpMost. * * @rdesc * LRESULT = acpMost > 65535L ? -1 : MAKELRESULT(acpMin, acpMost) * * @comm * This function outputs API cp's that may differ from the * corresponding internal Unicode cp's. */ LRESULT CTxtEdit::OnGetSel( LONG *pacpMin, //@parm Output parm to receive acpMin
LONG *pacpMost) //@parm Output parm to receive acpMost
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetSel");
CHARRANGE crSel;
OnExGetSel(&crSel); if(pacpMin) *pacpMin = crSel.cpMin; if(pacpMost) *pacpMost = crSel.cpMost;
return (crSel.cpMost > 65535l) ? (LRESULT) -1 : MAKELRESULT((WORD) crSel.cpMin, (WORD) crSel.cpMost); }
/*
* CTxtEdit::OnSetSel (acpMin, acpMost) * * @mfunc * Implements the EM_SETSEL message * * Algorithm: * There are three basic cases to handle * * cpMin < 0, cpMost ??? -- Collapse selection to insertion point * at text end if cpMost < 0 and else at * selection active end * cpMin >= 0, cpMost < 0 -- select from cpMin to text end with * active end at text end * * cpMin >= 0, cpMost >= 0 -- Treat as cpMin, cpMost with active * end at cpMost * * @comm * This function inputs API cp's that may differ from the * corresponding internal Unicode cp's. */ LRESULT CTxtEdit::OnSetSel( LONG acpMin, //@parm Input acpMin
LONG acpMost) //@parm Input acpMost
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetSel");
// Since this is only called from the window proc, we are always active
Assert(GetSel());
CTxtSelection * const psel = GetSel(); LONG cpMin, cpMost;
if(acpMin < 0) cpMin = cpMost = (acpMost < 0) ? tomForward : psel->GetCp(); else { cpMin = GetCpFromAcp(acpMin); cpMost = (acpMost < 0) ? tomForward : GetCpFromAcp(acpMost); } if(Get10Mode() && cpMost < cpMin) // In 10 mode, ensure
{ // cpMost >= cpMin. In
cpMin ^= cpMost; // SetSelection, we set active
cpMost ^= cpMin; // end to cpMost, which can be
cpMin ^= cpMost; // smaller than cpMin, in spite
} // of its name.
psel->SetSelection(cpMin, cpMost); return psel->GetCpMost(); }
/////////////////////////////// DROP FILES support //////////////////////////////////////
#ifndef NODROPFILES
LRESULT CTxtEdit::InsertFromFile ( LPCTSTR lpFile) { REOBJECT reobj; LPRICHEDITOLECALLBACK const precall = GetRECallback(); HRESULT hr = NOERROR;
if(!precall) return E_NOINTERFACE;
ZeroMemory(&reobj, sizeof(REOBJECT)); reobj.cbStruct = sizeof(REOBJECT);
// Get storage for the object from client
hr = precall->GetNewStorage(&reobj.pstg); if(hr) { TRACEERRORSZ("GetNewStorage() failed."); goto err; }
// Create an object site for new object
hr = GetClientSite(&reobj.polesite); if(!reobj.polesite) { TRACEERRORSZ("GetClientSite() failed."); goto err; }
hr = OleCreateLinkToFile(lpFile, IID_IOleObject, OLERENDER_DRAW, NULL, NULL, reobj.pstg, (LPVOID*)&reobj.poleobj); if(hr) { TRACEERRORSZ("Failure creating link object."); goto err; }
reobj.cp = REO_CP_SELECTION; reobj.dvaspect = DVASPECT_CONTENT;
//Get object clsid
hr = reobj.poleobj->GetUserClassID(&reobj.clsid); if(hr) { TRACEERRORSZ("GetUserClassID() failed."); goto err; }
// Let client know what we're up to
hr = precall->QueryInsertObject(&reobj.clsid, reobj.pstg, REO_CP_SELECTION); if(hr != NOERROR) { TRACEERRORSZ("QueryInsertObject() failed."); goto err; }
hr = reobj.poleobj->SetClientSite(reobj.polesite); if(hr) { TRACEERRORSZ("SetClientSite() failed."); goto err; }
if(hr = InsertObject(&reobj)) { TRACEERRORSZ("InsertObject() failed."); }
err: if(reobj.poleobj) reobj.poleobj->Release();
if(reobj.polesite) reobj.polesite->Release();
if(reobj.pstg) reobj.pstg->Release();
return hr; }
typedef void (WINAPI*DRAGFINISH)(HDROP); typedef UINT (WINAPI*DRAGQUERYFILEA)(HDROP, UINT, LPSTR, UINT); typedef UINT (WINAPI*DRAGQUERYFILEW)(HDROP, UINT, LPTSTR, UINT); typedef BOOL (WINAPI*DRAGQUERYPOINT)(HDROP, LPPOINT);
LRESULT CTxtEdit::OnDropFiles( HANDLE hDropFiles) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDropFiles");
UINT cFiles; UINT iFile; char szFile[MAX_PATH]; WCHAR wFile[MAX_PATH]; POINT ptDrop; CTxtSelection * const psel = GetSel(); HMODULE hDLL = NULL; DRAGFINISH fnDragFinish; DRAGQUERYFILEA fnDragQueryFileA; DRAGQUERYFILEW fnDragQueryFileW; DRAGQUERYPOINT fnDragQueryPoint;
if (_fReadOnly) return 0;
AssertSz((hDropFiles != NULL), "CTxtEdit::OnDropFiles invalid hDropFiles");
// dynamic load Shell32
hDLL = LoadLibrary (TEXT("Shell32.DLL")); if(hDLL) { fnDragFinish = (DRAGFINISH)GetProcAddress (hDLL, "DragFinish"); fnDragQueryFileA = (DRAGQUERYFILEA)GetProcAddress (hDLL, "DragQueryFileA"); fnDragQueryFileW = (DRAGQUERYFILEW)GetProcAddress (hDLL, "DragQueryFileW"); fnDragQueryPoint = (DRAGQUERYPOINT)GetProcAddress (hDLL, "DragQueryPoint"); } else return 0;
if(!fnDragFinish || !fnDragQueryFileA || !fnDragQueryFileW || !fnDragQueryPoint) { AssertSz(FALSE, "Shell32 GetProcAddress failed"); goto EXIT0; }
(*fnDragQueryPoint) ((HDROP)hDropFiles, &ptDrop); if(W32->OnWin9x()) cFiles = (*fnDragQueryFileA) ((HDROP)hDropFiles, (UINT)-1, NULL, 0); else cFiles = (*fnDragQueryFileW) ((HDROP)hDropFiles, (UINT)-1, NULL, 0);
if(cFiles) { LONG cp = 0; POINT ptl = ptDrop; CRchTxtPtr rtp(this); const CCharFormat *pCF;
if(_pdp->CpFromPoint(ptl, NULL, &rtp, NULL, FALSE) >= 0) { cp = rtp.GetCp(); pCF = rtp.GetCF(); } else { LONG iCF = psel->Get_iCF(); cp = psel->GetCp(); pCF = GetCharFormat(iCF); ReleaseFormats(iCF, -1); }
// Notify user for dropfile
if(_dwEventMask & ENM_DROPFILES) { ENDROPFILES endropfiles;
endropfiles.hDrop = hDropFiles; endropfiles.cp = Get10Mode() ? GetAcpFromCp(cp) : cp; endropfiles.fProtected = !!(pCF->_dwEffects & CFE_PROTECTED);
if(TxNotify(EN_DROPFILES, &endropfiles)) goto EXIT; // Ignore drop file
cp = Get10Mode() ? GetCpFromAcp(endropfiles.cp) : endropfiles.cp; // Allow callback to update cp
} psel->SetCp(cp); }
for (iFile = 0; iFile < cFiles; iFile++) { if(W32->OnWin9x()) { (*fnDragQueryFileA) ((HDROP)hDropFiles, iFile, szFile, MAX_PATH); MultiByteToWideChar(CP_ACP, 0, szFile, -1, wFile, MAX_PATH); } else (*fnDragQueryFileW) ((HDROP)hDropFiles, iFile, wFile, MAX_PATH);
InsertFromFile (wFile); }
EXIT: (*fnDragFinish) ((HDROP)hDropFiles);
EXIT0: FreeLibrary (hDLL); return 0; }
#else // NODROPFILES
LRESULT CTxtEdit::OnDropFiles(HANDLE hDropFiles) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDropFiles");
return 0; }
#endif // NODROPFILES
/////////////////////////////// Exposable methods //////////////////////////////////////
/*
* CTxtEdit::TxCharFromPos (ppt, plres) * * @mfunc * Get the acp at the point *ppt. * * @rdesc * HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK : * (CpFromPoint succeeded) ? S_OK : E_FAIL * @comm * This function outputs an API cp that may differ from the * corresponding internal Unicode cp. */ HRESULT CTxtEdit::TxCharFromPos( LPPOINT ppt, //@parm Point to find the acp for
LRESULT *plres) //@parm Output parm to receive the acp
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxCharFromPos");
if(!fInplaceActive()) { // We have no valid display rectangle if this object is not active
*plres = -1; return OLE_E_INVALIDRECT; } *plres = _pdp->CpFromPoint(*ppt, NULL, NULL, NULL, FALSE); if(*plres == -1) return E_FAIL;
*plres = GetAcpFromCp(*plres); return S_OK; }
/*
* CTxtEdit::TxPosFromChar (acp, ppt) * * @mfunc * Get the point at acp. * * @rdesc * HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK : * (PointFromTp succeeded) ? S_OK : E_FAIL * @comm * This function inputs an API cp that may differ from the * corresponding internal Unicode cp. */ HRESULT CTxtEdit::TxPosFromChar( LONG acp, //@parm Input cp to get the point for
POINT * ppt) //@parm Output parm to receive the point
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxPosFromChar");
if(!fInplaceActive()) return OLE_E_INVALIDRECT;
CRchTxtPtr rtp(this, GetCpFromAcp(acp));
if(_pdp->PointFromTp(rtp, NULL, FALSE, *ppt, NULL, TA_TOP) < 0) return E_FAIL;
return S_OK; }
/*
* CTxtEdit::TxFindWordBreak (nFunction, acp, plres) * * @mfunc * Find word break or classify character at acp. * * @rdesc * HRESULT = plRet ? S_OK : E_INVALIDARG * * @comm * This function inputs and exports API cp's and cch's that may differ * from the internal Unicode cp's and cch's. */ HRESULT CTxtEdit::TxFindWordBreak( INT nFunction, //@parm Word break function
LONG acp, //@parm Input cp
LRESULT *plres) //@parm cch moved to reach break
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxFindWordBreak");
CTxtPtr tp(this, GetCpFromAcp(acp)); // This validates cp
LONG cpSave = tp.GetCp(); // Save starting value
if(!plres) return E_INVALIDARG;
*plres = tp.FindWordBreak(nFunction);
// WB_CLASSIFY and WB_ISDELIMITER return values; others return offsets
// this function returns values, so it converts when necessary
if(nFunction != WB_CLASSIFY && nFunction != WB_ISDELIMITER) *plres = GetAcpFromCp(LONG(*plres + cpSave));
return S_OK; }
/*
* INT CTxtEdit::TxWordBreakProc (pch, ich, cb, action) * * @func * Default word break proc used in conjunction with FindWordBreak. ich * is character offset (start position) in the buffer pch, which is cb * bytes in length. Possible action values are: * * WB_CLASSIFY * Returns char class and word break flags of char at start position. * * WB_ISDELIMITER * Returns TRUE iff char at start position is a delimeter. * * WB_LEFT * Finds nearest word beginning before start position using word breaks. * * WB_LEFTBREAK * Finds nearest word end before start position using word breaks. * Used by CMeasurer::Measure() * * WB_MOVEWORDLEFT * Finds nearest word beginning before start position using class * differences. This value is used during CTRL+LEFT key processing. * * WB_MOVEWORDRIGHT * Finds nearest word beginning after start position using class * differences. This value is used during CTRL+RIGHT key processing. * * WB_RIGHT * Finds nearest word beginning after start position using word breaks. * Used by CMeasurer::Measure() * * WB_RIGHTBREAK * Finds nearest word end after start position using word breaks. * * @rdesc * Character offset from start of buffer (pch) of the word break */ INT CTxtEdit::TxWordBreakProc( TCHAR * pch, //@parm Char buffer
INT ich, //@parm Char offset of _cp in buffer
INT cb, //@parm Count of bytes in buffer
INT action, //@parm Type of breaking action
LONG cpStart, //@parm cp for first character in pch
LONG cp) //@parm cp associated to ich
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxWordBreakProc");
if (_pfnWB) { // Client overrode the wordbreak proc, delegate the call to it.
if (!Get10Mode()) { Assert(!_fExWordBreakProc); return _pfnWB(pch, ich, cb, action); } else { int ret = 0; char sz[256]; char* pach = sz; if (cb >= 255) pach = new char [cb + 1];
// this indicates if we have to adjust the pach because the api's for
// EDITWORDBREAKPROCEX and EDITWORDBREAKPROC are different when looking to the left
BOOL fAdjustPtr = _fExWordBreakProc && (action == WB_LEFT || action == WB_MOVEWORDLEFT || action == WB_LEFTBREAK);
// RichEdit 1.0, create a buffer, translate ich and WCTMB
// pch into the buffer. Need codepage to use. Then get translate
// return value. Translations are like GetCachFromCch() and
// GetCchFromCach()
if (_fExWordBreakProc) { Assert(ich == 0 || ich == 1 || ich == CchOfCb(cb));
// We need to adjust the cp to the starting point of the buffer
if (!fAdjustPtr) { cpStart += ich; pch += ich; cb -= (2 * ich); }
// initialize string w/ zero's so we can determine the length of the string for later
memset(pach, 0, cb + 1); }
int nLen = CchOfCb(cb); CRchTxtPtr rtp(this, cpStart); BYTE bCharSet = rtp.GetCF()->_bCharSet; if (WideCharToMultiByte(GetCodePage(bCharSet), 0, pch, nLen, pach, cb + 1, NULL, NULL)) { // Documentation stipulates we need to point to the end of the string
if (fAdjustPtr) pach += strlen(pach);
if (_fExWordBreakProc) ret = ((EDITWORDBREAKPROCEX)_pfnWB)(pach, nLen, bCharSet, action); else { ret = ((EDITWORDBREAKPROCA)_pfnWB)(pach, rtp.GetCachFromCch(ich), nLen, action);
// need to reset cp position because GetCachFromCch potentially moves the cp
if (ich) rtp.SetCp(cpStart); }
// For WB_ISDELIMITER and WB_CLASSIFY don't need to convert back
// to ich because return value represents a BOOL
if (action != WB_ISDELIMITER && action != WB_CLASSIFY) ret = rtp.GetCchFromCach(ret); }
// Delete any allocated memory
if (pach != sz) delete [] pach; return ret; } }
LONG cchBuff = CchOfCb(cb); LONG cch = cchBuff - ich; TCHAR ch; WORD cType3[MAX_CLASSIFY_CHARS]; INT kinsokuClassifications[MAX_CLASSIFY_CHARS]; WORD * pcType3; INT * pKinsoku1, *pKinsoku2; WORD * pwRes; WORD startType3 = 0; WORD wb = 0; WORD wClassifyData[MAX_CLASSIFY_CHARS]; // For batch classifying
Assert(cchBuff < MAX_CLASSIFY_CHARS); Assert(ich >= 0 && ich < cchBuff);
// Single character actions
if ( action == WB_CLASSIFY ) { // 1.0 COMPATABILITY - 1.0 returned 0 for apostrohpe's
TCHAR ch = pch[ich]; if (Get10Mode() && ( ch == 0x0027 /*APOSTRPHE*/ || ch == 0xFF07 /*FULLWIDTH APOSTROPHE*/)) { return 0; } return ClassifyChar(ch); }
if ( action == WB_ISDELIMITER ) return !!(ClassifyChar(pch[ich]) & WBF_BREAKLINE);
// Batch classify buffer for whitespace and kinsoku classes
BatchClassify(pch, cchBuff, cType3, kinsokuClassifications, wClassifyData);
if (_pbrk && cp > -1) { cp -= ich;
for (LONG cbrk = cchBuff-1; cbrk >= 0; --cbrk) { if (cp + cbrk >= 0 && _pbrk->CanBreakCp(BRK_WORD, cp + cbrk)) { // Mimic class open/close in Kinsoku classification.
kinsokuClassifications[cbrk] = brkclsOpen; if (cbrk > 0) { kinsokuClassifications[cbrk-1] = brkclsClose; wClassifyData[cbrk-1] |= WBF_WORDBREAKAFTER; } } } }
// Setup pointers
pKinsoku2 = kinsokuClassifications + ich; // Ptr to current kinsoku
pKinsoku1 = pKinsoku2 - 1; // Ptr to previous kinsoku
if(!(action & 1)) // WB_(MOVE)LEFTxxx
{ ich--; Assert(ich >= 0); } pwRes = &wClassifyData[ich]; pcType3 = &cType3[ich]; // for ideographics
switch(action) { case WB_LEFT: for(; ich >= 0 && *pwRes & WBF_BREAKLINE; // Skip preceding line
ich--, pwRes--) // break chars
; // Empty loop. Then fall
// thru to WB_LEFTBREAK
case WB_LEFTBREAK: for(; ich >= 0 && !CanBreak(*pKinsoku1, *pKinsoku2); ich--, pwRes--, pKinsoku1--, pKinsoku2--) ; // Empty loop
if(action == WB_LEFTBREAK) // Skip preceding line
{ // break chars
for(; ich >= 0 && *pwRes & WBF_BREAKLINE; ich--, pwRes--) ; // Empty loop
} return ich + 1;
case WB_MOVEWORDLEFT: for(; ich >= 0 && (*pwRes & WBF_CLASS) == 2;// Skip preceding blank
ich--, pwRes--, pcType3--) // chars
; if(ich >= 0) // Save starting wRes and
{ // startType3
wb = *pwRes--; // Really type1
startType3 = *pcType3--; // type3
ich--; } // Skip to beginning of current word
while(ich >= 0 && (*pwRes & WBF_CLASS) != 3 && !(*pwRes & WBF_WORDBREAKAFTER) && (IsSameClass(*pwRes, wb, *pcType3, startType3) || !wb && ich && ((ch = pch[ich]) == '\'' || ch == RQUOTE))) { ich--, pwRes--, pcType3--; } return ich + 1;
case WB_RIGHTBREAK: for(; cch > 0 && *pwRes & WBF_BREAKLINE; // Skip any leading line
cch--, pwRes++) // break chars
; // Empty loop
// Fall thru to WB_RIGHT
case WB_RIGHT: // Skip to end of current word
for(; cch > 0 && !CanBreak(*pKinsoku1, *pKinsoku2); cch--, pKinsoku1++, pKinsoku2++, pwRes++) ; if(action != WB_RIGHTBREAK) // Skip trailing line
{ // break chars
for(; cch > 0 && *pwRes & WBF_BREAKLINE; cch--, pwRes++) ; } return cchBuff - cch;
case WB_MOVEWORDRIGHT: if(cch <= 0) // Nothing to do
return ich;
wb = *pwRes; // Save start wRes
startType3 = *pcType3; // and startType3
// Skip to end of word
if (startType3 & C3_IDEOGRAPH || // If ideographic or
(*pwRes & WBF_CLASS) == 3) // tab/cell, just
{ cch--, pwRes++; // skip one char
} else while(cch > 0 && !(*pwRes & WBF_WORDBREAKAFTER) && (IsSameClass(*pwRes, wb, *pcType3, startType3) || !wb && ((ch = pch[cchBuff - cch]) == '\'' || ch == RQUOTE))) { cch--, pwRes++, pcType3++; }
for(; cch > 0 && ((*pwRes & WBF_CLASS) == 2 // Skip trailing blank
|| (*pwRes & WBF_WORDBREAKAFTER)); // Skip Thai break after
cch--, pwRes++) // chars
; return cchBuff - cch; }
TRACEERRSZSC("CTxtEdit::TxWordBreakProc: unknown action", action); return ich; }
/*
* CTxtEdit::TxFindText (flags, cpMin, cpMost, pch, pcpRet) * * @mfunc * Find text in direction specified by flags starting at cpMin if * forward search (flags & FR_DOWN nonzero) and cpMost if backward * search. * * @rdesc * HRESULT (success) ? NOERROR : S_FALSE * * @comm * Caller is responsible for setting cpMin to the appropriate end of * the selection depending on which way the search is proceding. */ HRESULT CTxtEdit::TxFindText( DWORD flags, //@parm Specify FR_DOWN, FR_MATCHCASE, FR_WHOLEWORD
LONG cpStart, //@parm Find start cp
LONG cpLimit, //@parm Find limit cp
const WCHAR*pch, //@parm Null terminated string to search for
LONG * pcpMin, //@parm Out parm to receive start of matched string
LONG * pcpMost) //@parm Out parm to receive end of matched string
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxFindText");
if(Get10Mode()) // RichEdit 1.0 only searches
{ flags |= FR_DOWN; // forward
if (cpLimit < -1) cpLimit = -1; }
DWORD cchText = GetTextLength(); LONG cchToFind; const BOOL fSetCur = (cchText >= 4096); HCURSOR hcur = NULL; // Init to keep compiler happy
Assert(pcpMin && pcpMost);
// Validate parameters
if(!pch || !(cchToFind = wcslen(pch)) || cpStart < 0 || cpLimit < -1) return E_INVALIDARG; // Nothing to search for
CTxtPtr tp(this, cpStart);
if(fSetCur) // In case this takes a while...
hcur = TxSetCursor(LoadCursor(0, IDC_WAIT), NULL);
*pcpMin = tp.FindText(cpLimit, flags, pch, cchToFind); *pcpMost = tp.GetCp();
if(fSetCur) TxSetCursor(hcur, NULL);
return *pcpMin >= 0 ? NOERROR : S_FALSE;; }
/*
* CTxtEdit::TxGetLineCount (plres) * * @mfunc * Get the line count. * * @rdesc * HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK : * (WaitForRecalc succeeded) ? S_OK : E_FAIL */ HRESULT CTxtEdit::TxGetLineCount( LRESULT *plres) //@parm Output parm to receive line count
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetLineCount");
AssertSz(plres, "CTxtEdit::TxGetLineCount invalid pcli");
if(!fInplaceActive()) return OLE_E_INVALIDRECT;
if(!_pdp->WaitForRecalc(GetTextLength(), -1)) return E_FAIL;
*plres = _pdp->LineCount(); Assert(*plres > 0);
return S_OK; }
/*
* CTxtEdit::TxLineFromCp (acp, plres) * * @mfunc * Get the line containing acp. * * @rdesc * HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK : * (LineFromCp succeeded) ? S_OK : E_FAIL * @comm * This function inputs an API cp that may differ from the * corresponding internal Unicode cp. */ HRESULT CTxtEdit::TxLineFromCp( LONG acp, //@parm Input cp
LRESULT *plres) //@parm Ouput parm to receive line number
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineFromCp");
BOOL fAtEnd = FALSE; LONG cp = 0;
AssertSz(plres, "CTxtEdit::TxLineFromCp invalid plres");
if(!fInplaceActive()) { AssertSz(*plres == 0, "CTxtEdit::TxLineFromCp error return lres not correct"); return OLE_E_INVALIDRECT; }
if(acp < 0) // Validate cp
{ if(_psel) { cp = _psel->GetCpMin(); fAtEnd = !_psel->GetCch() && _psel->CaretNotAtBOL(); } } else { LONG cchText = GetTextLength(); cp = GetCpFromAcp(acp); cp = min(cp, cchText); }
*plres = _pdp->LineFromCp(cp, fAtEnd);
HRESULT hr = *plres < 0 ? E_FAIL : S_OK;
// Old messages expect 0 as a result of this call if there is an error.
if(*plres == -1) *plres = 0;
return hr; }
/*
* CTxtEdit::TxLineLength (acp, plres) * * @mfunc * Get the line containing acp. * * @rdesc * HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK : * (GetSel() succeeded) ? S_OK : E_FAIL * @comm * This function inputs an API cp and outputs an API cch that * may differ from the corresponding internal Unicode cp and cch. */ HRESULT CTxtEdit::TxLineLength( LONG acp, //@parm Input cp
LRESULT *plres) //@parm Output parm to receive line length
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineLength");
LONG cch = 0; LONG cp;
AssertSz(plres, "CTxtEdit::TxLineLength Invalid plres parameter");
if(!fInplaceActive()) return OLE_E_INVALIDRECT;
if(acp < 0) { if(!_psel) return E_FAIL; cch = _psel->LineLength(&cp); } else { cp = GetCpFromAcp(acp); if(cp <= GetAdjustedTextLength()) { CLinePtr rp(_pdp); rp.RpSetCp(cp, FALSE); cp -= rp.GetIch(); // Goto start of line
cch = rp.GetAdjustedLineLength(); } } if(fCpMap()) // Can be time consuming, so
{ // don't do it unless asked
CRchTxtPtr rtp(this, cp); // for
cch = rtp.GetCachFromCch(cch); } *plres = cch; return S_OK; }
/*
* CTxtEdit::TxLineIndex (acp, plres) * * @mfunc * Get the line containing acp. * * @rdesc * HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK : * (LineCount() && WaitForRecalcIli succeeded) ? S_OK : E_FAIL * @comm * This function outputs an API cp that may differ from the * corresponding internal Unicode cp. */ HRESULT CTxtEdit::TxLineIndex( LONG ili, //@parm Line # to find acp for
LRESULT *plres) //@parm Output parm to receive acp
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineIndex");
HRESULT hr; AssertSz(plres, "CTxtEdit::TxLineIndex invalid plres");
*plres = -1; if(!fInplaceActive()) return OLE_E_INVALIDRECT;
if(ili == -1) { // Fetch line from the current cp.
LRESULT lres; // For 64-bit compatibility
hr = TxLineFromCp(-1, &lres); if(hr != NOERROR) return hr; ili = (LONG)lres; }
// ili is a zero-based *index*, whereas count returns the total # of lines.
// Therefore, we use >= for our comparisions.
if(ili >= _pdp->LineCount() && !_pdp->WaitForRecalcIli(ili)) return E_FAIL;
*plres = GetAcpFromCp(_pdp->CpFromLine(ili, NULL));
return S_OK; }
/////////////////////////////////// Miscellaneous messages ////////////////////////////////////
/*
* CTxtEdit::OnFindText (msg, flags, pftex) * * @mfunc * Find text. * * @rdesc * LRESULT = succeeded ? acpmin : -1 * * @comm * This function inputs and exports API cp's that may differ * from the internal Unicode cp's. */ LRESULT CTxtEdit::OnFindText( UINT msg, DWORD flags, FINDTEXTEX *pftex) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnFindText");
LONG cpMin, cpMost;
if(TxFindText(flags, GetCpFromAcp(pftex->chrg.cpMin), GetCpFromAcp(pftex->chrg.cpMost), pftex->lpstrText, &cpMin, &cpMost) != S_OK) { if(msg == EM_FINDTEXTEX || msg == EM_FINDTEXTEXW) { pftex->chrgText.cpMin = -1; pftex->chrgText.cpMost = -1; } return -1; }
LONG acpMin = GetAcpFromCp(cpMin); if(msg == EM_FINDTEXTEX || msg == EM_FINDTEXTEXW) // We send a message
{ // back to change
pftex->chrgText.cpMin = acpMin; // selection to this
pftex->chrgText.cpMost = GetAcpFromCp(cpMost); } return (LRESULT)acpMin; }
LRESULT CTxtEdit::OnGetWordBreakProc() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetWordBreakProc");
return (LRESULT) _pfnWB; }
// For plain-text instances, OnGetCharFormat(), OnGetParaFormat(),
// OnSetCharFormat(), and OnSetParaFormat() apply to whole story
LRESULT CTxtEdit::OnGetCharFormat( CHARFORMAT2 *pCF2, DWORD dwFlags) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetCharFormat");
UINT cb = pCF2->cbSize; UINT CodePage = 1200;
if(!IsValidCharFormatW(pCF2)) { if(!IsValidCharFormatA((CHARFORMATA *)pCF2)) return 0; CodePage = GetDefaultCodePage(EM_GETCHARFORMAT); }
if(cb == sizeof(CHARFORMATW) || cb == sizeof(CHARFORMATA)) dwFlags |= CFM2_CHARFORMAT; // Tell callees that only
// CHARFORMAT parms needed
CCharFormat CF; DWORD dwMask = CFM_ALL2;
if(dwFlags & SCF_SELECTION) dwMask = GetSel()->GetCharFormat(&CF, dwFlags); else CF = *GetCharFormat(-1);
if(dwFlags & CFM2_CHARFORMAT) // Maintain CHARFORMAT
{ // compatibility
CF._dwEffects &= CFM_EFFECTS; dwMask &= CFM_ALL; }
CF.Get(pCF2, CodePage); pCF2->dwMask = dwMask; return (LRESULT)dwMask; }
LRESULT CTxtEdit::OnGetParaFormat( PARAFORMAT2 *pPF2, DWORD dwFlags) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetParaFormat");
if(!IsValidParaFormat(pPF2)) return 0;
if(pPF2->cbSize == sizeof(PARAFORMAT)) // Tell callees that only
dwFlags |= PFM_PARAFORMAT; // PARAFORMAT parms needed
CParaFormat PF; DWORD dwMask = GetSel()->GetParaFormat(&PF, dwFlags);
if(dwFlags & PFM_PARAFORMAT) dwMask &= PFM_ALL;
PF.Get(pPF2); pPF2->dwMask = dwMask; return (LRESULT)dwMask; }
/*
* CTxtEdit::OnSetFontSize(yPoint, publdr) * * @mfunc * Set new font height by adding yPoint to current height * and rounding according to the table in cfpf.cpp * * @rdesc * LRESULT nonzero if success */ LRESULT CTxtEdit::OnSetFontSize( LONG yPoint, IUndoBuilder *publdr) //@parm Undobuilder to receive antievents
{ // TODO: ? Return nonzero if we set a new font size for some text.
CCharFormat CF; CF._yHeight = (SHORT)yPoint;
return OnSetCharFormat(SCF_SELECTION, &CF, publdr, CFM_SIZE, CFM2_CHARFORMAT | CFM2_USABLEFONT); }
/*
* CTxtEdit::OnSetFont(hfont) * * @mfunc * Set new default font from hfont * * @rdesc * LRESULT nonzero if success */ LRESULT CTxtEdit::OnSetFont( HFONT hfont) //@parm Handle of font to use for default
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetFont");
CCharFormat CF; if(FAILED(CF.InitDefault(hfont))) return 0;
DWORD dwMask2 = CFM2_CHARFORMAT; WPARAM wparam = SCF_ALL;
if(!GetAdjustedTextLength()) { dwMask2 = CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK; wparam = 0; }
return !FAILED(OnSetCharFormat(wparam, &CF, NULL, CFM_ALL, dwMask2)); }
/*
* CTxtEdit::OnSetCharFormat(wparam, pCF, publdr, dwMask, dwMask2) * * @mfunc * Set new default CCharFormat * * @rdesc * LRESULT nonzero if success */ LRESULT CTxtEdit::OnSetCharFormat( WPARAM wparam, //@parm Selection flag
CCharFormat * pCF, //@parm CCharFormat to apply
IUndoBuilder *publdr, //@parm Undobuilder to receive antievents
DWORD dwMask, //@parm CHARFORMAT2 mask
DWORD dwMask2) //@parm Second mask
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetCharFormat");
// This says that if there's a selection that's protected and the
// parent window wants protection notifications and doesn't want
// changes with a protected selection, then return 0. This is more
// stringent than RE 2.0, but it's more like 1.0.
if (_psel && _psel->IsProtected(0) == CTxtRange::PROTECTED_ASK && _dwEventMask & ENM_PROTECTED) { CHARFORMAT CF0; // Selection is protected, client
// wants protect notifications
CF0.cbSize = sizeof(CHARFORMAT);// and protected mask is on
CF0.dwEffects = pCF->_dwEffects;// Concoct CHARFORMAT for query
CF0.dwMask = dwMask; // Maybe need more fields...
if(QueryUseProtection(_psel, EM_SETCHARFORMAT, wparam, (LPARAM)&CF0)) return 0; // No deal
}
BOOL fRet = TRUE;
AssertSz(!_fSelChangeCharFormat || IsRich(), "Inconsistent _fSelChangeCharFormat flag");
if ((wparam & SCF_ALL) || !_fSelChangeCharFormat && _story.GetCFRuns() && !(wparam & SCF_SELECTION)) { CTxtRange rg(this, 0, -GetTextLength());
if(publdr) publdr->StopGroupTyping();
if ((dwMask & (CFM_CHARSET | CFM_FACE)) == (CFM_CHARSET | CFM_FACE)) { if(GetAdjustedTextLength()) { dwMask2 |= CFM2_MATCHFONT; if (_fAutoFontSizeAdjust) { dwMask2 |= CFM2_ADJUSTFONTSIZE; if (fUseUIFont()) dwMask2 |= CFM2_UIFONT; } } else dwMask2 |= CFM2_NOCHARSETCHECK; }
fRet = (rg.SetCharFormat(pCF, 0, publdr, dwMask, dwMask2) == NOERROR);
// If we have an insertion point, apply format to it as well
if (_psel && !_psel->GetCch() && _psel->SetCharFormat(pCF, wparam, publdr, dwMask, dwMask2) != NOERROR) { fRet = FALSE; } } else if(wparam & SCF_SELECTION) { // Change selection character format unless protected
if(!_psel || !IsRich()) return 0;
return _psel->SetCharFormat(pCF, wparam, publdr, dwMask, dwMask2) == NOERROR; }
// Change default character format
CCharFormat CF; // Local CF to party on
LONG iCF; // Possible new CF index
const CCharFormat *pCF1; // Ptr to current default CF
ICharFormatCache *pICFCache = GetCharFormatCache();
if(FAILED(pICFCache->Deref(Get_iCF(), &pCF1))) // Get ptr to current
{ // default CCharFormat
fRet = FALSE; goto Update; } CF = *pCF1; // Copy current default CF
CF.Apply(pCF, dwMask, dwMask2); // Modify copy
if(FAILED(pICFCache->Cache(&CF, &iCF))) // Cache modified copy
{ fRet = FALSE; goto Update; }
#ifdef LINESERVICES
if (g_pols) g_pols->DestroyLine(NULL); #endif
pICFCache->Release(Get_iCF()); // Release _iCF regardless
Set_iCF(iCF); // of whether _iCF = iCF,
// i.e., only 1 ref count
if(_psel && !_psel->GetCch() && _psel->Get_iFormat() == -1) _psel->UpdateCaret(FALSE);
if ((dwMask & (CFM_CHARSET | CFM_FACE)) == CFM_FACE && !GetFontName(pCF->_iFont)[0] && GetFontName(CF._iFont)[0] && IsBiDiCharSet(CF._bCharSet)) { // Client requested font/charset be chosen for it according to thread
// locale. If BiDi, then also set RTL para default
CParaFormat PF; PF._wEffects = PFE_RTLPARA; OnSetParaFormat(SPF_SETDEFAULT, &PF, publdr, PFM_RTLPARA | PFM_PARAFORMAT); }
Update: // FUTURE (alexgo): this may be unnecessary if the display handles
// updating more automatically.
_pdp->UpdateView(); return fRet; }
/*
* CTxtEdit::OnSetParaFormat(wparam, pPF, publdr, dwMask) * * @mfunc * Set new default CParaFormat * * @rdesc * LRESULT nonzero if success */ LRESULT CTxtEdit::OnSetParaFormat( WPARAM wparam, //@parm wparam passed thru to IsProtected()
CParaFormat *pPF, //@parm CParaFormat to use
IUndoBuilder *publdr, //@parm Undobuilder to receive antievents
DWORD dwMask) //@parm Mask to use
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetParaFormat");
// If we're using context direction in the control, then we disallow
// the paragraph direction property and the alignment property (unless
// it's for center alignment).
if(IsStrongContext(_nContextDir) || IsStrongContext(_nContextAlign)) { Assert(!IsRich()); if(dwMask & (PFM_RTLPARA | PFM_ALIGNMENT)) { if (IsStrongContext(_nContextAlign) && (pPF->_bAlignment == PFA_LEFT || pPF->_bAlignment == PFA_RIGHT)) { dwMask &= ~PFM_ALIGNMENT; } if(IsStrongContext(_nContextDir)) dwMask &= ~PFM_RTLPARA; } } BOOL fMatchKbdToPara = FALSE;
if(dwMask & PFM_RTLPARA) { // In plain text allow DIR changes to change DIR and ALIGNMENT
if(!IsRich()) { // Clear all para masks, except for DIR and ALIGN
dwMask &= (PFM_RTLPARA | PFM_ALIGNMENT); wparam |= SPF_SETDEFAULT; } if(_psel && _fFocus) fMatchKbdToPara = TRUE; } if(!(wparam & SPF_SETDEFAULT)) { // If DEFAULT flag is specified, don't change selection
if(!_psel || IsProtected(EM_SETPARAFORMAT, wparam, (LPARAM)pPF)) return 0;
LRESULT lres = NOERROR == (pPF->fSetStyle(dwMask) ? _psel->SetParaStyle(pPF, publdr, dwMask) : _psel->SetParaFormat(pPF, publdr, dwMask));
// This is a bit funky, but basically, if the text is empty
// then we also need to set the default paragraph format
// (done in the code below). Thus, if we hit a failure or
// if the document is not empty, go ahead and return.
// Otherwise, fall through to the default case.
if(!lres || GetAdjustedTextLength()) { if(fMatchKbdToPara) _psel->MatchKeyboardToPara(); return lres; } }
// No text in document or (wparam & SPF_SETDEFAULT): set default format
LONG iPF; // Possible new PF index
CParaFormat PF = *GetParaFormat(-1); // Local PF to party on
IParaFormatCache *pPFCache = GetParaFormatCache();
PF.Apply(pPF, dwMask); // Modify copy
if(FAILED(pPFCache->Cache(&PF, &iPF))) // Cache modified copy
return 0; pPFCache->Release(Get_iPF()); // Release _iPF regardless of
Set_iPF(iPF); // Update default format index
if(PF.IsRtlPara()) OrCharFlags(fBIDI, publdr); // BiDi in backing store
if(!IsRich() && dwMask & PFM_RTLPARA) // Changing plain-text default PF
{ ItemizeDoc(publdr); // causing re-itemize the whole doc.
// (#6503) We cant undo the -1 format change in plaintext and that causes
// many problems when we undo ReplaceRange event happening before the paragraph
// switches. We better abandon the whole stack for now. (wchao)
// -FUTURE- We should create an antievent for -1 PF change.
ClearUndo(publdr); } _pdp->UpdateView(); if (_psel) _psel->UpdateCaret(!Get10Mode() || _psel->IsCaretInView()); if(fMatchKbdToPara) _psel->MatchKeyboardToPara(); return TRUE; }
//////////////////////////////// System notifications ////////////////////////////////
LRESULT CTxtEdit::OnSetFocus() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetFocus");
_fFocus = TRUE;
// Update our idea of the current keyboard layout
W32->RefreshKeyboardLayout();
InitKeyboardFlags();
if(!_psel) return 0;
// _fMouseDown may sometimes be true.
// This can happen when somebody steals our focus when we were doing
// something with the mouse down--like processing a click. Thus, we'll
// never get the MouseUpMessage.
if(_fMouseDown) { TRACEWARNSZ("Getting the focus, yet we think the mouse is down"); } _fMouseDown = FALSE;
// BUG FIX #5369
// Special case where we don't have a selection (or a caret). We need
// to display something on focus so display a caret
_psel->UpdateCaret(_fScrollCaretOnFocus, _psel->GetCch() == 0); _fScrollCaretOnFocus = FALSE;
_psel->ShowSelection(TRUE);
// if there is an in-place active object, we need to set the focus to
// it. (in addition to the work that we do; this maintains compatibility
// with RichEdit 1.0).
if(_pobjmgr) { COleObject *pobj = _pobjmgr->GetInPlaceActiveObject(); if(pobj) { IOleInPlaceObject *pipobj;
if(pobj->GetIUnknown()->QueryInterface(IID_IOleInPlaceObject, (void **)&pipobj) == NOERROR) { HWND hwnd; pipobj->GetWindow(&hwnd);
if(hwnd) SetFocus(hwnd); pipobj->Release(); } } }
TxNotify(EN_SETFOCUS, NULL); return 0; }
LRESULT CTxtEdit::OnKillFocus() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnKillFocus");
StopMagellanScroll();
if(_pundo) _pundo->StopGroupTyping();
if(_psel) { // Scroll back to beginning if necessary
if (_fScrollCPOnKillFocus) { bool fHideSelectionLocal = _fHideSelection;
// cannot hide Selection so cp=0 will be scroll into view.
_fHideSelection = 0; OnSetSel(0, 0); _fHideSelection = fHideSelectionLocal; }
_psel->DeleteCaretBitmap(TRUE); // Delete caret bitmap if one exists
if(_fHideSelection) _psel->ShowSelection(FALSE); }
_fFocus = FALSE; DestroyCaret(); TxNotify(EN_KILLFOCUS, NULL);
_fScrollCaretOnFocus = FALSE; // Just to be safe, clear this
return 0; }
#if defined(DEBUG)
void CTxtEdit::OnDumpPed() { #ifndef NOPEDDUMP
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDumpPed");
char sz[256]; CTxtSelection * const psel = GetSel(); SELCHANGE selchg;
psel->SetSelectionInfo(&selchg);
wsprintfA(sz, "cchText = %ld cchTextMost = %ld\r\n" "cpSelActive = %ld cchSel = %ld\r\n" "wSelType = %x # lines = %ld\r\n" "SysDefLCID = %lx UserDefLCID = %lx", GetTextLength(), TxGetMaxLength(), psel->GetCp(), psel->GetCch(), selchg.seltyp, _pdp->LineCount(), GetSystemDefaultLCID(), GetUserDefaultLCID() ); Tracef(TRCSEVINFO, "%s", sz); MessageBoxA(0, sz, "ED", MB_OK); #endif // NOPEDDUMP
} #endif // DEBUG
///////////////////////////// Scrolling Commands //////////////////////////////////////
HRESULT CTxtEdit::TxHScroll( WORD wCode, int xPos) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxHScroll");
if(!fInplaceActive()) return OLE_E_INVALIDRECT;
_pdp->HScroll(wCode, xPos); return S_OK; }
LRESULT CTxtEdit::TxVScroll( WORD wCode, int yPos) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxVScroll");
return _pdp->VScroll(wCode, yPos); }
HRESULT CTxtEdit::TxLineScroll( LONG cli, LONG cch) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineScroll");
// Currently cch does nothing in the following call, so we ignore
// its translation from cach to cch (need to instantiate an rtp
// for the current line
_pdp->LineScroll(cli, cch); return S_OK; }
void CTxtEdit::OnScrollCaret() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnScrollCaret");
if(_psel) { _psel->SetForceScrollCaret(TRUE); _psel->UpdateCaret(TRUE); _psel->SetForceScrollCaret(FALSE); } }
///////////////////////////////// Editing messages /////////////////////////////////
void CTxtEdit::OnClear( IUndoBuilder *publdr) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnClear");
if(!_psel || TxGetReadOnly()) { Beep(); return; }
if(_psel->GetCch() && !IsProtected(WM_CLEAR, 0, 0)) { _psel->StopGroupTyping(); _psel->ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE); } }
void CTxtEdit::Beep() { if(_fAllowBeep) MessageBeep(0); }
/////////////////////////////////// Miscellaneous ///////////////////////////////////////////
/*
* CTxtEdit::ItemizeDoc(publdr, cchRange) * * @mfunc * Helper routine to itemize the cchRange size of document content * called by various clients outside CTxtRange. */ void CTxtEdit::ItemizeDoc( IUndoBuilder * publdr, LONG cchRange) { // If cchRange = -1, itemize the whole doc
if (cchRange == -1) cchRange = GetTextLength();
// We wouldnt itemize if the doc only contains a single EOP
// because we dont want Check_rpPF being called when the -1
// PF format hasnt been properly established.
// This is kind of hack, should be removed in the future.
//
if(cchRange && GetAdjustedTextLength()) { // Only itemize if more than
CTxtRange rg(this, 0, -cchRange); // final EOP
rg.ItemizeRuns(publdr); }
#if 0
// =FUTURE=
// Once we open SPF_SETDEFAULT to public. We shall incorporate this code.
// Basically, one can change the default paragraph reading order at runtime. All
// PF runs referencing to -1 PF format then need to be reassigned a new paragraph
// level value and reitemized.(6-10-99, wchao)
//
if(cchRange > 0) { CTxtRange rg(this, 0, -cchRange);
// -1 PF format may have changed.
// We shall make sure that the level of each PF run match the reading order
// before start itemization.
//
if (rg.Check_rpPF()) { LONG cchLeft = cchRange; LONG cchAdvance = 0; LONG cch;
while (cchLeft > 0) { rg._rpPF.GetRun(0)->_level._value = rg.IsParaRTL() ? 1 : 0;
cch = rg._rpPF.GetCchLeft();
if (!rg._rpPF.NextRun()) break; // no more run
cchAdvance += cch; cchLeft -= cch; }
Assert (cchAdvance + cchLeft == cchRange);
rg._rpPF.AdvanceCp(-cchAdvance); // fly back to cp = 0
}
// Now we rerun itemization
rg.ItemizeRuns(publdr); } #endif
}
/*
* CTxtEdit::OrCharFlags(dwFlags, publdr) * * @mfunc * Or in new char flags and activate LineServices and Uniscribe * if complex script chars occur. */ void CTxtEdit::OrCharFlags( DWORD dwFlags, IUndoBuilder* publdr) { // REVIEW: Should we send a notification for LS turn on?
// Convert dwFlags to new on flags
dwFlags &= dwFlags ^ _dwCharFlags; if(dwFlags) { _dwCharFlags |= dwFlags; // Update flags
dwFlags &= fCOMPLEX_SCRIPT;
if(dwFlags && (_dwCharFlags & fCOMPLEX_SCRIPT) == dwFlags) { // REVIEW: Need to check if Uniscribe and LineServices are available...
OnSetTypographyOptions(TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY); ItemizeDoc();
// FUTURE: (#6838) We cant undo operations before the first itemization.
ClearUndo(publdr); _fAutoKeyboard = IsBiDi(); }
UINT brk = 0;
if (dwFlags & fNEEDWORDBREAK) brk += BRK_WORD; if (dwFlags & fNEEDCHARBREAK) brk += BRK_CLUSTER; if (brk) { CUniscribe* pusp = Getusp();
if (!_pbrk && pusp && pusp->IsValid()) { // First time detecting the script that needs word/cluster-breaker
// (such as Thai, Indic, Lao etc.)
_pbrk = new CTxtBreaker(this); Assert(_pbrk); }
if (_pbrk && _pbrk->AddBreaker(brk)) { // Sync up the breaking array(s)
_pbrk->Refresh(); } } } }
/*
* CTxtEdit::OnSetTypographyOptions(wparam, lparam) * * @mfunc * If CTxtEdit isn't a password or accelerator control and wparam * differs from _bTypography, update the latter and the view. * * @rdesc * HRESULT = S_OK */ HRESULT CTxtEdit::OnSetTypographyOptions( WPARAM wparam, //@parm Typography flags
LPARAM lparam) //@parm Typography mask
{ // Currently only TO_SIMPLELINEBREAK and TO_ADVANCEDTYPOGRAPHY are defined
if(wparam & ~(TO_SIMPLELINEBREAK | TO_ADVANCEDTYPOGRAPHY)) return E_INVALIDARG;
DWORD dwTypography = _bTypography & ~lparam; // Kill current flag values
dwTypography |= wparam & lparam; // Or in new values
if(_cpAccelerator == -1 && _bTypography != (BYTE)dwTypography) { _bTypography = (BYTE)dwTypography; _pdp->InvalidateRecalc(); TxInvalidateRect(NULL, FALSE); } return S_OK; }
void CTxtEdit::TxGetViewInset( LPRECT prc, CDisplay *pdp) const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetViewInset");
// Get inset, which is in HIMETRIC
RECT rcHiMetricViewInset;
if(SUCCEEDED(_phost->TxGetViewInset(&rcHiMetricViewInset))) { LONG vileft = rcHiMetricViewInset.left; LONG vitop = rcHiMetricViewInset.top; LONG viright = rcHiMetricViewInset.right; LONG vibottom = rcHiMetricViewInset.bottom;
if(!pdp) // If no display is specified,
pdp = _pdp; // use main display
AssertSz(pdp->IsValid(), "CTxtEdit::TxGetViewInset Device not valid");
// Convert HIMETRIC to pixels
prc->left = vileft ? pdp->HimetricXtoDX( vileft ) : 0; prc->top = vitop ? pdp->HimetricYtoDY(rcHiMetricViewInset.top) : 0; prc->right = viright ? pdp->HimetricXtoDX(rcHiMetricViewInset.right) : 0; prc->bottom = vibottom ? pdp->HimetricYtoDY(rcHiMetricViewInset.bottom) : 0; } else { // The call to the host failed. While this is highly improbable, we do
// want to something reasonably sensible. Therefore, we will just pretend
// there is no inset and continue.
ZeroMemory(prc, sizeof(RECT)); } }
#if 0
// Interchange horizontal and vertical commands
WORD wConvScroll(WORD wparam) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "wConvScroll");
switch(wparam) { case SB_BOTTOM: return SB_TOP;
case SB_LINEDOWN: return SB_LINEUP;
case SB_LINEUP: return SB_LINEDOWN;
case SB_PAGEDOWN: return SB_PAGEUP;
case SB_PAGEUP: return SB_PAGEDOWN;
case SB_TOP: return SB_BOTTOM;
default: return wparam; } } #endif
//
// helper functions. FUTURE (alexgo) maybe we should get rid of
// some of these
//
/* FUTURE (murrays): Unless they are called a lot, the TxGetBit routines
might be done more compactly as:
BOOL CTxtEdit::TxGetBit( DWORD dwMask) { DWORD dwBits = 0; _phost->TxGetPropertyBits(dwMask, &dwBits); return dwBits != 0; }
e.g., instead of TxGetSelectionBar(), we use TxGetBit(TXTBIT_SELECTIONBAR). If they are called a lot (like TxGetSelectionBar()), the bits should probably be cached, since that saves a bunch of cache misses incurred in going over to the host.
*/
BOOL CTxtEdit::IsLeftScrollbar() const { if(!_fHost2) return FALSE;
DWORD dwStyle, dwExStyle;
_phost->TxGetWindowStyles(&dwStyle, &dwExStyle); return dwExStyle & WS_EX_LEFTSCROLLBAR; }
TXTBACKSTYLE CTxtEdit::TxGetBackStyle() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetBackStyle");
TXTBACKSTYLE style = TXTBACK_OPAQUE; _phost->TxGetBackStyle(&style); return style; }
BOOL CTxtEdit::TxGetAutoSize() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetAutoSize");
return (_dwEventMask & ENM_REQUESTRESIZE); }
BOOL CTxtEdit::TxGetAutoWordSel() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetAutoWordSel");
DWORD dwBits = 0; _phost->TxGetPropertyBits(TXTBIT_AUTOWORDSEL, &dwBits); return dwBits != 0; }
DWORD CTxtEdit::TxGetMaxLength() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetMaxLength");
// Keep this a DWORD in case client uses a cpMost of 0xFFFFFFFF, which is
// admittedly a little large, at least for 32-bit address spaces!
// tomForward would be a more reasonable max length, altho it's also
// probably larger than possible in a 32-bit address space.
return _cchTextMost; }
/*
* CTxtEdit::TxSetMaxToMaxText(LONG cExtra) * * @mfunc * Set new maximum text length based on length of text and possibly extra chars * to accomodate. * */ void CTxtEdit::TxSetMaxToMaxText(LONG cExtra) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxSetMaxToMaxText");
// See if we need to update the text max
LONG cchRealLen = GetAdjustedTextLength() + cExtra;
if(_fInOurHost && _cchTextMost < (DWORD)cchRealLen) _cchTextMost = cchRealLen; }
TCHAR CTxtEdit::TxGetPasswordChar() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetPasswordChar");
if(_fUsePassword) { TCHAR ch = L'*'; _phost->TxGetPasswordChar(&ch);
// We don't allow these characters as password chars
if(ch < 32 || ch == WCH_EMBEDDING) return L'*'; return ch; } return 0; }
DWORD CTxtEdit::TxGetScrollBars() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetScrollBars");
DWORD dwScroll; _phost->TxGetScrollBars(&dwScroll); return dwScroll; }
LONG CTxtEdit::TxGetSelectionBarWidth() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetSelectionBarWidth");
LONG lSelBarWidth; _phost->TxGetSelectionBarWidth(&lSelBarWidth); return lSelBarWidth; }
BOOL CTxtEdit::TxGetWordWrap() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetWordWrap");
DWORD dwBits = 0; _phost->TxGetPropertyBits(TXTBIT_WORDWRAP, &dwBits); return dwBits != 0; }
BOOL CTxtEdit::TxGetSaveSelection() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetSaveSelection");
DWORD dwBits = 0; _phost->TxGetPropertyBits(TXTBIT_SAVESELECTION, &dwBits); return dwBits != 0; }
/*
* CTxtEdit::ClearUndo() * * @mfunc Clear all undo buffers */ void CTxtEdit::ClearUndo( IUndoBuilder *publdr) //@parm the current undo context (may be NULL)
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::ClearUndo");
if(_pundo) _pundo->ClearAll();
if(_predo) _predo->ClearAll();
if(publdr) publdr->Discard(); }
/////////////////////////////// ITextHost2 Extensions //////////////////////////////
/*
* CTxtEdit::TxIsDoubleClickPending () * * @mfunc calls host via ITextHost2 to find out if double click is pending. * * @rdesc TRUE/FALSE */ BOOL CTxtEdit::TxIsDoubleClickPending() { return _fHost2 ? _phost->TxIsDoubleClickPending() : FALSE; }
/*
* CTxtEdit::TxGetWindow(phwnd) * * @mfunc calls host via ITextHost2 to get current window for this edit * instance. This is very helpful for OLE object support * * @rdesc HRESULT */ HRESULT CTxtEdit::TxGetWindow( HWND *phwnd) { return _fHost2 ? _phost->TxGetWindow(phwnd) : E_NOINTERFACE; }
/*
* CTxtEdit::TxSetForegroundWindow () * * @mfunc calls host via ITextHost2 to make our window the foreground * window. Used to support drag/drop. * * @rdesc HRESULT */ HRESULT CTxtEdit::TxSetForegroundWindow() { return _fHost2 ? _phost->TxSetForegroundWindow() : E_NOINTERFACE; }
/*
* CTxtEdit::TxGetPalette() * * @mfunc calls host via ITextHost2 to get current palette * * @rdesc HPALETTE */ HPALETTE CTxtEdit::TxGetPalette() { return _fHost2 ? _phost->TxGetPalette() : NULL; }
/*
* CTxtEdit::TxGetFEFlags(pFEFlags) * * @mfunc calls host via ITextHost2 to get current FE settings * * @rdesc HRESULT */ HRESULT CTxtEdit::TxGetFEFlags( LONG *pFEFlags) { *pFEFlags = 0; // In case no ITextHost2 methods
HRESULT hResult = _fHost2 ? _phost->TxGetFEFlags(pFEFlags) : E_NOINTERFACE;
if (hResult == NOERROR && Get10Mode()) *pFEFlags |= tomRE10Mode;
return hResult; }
//
// Event Notification methods
//
/*
* CTxtEdit::TxNotify(iNotify, pv) * * @mfunc This function checks bit masks and sends notifications to the * host. * * @devnote Callers should check to see if a special purpose notification * method has already been provided. * * @rdesc S_OK, S_FALSE, or some error */ HRESULT CTxtEdit::TxNotify( DWORD iNotify, //@parm Notification to send
void *pv) //@parm Data associated with notification
{ // First, disallow notifications that we handle elsewhere
Assert(iNotify != EN_SELCHANGE); //see SetSelectionChanged
Assert(iNotify != EN_ERRSPACE); //see SetOutOfMemory
Assert(iNotify != EN_CHANGE); //see SetChangedEvent
Assert(iNotify != EN_HSCROLL); //see SendScrollEvent
Assert(iNotify != EN_VSCROLL); //see SendScrollEvent
Assert(iNotify != EN_MAXTEXT); //see SetMaxText
Assert(iNotify != EN_MSGFILTER); //this is handled specially
// in TxSendMessage
// Switch on the event to check masks.
DWORD dwMask; switch(iNotify) { case EN_DROPFILES: dwMask = ENM_DROPFILES; goto Notify;
case EN_PROTECTED: dwMask = ENM_PROTECTED; goto Notify;
case EN_REQUESTRESIZE: dwMask = ENM_REQUESTRESIZE; goto Notify;
case EN_PARAGRAPHEXPANDED: dwMask = ENM_PARAGRAPHEXPANDED; goto Notify;
case EN_IMECHANGE: if (!Get10Mode()) return S_FALSE; dwMask = ENM_IMECHANGE; goto Notify;
case EN_UPDATE: if (!Get10Mode()) break; dwMask = ENM_UPDATE; //FALL THROUGH CASE
Notify: if(!(_dwEventMask & dwMask)) return NOERROR;
}
return _phost->TxNotify(iNotify, pv); }
/*
* CTxtEdit::SendScrollEvent(iNotify) * * @mfunc Sends scroll event if appropriate * * @comm Scroll events must be sent before any view updates have * been requested and only if ENM_SCROLL is set. */ void CTxtEdit::SendScrollEvent( DWORD iNotify) //@parm Notification to send
{ Assert(iNotify == EN_HSCROLL || iNotify == EN_VSCROLL);
// FUTURE (alexgo/ricksa). The display code can't really
// handle this assert yet. Basically, we're trying to
// say that scrollbar notifications have to happen
// _before_ the window is updated. When we do the
// display rewrite, try to handle this better.
// Assert(_fUpdateRequested == FALSE);
if(_dwEventMask & ENM_SCROLL) _phost->TxNotify(iNotify, NULL); }
/*
* CTxtEdit::HandleLinkNotification (msg, wparam, lparam, pfInLink) * * @mfunc Handles sending EN_LINK notifications. * * @rdesc TRUE if the EN_LINK message was sent and * processed successfully. Typically, that means the * caller should stop whatever processing it was doing. */ BOOL CTxtEdit::HandleLinkNotification( UINT msg, //@parm msg prompting the link notification
WPARAM wparam, //@parm wparam of the message
LPARAM lparam, //@parm lparam of the message
BOOL * pfInLink) //@parm if non-NULL, indicate if over a link
{ if(pfInLink) *pfInLink = FALSE;
if(!(_dwEventMask & ENM_LINK) || !_fInPlaceActive) return FALSE;
HITTEST Hit; POINT pt = {LOWORD(lparam), HIWORD(lparam)};
if(msg == WM_SETCURSOR) { GetCursorPos(&pt); if(!_phost->TxScreenToClient(&pt)) return FALSE; }
LONG cp = _pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);
if(Hit != HT_Link) // Not a hyperlink
return FALSE;
LONG cpMin, cpMost; // It's a hyperlink
ENLINK enlink; CTxtRange rg(this, cp, 0);
ZeroMemory(&enlink, sizeof(enlink)); if (fInOurHost()) { GetWindow((LONG *) &enlink.nmhdr.hwndFrom); enlink.nmhdr.idFrom = GetWindowLong(enlink.nmhdr.hwndFrom, GWL_ID); } enlink.nmhdr.code = EN_LINK;
if(pfInLink) *pfInLink = TRUE; rg.Expander(tomLink, TRUE, NULL, &cpMin, &cpMost);
// Fill in ENLINK data structure for our EN_LINK
// callback asking client what we should do
enlink.msg = msg; enlink.wParam = wparam; enlink.lParam = lparam; enlink.chrg.cpMin = GetAcpFromCp(cpMin); enlink.chrg.cpMost = GetAcpFromCp(cpMost);
return _phost->TxNotify(EN_LINK, &enlink) == S_FALSE; }
/*
* CTxtEdit::QueryUseProtection(prg, msg, wparam, lparam) * * @mfunc sends EN_PROTECTED to the host, asking if we should continue * to honor the protection on a given range of characters * * @rdesc TRUE if protection should be honored, FALSE otherwise */ BOOL CTxtEdit::QueryUseProtection( CTxtRange *prg, //@parm range to check for
UINT msg, //@parm msg used
WPARAM wparam, //@parm wparam of the msg
LPARAM lparam) //@parm lparam of the msg
{ LONG cpMin, cpMost; ENPROTECTED enp; BOOL fRet = FALSE; CCallMgr * pcallmgr = GetCallMgr();
Assert(_dwEventMask & ENM_PROTECTED);
if( pcallmgr->GetInProtected() || _fSuppressNotify) // Don't ask host if we don't want to send notification
return FALSE;
pcallmgr->SetInProtected(TRUE);
ZeroMemory(&enp, sizeof(ENPROTECTED));
prg->GetRange(cpMin, cpMost);
enp.msg = msg; enp.wParam = wparam; enp.lParam = lparam; enp.chrg.cpMin = GetAcpFromCp(cpMin); enp.chrg.cpMost = GetAcpFromCp(cpMost);
if(_phost->TxNotify(EN_PROTECTED, &enp) == S_FALSE) fRet = TRUE;
pcallmgr->SetInProtected(FALSE);
return fRet; }
#ifdef DEBUG
//This is a debug api used to dump the document runs.
//If a pointer to the ped is passed, it is saved and
//used. If NULL is passed, the previously saved ped
//pointer is used. This allows the "context" to be
//setup by a function that has access to the ped and
//DumpDoc can be called lower down in a function that
//does not have access to the ped.
extern "C" { void DumpStory(void *ped) { static CTxtEdit *pedSave = (CTxtEdit *)ped; if(pedSave) { CTxtStory * pStory = pedSave->GetTxtStory(); if(pStory) pStory->DbgDumpStory();
CObjectMgr * pobjmgr = pedSave->GetObjectMgr(); if(pobjmgr) pobjmgr->DbgDump(); } } } #endif
/*
* CTxtEdit::TxGetDefaultCharFormat (pCF) * * @mfunc helper function to retrieve character formats from the * host. Does relevant argument checking * * @rdesc HRESULT */ HRESULT CTxtEdit::TxGetDefaultCharFormat( CCharFormat *pCF, //@parm Character format to fill in
DWORD & dwMask) //@parm Mask supplied by host or default
{ HRESULT hr = pCF->InitDefault(0); dwMask = CFM_ALL2;
const CHARFORMAT2 *pCF2 = NULL;
if (_phost->TxGetCharFormat((const CHARFORMAT **)&pCF2) != NOERROR || !IsValidCharFormatW(pCF2)) { return hr; }
dwMask = pCF2->dwMask; DWORD dwMask2 = 0; if(pCF2->cbSize == sizeof(CHARFORMAT)) { // Suppress CHARFORMAT2 specifications (except for Forms^3 disabled)
dwMask &= fInOurHost() ? CFM_ALL : (CFM_ALL | CFM_DISABLED); dwMask2 = CFM2_CHARFORMAT; }
CCharFormat CF; // Transfer external CHARFORMAT(2)
CF.Set(pCF2, 1200); // parms to internal CCharFormat
return pCF->Apply(&CF, dwMask, dwMask2); }
/*
* CTxtEdit::TxGetDefaultParaFormat (pPF) * * @mfunc helper function to retrieve paragraph formats. Does * the relevant argument checking. * * @rdesc HRESULT */ HRESULT CTxtEdit::TxGetDefaultParaFormat( CParaFormat *pPF) //@parm Paragraph format to fill in
{ HRESULT hr = pPF->InitDefault(0);
const PARAFORMAT2 *pPF2 = NULL;
if (_phost->TxGetParaFormat((const PARAFORMAT **)&pPF2) != NOERROR || !IsValidParaFormat(pPF2)) { return hr; }
DWORD dwMask = pPF2->dwMask; if(pPF2->cbSize == sizeof(PARAFORMAT)) // Suppress all but PARAFORMAT
{ // specifications
dwMask &= PFM_ALL; dwMask |= PFM_PARAFORMAT; // Tell Apply() that PARAFORMAT
} // was used
CParaFormat PF; // Transfer external PARAFORMAT(2)
PF.Set(pPF2); // parms to internal CParaFormat
return pPF->Apply(&PF, dwMask); // Apply parms identified by dwMask
}
/*
* CTxtEdit::SetContextDirection(fUseKbd) * * @mfunc * Determine the paragraph direction and/or alignment based on the context * rules (direction/alignment follows first strong character in the * control) and apply this direction and/or alignment to the default * format. * * @comment * Context direction only works for plain text controls. Note that * this routine only switches the default CParaFormat to RTL para if it * finds an RTL char. IsBiDi() will automatically be TRUE for this case, * since each char is checked before entering the backing store. */ void CTxtEdit::SetContextDirection( BOOL fUseKbd) //@parm Use keyboard to set context when CTX_NEUTRAL
{ // It turns out that Forms^3 can send EM_SETBIDIOPTIONS even for non BiDi controls.
// AssertSz(IsBiDi(), "CTxtEdit::SetContextDirection called for nonBiDi control");
if(IsRich() || !IsBiDi() || _nContextDir == CTX_NONE && _nContextAlign == CTX_NONE) return;
LONG cch = GetTextLength(); CTxtPtr tp(this, 0); TCHAR ch = tp.GetChar(); WORD ctx = CTX_NEUTRAL; BOOL fChanged = FALSE;
// Find first strongly directional character
while (cch && !IsStrongDirectional(MECharClass(ch))) { ch = tp.NextChar(); cch--; }
// Set new context based on first strong character
if(cch) ctx = IsRTL(MECharClass(ch)) ? CTX_RTL : CTX_LTR;
// Has context direction or alignment changed?
if (_nContextDir != CTX_NONE && _nContextDir != ctx || _nContextAlign != CTX_NONE && _nContextAlign != ctx) { // Start with current default CParaFormat
CParaFormat PF = *GetParaFormat(-1);
// If direction has changed...
if(_nContextDir != CTX_NONE && _nContextDir != ctx) { if(ctx == CTX_LTR || ctx == CTX_RTL || fUseKbd) { if (ctx == CTX_RTL || ctx == CTX_NEUTRAL && W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0)))) { PF._wEffects |= PFE_RTLPARA; } else { Assert(ctx == CTX_LTR || ctx == CTX_NEUTRAL); PF._wEffects &= ~PFE_RTLPARA; } fChanged = TRUE; } _nContextDir = ctx; }
// If the alignment has changed...
if(_nContextAlign != CTX_NONE && _nContextAlign != ctx) { if(PF._bAlignment != PFA_CENTER) { if(ctx == CTX_LTR || ctx == CTX_RTL || fUseKbd) { if (ctx == CTX_RTL || ctx == CTX_NEUTRAL && W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0)))) { PF._bAlignment = PFA_RIGHT; } else { Assert(ctx == CTX_LTR || ctx == CTX_NEUTRAL); PF._bAlignment = PFA_LEFT; } } } _nContextAlign = ctx; }
// Modify default CParaFormat
IParaFormatCache *pPFCache = GetParaFormatCache(); LONG iPF;
if(SUCCEEDED(pPFCache->Cache(&PF, &iPF))) { pPFCache->Release(Get_iPF()); // Release _iPF regardless of
Set_iPF(iPF); // Update default format index
if (fChanged) ItemizeDoc(NULL);
// Refresh display
Assert(_pdp); if(!_pdp->IsPrinter()) { _pdp->InvalidateRecalc(); TxInvalidateRect(NULL, FALSE); } } }
// Reset the first strong cp.
_cpFirstStrong = tp.GetCp();
Assert(_nContextDir != CTX_NONE || _nContextAlign != CTX_NONE); }
/*
* CTxtEdit::GetAdjustedTextLength () * * @mfunc * retrieve text length adjusted for the default end-of-document marker * * @rdesc * Text length without final EOP * * @devnote * For Word and RichEdit compatibility, we insert a CR or CRLF at the * end of every new rich-text control. This routine calculates the * length of the document _without_ this final EOD marker. * * For 1.0 compatibility, we insert a CRLF. However, TOM (and Word) * requires that we use a CR, from 2.0 on, we do that instead. */ LONG CTxtEdit::GetAdjustedTextLength() { LONG cchAdjText = GetTextLength();
Assert(!Get10Mode() || IsRich()); // No RE10 plain-text controls
if(IsRich()) cchAdjText -= fUseCRLF() ? 2 : 1; // Subtract cch of final EOP
return cchAdjText; }
/*
* CTxtEdit::Set10Mode() * * @mfunc * Turns on the 1.0 compatibility mode bit. If the control is * rich text, it already has a default 'CR' at the end, which * needs to turn into a CRLF for compatibility with RichEdit 1.0. * * @devnote * This function should only be called _immediately_ after * creation of text services and before all other work. There * are Asserts to help ensure this. Remark (murrays): why not * allow the change provided the control is empty except for the * final CR? * * FUTURE: we might want to split _f10Mode into three flags: * 1) _fMapCps // API cp's are MBCS and need conversion to Unicode
* 2) _fCRLF // Use CRLFs for EOPs instead of CRs
* 3) _f10Mode // All other RE 1.0 compatibility things
* * Category 3 includes 1) automatically using FR_DOWN in searches, * 2) ignoring direction in CDataTransferObj::EnumFormatEtc(), * 3) not resetting _fModified when switching to a new doc, */ void CTxtEdit::Set10Mode() { CCallMgr callmgr(this); _f10Mode = TRUE;
// Make sure nothing important has happened to the control.
// If these values are non-NULL, then somebody is probably trying
// to put us into 1.0 mode after we've already done work as
// a 2.0 control.
Assert(GetTextLength() == cchCR); Assert(_psel == NULL); Assert(_fModified == NULL);
SetRichDocEndEOP(cchCR);
if(!_pundo) CreateUndoMgr(1, US_UNDO);
if(_pundo) ((CUndoStack *)_pundo)->EnableSingleLevelMode();
// Turn off dual font
_fDualFont = FALSE;
// Turn on auto sizing for NTFE systems
if (OnWinNTFE()) _fAutoFontSizeAdjust = TRUE; }
/*
* CTxtEdit::SetRichDocEndEOP(cchToReplace) * * @mfunc Place automatic EOP at end of a rich text document. */ void CTxtEdit::SetRichDocEndEOP( LONG cchToReplace) { CRchTxtPtr rtp(this, 0);
// Assume this is a 2.0 Doc
LONG cchEOP = cchCR; const WCHAR *pszEOP = szCR;
if(_f10Mode) { // Reset update values for a 1.0 doc
cchEOP = cchCRLF; pszEOP = szCRLF; }
rtp.ReplaceRange(cchToReplace, cchEOP, pszEOP, NULL, -1);
_fModified = FALSE; _fSaved = TRUE; GetCallMgr()->ClearChangeEvent(); }
/*
* CTxtEdit::PopAndExecuteAntiEvent(pundomgr, void *pAE) * * @mfunc Freeze display and execute anti-event * * @rdesc HRESULT from IUndoMgr::PopAndExecuteAntiEvent */ HRESULT CTxtEdit::PopAndExecuteAntiEvent( IUndoMgr *pundomgr, //@parm Undo manager to direct call to
void *pAE) //@parm AntiEvent for undo manager
{ HRESULT hr; // Let stack based classes clean up before restoring selection
{ CFreezeDisplay fd(_pdp); CSelPhaseAdjuster selpa(this);
hr = pundomgr->PopAndExecuteAntiEvent(pAE); }
if(_psel) { // Once undo/redo has been executed, flush insertion point formatting
_psel->Update_iFormat(-1); _psel->Update(TRUE); } return hr; }
/*
* CTxtEdit::PasteDataObjectToRange(pdo, prg, cf, rps, publdr, dwFlags) * * @mfunc Freeze display and paste object * * @rdesc HRESULT from IDataTransferEngine::PasteDataObjectToRange */ HRESULT CTxtEdit::PasteDataObjectToRange( IDataObject * pdo, CTxtRange * prg, CLIPFORMAT cf, REPASTESPECIAL *rps, IUndoBuilder * publdr, DWORD dwFlags) { HRESULT hr = _ldte.PasteDataObjectToRange(pdo, prg, cf, rps, publdr, dwFlags);
if(_psel) _psel->Update(TRUE); // now update the caret
return hr; }
/*
* GetECDefaultHeightAndWidth (pts, hdc, lZoomNumerator, lZoomDenominator, * yPixelsPerInch, pxAveWidth, pxOverhang, pxUnderhang) * * @mfunc Helper for host to get ave char width and height for default * character set for the control. * * @rdesc Height of default character set * * @devnote: * This really only s/b called by the window's host. */ LONG GetECDefaultHeightAndWidth( ITextServices *pts, //@parm ITextServices to conver to CTxtEdit.
HDC hdc, //@parm DC to use for retrieving the font.
LONG lZoomNumerator, //@parm Zoom numerator
LONG lZoomDenominator, //@parm Zoom denominator
LONG yPixelsPerInch, //@parm Pixels per inch for hdc
LONG *pxAveWidth, //@parm Optional ave width of character
LONG *pxOverhang, //@parm Optional overhang
LONG *pxUnderhang) //@parm Optional underhang
{ CLock lock; // Uses global (shared) FontCache
// Convert the text-edit ptr
CTxtEdit *ped = (CTxtEdit *) pts;
// Get the CCcs that has all the information we need
yPixelsPerInch = MulDiv(yPixelsPerInch, lZoomNumerator, lZoomDenominator); CCcs *pccs = fc().GetCcs(ped->GetCharFormat(-1), yPixelsPerInch);
if(!pccs) return 0;
if(pxAveWidth) *pxAveWidth = pccs->_xAveCharWidth;
if(pxOverhang) *pxOverhang = pccs->_xOverhang; // Return overhang
if(pxUnderhang) *pxUnderhang = pccs->_xUnderhang; // Return underhang
SHORT yAdjustFE = pccs->AdjustFEHeight(!ped->fUseUIFont() && ped->_pdp->IsMultiLine()); LONG yHeight = pccs->_yHeight + (yAdjustFE << 1);
pccs->Release(); // Release the CCcs
return yHeight; }
/*
* CTxtEdit::TxScrollWindowEx (dx, dy, lprcScroll, lprcClip, hrgnUpdate, * lprcUpdate, fuScroll) * @mfunc * Request Text Host to scroll the content of the specified client area * * @comm * This method is only valid when the control is in-place active; * calls while inactive may fail. */ void CTxtEdit::TxScrollWindowEx ( INT dx, //@parm Amount of horizontal scrolling
INT dy, //@parm Amount of vertical scrolling
LPCRECT lprcScroll, //@parm Scroll rectangle
LPCRECT lprcClip, //@parm Clip rectangle
HRGN hrgnUpdate, //@parm Handle of update region
LPRECT lprcUpdate, //@parm Update rectangle
UINT fuScroll) //@parm Scrolling flags
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEEXTERN, "CTxtEdit::TxScrollWindowEx");
if(_fInPlaceActive) { #if !defined(NOMAGELLAN)
CMagellanBMPStateWrap bmpOff(*this, NULL); #endif
_phost->TxScrollWindowEx(dx, dy, lprcScroll, lprcClip, hrgnUpdate, lprcUpdate, fuScroll);
// Tell all objects that they may need to update their position
// RECTs if scrolling occurred.
if(_pobjmgr) { RECT rcClient;
if(!lprcScroll) { TxGetClientRect(&rcClient); lprcScroll = &rcClient; } _pobjmgr->ScrollObjects(dx, dy, lprcScroll); } } }
/*
* CTxtEdit::GetAcpFromCp (cp) * * @mfunc * Get API cp (acp) from Unicode cp in this text instance. The API cp * may be Unicode, in which case it equals cp, or MBCS, in which case * it's greater than cp if any Unicode characters preceding cp convert * to double-byte characters. An MBCS cp is the BYTE index of a character * relative to the start of the story, while a Unicode cp is the character * index. The values are the same if all charsets are represented by * SBCS charsets, e.g., ASCII. If all characters are represented by * double-byte characters, then acp = 2*cp. * * @rdesc * MBCS Acp from Unicode cp in this text instance * * @devnote * This could be made more efficient by having the selection maintain * the acp that corresponds to its _rpTX._cp, provided RE 1.0 mode is * active. Alternatively CTxtEdit could have a _prg that tracks this * value, but at a higher cost (17 DWORDs instead of 1 per instance). * * FUTURE: we might want to have a conversion-mode state instead of just * _f10Mode, since some people might want to know use MBCS cp's even in * RE 3.0. If so, use the corresponding new state flag instead of * Get10Mode() in the following. */ LONG CTxtEdit::GetAcpFromCp( LONG cp, //@parm Unicode cp to convert to MBCS cp
BOOL fPrecise) //@parm fPrecise flag to get byte count for MBCS
{ if(!(IsFE() && (fCpMap() || fPrecise))) // RE 2.0 and higher use char-count
return cp; // cp's, while RE 1.0 uses byte
// counts
// bPrecise is for Ansi Apps that want byte counts
// (e.g. Outlook Subject line)
CRchTxtPtr rtp(this); // Start at cp = 0
return rtp.GetCachFromCch(cp); }
LONG CTxtEdit::GetCpFromAcp( LONG acp, //@parm MBCS cp to convert to Unicode cp
BOOL fPrecise) //@parm fPrecise flag to get Unicode cp for MBCS
{ if( acp == -1 || !(IsFE() && (fCpMap() || fPrecise))) return acp;
CRchTxtPtr rtp(this); // Start at cp = 0
return rtp.GetCchFromCach(acp); }
/*
* CTxtEdit::GetViewKind (plres) * * @mfunc * get view mode * * @rdesc * HRESULT = (plres) ? NOERROR : E_INVALIDARG * * @devnote * This could be a TOM property method (along with SetViewMode()) */ HRESULT CTxtEdit::GetViewKind( LRESULT *plres) //@parm Out parm to receive view mode
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetViewKind");
if(!plres) return E_INVALIDARG;
*plres = IsInOutlineView() ? VM_OUTLINE : VM_NORMAL; return NOERROR; }
/*
* CTxtEdit::SetViewKind (Value) * * @mfunc * Turn outline mode on or off * * @rdesc * HRESULT = IsRich() ? NOERROR : S_FALSE * * @devnote * This could be a TOM property method (along with GetViewMode()) */ HRESULT CTxtEdit::SetViewKind( long Value) //@parm Turn outline mode on/off for Value nonzero/zero
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::SetViewKind");
if(!IsRich() || !_pdp->IsMultiLine()) return S_FALSE;
Value = (Value == VM_OUTLINE); // Convert to 1/0
if(_fOutlineView != Value) { HCURSOR hcur = TxSetCursor(LoadCursor(0, IDC_WAIT), NULL); CTxtSelection *psel = GetSel();
_fOutlineView = (WORD)Value; if(!GetAdjustedTextLength()) // No text in control: in outline
{ // view, use Heading 1; in normal
CParaFormat PF; // view, use Normal style
PF._sStyle = (SHORT)(IsInOutlineView() ? STYLE_HEADING_1 : STYLE_NORMAL); psel->SetParaStyle(&PF, NULL, PFM_STYLE); } else { // There is text. Make sure there is paragraph formatting.
_psel->Check_rpPF(); }
psel->CheckIfSelHasEOP(-1, 0); _pdp->UpdateView(); psel->Update(TRUE); TxSetCursor(hcur, NULL); } return NOERROR; }
/*
* CTxtEdit::GetViewScale (pValue) * * @mfunc * get view zoom scale in percent * * @rdesc * HRESULT = (pValue) ? NOERROR : E_INVALIDARG * * @devnote * This could be a TOM property method (along with SetViewScale()) */ HRESULT CTxtEdit::GetViewScale( long *pValue) //@parm Get % zoom factor
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetViewScale");
if(!pValue) return E_INVALIDARG;
*pValue = 100; if(GetZoomNumerator() && GetZoomDenominator()) *pValue = (100*GetZoomNumerator())/GetZoomDenominator();
return NOERROR; }
/*
* CTxtEdit::SetViewScale (Value) * * @mfunc * Set zoom numerator equal to the scale percentage Value and * zoom denominator equal to 100 * * @rdesc * NOERROR * * @devnote * This could be a TOM property method (along with GetViewScale()) */ HRESULT CTxtEdit::SetViewScale( long Value) //@parm Set view scale factor
{ TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::SetViewScale");
if((unsigned)Value > 2000) return E_INVALIDARG;
SetZoomNumerator(Value); SetZoomDenominator(100); return NOERROR; }
/*
* CTxtEdit::UpdateOutline() * * @mfunc * Update selection and screen after ExpandOutline() operation * * @comm * This method is only valid when the control is in-place active; * calls while inactive may fail. */ HRESULT CTxtEdit::UpdateOutline() { Assert(IsInOutlineView());
GetSel()->Update(FALSE); TxInvalidateRect(NULL, TRUE); return NOERROR; }
/*
* CTxtEdit::MoveSelection(lparam, publdr) * * @mfunc * Move selected text up/down by the number of paragraphs given by * LOWORD(lparam). * * @rdesc * TRUE iff movement occurred */ HRESULT CTxtEdit::MoveSelection ( LPARAM lparam, //@parm # paragraphs to move by
IUndoBuilder *publdr) //@parm undo builder to receive antievents
{ TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::MoveSelection");
CFreezeDisplay fd(_pdp); CTxtSelection * psel = GetSel(); LONG cch; LONG cchSel = psel->GetCch(); LONG cpMin, cpMost; LONG cpSel = psel->GetCp(); IDataObject * pdo = NULL; CTxtRange rg(*psel); LONG cpNext = 0; LONG cpCur = 0; BOOL fDeleteCR = FALSE;
if(publdr) publdr->StopGroupTyping();
rg.Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost); CPFRunPtr rp(rg); cch = rp.FindExpanded(); // Include subordinate paras
if(cch < 0) cch = tomForward; rg.SetExtend(TRUE); rg.Advance(cch); cpMost = rg.GetCpMost();
if(lparam > 0 && cpMost == GetTextLength()) { Beep(); // Already at end
return S_FALSE; }
HRESULT hr = _ldte.RangeToDataObject(&rg, SF_RTF, &pdo); if(hr != NOERROR) goto error;
if(lparam > 0) psel->EndOf(tomParagraph, FALSE, NULL); else psel->StartOf(tomParagraph, FALSE, NULL);
cpCur = psel->GetCp(); hr = psel->Move(tomParagraph, lparam, NULL); if(psel->GetCp() == cpCur) { psel->Set(cpSel, cchSel); Beep(); goto error; }
// Since psel->Move() calls psel->Update(), the selection is forced
// to be in noncollapsed text. Going backward, this might leave the
// selection just before the EOP of a paragraph, instead of being at the
// start of the paragraph where it should be. Going forward it may have
// tried to reach the EOD, but was adjusted backward. This case gets
// a bit awkward...
if(psel->GetCp() < cpCur) // Going backward: be sure
psel->StartOf(tomParagraph, FALSE, NULL);// end up at start of para
else if(!psel->_rpTX.IsAfterEOP()) // Going forward and sel
{ // adjusted backward
psel->SetExtend(FALSE); psel->Advance(tomForward); // Go to final CR, insert a CR
CTxtRange rgDel(*psel); // use psel because UI
rgDel.ReplaceRange(1, szCR, publdr, SELRR_REMEMBERRANGE); psel->Advance(1); fDeleteCR = TRUE; // Remember to delete it
}
cpCur = psel->GetCp(); hr = _ldte.PasteDataObjectToRange(pdo, psel, 0, NULL, publdr, PDOR_NONE); if(hr != NOERROR) goto error;
if(fDeleteCR) // Delete CR (final CR becomes
{ // CR for this para). Don't
CTxtRange rgDel(*psel); // use psel because UI
Assert(rgDel._rpTX.IsAfterEOP()); // restricts it's ability to
rgDel.Delete(tomCharacter, -1, &cch); // delete
}
cpNext = psel->GetCp(); psel->Set(cpCur, 0); psel->CheckOutlineLevel(publdr); psel->Set(cpNext, 0); psel->CheckOutlineLevel(publdr);
// Now set selection anti-events. If selection preceded paste point,
// subtract its length from redo position, since selection will get
// deleted if we are doing a DRAGMOVE within this instance.
cch = cpMost - cpMin; // cch of rg
if(cpSel < cpCur) cpNext -= cch;
psel->Set(psel->GetCp() + fDeleteCR, cch); // Include final CR
// rg.ReplaceRange won't delete final CR, so remember if it's included
fDeleteCR = rg.GetCpMost() == GetTextLength(); rg.ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);
if(fDeleteCR) // Needed to delete final CR
rg.DeleteTerminatingEOP(publdr); // Delete one immediately
// before it instead
rg.CheckOutlineLevel(publdr); if(publdr) { HandleSelectionAEInfo(this, publdr, cpSel, cchSel, cpNext, cch, SELAE_FORCEREPLACE); } hr = NOERROR;
error: if(pdo) pdo->Release(); return hr; }
/*
* CTxtEdit::SetReleaseHost * * @mfunc Handles notification that edit control must keep its * reference to the host alive. * * @rdesc None. */ void CTxtEdit::SetReleaseHost() { _phost->AddRef(); _fReleaseHost = TRUE; }
#if !defined(NOMAGELLAN)
/*
* CTxtEdit::HandleMouseWheel(wparam, lparam) * * @mfunc Handles scrolling as a result of rotating a mouse roller wheel. * * @rdesc LRESULT */ LRESULT CTxtEdit::HandleMouseWheel( WPARAM wparam, LPARAM lparam) { // This bit of global state is OK
static LONG gcWheelDelta = 0; short zdelta = (short)HIWORD(wparam); BOOL fScrollByPages = FALSE;
// Cancel middle mouse scrolling if it's going.
OnTxMButtonUp(0, 0, 0);
// Handle zoom or data zoom
if((wparam & MK_CONTROL) == MK_CONTROL) { // bug fix 5760
// prevent zooming if control is NOT rich or
// is a single line control
if (!_pdp->IsMultiLine()) return 0;
LONG lViewScale; GetViewScale(&lViewScale); lViewScale += (zdelta/WHEEL_DELTA) * 10; // 10% per click
if(lViewScale <= 500 && lViewScale >= 10) // Word's limits
{ SetViewScale(lViewScale); _pdp->UpdateView(); } return 0; }
if(wparam & (MK_SHIFT | MK_CONTROL)) return 0;
gcWheelDelta += zdelta;
if(abs(gcWheelDelta) >= WHEEL_DELTA) { LONG cLineScroll = W32->GetRollerLineScrollCount(); if(cLineScroll != -1) cLineScroll *= abs(gcWheelDelta)/WHEEL_DELTA;
gcWheelDelta %= WHEEL_DELTA;
// -1 means scroll by pages; so simply call page up/down.
if(cLineScroll == -1) { fScrollByPages = TRUE; if(_pdp) _pdp->VScroll(zdelta < 0 ? SB_PAGEDOWN : SB_PAGEUP, 0); } else { mouse.MagellanRollScroll(_pdp, zdelta, cLineScroll, SMOOTH_ROLL_NUM, SMOOTH_ROLL_DENOM, TRUE); }
// notify through the messagefilter that we scrolled
if(_dwEventMask & ENM_SCROLLEVENTS) { MSGFILTER msgfltr; ZeroMemory(&msgfltr, sizeof(MSGFILTER)); msgfltr.msg = WM_VSCROLL; msgfltr.wParam = fScrollByPages ? (zdelta < 0 ? SB_PAGEDOWN: SB_PAGEUP): (zdelta < 0 ? SB_LINEDOWN: SB_LINEUP);
// We don't check the result of this call --
// it's not a message we received and we're not going to
// process it any further
_phost->TxNotify(EN_MSGFILTER, &msgfltr); } return TRUE; } return 0; } #endif
|