6345 lines
184 KiB
C++
6345 lines
184 KiB
C++
/*
|
|
* @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
|