// Scintilla source code edit control /** @file ScintillaWin.cxx ** Windows specific subclass of ScintillaBase. **/ // Copyright 1998-2003 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #undef _WIN32_WINNT #define _WIN32_WINNT 0x0500 #undef WINVER #define WINVER 0x0500 #include #include #include #include #if defined(NTDDI_WIN7) && !defined(DISABLE_D2D) #define USE_D2D 1 #endif #if defined(USE_D2D) #include #include #endif #include "Platform.h" #include "ILexer.h" #include "Scintilla.h" #ifdef SCI_LEXER #include "SciLexer.h" #include "LexerModule.h" #endif #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "ContractionState.h" #include "CellBuffer.h" #include "CallTip.h" #include "KeyMap.h" #include "Indicator.h" #include "XPM.h" #include "LineMarker.h" #include "Style.h" #include "AutoComplete.h" #include "ViewStyle.h" #include "CharClassify.h" #include "Decoration.h" #include "CaseFolder.h" #include "Document.h" #include "Selection.h" #include "PositionCache.h" #include "Editor.h" #include "ScintillaBase.h" #include "UniConversion.h" #include "CaseConvert.h" #include "PlatWin.h" #ifdef SCI_LEXER #include "ExternalLexer.h" #endif #ifndef SPI_GETWHEELSCROLLLINES #define SPI_GETWHEELSCROLLLINES 104 #endif #ifndef WM_UNICHAR #define WM_UNICHAR 0x0109 #endif #ifndef UNICODE_NOCHAR #define UNICODE_NOCHAR 0xFFFF #endif #ifndef WM_IME_STARTCOMPOSITION #include #endif #include #include #include #ifndef MK_ALT #define MK_ALT 32 #endif #define SC_WIN_IDLE 5001 typedef BOOL (WINAPI *TrackMouseEventSig)(LPTRACKMOUSEEVENT); // GCC has trouble with the standard COM ABI so do it the old C way with explicit vtables. const TCHAR scintillaClassName[] = TEXT("Scintilla"); const TCHAR callClassName[] = TEXT("CallTip"); #ifdef SCI_NAMESPACE using namespace Scintilla; #endif // Take care of 32/64 bit pointers #ifdef GetWindowLongPtr static void *PointerFromWindow(HWND hWnd) { return reinterpret_cast(::GetWindowLongPtr(hWnd, 0)); } static void SetWindowPointer(HWND hWnd, void *ptr) { ::SetWindowLongPtr(hWnd, 0, reinterpret_cast(ptr)); } static void SetWindowID(HWND hWnd, int identifier) { ::SetWindowLongPtr(hWnd, GWLP_ID, identifier); } #else static void *PointerFromWindow(HWND hWnd) { return reinterpret_cast(::GetWindowLong(hWnd, 0)); } static void SetWindowPointer(HWND hWnd, void *ptr) { ::SetWindowLong(hWnd, 0, reinterpret_cast(ptr)); } static void SetWindowID(HWND hWnd, int identifier) { ::SetWindowLong(hWnd, GWL_ID, identifier); } #endif class ScintillaWin; // Forward declaration for COM interface subobjects typedef void VFunction(void); /** */ class FormatEnumerator { public: VFunction **vtbl; int ref; int pos; CLIPFORMAT formats[2]; int formatsLen; FormatEnumerator(int pos_, CLIPFORMAT formats_[], int formatsLen_); }; /** */ class DropSource { public: VFunction **vtbl; ScintillaWin *sci; DropSource(); }; /** */ class DataObject { public: VFunction **vtbl; ScintillaWin *sci; DataObject(); }; /** */ class DropTarget { public: VFunction **vtbl; ScintillaWin *sci; DropTarget(); }; /** */ class ScintillaWin : public ScintillaBase { bool lastKeyDownConsumed; bool capturedMouse; bool trackedMouseLeave; TrackMouseEventSig TrackMouseEventFn; unsigned int linesPerScroll; ///< Intellimouse support int wheelDelta; ///< Wheel delta from roll HRGN hRgnUpdate; bool hasOKText; CLIPFORMAT cfColumnSelect; CLIPFORMAT cfLineSelect; HRESULT hrOle; DropSource ds; DataObject dob; DropTarget dt; static HINSTANCE hInstance; #if defined(USE_D2D) ID2D1HwndRenderTarget *pRenderTarget; bool renderTargetValid; #endif ScintillaWin(HWND hwnd); ScintillaWin(const ScintillaWin &); virtual ~ScintillaWin(); ScintillaWin &operator=(const ScintillaWin &); virtual void Initialise(); virtual void Finalise(); #if defined(USE_D2D) void EnsureRenderTarget(); void DropRenderTarget(); #endif HWND MainHWND(); static sptr_t DirectFunction( ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam); static sptr_t PASCAL SWndProc( HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam); static sptr_t PASCAL CTWndProc( HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam); enum { invalidTimerID, standardTimerID, idleTimerID }; virtual bool DragThreshold(Point ptStart, Point ptNow); virtual void StartDrag(); sptr_t WndPaint(uptr_t wParam); sptr_t HandleComposition(uptr_t wParam, sptr_t lParam); UINT CodePageOfDocument(); virtual bool ValidCodePage(int codePage) const; virtual sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam); virtual bool SetIdle(bool on); virtual void SetTicking(bool on); virtual void SetMouseCapture(bool on); virtual bool HaveMouseCapture(); virtual void SetTrackMouseLeaveEvent(bool on); virtual bool PaintContains(PRectangle rc); virtual void ScrollText(int linesToMove); virtual void UpdateSystemCaret(); virtual void SetVerticalScrollPos(); virtual void SetHorizontalScrollPos(); virtual bool ModifyScrollBars(int nMax, int nPage); virtual void NotifyChange(); virtual void NotifyFocus(bool focus); virtual void SetCtrlID(int identifier); virtual int GetCtrlID(); virtual void NotifyParent(SCNotification scn); virtual void NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt); virtual CaseFolder *CaseFolderForEncoding(); virtual std::string CaseMapString(const std::string &s, int caseMapping); virtual void Copy(); virtual void CopyAllowLine(); virtual bool CanPaste(); virtual void Paste(); virtual void CreateCallTipWindow(PRectangle rc); virtual void AddToPopUp(const char *label, int cmd = 0, bool enabled = true); virtual void ClaimSelection(); // DBCS void ImeStartComposition(); void ImeEndComposition(); void AddCharBytes(char b0, char b1); void GetIntelliMouseParameters(); virtual void CopyToClipboard(const SelectionText &selectedText); void ScrollMessage(WPARAM wParam); void HorizontalScrollMessage(WPARAM wParam); void FullPaint(); void FullPaintDC(HDC dc); bool IsCompatibleDC(HDC dc); DWORD EffectFromState(DWORD grfKeyState) const; virtual int SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw); virtual bool GetScrollInfo(int nBar, LPSCROLLINFO lpsi); void ChangeScrollPos(int barType, int pos); void InsertPasteText(const char *text, int len, SelectionPosition selStart, bool isRectangular, bool isLine); public: // Public for benefit of Scintilla_DirectFunction virtual sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam); /// Implement IUnknown STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv); STDMETHODIMP_(ULONG)AddRef(); STDMETHODIMP_(ULONG)Release(); /// Implement IDropTarget STDMETHODIMP DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect); STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect); STDMETHODIMP DragLeave(); STDMETHODIMP Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect); /// Implement important part of IDataObject STDMETHODIMP GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM); static bool Register(HINSTANCE hInstance_); static bool Unregister(); friend class DropSource; friend class DataObject; friend class DropTarget; bool DragIsRectangularOK(CLIPFORMAT fmt) const { return drag.rectangular && (fmt == cfColumnSelect); } private: // For use in creating a system caret bool HasCaretSizeChanged() const; BOOL CreateSystemCaret(); BOOL DestroySystemCaret(); HBITMAP sysCaretBitmap; int sysCaretWidth; int sysCaretHeight; bool keysAlwaysUnicode; }; HINSTANCE ScintillaWin::hInstance = 0; ScintillaWin::ScintillaWin(HWND hwnd) { lastKeyDownConsumed = false; capturedMouse = false; trackedMouseLeave = false; TrackMouseEventFn = 0; linesPerScroll = 0; wheelDelta = 0; // Wheel delta from roll hRgnUpdate = 0; hasOKText = false; // There does not seem to be a real standard for indicating that the clipboard // contains a rectangular selection, so copy Developer Studio. cfColumnSelect = static_cast( ::RegisterClipboardFormat(TEXT("MSDEVColumnSelect"))); // Likewise for line-copy (copies a full line when no text is selected) cfLineSelect = static_cast( ::RegisterClipboardFormat(TEXT("MSDEVLineSelect"))); hrOle = E_FAIL; wMain = hwnd; dob.sci = this; ds.sci = this; dt.sci = this; sysCaretBitmap = 0; sysCaretWidth = 0; sysCaretHeight = 0; #if defined(USE_D2D) pRenderTarget = 0; renderTargetValid = true; #endif keysAlwaysUnicode = false; caret.period = ::GetCaretBlinkTime(); if (caret.period < 0) caret.period = 0; Initialise(); } ScintillaWin::~ScintillaWin() {} void ScintillaWin::Initialise() { // Initialize COM. If the app has already done this it will have // no effect. If the app hasnt, we really shouldnt ask them to call // it just so this internal feature works. hrOle = ::OleInitialize(NULL); // Find TrackMouseEvent which is available on Windows > 95 HMODULE user32 = ::GetModuleHandle(TEXT("user32.dll")); if (user32) TrackMouseEventFn = (TrackMouseEventSig)::GetProcAddress(user32, "TrackMouseEvent"); if (TrackMouseEventFn == NULL) { // Windows 95 has an emulation in comctl32.dll:_TrackMouseEvent HMODULE commctrl32 = ::LoadLibrary(TEXT("comctl32.dll")); if (commctrl32 != NULL) { TrackMouseEventFn = (TrackMouseEventSig) ::GetProcAddress(commctrl32, "_TrackMouseEvent"); } } } void ScintillaWin::Finalise() { ScintillaBase::Finalise(); SetTicking(false); SetIdle(false); #if defined(USE_D2D) DropRenderTarget(); #endif ::RevokeDragDrop(MainHWND()); if (SUCCEEDED(hrOle)) { ::OleUninitialize(); } } #if defined(USE_D2D) void ScintillaWin::EnsureRenderTarget() { if (!renderTargetValid) { DropRenderTarget(); renderTargetValid = true; } if (pD2DFactory && !pRenderTarget) { RECT rc; HWND hw = MainHWND(); GetClientRect(hw, &rc); D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); // Create a Direct2D render target. #if 1 D2D1_HWND_RENDER_TARGET_PROPERTIES dhrtp; dhrtp.hwnd = hw; dhrtp.pixelSize = size; dhrtp.presentOptions = D2D1_PRESENT_OPTIONS_NONE; D2D1_RENDER_TARGET_PROPERTIES drtp; drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; drtp.pixelFormat.format = DXGI_FORMAT_UNKNOWN; drtp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN; drtp.dpiX = 96.0; drtp.dpiY = 96.0; drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE; drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; pD2DFactory->CreateHwndRenderTarget(drtp, dhrtp, &pRenderTarget); #else pD2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_DEFAULT , D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), 96.0f, 96.0f, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT), D2D1::HwndRenderTargetProperties(hw, size), &pRenderTarget); #endif // Pixmaps were created to be compatible with previous render target so // need to be recreated. DropGraphics(false); } } void ScintillaWin::DropRenderTarget() { if (pRenderTarget) { pRenderTarget->Release(); pRenderTarget = 0; } } #endif HWND ScintillaWin::MainHWND() { return reinterpret_cast(wMain.GetID()); } bool ScintillaWin::DragThreshold(Point ptStart, Point ptNow) { int xMove = abs(ptStart.x - ptNow.x); int yMove = abs(ptStart.y - ptNow.y); return (xMove > ::GetSystemMetrics(SM_CXDRAG)) || (yMove > ::GetSystemMetrics(SM_CYDRAG)); } void ScintillaWin::StartDrag() { inDragDrop = ddDragging; DWORD dwEffect = 0; dropWentOutside = true; IDataObject *pDataObject = reinterpret_cast(&dob); IDropSource *pDropSource = reinterpret_cast(&ds); //Platform::DebugPrintf("About to DoDragDrop %x %x\n", pDataObject, pDropSource); HRESULT hr = ::DoDragDrop( pDataObject, pDropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect); //Platform::DebugPrintf("DoDragDrop = %x\n", hr); if (SUCCEEDED(hr)) { if ((hr == DRAGDROP_S_DROP) && (dwEffect == DROPEFFECT_MOVE) && dropWentOutside) { // Remove dragged out text ClearSelection(); } } inDragDrop = ddNone; SetDragPosition(SelectionPosition(invalidPosition)); } // Avoid warnings everywhere for old style casts by concentrating them here static WORD LoWord(DWORD l) { return LOWORD(l); } static WORD HiWord(DWORD l) { return HIWORD(l); } static int InputCodePage() { HKL inputLocale = ::GetKeyboardLayout(0); LANGID inputLang = LOWORD(inputLocale); char sCodePage[10]; int res = ::GetLocaleInfoA(MAKELCID(inputLang, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE, sCodePage, sizeof(sCodePage)); if (!res) return 0; return atoi(sCodePage); } #ifndef VK_OEM_2 static const int VK_OEM_2=0xbf; static const int VK_OEM_3=0xc0; static const int VK_OEM_4=0xdb; static const int VK_OEM_5=0xdc; static const int VK_OEM_6=0xdd; #endif /** Map the key codes to their equivalent SCK_ form. */ static int KeyTranslate(int keyIn) { //PLATFORM_ASSERT(!keyIn); switch (keyIn) { case VK_DOWN: return SCK_DOWN; case VK_UP: return SCK_UP; case VK_LEFT: return SCK_LEFT; case VK_RIGHT: return SCK_RIGHT; case VK_HOME: return SCK_HOME; case VK_END: return SCK_END; case VK_PRIOR: return SCK_PRIOR; case VK_NEXT: return SCK_NEXT; case VK_DELETE: return SCK_DELETE; case VK_INSERT: return SCK_INSERT; case VK_ESCAPE: return SCK_ESCAPE; case VK_BACK: return SCK_BACK; case VK_TAB: return SCK_TAB; case VK_RETURN: return SCK_RETURN; case VK_ADD: return SCK_ADD; case VK_SUBTRACT: return SCK_SUBTRACT; case VK_DIVIDE: return SCK_DIVIDE; case VK_LWIN: return SCK_WIN; case VK_RWIN: return SCK_RWIN; case VK_APPS: return SCK_MENU; case VK_OEM_2: return '/'; case VK_OEM_3: return '`'; case VK_OEM_4: return '['; case VK_OEM_5: return '\\'; case VK_OEM_6: return ']'; default: return keyIn; } } LRESULT ScintillaWin::WndPaint(uptr_t wParam) { //ElapsedTime et; // Redirect assertions to debug output and save current state bool assertsPopup = Platform::ShowAssertionPopUps(false); paintState = painting; PAINTSTRUCT ps; PAINTSTRUCT *pps; bool IsOcxCtrl = (wParam != 0); // if wParam != 0, it contains // a PAINSTRUCT* from the OCX // Removed since this interferes with reporting other assertions as it occurs repeatedly //PLATFORM_ASSERT(hRgnUpdate == NULL); hRgnUpdate = ::CreateRectRgn(0, 0, 0, 0); if (IsOcxCtrl) { pps = reinterpret_cast(wParam); } else { ::GetUpdateRgn(MainHWND(), hRgnUpdate, FALSE); pps = &ps; ::BeginPaint(MainHWND(), pps); } rcPaint = PRectangle(pps->rcPaint.left, pps->rcPaint.top, pps->rcPaint.right, pps->rcPaint.bottom); PRectangle rcClient = GetClientRectangle(); paintingAllText = rcPaint.Contains(rcClient); if (technology == SC_TECHNOLOGY_DEFAULT) { AutoSurface surfaceWindow(pps->hdc, this); if (surfaceWindow) { Paint(surfaceWindow, rcPaint); surfaceWindow->Release(); } } else { #if defined(USE_D2D) for (int attempt=0;attempt<2;attempt++) { EnsureRenderTarget(); AutoSurface surfaceWindow(pRenderTarget, this); if (surfaceWindow) { pRenderTarget->BeginDraw(); Paint(surfaceWindow, rcPaint); surfaceWindow->Release(); HRESULT hr = pRenderTarget->EndDraw(); if (hr == D2DERR_RECREATE_TARGET) { DropRenderTarget(); } else { break; } } } #endif } if (hRgnUpdate) { ::DeleteRgn(hRgnUpdate); hRgnUpdate = 0; } if (!IsOcxCtrl) ::EndPaint(MainHWND(), pps); if (paintState == paintAbandoned) { // Painting area was insufficient to cover new styling or brace highlight positions FullPaint(); } paintState = notPainting; // Restore debug output state Platform::ShowAssertionPopUps(assertsPopup); //Platform::DebugPrintf("Paint took %g\n", et.Duration()); return 0l; } sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) { if (lParam & GCS_RESULTSTR) { HIMC hIMC = ::ImmGetContext(MainHWND()); if (hIMC) { const int maxLenInputIME = 200; wchar_t wcs[maxLenInputIME]; LONG bytes = ::ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2); int wides = bytes / 2; if (IsUnicodeMode()) { char utfval[maxLenInputIME * 3]; unsigned int len = UTF8Length(wcs, wides); UTF8FromUTF16(wcs, wides, utfval, len); utfval[len] = '\0'; AddCharUTF(utfval, len); } else { char dbcsval[maxLenInputIME * 2]; int size = ::WideCharToMultiByte(InputCodePage(), 0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0); for (int i=0; idbcsCodePage); } sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { try { //Platform::DebugPrintf("S M:%x WP:%x L:%x\n", iMessage, wParam, lParam); iMessage = SciMessageFromEM(iMessage); switch (iMessage) { case WM_CREATE: ctrlID = ::GetDlgCtrlID(reinterpret_cast(wMain.GetID())); // Get Intellimouse scroll line parameters GetIntelliMouseParameters(); ::RegisterDragDrop(MainHWND(), reinterpret_cast(&dt)); break; case WM_COMMAND: Command(LoWord(wParam)); break; case WM_PAINT: return WndPaint(wParam); case WM_PRINTCLIENT: { HDC hdc = reinterpret_cast(wParam); if (!IsCompatibleDC(hdc)) { return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); } FullPaintDC(hdc); } break; case WM_VSCROLL: ScrollMessage(wParam); break; case WM_HSCROLL: HorizontalScrollMessage(wParam); break; case WM_SIZE: { #if defined(USE_D2D) if (paintState == notPainting) { DropRenderTarget(); } else { renderTargetValid = false; } #endif //Platform::DebugPrintf("Scintilla WM_SIZE %d %d\n", LoWord(lParam), HiWord(lParam)); ChangeSize(); } break; case WM_MOUSEWHEEL: // if autocomplete list active then send mousewheel message to it if (ac.Active()) { HWND hWnd = reinterpret_cast(ac.lb->GetID()); ::SendMessage(hWnd, iMessage, wParam, lParam); break; } // Don't handle datazoom. // (A good idea for datazoom would be to "fold" or "unfold" details. // i.e. if datazoomed out only class structures are visible, when datazooming in the control // structures appear, then eventually the individual statements...) if (wParam & MK_SHIFT) { return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); } // Either SCROLL or ZOOM. We handle the wheel steppings calculation wheelDelta -= static_cast(HiWord(wParam)); if (abs(wheelDelta) >= WHEEL_DELTA && linesPerScroll > 0) { int linesToScroll = linesPerScroll; if (linesPerScroll == WHEEL_PAGESCROLL) linesToScroll = LinesOnScreen() - 1; if (linesToScroll == 0) { linesToScroll = 1; } linesToScroll *= (wheelDelta / WHEEL_DELTA); if (wheelDelta >= 0) wheelDelta = wheelDelta % WHEEL_DELTA; else wheelDelta = - (-wheelDelta % WHEEL_DELTA); if (wParam & MK_CONTROL) { // Zoom! We play with the font sizes in the styles. // Number of steps/line is ignored, we just care if sizing up or down if (linesToScroll < 0) { KeyCommand(SCI_ZOOMIN); } else { KeyCommand(SCI_ZOOMOUT); } } else { // Scroll ScrollTo(topLine + linesToScroll); } } return 0; case WM_TIMER: if (wParam == standardTimerID && timer.ticking) { Tick(); } else if (wParam == idleTimerID && idler.state) { SendMessage(MainHWND(), SC_WIN_IDLE, 0, 1); } else { return 1; } break; case SC_WIN_IDLE: // wParam=dwTickCountInitial, or 0 to initialize. lParam=bSkipUserInputTest if (idler.state) { if (lParam || (WAIT_TIMEOUT == MsgWaitForMultipleObjects(0, 0, 0, 0, QS_INPUT|QS_HOTKEY))) { if (Idle()) { // User input was given priority above, but all events do get a turn. Other // messages, notifications, etc. will get interleaved with the idle messages. // However, some things like WM_PAINT are a lower priority, and will not fire // when there's a message posted. So, several times a second, we stop and let // the low priority events have a turn (after which the timer will fire again). DWORD dwCurrent = GetTickCount(); DWORD dwStart = wParam ? wParam : dwCurrent; const DWORD maxWorkTime = 50; if (dwCurrent >= dwStart && dwCurrent > maxWorkTime && dwCurrent - maxWorkTime < dwStart) PostMessage(MainHWND(), SC_WIN_IDLE, dwStart, 0); } else { SetIdle(false); } } } break; case WM_GETMINMAXINFO: return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_LBUTTONDOWN: { // For IME, set the composition string as the result string. HIMC hIMC = ::ImmGetContext(MainHWND()); ::ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); ::ImmReleaseContext(MainHWND(), hIMC); // //Platform::DebugPrintf("Buttdown %d %x %x %x %x %x\n",iMessage, wParam, lParam, // Platform::IsKeyDown(VK_SHIFT), // Platform::IsKeyDown(VK_CONTROL), // Platform::IsKeyDown(VK_MENU)); ::SetFocus(MainHWND()); ButtonDown(Point::FromLong(lParam), ::GetMessageTime(), (wParam & MK_SHIFT) != 0, (wParam & MK_CONTROL) != 0, Platform::IsKeyDown(VK_MENU)); } break; case WM_MBUTTONDOWN: ::SetFocus(MainHWND()); break; case WM_MOUSEMOVE: SetTrackMouseLeaveEvent(true); ButtonMove(Point::FromLong(lParam)); break; case WM_MOUSELEAVE: SetTrackMouseLeaveEvent(false); MouseLeave(); return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_LBUTTONUP: ButtonUp(Point::FromLong(lParam), ::GetMessageTime(), (wParam & MK_CONTROL) != 0); break; case WM_RBUTTONDOWN: ::SetFocus(MainHWND()); if (!PointInSelection(Point::FromLong(lParam))) { CancelModes(); SetEmptySelection(PositionFromLocation(Point::FromLong(lParam))); } break; case WM_SETCURSOR: if (LoWord(lParam) == HTCLIENT) { if (inDragDrop == ddDragging) { DisplayCursor(Window::cursorUp); } else { // Display regular (drag) cursor over selection POINT pt; if (0 != ::GetCursorPos(&pt)) { ::ScreenToClient(MainHWND(), &pt); if (PointInSelMargin(Point(pt.x, pt.y))) { DisplayCursor(GetMarginCursor(Point(pt.x, pt.y))); } else if (PointInSelection(Point(pt.x, pt.y)) && !SelectionEmpty()) { DisplayCursor(Window::cursorArrow); } else if (PointIsHotspot(Point(pt.x, pt.y))) { DisplayCursor(Window::cursorHand); } else { DisplayCursor(Window::cursorText); } } } return TRUE; } else { return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); } case WM_CHAR: if (((wParam >= 128) || !iscntrl(wParam)) || !lastKeyDownConsumed) { if (::IsWindowUnicode(MainHWND()) || keysAlwaysUnicode) { wchar_t wcs[2] = {static_cast(wParam), 0}; if (IsUnicodeMode()) { // For a wide character version of the window: char utfval[4]; unsigned int len = UTF8Length(wcs, 1); UTF8FromUTF16(wcs, 1, utfval, len); AddCharUTF(utfval, len); } else { UINT cpDest = CodePageOfDocument(); char inBufferCP[20]; int size = ::WideCharToMultiByte(cpDest, 0, wcs, 1, inBufferCP, sizeof(inBufferCP) - 1, 0, 0); inBufferCP[size] = '\0'; AddCharUTF(inBufferCP, size); } } else { if (IsUnicodeMode()) { AddCharBytes('\0', LOBYTE(wParam)); } else { AddChar(LOBYTE(wParam)); } } } return 0; case WM_UNICHAR: if (wParam == UNICODE_NOCHAR) { return IsUnicodeMode() ? 1 : 0; } else if (lastKeyDownConsumed) { return 1; } else { if (IsUnicodeMode()) { char utfval[4]; wchar_t wcs[2] = {static_cast(wParam), 0}; unsigned int len = UTF8Length(wcs, 1); UTF8FromUTF16(wcs, 1, utfval, len); AddCharUTF(utfval, len); return 1; } else { return 0; } } case WM_SYSKEYDOWN: case WM_KEYDOWN: { //Platform::DebugPrintf("S keydown %d %x %x %x %x\n",iMessage, wParam, lParam, ::IsKeyDown(VK_SHIFT), ::IsKeyDown(VK_CONTROL)); lastKeyDownConsumed = false; int ret = KeyDown(KeyTranslate(wParam), Platform::IsKeyDown(VK_SHIFT), Platform::IsKeyDown(VK_CONTROL), Platform::IsKeyDown(VK_MENU), &lastKeyDownConsumed); if (!ret && !lastKeyDownConsumed) { return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); } break; } case WM_IME_KEYDOWN: return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_KEYUP: //Platform::DebugPrintf("S keyup %d %x %x\n",iMessage, wParam, lParam); return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_SETTINGCHANGE: //Platform::DebugPrintf("Setting Changed\n"); InvalidateStyleData(); // Get Intellimouse scroll line parameters GetIntelliMouseParameters(); break; case WM_GETDLGCODE: return DLGC_HASSETSEL | DLGC_WANTALLKEYS; case WM_KILLFOCUS: { HWND wOther = reinterpret_cast(wParam); HWND wThis = MainHWND(); HWND wCT = reinterpret_cast(ct.wCallTip.GetID()); if (!wParam || !(::IsChild(wThis, wOther) || (wOther == wCT))) { SetFocusState(false); DestroySystemCaret(); } } break; case WM_SETFOCUS: SetFocusState(true); DestroySystemCaret(); CreateSystemCaret(); break; case WM_SYSCOLORCHANGE: //Platform::DebugPrintf("Setting Changed\n"); InvalidateStyleData(); break; case WM_IME_STARTCOMPOSITION: // dbcs ImeStartComposition(); return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_IME_ENDCOMPOSITION: // dbcs ImeEndComposition(); return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_IME_COMPOSITION: return HandleComposition(wParam, lParam); case WM_IME_CHAR: { AddCharBytes(HIBYTE(wParam), LOBYTE(wParam)); return 0; } case WM_CONTEXTMENU: if (displayPopupMenu) { Point pt = Point::FromLong(lParam); if ((pt.x == -1) && (pt.y == -1)) { // Caused by keyboard so display menu near caret pt = PointMainCaret(); POINT spt = {static_cast(pt.x), static_cast(pt.y)}; ::ClientToScreen(MainHWND(), &spt); pt = Point(spt.x, spt.y); } ContextMenu(pt); return 0; } return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_INPUTLANGCHANGE: //::SetThreadLocale(LOWORD(lParam)); return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_INPUTLANGCHANGEREQUEST: return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case WM_ERASEBKGND: return 1; // Avoid any background erasure as whole window painted. case WM_CAPTURECHANGED: capturedMouse = false; return 0; // These are not handled in Scintilla and its faster to dispatch them here. // Also moves time out to here so profile doesn't count lots of empty message calls. case WM_MOVE: case WM_MOUSEACTIVATE: case WM_NCHITTEST: case WM_NCCALCSIZE: case WM_NCPAINT: case WM_NCMOUSEMOVE: case WM_NCLBUTTONDOWN: case WM_IME_SETCONTEXT: case WM_IME_NOTIFY: case WM_SYSCOMMAND: case WM_WINDOWPOSCHANGING: case WM_WINDOWPOSCHANGED: return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); case EM_LINEFROMCHAR: if (static_cast(wParam) < 0) { wParam = SelectionStart().Position(); } return pdoc->LineFromPosition(wParam); case EM_EXLINEFROMCHAR: return pdoc->LineFromPosition(lParam); case EM_GETSEL: if (wParam) { *reinterpret_cast(wParam) = SelectionStart().Position(); } if (lParam) { *reinterpret_cast(lParam) = SelectionEnd().Position(); } return MAKELONG(SelectionStart().Position(), SelectionEnd().Position()); case EM_EXGETSEL: { if (lParam == 0) { return 0; } Sci_CharacterRange *pCR = reinterpret_cast(lParam); pCR->cpMin = SelectionStart().Position(); pCR->cpMax = SelectionEnd().Position(); } break; case EM_SETSEL: { int nStart = static_cast(wParam); int nEnd = static_cast(lParam); if (nStart == 0 && nEnd == -1) { nEnd = pdoc->Length(); } if (nStart == -1) { nStart = nEnd; // Remove selection } if (nStart > nEnd) { SetSelection(nEnd, nStart); } else { SetSelection(nStart, nEnd); } EnsureCaretVisible(); } break; case EM_EXSETSEL: { if (lParam == 0) { return 0; } Sci_CharacterRange *pCR = reinterpret_cast(lParam); sel.selType = Selection::selStream; if (pCR->cpMin == 0 && pCR->cpMax == -1) { SetSelection(pCR->cpMin, pdoc->Length()); } else { SetSelection(pCR->cpMin, pCR->cpMax); } EnsureCaretVisible(); return pdoc->LineFromPosition(SelectionStart().Position()); } case SCI_GETDIRECTFUNCTION: return reinterpret_cast(DirectFunction); case SCI_GETDIRECTPOINTER: return reinterpret_cast(this); case SCI_GRABFOCUS: ::SetFocus(MainHWND()); break; case SCI_SETKEYSUNICODE: keysAlwaysUnicode = wParam != 0; break; case SCI_GETKEYSUNICODE: return keysAlwaysUnicode; case SCI_SETTECHNOLOGY: if ((wParam == SC_TECHNOLOGY_DEFAULT) || (wParam == SC_TECHNOLOGY_DIRECTWRITE)) { if (technology != static_cast(wParam)) { if (static_cast(wParam) == SC_TECHNOLOGY_DIRECTWRITE) { #if defined(USE_D2D) if (!LoadD2D()) // Failed to load Direct2D or DirectWrite so no effect return 0; #else return 0; #endif } technology = wParam; // Invalidate all cached information including layout. DropGraphics(true); InvalidateStyleRedraw(); } } break; #ifdef SCI_LEXER case SCI_LOADLEXERLIBRARY: LexerManager::GetInstance()->Load(reinterpret_cast(lParam)); break; #endif default: return ScintillaBase::WndProc(iMessage, wParam, lParam); } } catch (std::bad_alloc &) { errorStatus = SC_STATUS_BADALLOC; } catch (...) { errorStatus = SC_STATUS_FAILURE; } return 0l; } bool ScintillaWin::ValidCodePage(int codePage) const { return codePage == 0 || codePage == SC_CP_UTF8 || codePage == 932 || codePage == 936 || codePage == 949 || codePage == 950 || codePage == 1361; } sptr_t ScintillaWin::DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); } void ScintillaWin::SetTicking(bool on) { if (timer.ticking != on) { timer.ticking = on; if (timer.ticking) { timer.tickerID = ::SetTimer(MainHWND(), standardTimerID, timer.tickSize, NULL) ? reinterpret_cast(standardTimerID) : 0; } else { ::KillTimer(MainHWND(), reinterpret_cast(timer.tickerID)); timer.tickerID = 0; } } timer.ticksToWait = caret.period; } bool ScintillaWin::SetIdle(bool on) { // On Win32 the Idler is implemented as a Timer on the Scintilla window. This // takes advantage of the fact that WM_TIMER messages are very low priority, // and are only posted when the message queue is empty, i.e. during idle time. if (idler.state != on) { if (on) { idler.idlerID = ::SetTimer(MainHWND(), idleTimerID, 10, NULL) ? reinterpret_cast(idleTimerID) : 0; } else { ::KillTimer(MainHWND(), reinterpret_cast(idler.idlerID)); idler.idlerID = 0; } idler.state = idler.idlerID != 0; } return idler.state; } void ScintillaWin::SetMouseCapture(bool on) { if (mouseDownCaptures) { if (on) { ::SetCapture(MainHWND()); } else { ::ReleaseCapture(); } } capturedMouse = on; } bool ScintillaWin::HaveMouseCapture() { // Cannot just see if GetCapture is this window as the scroll bar also sets capture for the window return capturedMouse; //return capturedMouse && (::GetCapture() == MainHWND()); } void ScintillaWin::SetTrackMouseLeaveEvent(bool on) { if (on && TrackMouseEventFn && !trackedMouseLeave) { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = MainHWND(); TrackMouseEventFn(&tme); } trackedMouseLeave = on; } bool ScintillaWin::PaintContains(PRectangle rc) { bool contains = true; if ((paintState == painting) && (!rc.Empty())) { if (!rcPaint.Contains(rc)) { contains = false; } else { // In bounding rectangle so check more accurately using region HRGN hRgnRange = ::CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); if (hRgnRange) { HRGN hRgnDest = ::CreateRectRgn(0, 0, 0, 0); if (hRgnDest) { int combination = ::CombineRgn(hRgnDest, hRgnRange, hRgnUpdate, RGN_DIFF); if (combination != NULLREGION) { contains = false; } ::DeleteRgn(hRgnDest); } ::DeleteRgn(hRgnRange); } } } return contains; } void ScintillaWin::ScrollText(int /* linesToMove */) { //Platform::DebugPrintf("ScintillaWin::ScrollText %d\n", linesToMove); //::ScrollWindow(MainHWND(), 0, // vs.lineHeight * linesToMove, 0, 0); //::UpdateWindow(MainHWND()); Redraw(); } void ScintillaWin::UpdateSystemCaret() { if (hasFocus) { if (HasCaretSizeChanged()) { DestroySystemCaret(); CreateSystemCaret(); } Point pos = PointMainCaret(); ::SetCaretPos(pos.x, pos.y); } } int ScintillaWin::SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw) { return ::SetScrollInfo(MainHWND(), nBar, lpsi, bRedraw); } bool ScintillaWin::GetScrollInfo(int nBar, LPSCROLLINFO lpsi) { return ::GetScrollInfo(MainHWND(), nBar, lpsi) ? true : false; } // Change the scroll position but avoid repaint if changing to same value void ScintillaWin::ChangeScrollPos(int barType, int pos) { SCROLLINFO sci = { sizeof(sci), 0, 0, 0, 0, 0, 0 }; sci.fMask = SIF_POS; GetScrollInfo(barType, &sci); if (sci.nPos != pos) { DwellEnd(true); sci.nPos = pos; SetScrollInfo(barType, &sci, TRUE); } } void ScintillaWin::SetVerticalScrollPos() { ChangeScrollPos(SB_VERT, topLine); } void ScintillaWin::SetHorizontalScrollPos() { ChangeScrollPos(SB_HORZ, xOffset); } bool ScintillaWin::ModifyScrollBars(int nMax, int nPage) { bool modified = false; SCROLLINFO sci = { sizeof(sci), 0, 0, 0, 0, 0, 0 }; sci.fMask = SIF_PAGE | SIF_RANGE; GetScrollInfo(SB_VERT, &sci); int vertEndPreferred = nMax; if (!verticalScrollBarVisible) nPage = vertEndPreferred + 1; if ((sci.nMin != 0) || (sci.nMax != vertEndPreferred) || (sci.nPage != static_cast(nPage)) || (sci.nPos != 0)) { sci.fMask = SIF_PAGE | SIF_RANGE; sci.nMin = 0; sci.nMax = vertEndPreferred; sci.nPage = nPage; sci.nPos = 0; sci.nTrackPos = 1; SetScrollInfo(SB_VERT, &sci, TRUE); modified = true; } PRectangle rcText = GetTextRectangle(); int horizEndPreferred = scrollWidth; if (horizEndPreferred < 0) horizEndPreferred = 0; unsigned int pageWidth = rcText.Width(); if (!horizontalScrollBarVisible || (wrapState != eWrapNone)) pageWidth = horizEndPreferred + 1; sci.fMask = SIF_PAGE | SIF_RANGE; GetScrollInfo(SB_HORZ, &sci); if ((sci.nMin != 0) || (sci.nMax != horizEndPreferred) || (sci.nPage != pageWidth) || (sci.nPos != 0)) { sci.fMask = SIF_PAGE | SIF_RANGE; sci.nMin = 0; sci.nMax = horizEndPreferred; sci.nPage = pageWidth; sci.nPos = 0; sci.nTrackPos = 1; SetScrollInfo(SB_HORZ, &sci, TRUE); modified = true; if (scrollWidth < static_cast(pageWidth)) { HorizontalScrollTo(0); } } return modified; } void ScintillaWin::NotifyChange() { ::SendMessage(::GetParent(MainHWND()), WM_COMMAND, MAKELONG(GetCtrlID(), SCEN_CHANGE), reinterpret_cast(MainHWND())); } void ScintillaWin::NotifyFocus(bool focus) { ::SendMessage(::GetParent(MainHWND()), WM_COMMAND, MAKELONG(GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), reinterpret_cast(MainHWND())); } void ScintillaWin::SetCtrlID(int identifier) { ::SetWindowID(reinterpret_cast(wMain.GetID()), identifier); } int ScintillaWin::GetCtrlID() { return ::GetDlgCtrlID(reinterpret_cast(wMain.GetID())); } void ScintillaWin::NotifyParent(SCNotification scn) { scn.nmhdr.hwndFrom = MainHWND(); scn.nmhdr.idFrom = GetCtrlID(); ::SendMessage(::GetParent(MainHWND()), WM_NOTIFY, GetCtrlID(), reinterpret_cast(&scn)); } void ScintillaWin::NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt) { //Platform::DebugPrintf("ScintillaWin Double click 0\n"); ScintillaBase::NotifyDoubleClick(pt, shift, ctrl, alt); // Send myself a WM_LBUTTONDBLCLK, so the container can handle it too. ::SendMessage(MainHWND(), WM_LBUTTONDBLCLK, shift ? MK_SHIFT : 0, MAKELPARAM(pt.x, pt.y)); } class CaseFolderDBCS : public CaseFolderTable { // Allocate the expandable storage here so that it does not need to be reallocated // for each call to Fold. std::vector utf16Mixed; std::vector utf16Folded; UINT cp; public: CaseFolderDBCS(UINT cp_) : cp(cp_) { StandardASCII(); } virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) { if ((lenMixed == 1) && (sizeFolded > 0)) { folded[0] = mapping[static_cast(mixed[0])]; return 1; } else { if (lenMixed > utf16Mixed.size()) { utf16Mixed.resize(lenMixed + 8); } size_t nUtf16Mixed = ::MultiByteToWideChar(cp, 0, mixed, static_cast(lenMixed), &utf16Mixed[0], static_cast(utf16Mixed.size())); if (nUtf16Mixed == 0) { // Failed to convert -> bad input folded[0] = '\0'; return 1; } unsigned int lenFlat = 0; for (size_t mixIndex=0; mixIndex < nUtf16Mixed; mixIndex++) { if ((lenFlat + 20) > utf16Folded.size()) utf16Folded.resize(lenFlat + 60); const char *foldedUTF8 = CaseConvert(utf16Mixed[mixIndex], CaseConversionFold); if (foldedUTF8) { // Maximum length of a case conversion is 6 bytes, 3 characters wchar_t wFolded[20]; unsigned int charsConverted = UTF16FromUTF8(foldedUTF8, static_cast(strlen(foldedUTF8)), wFolded, sizeof(wFolded)/sizeof(wFolded[0])); for (size_t j=0;j(lenOut), NULL, 0); return lenOut; } else { return 0; } } } }; CaseFolder *ScintillaWin::CaseFolderForEncoding() { UINT cpDest = CodePageOfDocument(); if (cpDest == SC_CP_UTF8) { return new CaseFolderUnicode(); } else { if (pdoc->dbcsCodePage == 0) { CaseFolderTable *pcf = new CaseFolderTable(); pcf->StandardASCII(); // Only for single byte encodings UINT cpDoc = CodePageOfDocument(); for (int i=0x80; i<0x100; i++) { char sCharacter[2] = "A"; sCharacter[0] = static_cast(i); wchar_t wCharacter[20]; unsigned int lengthUTF16 = ::MultiByteToWideChar(cpDoc, 0, sCharacter, 1, wCharacter, sizeof(wCharacter)/sizeof(wCharacter[0])); if (lengthUTF16 == 1) { const char *caseFolded = CaseConvert(wCharacter[0], CaseConversionFold); if (caseFolded) { wchar_t wLower[20]; unsigned int charsConverted = UTF16FromUTF8(caseFolded, static_cast(strlen(caseFolded)), wLower, sizeof(wLower)/sizeof(wLower[0])); if (charsConverted == 1) { char sCharacterLowered[20]; unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0, wLower, charsConverted, sCharacterLowered, sizeof(sCharacterLowered), NULL, 0); if ((lengthConverted == 1) && (sCharacter[0] != sCharacterLowered[0])) { pcf->SetTranslation(sCharacter[0], sCharacterLowered[0]); } } } } } return pcf; } else { return new CaseFolderDBCS(cpDest); } } } std::string ScintillaWin::CaseMapString(const std::string &s, int caseMapping) { if ((s.size() == 0) || (caseMapping == cmSame)) return s; UINT cpDoc = CodePageOfDocument(); if (cpDoc == SC_CP_UTF8) { std::string retMapped(s.length() * maxExpansionCaseConversion, 0); size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(), (caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower); retMapped.resize(lenMapped); return retMapped; } unsigned int lengthUTF16 = ::MultiByteToWideChar(cpDoc, 0, s.c_str(), static_cast(s.size()), NULL, 0); if (lengthUTF16 == 0) // Failed to convert return s; DWORD mapFlags = LCMAP_LINGUISTIC_CASING | ((caseMapping == cmUpper) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE); // Change text to UTF-16 std::vector vwcText(lengthUTF16); ::MultiByteToWideChar(cpDoc, 0, s.c_str(), static_cast(s.size()), &vwcText[0], lengthUTF16); // Change case int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags, &vwcText[0], lengthUTF16, NULL, 0); std::vector vwcConverted(charsConverted); ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags, &vwcText[0], lengthUTF16, &vwcConverted[0], charsConverted); // Change back to document encoding unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0, &vwcConverted[0], static_cast(vwcConverted.size()), NULL, 0, NULL, 0); std::vector vcConverted(lengthConverted); ::WideCharToMultiByte(cpDoc, 0, &vwcConverted[0], static_cast(vwcConverted.size()), &vcConverted[0], static_cast(vcConverted.size()), NULL, 0); return std::string(&vcConverted[0], vcConverted.size()); } void ScintillaWin::Copy() { //Platform::DebugPrintf("Copy\n"); if (!sel.Empty()) { SelectionText selectedText; CopySelectionRange(&selectedText); CopyToClipboard(selectedText); } } void ScintillaWin::CopyAllowLine() { SelectionText selectedText; CopySelectionRange(&selectedText, true); CopyToClipboard(selectedText); } bool ScintillaWin::CanPaste() { if (!Editor::CanPaste()) return false; if (::IsClipboardFormatAvailable(CF_TEXT)) return true; if (IsUnicodeMode()) return ::IsClipboardFormatAvailable(CF_UNICODETEXT) != 0; return false; } class GlobalMemory { HGLOBAL hand; public: void *ptr; GlobalMemory() : hand(0), ptr(0) { } GlobalMemory(HGLOBAL hand_) : hand(hand_), ptr(0) { if (hand) { ptr = ::GlobalLock(hand); } } ~GlobalMemory() { PLATFORM_ASSERT(!ptr); } void Allocate(size_t bytes) { hand = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes); if (hand) { ptr = ::GlobalLock(hand); } } HGLOBAL Unlock() { PLATFORM_ASSERT(ptr); HGLOBAL handCopy = hand; ::GlobalUnlock(hand); ptr = 0; hand = 0; return handCopy; } void SetClip(UINT uFormat) { ::SetClipboardData(uFormat, Unlock()); } operator bool() const { return ptr != 0; } SIZE_T Size() { return ::GlobalSize(hand); } }; void ScintillaWin::InsertPasteText(const char *text, int len, SelectionPosition selStart, bool isRectangular, bool isLine) { if (isRectangular) { PasteRectangular(selStart, text, len); } else { std::string convertedText; if (convertPastes) { // Convert line endings of the paste into our local line-endings mode convertedText = Document::TransformLineEnds(text, len, pdoc->eolMode); len = static_cast(convertedText.length()); text = convertedText.c_str(); } if (isLine) { int insertPos = pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret())); pdoc->InsertString(insertPos, text, len); // add the newline if necessary if ((len > 0) && (text[len-1] != '\n' && text[len-1] != '\r')) { const char *endline = StringFromEOLMode(pdoc->eolMode); pdoc->InsertString(insertPos + len, endline, static_cast(strlen(endline))); len += static_cast(strlen(endline)); } if (sel.MainCaret() == insertPos) { SetEmptySelection(sel.MainCaret() + len); } } else { InsertPaste(selStart, text, len); } } } void ScintillaWin::Paste() { if (!::OpenClipboard(MainHWND())) return; UndoGroup ug(pdoc); bool isLine = SelectionEmpty() && (::IsClipboardFormatAvailable(cfLineSelect) != 0); ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH); SelectionPosition selStart = sel.IsRectangular() ? sel.Rectangular().Start() : sel.Range(sel.Main()).Start(); bool isRectangular = ::IsClipboardFormatAvailable(cfColumnSelect) != 0; // Always use CF_UNICODETEXT if available GlobalMemory memUSelection(::GetClipboardData(CF_UNICODETEXT)); if (memUSelection) { wchar_t *uptr = static_cast(memUSelection.ptr); if (uptr) { unsigned int len; std::vector putf; // Default Scintilla behaviour in Unicode mode if (IsUnicodeMode()) { unsigned int bytes = memUSelection.Size(); len = UTF8Length(uptr, bytes / 2); putf.resize(len + 1); UTF8FromUTF16(uptr, bytes / 2, &putf[0], len); } else { // CF_UNICODETEXT available, but not in Unicode mode // Convert from Unicode to current Scintilla code page UINT cpDest = CodePageOfDocument(); len = ::WideCharToMultiByte(cpDest, 0, uptr, -1, NULL, 0, NULL, NULL) - 1; // subtract 0 terminator putf.resize(len + 1); ::WideCharToMultiByte(cpDest, 0, uptr, -1, &putf[0], len + 1, NULL, NULL); } InsertPasteText(&putf[0], len, selStart, isRectangular, isLine); } memUSelection.Unlock(); } else { // CF_UNICODETEXT not available, paste ANSI text GlobalMemory memSelection(::GetClipboardData(CF_TEXT)); if (memSelection) { char *ptr = static_cast(memSelection.ptr); if (ptr) { unsigned int bytes = memSelection.Size(); unsigned int len = bytes; for (unsigned int i = 0; i < bytes; i++) { if ((len == bytes) && (0 == ptr[i])) len = i; } // In Unicode mode, convert clipboard text to UTF-8 if (IsUnicodeMode()) { std::vector uptr(len+1); unsigned int ulen = ::MultiByteToWideChar(CP_ACP, 0, ptr, len, &uptr[0], len+1); unsigned int mlen = UTF8Length(&uptr[0], ulen); std::vector putf(mlen+1); // CP_UTF8 not available on Windows 95, so use UTF8FromUTF16() UTF8FromUTF16(&uptr[0], ulen, &putf[0], mlen); InsertPasteText(&putf[0], mlen, selStart, isRectangular, isLine); } else { InsertPasteText(ptr, len, selStart, isRectangular, isLine); } } memSelection.Unlock(); } } ::CloseClipboard(); Redraw(); } void ScintillaWin::CreateCallTipWindow(PRectangle) { if (!ct.wCallTip.Created()) { ct.wCallTip = ::CreateWindow(callClassName, TEXT("ACallTip"), WS_POPUP, 100, 100, 150, 20, MainHWND(), 0, GetWindowInstance(MainHWND()), this); ct.wDraw = ct.wCallTip; } } void ScintillaWin::AddToPopUp(const char *label, int cmd, bool enabled) { HMENU hmenuPopup = reinterpret_cast(popup.GetID()); if (!label[0]) ::AppendMenuA(hmenuPopup, MF_SEPARATOR, 0, ""); else if (enabled) ::AppendMenuA(hmenuPopup, MF_STRING, cmd, label); else ::AppendMenuA(hmenuPopup, MF_STRING | MF_DISABLED | MF_GRAYED, cmd, label); } void ScintillaWin::ClaimSelection() { // Windows does not have a primary selection } /// Implement IUnknown STDMETHODIMP_(ULONG)FormatEnumerator_AddRef(FormatEnumerator *fe); STDMETHODIMP FormatEnumerator_QueryInterface(FormatEnumerator *fe, REFIID riid, PVOID *ppv) { //Platform::DebugPrintf("EFE QI"); *ppv = NULL; if (riid == IID_IUnknown) *ppv = reinterpret_cast(fe); if (riid == IID_IEnumFORMATETC) *ppv = reinterpret_cast(fe); if (!*ppv) return E_NOINTERFACE; FormatEnumerator_AddRef(fe); return S_OK; } STDMETHODIMP_(ULONG)FormatEnumerator_AddRef(FormatEnumerator *fe) { return ++fe->ref; } STDMETHODIMP_(ULONG)FormatEnumerator_Release(FormatEnumerator *fe) { fe->ref--; if (fe->ref > 0) return fe->ref; delete fe; return 0; } /// Implement IEnumFORMATETC STDMETHODIMP FormatEnumerator_Next(FormatEnumerator *fe, ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) { //Platform::DebugPrintf("EFE Next %d %d", fe->pos, celt); if (rgelt == NULL) return E_POINTER; // We only support one format, so this is simple. unsigned int putPos = 0; while ((fe->pos < fe->formatsLen) && (putPos < celt)) { rgelt->cfFormat = fe->formats[fe->pos]; rgelt->ptd = 0; rgelt->dwAspect = DVASPECT_CONTENT; rgelt->lindex = -1; rgelt->tymed = TYMED_HGLOBAL; fe->pos++; putPos++; } if (pceltFetched) *pceltFetched = putPos; return putPos ? S_OK : S_FALSE; } STDMETHODIMP FormatEnumerator_Skip(FormatEnumerator *fe, ULONG celt) { fe->pos += celt; return S_OK; } STDMETHODIMP FormatEnumerator_Reset(FormatEnumerator *fe) { fe->pos = 0; return S_OK; } STDMETHODIMP FormatEnumerator_Clone(FormatEnumerator *fe, IEnumFORMATETC **ppenum) { FormatEnumerator *pfe; try { pfe = new FormatEnumerator(fe->pos, fe->formats, fe->formatsLen); } catch (...) { return E_OUTOFMEMORY; } return FormatEnumerator_QueryInterface(pfe, IID_IEnumFORMATETC, reinterpret_cast(ppenum)); } static VFunction *vtFormatEnumerator[] = { (VFunction *)(FormatEnumerator_QueryInterface), (VFunction *)(FormatEnumerator_AddRef), (VFunction *)(FormatEnumerator_Release), (VFunction *)(FormatEnumerator_Next), (VFunction *)(FormatEnumerator_Skip), (VFunction *)(FormatEnumerator_Reset), (VFunction *)(FormatEnumerator_Clone) }; FormatEnumerator::FormatEnumerator(int pos_, CLIPFORMAT formats_[], int formatsLen_) { vtbl = vtFormatEnumerator; ref = 0; // First QI adds first reference... pos = pos_; formatsLen = formatsLen_; for (int i=0; isci->QueryInterface(riid, ppv); } STDMETHODIMP_(ULONG)DropSource_AddRef(DropSource *ds) { return ds->sci->AddRef(); } STDMETHODIMP_(ULONG)DropSource_Release(DropSource *ds) { return ds->sci->Release(); } /// Implement IDropSource STDMETHODIMP DropSource_QueryContinueDrag(DropSource *, BOOL fEsc, DWORD grfKeyState) { if (fEsc) return DRAGDROP_S_CANCEL; if (!(grfKeyState & MK_LBUTTON)) return DRAGDROP_S_DROP; return S_OK; } STDMETHODIMP DropSource_GiveFeedback(DropSource *, DWORD) { return DRAGDROP_S_USEDEFAULTCURSORS; } static VFunction *vtDropSource[] = { (VFunction *)(DropSource_QueryInterface), (VFunction *)(DropSource_AddRef), (VFunction *)(DropSource_Release), (VFunction *)(DropSource_QueryContinueDrag), (VFunction *)(DropSource_GiveFeedback) }; DropSource::DropSource() { vtbl = vtDropSource; sci = 0; } /// Implement IUnkown STDMETHODIMP DataObject_QueryInterface(DataObject *pd, REFIID riid, PVOID *ppv) { //Platform::DebugPrintf("DO QI %x\n", pd); return pd->sci->QueryInterface(riid, ppv); } STDMETHODIMP_(ULONG)DataObject_AddRef(DataObject *pd) { return pd->sci->AddRef(); } STDMETHODIMP_(ULONG)DataObject_Release(DataObject *pd) { return pd->sci->Release(); } /// Implement IDataObject STDMETHODIMP DataObject_GetData(DataObject *pd, FORMATETC *pFEIn, STGMEDIUM *pSTM) { return pd->sci->GetData(pFEIn, pSTM); } STDMETHODIMP DataObject_GetDataHere(DataObject *, FORMATETC *, STGMEDIUM *) { //Platform::DebugPrintf("DOB GetDataHere\n"); return E_NOTIMPL; } STDMETHODIMP DataObject_QueryGetData(DataObject *pd, FORMATETC *pFE) { if (pd->sci->DragIsRectangularOK(pFE->cfFormat) && pFE->ptd == 0 && (pFE->dwAspect & DVASPECT_CONTENT) != 0 && pFE->lindex == -1 && (pFE->tymed & TYMED_HGLOBAL) != 0 ) { return S_OK; } bool formatOK = (pFE->cfFormat == CF_TEXT) || ((pFE->cfFormat == CF_UNICODETEXT) && pd->sci->IsUnicodeMode()); if (!formatOK || pFE->ptd != 0 || (pFE->dwAspect & DVASPECT_CONTENT) == 0 || pFE->lindex != -1 || (pFE->tymed & TYMED_HGLOBAL) == 0 ) { //Platform::DebugPrintf("DOB QueryGetData No %x\n",pFE->cfFormat); //return DATA_E_FORMATETC; return S_FALSE; } //Platform::DebugPrintf("DOB QueryGetData OK %x\n",pFE->cfFormat); return S_OK; } STDMETHODIMP DataObject_GetCanonicalFormatEtc(DataObject *pd, FORMATETC *, FORMATETC *pFEOut) { //Platform::DebugPrintf("DOB GetCanon\n"); if (pd->sci->IsUnicodeMode()) pFEOut->cfFormat = CF_UNICODETEXT; else pFEOut->cfFormat = CF_TEXT; pFEOut->ptd = 0; pFEOut->dwAspect = DVASPECT_CONTENT; pFEOut->lindex = -1; pFEOut->tymed = TYMED_HGLOBAL; return S_OK; } STDMETHODIMP DataObject_SetData(DataObject *, FORMATETC *, STGMEDIUM *, BOOL) { //Platform::DebugPrintf("DOB SetData\n"); return E_FAIL; } STDMETHODIMP DataObject_EnumFormatEtc(DataObject *pd, DWORD dwDirection, IEnumFORMATETC **ppEnum) { try { //Platform::DebugPrintf("DOB EnumFormatEtc %d\n", dwDirection); if (dwDirection != DATADIR_GET) { *ppEnum = 0; return E_FAIL; } FormatEnumerator *pfe; if (pd->sci->IsUnicodeMode()) { CLIPFORMAT formats[] = {CF_UNICODETEXT, CF_TEXT}; pfe = new FormatEnumerator(0, formats, 2); } else { CLIPFORMAT formats[] = {CF_TEXT}; pfe = new FormatEnumerator(0, formats, 1); } return FormatEnumerator_QueryInterface(pfe, IID_IEnumFORMATETC, reinterpret_cast(ppEnum)); } catch (std::bad_alloc &) { pd->sci->errorStatus = SC_STATUS_BADALLOC; return E_OUTOFMEMORY; } catch (...) { pd->sci->errorStatus = SC_STATUS_FAILURE; return E_FAIL; } } STDMETHODIMP DataObject_DAdvise(DataObject *, FORMATETC *, DWORD, IAdviseSink *, PDWORD) { //Platform::DebugPrintf("DOB DAdvise\n"); return E_FAIL; } STDMETHODIMP DataObject_DUnadvise(DataObject *, DWORD) { //Platform::DebugPrintf("DOB DUnadvise\n"); return E_FAIL; } STDMETHODIMP DataObject_EnumDAdvise(DataObject *, IEnumSTATDATA **) { //Platform::DebugPrintf("DOB EnumDAdvise\n"); return E_FAIL; } static VFunction *vtDataObject[] = { (VFunction *)(DataObject_QueryInterface), (VFunction *)(DataObject_AddRef), (VFunction *)(DataObject_Release), (VFunction *)(DataObject_GetData), (VFunction *)(DataObject_GetDataHere), (VFunction *)(DataObject_QueryGetData), (VFunction *)(DataObject_GetCanonicalFormatEtc), (VFunction *)(DataObject_SetData), (VFunction *)(DataObject_EnumFormatEtc), (VFunction *)(DataObject_DAdvise), (VFunction *)(DataObject_DUnadvise), (VFunction *)(DataObject_EnumDAdvise) }; DataObject::DataObject() { vtbl = vtDataObject; sci = 0; } /// Implement IUnknown STDMETHODIMP DropTarget_QueryInterface(DropTarget *dt, REFIID riid, PVOID *ppv) { //Platform::DebugPrintf("DT QI %x\n", dt); return dt->sci->QueryInterface(riid, ppv); } STDMETHODIMP_(ULONG)DropTarget_AddRef(DropTarget *dt) { return dt->sci->AddRef(); } STDMETHODIMP_(ULONG)DropTarget_Release(DropTarget *dt) { return dt->sci->Release(); } /// Implement IDropTarget by forwarding to Scintilla STDMETHODIMP DropTarget_DragEnter(DropTarget *dt, LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { try { return dt->sci->DragEnter(pIDataSource, grfKeyState, pt, pdwEffect); } catch (...) { dt->sci->errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } STDMETHODIMP DropTarget_DragOver(DropTarget *dt, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { try { return dt->sci->DragOver(grfKeyState, pt, pdwEffect); } catch (...) { dt->sci->errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } STDMETHODIMP DropTarget_DragLeave(DropTarget *dt) { try { return dt->sci->DragLeave(); } catch (...) { dt->sci->errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } STDMETHODIMP DropTarget_Drop(DropTarget *dt, LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { try { return dt->sci->Drop(pIDataSource, grfKeyState, pt, pdwEffect); } catch (...) { dt->sci->errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } static VFunction *vtDropTarget[] = { (VFunction *)(DropTarget_QueryInterface), (VFunction *)(DropTarget_AddRef), (VFunction *)(DropTarget_Release), (VFunction *)(DropTarget_DragEnter), (VFunction *)(DropTarget_DragOver), (VFunction *)(DropTarget_DragLeave), (VFunction *)(DropTarget_Drop) }; DropTarget::DropTarget() { vtbl = vtDropTarget; sci = 0; } /** * DBCS: support Input Method Editor (IME). * Called when IME Window opened. */ void ScintillaWin::ImeStartComposition() { if (caret.active) { // Move IME Window to current caret position HIMC hIMC = ::ImmGetContext(MainHWND()); Point pos = PointMainCaret(); COMPOSITIONFORM CompForm; CompForm.dwStyle = CFS_POINT; CompForm.ptCurrentPos.x = pos.x; CompForm.ptCurrentPos.y = pos.y; ::ImmSetCompositionWindow(hIMC, &CompForm); // Set font of IME window to same as surrounded text. if (stylesValid) { // Since the style creation code has been made platform independent, // The logfont for the IME is recreated here. int styleHere = (pdoc->StyleAt(sel.MainCaret())) & 31; LOGFONTA lf = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ""}; int sizeZoomed = vs.styles[styleHere].size + vs.zoomLevel * SC_FONT_SIZE_MULTIPLIER; if (sizeZoomed <= 2 * SC_FONT_SIZE_MULTIPLIER) // Hangs if sizeZoomed <= 1 sizeZoomed = 2 * SC_FONT_SIZE_MULTIPLIER; AutoSurface surface(this); int deviceHeight = sizeZoomed; if (surface) { deviceHeight = (sizeZoomed * surface->LogPixelsY()) / 72; } // The negative is to allow for leading lf.lfHeight = -(abs(deviceHeight / SC_FONT_SIZE_MULTIPLIER)); lf.lfWeight = vs.styles[styleHere].weight; lf.lfItalic = static_cast(vs.styles[styleHere].italic ? 1 : 0); lf.lfCharSet = DEFAULT_CHARSET; lf.lfFaceName[0] = '\0'; if (vs.styles[styleHere].fontName) strcpy(lf.lfFaceName, vs.styles[styleHere].fontName); ::ImmSetCompositionFontA(hIMC, &lf); } ::ImmReleaseContext(MainHWND(), hIMC); // Caret is displayed in IME window. So, caret in Scintilla is useless. DropCaret(); } } /** Called when IME Window closed. */ void ScintillaWin::ImeEndComposition() { ShowCaretAtCurrentPosition(); } void ScintillaWin::AddCharBytes(char b0, char b1) { int inputCodePage = InputCodePage(); if (inputCodePage && IsUnicodeMode()) { char utfval[4] = "\0\0\0"; char ansiChars[3]; wchar_t wcs[2]; if (b0) { // Two bytes from IME ansiChars[0] = b0; ansiChars[1] = b1; ansiChars[2] = '\0'; ::MultiByteToWideChar(inputCodePage, 0, ansiChars, 2, wcs, 1); } else { ansiChars[0] = b1; ansiChars[1] = '\0'; ::MultiByteToWideChar(inputCodePage, 0, ansiChars, 1, wcs, 1); } unsigned int len = UTF8Length(wcs, 1); UTF8FromUTF16(wcs, 1, utfval, len); utfval[len] = '\0'; AddCharUTF(utfval, len ? len : 1); } else if (b0) { char dbcsChars[3]; dbcsChars[0] = b0; dbcsChars[1] = b1; dbcsChars[2] = '\0'; AddCharUTF(dbcsChars, 2, true); } else { AddChar(b1); } } void ScintillaWin::GetIntelliMouseParameters() { // This retrieves the number of lines per scroll as configured inthe Mouse Properties sheet in Control Panel ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0); } void ScintillaWin::CopyToClipboard(const SelectionText &selectedText) { if (!::OpenClipboard(MainHWND())) return; ::EmptyClipboard(); GlobalMemory uniText; // Default Scintilla behaviour in Unicode mode if (IsUnicodeMode()) { int uchars = UTF16Length(selectedText.Data(), static_cast(selectedText.LengthWithTerminator())); uniText.Allocate(2 * uchars); if (uniText) { UTF16FromUTF8(selectedText.Data(), static_cast(selectedText.LengthWithTerminator()), static_cast(uniText.ptr), uchars); } } else { // Not Unicode mode // Convert to Unicode using the current Scintilla code page UINT cpSrc = CodePageFromCharSet( selectedText.characterSet, selectedText.codePage); int uLen = ::MultiByteToWideChar(cpSrc, 0, selectedText.Data(), static_cast(selectedText.LengthWithTerminator()), 0, 0); uniText.Allocate(2 * uLen); if (uniText) { ::MultiByteToWideChar(cpSrc, 0, selectedText.Data(), static_cast(selectedText.LengthWithTerminator()), static_cast(uniText.ptr), uLen); } } if (uniText) { if (!IsNT()) { // Copy ANSI text to clipboard on Windows 9x // Convert from Unicode text, so other ANSI programs can // paste the text // Windows NT, 2k, XP automatically generates CF_TEXT GlobalMemory ansiText; ansiText.Allocate(selectedText.LengthWithTerminator()); if (ansiText) { ::WideCharToMultiByte(CP_ACP, 0, static_cast(uniText.ptr), -1, static_cast(ansiText.ptr), static_cast(selectedText.LengthWithTerminator()), NULL, NULL); ansiText.SetClip(CF_TEXT); } } uniText.SetClip(CF_UNICODETEXT); } else { // There was a failure - try to copy at least ANSI text GlobalMemory ansiText; ansiText.Allocate(selectedText.LengthWithTerminator()); if (ansiText) { memcpy(static_cast(ansiText.ptr), selectedText.Data(), selectedText.LengthWithTerminator()); ansiText.SetClip(CF_TEXT); } } if (selectedText.rectangular) { ::SetClipboardData(cfColumnSelect, 0); } if (selectedText.lineCopy) { ::SetClipboardData(cfLineSelect, 0); } ::CloseClipboard(); } void ScintillaWin::ScrollMessage(WPARAM wParam) { //DWORD dwStart = timeGetTime(); //Platform::DebugPrintf("Scroll %x %d\n", wParam, lParam); SCROLLINFO sci; memset(&sci, 0, sizeof(sci)); sci.cbSize = sizeof(sci); sci.fMask = SIF_ALL; GetScrollInfo(SB_VERT, &sci); //Platform::DebugPrintf("ScrollInfo %d mask=%x min=%d max=%d page=%d pos=%d track=%d\n", b,sci.fMask, //sci.nMin, sci.nMax, sci.nPage, sci.nPos, sci.nTrackPos); int topLineNew = topLine; switch (LoWord(wParam)) { case SB_LINEUP: topLineNew -= 1; break; case SB_LINEDOWN: topLineNew += 1; break; case SB_PAGEUP: topLineNew -= LinesToScroll(); break; case SB_PAGEDOWN: topLineNew += LinesToScroll(); break; case SB_TOP: topLineNew = 0; break; case SB_BOTTOM: topLineNew = MaxScrollPos(); break; case SB_THUMBPOSITION: topLineNew = sci.nTrackPos; break; case SB_THUMBTRACK: topLineNew = sci.nTrackPos; break; } ScrollTo(topLineNew); } void ScintillaWin::HorizontalScrollMessage(WPARAM wParam) { int xPos = xOffset; PRectangle rcText = GetTextRectangle(); int pageWidth = rcText.Width() * 2 / 3; switch (LoWord(wParam)) { case SB_LINEUP: xPos -= 20; break; case SB_LINEDOWN: // May move past the logical end xPos += 20; break; case SB_PAGEUP: xPos -= pageWidth; break; case SB_PAGEDOWN: xPos += pageWidth; if (xPos > scrollWidth - rcText.Width()) { // Hit the end exactly xPos = scrollWidth - rcText.Width(); } break; case SB_TOP: xPos = 0; break; case SB_BOTTOM: xPos = scrollWidth; break; case SB_THUMBPOSITION: case SB_THUMBTRACK: { // Do NOT use wParam, its 16 bit and not enough for very long lines. Its still possible to overflow the 32 bit but you have to try harder =] SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_TRACKPOS; if (GetScrollInfo(SB_HORZ, &si)) { xPos = si.nTrackPos; } } break; } HorizontalScrollTo(xPos); } /** * Redraw all of text area. * This paint will not be abandoned. */ void ScintillaWin::FullPaint() { if (technology == SC_TECHNOLOGY_DEFAULT) { HDC hdc = ::GetDC(MainHWND()); FullPaintDC(hdc); ::ReleaseDC(MainHWND(), hdc); } else { FullPaintDC(0); } } /** * Redraw all of text area on the specified DC. * This paint will not be abandoned. */ void ScintillaWin::FullPaintDC(HDC hdc) { paintState = painting; rcPaint = GetClientRectangle(); paintingAllText = true; if (technology == SC_TECHNOLOGY_DEFAULT) { AutoSurface surfaceWindow(hdc, this); if (surfaceWindow) { Paint(surfaceWindow, rcPaint); surfaceWindow->Release(); } } else { #if defined(USE_D2D) EnsureRenderTarget(); AutoSurface surfaceWindow(pRenderTarget, this); if (surfaceWindow) { pRenderTarget->BeginDraw(); Paint(surfaceWindow, rcPaint); surfaceWindow->Release(); HRESULT hr = pRenderTarget->EndDraw(); if (hr == D2DERR_RECREATE_TARGET) { DropRenderTarget(); } } #endif } paintState = notPainting; } static bool CompareDevCap(HDC hdc, HDC hOtherDC, int nIndex) { return ::GetDeviceCaps(hdc, nIndex) == ::GetDeviceCaps(hOtherDC, nIndex); } bool ScintillaWin::IsCompatibleDC(HDC hOtherDC) { HDC hdc = ::GetDC(MainHWND()); bool isCompatible = CompareDevCap(hdc, hOtherDC, TECHNOLOGY) && CompareDevCap(hdc, hOtherDC, LOGPIXELSY) && CompareDevCap(hdc, hOtherDC, LOGPIXELSX) && CompareDevCap(hdc, hOtherDC, BITSPIXEL) && CompareDevCap(hdc, hOtherDC, PLANES); ::ReleaseDC(MainHWND(), hdc); return isCompatible; } DWORD ScintillaWin::EffectFromState(DWORD grfKeyState) const { // These are the Wordpad semantics. DWORD dwEffect; if (inDragDrop == ddDragging) // Internal defaults to move dwEffect = DROPEFFECT_MOVE; else dwEffect = DROPEFFECT_COPY; if (grfKeyState & MK_ALT) dwEffect = DROPEFFECT_MOVE; if (grfKeyState & MK_CONTROL) dwEffect = DROPEFFECT_COPY; return dwEffect; } /// Implement IUnknown STDMETHODIMP ScintillaWin::QueryInterface(REFIID riid, PVOID *ppv) { *ppv = NULL; if (riid == IID_IUnknown) *ppv = reinterpret_cast(&dt); if (riid == IID_IDropSource) *ppv = reinterpret_cast(&ds); if (riid == IID_IDropTarget) *ppv = reinterpret_cast(&dt); if (riid == IID_IDataObject) *ppv = reinterpret_cast(&dob); if (!*ppv) return E_NOINTERFACE; return S_OK; } STDMETHODIMP_(ULONG) ScintillaWin::AddRef() { return 1; } STDMETHODIMP_(ULONG) ScintillaWin::Release() { return 1; } /// Implement IDropTarget STDMETHODIMP ScintillaWin::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL, PDWORD pdwEffect) { if (pIDataSource == NULL) return E_POINTER; FORMATETC fmtu = {CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; HRESULT hrHasUText = pIDataSource->QueryGetData(&fmtu); hasOKText = (hrHasUText == S_OK); if (!hasOKText) { FORMATETC fmte = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; HRESULT hrHasText = pIDataSource->QueryGetData(&fmte); hasOKText = (hrHasText == S_OK); } if (!hasOKText) { *pdwEffect = DROPEFFECT_NONE; return S_OK; } *pdwEffect = EffectFromState(grfKeyState); return S_OK; } STDMETHODIMP ScintillaWin::DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { try { if (!hasOKText || pdoc->IsReadOnly()) { *pdwEffect = DROPEFFECT_NONE; return S_OK; } *pdwEffect = EffectFromState(grfKeyState); // Update the cursor. POINT rpt = {pt.x, pt.y}; ::ScreenToClient(MainHWND(), &rpt); SetDragPosition(SPositionFromLocation(Point(rpt.x, rpt.y), false, false, UserVirtualSpace())); return S_OK; } catch (...) { errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } STDMETHODIMP ScintillaWin::DragLeave() { try { SetDragPosition(SelectionPosition(invalidPosition)); return S_OK; } catch (...) { errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } STDMETHODIMP ScintillaWin::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { try { *pdwEffect = EffectFromState(grfKeyState); if (pIDataSource == NULL) return E_POINTER; SetDragPosition(SelectionPosition(invalidPosition)); STGMEDIUM medium = {0, {0}, 0}; std::vector data; // Includes terminating NUL FORMATETC fmtu = {CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; HRESULT hr = pIDataSource->GetData(&fmtu, &medium); if (SUCCEEDED(hr) && medium.hGlobal) { GlobalMemory memUDrop(medium.hGlobal); wchar_t *udata = static_cast(memUDrop.ptr); if (udata) { if (IsUnicodeMode()) { int tlen = memUDrop.Size(); // Convert UTF-16 to UTF-8 int dataLen = UTF8Length(udata, tlen/2); data.resize(dataLen+1); UTF8FromUTF16(udata, tlen/2, &data[0], dataLen); } else { // Convert UTF-16 to ANSI // // Default Scintilla behavior in Unicode mode // CF_UNICODETEXT available, but not in Unicode mode // Convert from Unicode to current Scintilla code page UINT cpDest = CodePageOfDocument(); int tlen = ::WideCharToMultiByte(cpDest, 0, udata, -1, NULL, 0, NULL, NULL) - 1; // subtract 0 terminator data.resize(tlen + 1); ::WideCharToMultiByte(cpDest, 0, udata, -1, &data[0], tlen + 1, NULL, NULL); } } memUDrop.Unlock(); } else { FORMATETC fmte = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; hr = pIDataSource->GetData(&fmte, &medium); if (SUCCEEDED(hr) && medium.hGlobal) { GlobalMemory memDrop(medium.hGlobal); const char *cdata = static_cast(memDrop.ptr); if (cdata) data.assign(cdata, cdata+strlen(cdata)+1); memDrop.Unlock(); } } if (!data.empty() && convertPastes) { // Convert line endings of the drop into our local line-endings mode std::string convertedText = Document::TransformLineEnds(&data[0], data.size() - 1, pdoc->eolMode); data.assign(convertedText.c_str(), convertedText.c_str()+convertedText.length()+1); } if (!SUCCEEDED(hr) || data.empty()) { //Platform::DebugPrintf("Bad data format: 0x%x\n", hres); return hr; } FORMATETC fmtr = {cfColumnSelect, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; HRESULT hrRectangular = pIDataSource->QueryGetData(&fmtr); POINT rpt = {pt.x, pt.y}; ::ScreenToClient(MainHWND(), &rpt); SelectionPosition movePos = SPositionFromLocation(Point(rpt.x, rpt.y), false, false, UserVirtualSpace()); DropAt(movePos, &data[0], data.size() - 1, *pdwEffect == DROPEFFECT_MOVE, hrRectangular == S_OK); // Free data if (medium.pUnkForRelease != NULL) medium.pUnkForRelease->Release(); else ::GlobalFree(medium.hGlobal); return S_OK; } catch (...) { errorStatus = SC_STATUS_FAILURE; } return E_FAIL; } /// Implement important part of IDataObject STDMETHODIMP ScintillaWin::GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) { bool formatOK = (pFEIn->cfFormat == CF_TEXT) || ((pFEIn->cfFormat == CF_UNICODETEXT) && IsUnicodeMode()); if (!formatOK || pFEIn->ptd != 0 || (pFEIn->dwAspect & DVASPECT_CONTENT) == 0 || pFEIn->lindex != -1 || (pFEIn->tymed & TYMED_HGLOBAL) == 0 ) { //Platform::DebugPrintf("DOB GetData No %d %x %x fmt=%x\n", lenDrag, pFEIn, pSTM, pFEIn->cfFormat); return DATA_E_FORMATETC; } pSTM->tymed = TYMED_HGLOBAL; //Platform::DebugPrintf("DOB GetData OK %d %x %x\n", lenDrag, pFEIn, pSTM); GlobalMemory text; if (pFEIn->cfFormat == CF_UNICODETEXT) { int uchars = UTF16Length(drag.Data(), static_cast(drag.LengthWithTerminator())); text.Allocate(2 * uchars); if (text) { UTF16FromUTF8(drag.Data(), static_cast(drag.LengthWithTerminator()), static_cast(text.ptr), uchars); } } else { text.Allocate(drag.LengthWithTerminator()); if (text) { memcpy(static_cast(text.ptr), drag.Data(), drag.LengthWithTerminator()); } } pSTM->hGlobal = text ? text.Unlock() : 0; pSTM->pUnkForRelease = 0; return S_OK; } bool ScintillaWin::Register(HINSTANCE hInstance_) { hInstance = hInstance_; bool result; // Register the Scintilla class if (IsNT()) { // Register Scintilla as a wide character window WNDCLASSEXW wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = ScintillaWin::SWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = sizeof(ScintillaWin *); wndclass.hInstance = hInstance; wndclass.hIcon = NULL; wndclass.hCursor = NULL; wndclass.hbrBackground = NULL; wndclass.lpszMenuName = NULL; wndclass.lpszClassName = L"Scintilla"; wndclass.hIconSm = 0; result = ::RegisterClassExW(&wndclass) != 0; } else { // Register Scintilla as a normal character window WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = ScintillaWin::SWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = sizeof(ScintillaWin *); wndclass.hInstance = hInstance; wndclass.hIcon = NULL; wndclass.hCursor = NULL; wndclass.hbrBackground = NULL; wndclass.lpszMenuName = NULL; wndclass.lpszClassName = scintillaClassName; wndclass.hIconSm = 0; result = ::RegisterClassEx(&wndclass) != 0; } if (result) { // Register the CallTip class WNDCLASSEX wndclassc; wndclassc.cbSize = sizeof(wndclassc); wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; wndclassc.cbClsExtra = 0; wndclassc.cbWndExtra = sizeof(ScintillaWin *); wndclassc.hInstance = hInstance; wndclassc.hIcon = NULL; wndclassc.hbrBackground = NULL; wndclassc.lpszMenuName = NULL; wndclassc.lpfnWndProc = ScintillaWin::CTWndProc; wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW); wndclassc.lpszClassName = callClassName; wndclassc.hIconSm = 0; result = ::RegisterClassEx(&wndclassc) != 0; } return result; } bool ScintillaWin::Unregister() { bool result = ::UnregisterClass(scintillaClassName, hInstance) != 0; if (::UnregisterClass(callClassName, hInstance) == 0) result = false; return result; } bool ScintillaWin::HasCaretSizeChanged() const { if ( ( (0 != vs.caretWidth) && (sysCaretWidth != vs.caretWidth) ) || ((0 != vs.lineHeight) && (sysCaretHeight != vs.lineHeight)) ) { return true; } return false; } BOOL ScintillaWin::CreateSystemCaret() { sysCaretWidth = vs.caretWidth; if (0 == sysCaretWidth) { sysCaretWidth = 1; } sysCaretHeight = vs.lineHeight; int bitmapSize = (((sysCaretWidth + 15) & ~15) >> 3) * sysCaretHeight; std::vector bits(bitmapSize); sysCaretBitmap = ::CreateBitmap(sysCaretWidth, sysCaretHeight, 1, 1, reinterpret_cast(&bits[0])); BOOL retval = ::CreateCaret( MainHWND(), sysCaretBitmap, sysCaretWidth, sysCaretHeight); ::ShowCaret(MainHWND()); return retval; } BOOL ScintillaWin::DestroySystemCaret() { ::HideCaret(MainHWND()); BOOL retval = ::DestroyCaret(); if (sysCaretBitmap) { ::DeleteObject(sysCaretBitmap); sysCaretBitmap = 0; } return retval; } sptr_t PASCAL ScintillaWin::CTWndProc( HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam) { // Find C++ object associated with window. ScintillaWin *sciThis = reinterpret_cast(PointerFromWindow(hWnd)); try { // ctp will be zero if WM_CREATE not seen yet if (sciThis == 0) { if (iMessage == WM_CREATE) { // Associate CallTip object with window CREATESTRUCT *pCreate = reinterpret_cast(lParam); SetWindowPointer(hWnd, pCreate->lpCreateParams); return 0; } else { return ::DefWindowProc(hWnd, iMessage, wParam, lParam); } } else { if (iMessage == WM_NCDESTROY) { ::SetWindowLong(hWnd, 0, 0); return ::DefWindowProc(hWnd, iMessage, wParam, lParam); } else if (iMessage == WM_PAINT) { PAINTSTRUCT ps; ::BeginPaint(hWnd, &ps); Surface *surfaceWindow = Surface::Allocate(sciThis->technology); if (surfaceWindow) { #if defined(USE_D2D) ID2D1HwndRenderTarget *pCTRenderTarget = 0; #endif RECT rc; GetClientRect(hWnd, &rc); // Create a Direct2D render target. if (sciThis->technology == SC_TECHNOLOGY_DEFAULT) { surfaceWindow->Init(ps.hdc, hWnd); } else { #if defined(USE_D2D) D2D1_HWND_RENDER_TARGET_PROPERTIES dhrtp; dhrtp.hwnd = hWnd; dhrtp.pixelSize = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); dhrtp.presentOptions = D2D1_PRESENT_OPTIONS_NONE; D2D1_RENDER_TARGET_PROPERTIES drtp; drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; drtp.pixelFormat.format = DXGI_FORMAT_UNKNOWN; drtp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN; drtp.dpiX = 96.0; drtp.dpiY = 96.0; drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE; drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; if (!SUCCEEDED(pD2DFactory->CreateHwndRenderTarget(drtp, dhrtp, &pCTRenderTarget))) { surfaceWindow->Release(); delete surfaceWindow; ::EndPaint(hWnd, &ps); return 0; } surfaceWindow->Init(pCTRenderTarget, hWnd); pCTRenderTarget->BeginDraw(); #endif } surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == sciThis->ct.codePage); surfaceWindow->SetDBCSMode(sciThis->ct.codePage); sciThis->ct.PaintCT(surfaceWindow); #if defined(USE_D2D) if (pCTRenderTarget) pCTRenderTarget->EndDraw(); #endif surfaceWindow->Release(); delete surfaceWindow; #if defined(USE_D2D) if (pCTRenderTarget) pCTRenderTarget->Release(); #endif } ::EndPaint(hWnd, &ps); return 0; } else if ((iMessage == WM_NCLBUTTONDOWN) || (iMessage == WM_NCLBUTTONDBLCLK)) { POINT pt; pt.x = static_cast(LOWORD(lParam)); pt.y = static_cast(HIWORD(lParam)); ScreenToClient(hWnd, &pt); sciThis->ct.MouseClick(Point(pt.x, pt.y)); sciThis->CallTipClick(); return 0; } else if (iMessage == WM_LBUTTONDOWN) { // This does not fire due to the hit test code sciThis->ct.MouseClick(Point::FromLong(lParam)); sciThis->CallTipClick(); return 0; } else if (iMessage == WM_SETCURSOR) { ::SetCursor(::LoadCursor(NULL, IDC_ARROW)); return 0; } else if (iMessage == WM_NCHITTEST) { return HTCAPTION; } else { return ::DefWindowProc(hWnd, iMessage, wParam, lParam); } } } catch (...) { sciThis->errorStatus = SC_STATUS_FAILURE; } return ::DefWindowProc(hWnd, iMessage, wParam, lParam); } sptr_t ScintillaWin::DirectFunction( ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam) { PLATFORM_ASSERT(::GetCurrentThreadId() == ::GetWindowThreadProcessId(sci->MainHWND(), NULL)); return sci->WndProc(iMessage, wParam, lParam); } extern "C" #ifndef STATIC_BUILD __declspec(dllexport) #endif sptr_t __stdcall Scintilla_DirectFunction( ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam) { return sci->WndProc(iMessage, wParam, lParam); } sptr_t PASCAL ScintillaWin::SWndProc( HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam) { //Platform::DebugPrintf("S W:%x M:%x WP:%x L:%x\n", hWnd, iMessage, wParam, lParam); // Find C++ object associated with window. ScintillaWin *sci = reinterpret_cast(PointerFromWindow(hWnd)); // sci will be zero if WM_CREATE not seen yet if (sci == 0) { try { if (iMessage == WM_CREATE) { // Create C++ object associated with window sci = new ScintillaWin(hWnd); SetWindowPointer(hWnd, sci); return sci->WndProc(iMessage, wParam, lParam); } } catch (...) { } return ::DefWindowProc(hWnd, iMessage, wParam, lParam); } else { if (iMessage == WM_NCDESTROY) { try { sci->Finalise(); delete sci; } catch (...) { } ::SetWindowLong(hWnd, 0, 0); return ::DefWindowProc(hWnd, iMessage, wParam, lParam); } else { return sci->WndProc(iMessage, wParam, lParam); } } } // This function is externally visible so it can be called from container when building statically. // Must be called once only. int Scintilla_RegisterClasses(void *hInstance) { Platform_Initialise(hInstance); bool result = ScintillaWin::Register(reinterpret_cast(hInstance)); #ifdef SCI_LEXER Scintilla_LinkLexers(); #endif return result; } // This function is externally visible so it can be called from container when building statically. int Scintilla_ReleaseResources() { bool result = ScintillaWin::Unregister(); Platform_Finalise(); return result; } #ifndef STATIC_BUILD extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) { //Platform::DebugPrintf("Scintilla::DllMain %d %d\n", hInstance, dwReason); if (dwReason == DLL_PROCESS_ATTACH) { if (!Scintilla_RegisterClasses(hInstance)) return FALSE; } else if (dwReason == DLL_PROCESS_DETACH) { Scintilla_ReleaseResources(); } return TRUE; } #endif