Merge branch 'scintilla-551'
[TortoiseGit.git] / ext / scintilla / win32 / ScintillaWin.cxx
blobaf76eea2cf2d7b7240444fa5efa170e71a5d6db5
1 // Scintilla source code edit control
2 /** @file ScintillaWin.cxx
3 ** Windows specific subclass of ScintillaBase.
4 **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstdint>
11 #include <cassert>
12 #include <cstring>
13 #include <cstdio>
14 #include <cmath>
15 #include <climits>
17 #include <stdexcept>
18 #include <new>
19 #include <string>
20 #include <string_view>
21 #include <vector>
22 #include <map>
23 #include <set>
24 #include <optional>
25 #include <algorithm>
26 #include <memory>
27 #include <chrono>
28 #include <mutex>
30 // Want to use std::min and std::max so don't want Windows.h version of min and max
31 #if !defined(NOMINMAX)
32 #define NOMINMAX
33 #endif
34 #undef _WIN32_WINNT
35 #define _WIN32_WINNT 0x0A00
36 #undef WINVER
37 #define WINVER 0x0A00
38 #define WIN32_LEAN_AND_MEAN 1
39 #include <windows.h>
40 #include <commctrl.h>
41 #include <richedit.h>
42 #include <windowsx.h>
43 #include <zmouse.h>
44 #include <ole2.h>
46 #if !defined(DISABLE_D2D)
47 #define USE_D2D 1
48 #endif
50 #if defined(USE_D2D)
51 #include <d2d1.h>
52 #include <dwrite.h>
53 #endif
55 #include "ScintillaTypes.h"
56 #include "ScintillaMessages.h"
57 #include "ScintillaStructures.h"
58 #include "ILoader.h"
59 #include "ILexer.h"
61 #include "Debugging.h"
62 #include "Geometry.h"
63 #include "Platform.h"
65 #include "CharacterCategoryMap.h"
66 #include "Position.h"
67 #include "UniqueString.h"
68 #include "SplitVector.h"
69 #include "Partitioning.h"
70 #include "RunStyles.h"
71 #include "ContractionState.h"
72 #include "CellBuffer.h"
73 #include "CallTip.h"
74 #include "KeyMap.h"
75 #include "Indicator.h"
76 #include "LineMarker.h"
77 #include "Style.h"
78 #include "ViewStyle.h"
79 #include "CharClassify.h"
80 #include "Decoration.h"
81 #include "CaseFolder.h"
82 #include "Document.h"
83 #include "CaseConvert.h"
84 #include "UniConversion.h"
85 #include "Selection.h"
86 #include "PositionCache.h"
87 #include "EditModel.h"
88 #include "MarginView.h"
89 #include "EditView.h"
90 #include "Editor.h"
91 #include "ElapsedPeriod.h"
93 #include "AutoComplete.h"
94 #include "ScintillaBase.h"
96 #include "WinTypes.h"
97 #include "PlatWin.h"
98 #include "HanjaDic.h"
99 #include "ScintillaWin.h"
101 namespace {
103 // Two idle messages SC_WIN_IDLE and SC_WORK_IDLE.
105 // SC_WIN_IDLE is low priority so should occur after the next WM_PAINT
106 // It is for lengthy actions like wrapping and background styling
107 constexpr UINT SC_WIN_IDLE = 5001;
108 // SC_WORK_IDLE is high priority and should occur before the next WM_PAINT
109 // It is for shorter actions like restyling the text just inserted
110 // and delivering SCN_UPDATEUI
111 constexpr UINT SC_WORK_IDLE = 5002;
113 constexpr int IndicatorInput = static_cast<int>(Scintilla::IndicatorNumbers::Ime);
114 constexpr int IndicatorTarget = IndicatorInput + 1;
115 constexpr int IndicatorConverted = IndicatorInput + 2;
116 constexpr int IndicatorUnknown = IndicatorInput + 3;
118 typedef UINT_PTR (WINAPI *SetCoalescableTimerSig)(HWND hwnd, UINT_PTR nIDEvent,
119 UINT uElapse, TIMERPROC lpTimerFunc, ULONG uToleranceDelay);
123 // GCC has trouble with the standard COM ABI so do it the old C way with explicit vtables.
125 using namespace Scintilla;
126 using namespace Scintilla::Internal;
128 namespace {
130 const TCHAR callClassName[] = TEXT("CallTip");
132 void SetWindowID(HWND hWnd, int identifier) noexcept {
133 ::SetWindowLongPtr(hWnd, GWLP_ID, identifier);
136 constexpr POINT POINTFromLParam(sptr_t lParam) noexcept {
137 return { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
140 constexpr Point PointFromLParam(sptr_t lpoint) noexcept {
141 return Point::FromInts(GET_X_LPARAM(lpoint), GET_Y_LPARAM(lpoint));
144 bool KeyboardIsKeyDown(int key) noexcept {
145 return (::GetKeyState(key) & 0x80000000) != 0;
148 // Bit 24 is the extended keyboard flag and the numeric keypad is non-extended
149 constexpr sptr_t extendedKeyboard = 1 << 24;
151 constexpr bool KeyboardIsNumericKeypadFunction(uptr_t wParam, sptr_t lParam) {
152 if ((lParam & extendedKeyboard) != 0) {
153 // Not from the numeric keypad
154 return false;
157 switch (wParam) {
158 case VK_INSERT: // 0
159 case VK_END: // 1
160 case VK_DOWN: // 2
161 case VK_NEXT: // 3
162 case VK_LEFT: // 4
163 case VK_CLEAR: // 5
164 case VK_RIGHT: // 6
165 case VK_HOME: // 7
166 case VK_UP: // 8
167 case VK_PRIOR: // 9
168 return true;
169 default:
170 return false;
176 class FormatEnumerator final : public IEnumFORMATETC {
177 public:
178 ULONG ref;
179 ULONG pos;
180 std::vector<CLIPFORMAT> formats;
181 FormatEnumerator(ULONG pos_, const CLIPFORMAT formats_[], size_t formatsLen_);
183 // IUnknown
184 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
185 STDMETHODIMP_(ULONG)AddRef() override;
186 STDMETHODIMP_(ULONG)Release() override;
188 // IEnumFORMATETC
189 STDMETHODIMP Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) override;
190 STDMETHODIMP Skip(ULONG celt) override;
191 STDMETHODIMP Reset() override;
192 STDMETHODIMP Clone(IEnumFORMATETC **ppenum) override;
197 class DropSource final : public IDropSource {
198 public:
199 ScintillaWin *sci = nullptr;
201 // IUnknown
202 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
203 STDMETHODIMP_(ULONG)AddRef() override;
204 STDMETHODIMP_(ULONG)Release() override;
206 // IDropSource
207 STDMETHODIMP QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) override;
208 STDMETHODIMP GiveFeedback(DWORD) override;
213 class DataObject final : public IDataObject {
214 public:
215 ScintillaWin *sci = nullptr;
217 // IUnknown
218 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
219 STDMETHODIMP_(ULONG)AddRef() override;
220 STDMETHODIMP_(ULONG)Release() override;
222 // IDataObject
223 STDMETHODIMP GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) override;
224 STDMETHODIMP GetDataHere(FORMATETC *, STGMEDIUM *) override;
225 STDMETHODIMP QueryGetData(FORMATETC *pFE) override;
226 STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *, FORMATETC *pFEOut) override;
227 STDMETHODIMP SetData(FORMATETC *, STGMEDIUM *, BOOL) override;
228 STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnum) override;
229 STDMETHODIMP DAdvise(FORMATETC *, DWORD, IAdviseSink *, PDWORD) override;
230 STDMETHODIMP DUnadvise(DWORD) override;
231 STDMETHODIMP EnumDAdvise(IEnumSTATDATA **) override;
236 class DropTarget final : public IDropTarget {
237 public:
238 ScintillaWin *sci = nullptr;
240 // IUnknown
241 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
242 STDMETHODIMP_(ULONG)AddRef() override;
243 STDMETHODIMP_(ULONG)Release() override;
245 // IDropTarget
246 STDMETHODIMP DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) override;
247 STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) override;
248 STDMETHODIMP DragLeave() override;
249 STDMETHODIMP Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) override;
252 class IMContext {
253 HWND hwnd;
254 public:
255 HIMC hIMC;
256 IMContext(HWND hwnd_) noexcept :
257 hwnd(hwnd_), hIMC(::ImmGetContext(hwnd_)) {
259 // Deleted so IMContext objects can not be copied.
260 IMContext(const IMContext &) = delete;
261 IMContext(IMContext &&) = delete;
262 IMContext &operator=(const IMContext &) = delete;
263 IMContext &operator=(IMContext &&) = delete;
264 ~IMContext() {
265 if (hIMC)
266 ::ImmReleaseContext(hwnd, hIMC);
269 unsigned int GetImeCaretPos() const noexcept {
270 return ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, nullptr, 0);
273 std::vector<BYTE> GetImeAttributes() {
274 const int attrLen = ::ImmGetCompositionStringW(hIMC, GCS_COMPATTR, nullptr, 0);
275 std::vector<BYTE> attr(attrLen, 0);
276 ::ImmGetCompositionStringW(hIMC, GCS_COMPATTR, &attr[0], static_cast<DWORD>(attr.size()));
277 return attr;
280 LONG GetCompositionStringLength(DWORD dwIndex) const noexcept {
281 const LONG byteLen = ::ImmGetCompositionStringW(hIMC, dwIndex, nullptr, 0);
282 return byteLen / sizeof(wchar_t);
285 std::wstring GetCompositionString(DWORD dwIndex) {
286 const LONG byteLen = ::ImmGetCompositionStringW(hIMC, dwIndex, nullptr, 0);
287 std::wstring wcs(byteLen / 2, 0);
288 ::ImmGetCompositionStringW(hIMC, dwIndex, &wcs[0], byteLen);
289 return wcs;
293 class GlobalMemory;
295 class ReverseArrowCursor {
296 HCURSOR cursor {};
297 bool valid = false;
299 public:
300 ReverseArrowCursor() noexcept {}
301 // Deleted so ReverseArrowCursor objects can not be copied.
302 ReverseArrowCursor(const ReverseArrowCursor &) = delete;
303 ReverseArrowCursor(ReverseArrowCursor &&) = delete;
304 ReverseArrowCursor &operator=(const ReverseArrowCursor &) = delete;
305 ReverseArrowCursor &operator=(ReverseArrowCursor &&) = delete;
306 ~ReverseArrowCursor() {
307 if (cursor) {
308 ::DestroyCursor(cursor);
312 void Invalidate() noexcept {
313 valid = false;
316 HCURSOR Load(UINT dpi) noexcept {
317 if (cursor) {
318 if (valid) {
319 return cursor;
321 ::DestroyCursor(cursor);
324 valid = true;
325 cursor = LoadReverseArrowCursor(dpi);
326 return cursor ? cursor : ::LoadCursor({}, IDC_ARROW);
330 struct HorizontalScrollRange {
331 int pageWidth;
332 int documentWidth;
335 CLIPFORMAT RegisterClipboardType(LPCWSTR lpszFormat) noexcept {
336 // Registered clipboard format values are 0xC000 through 0xFFFF.
337 // RegisterClipboardFormatW returns 32-bit unsigned and CLIPFORMAT is 16-bit
338 // unsigned so choose the low 16-bits with &.
339 return ::RegisterClipboardFormatW(lpszFormat) & 0xFFFF;
344 namespace Scintilla::Internal {
348 class ScintillaWin :
349 public ScintillaBase {
351 bool lastKeyDownConsumed;
352 wchar_t lastHighSurrogateChar;
354 bool capturedMouse;
355 bool trackedMouseLeave;
356 BOOL typingWithoutCursor;
357 bool cursorIsHidden;
358 SetCoalescableTimerSig SetCoalescableTimerFn;
360 unsigned int linesPerScroll; ///< Intellimouse support
361 unsigned int charsPerScroll; ///< Intellimouse support
362 MouseWheelDelta verticalWheelDelta;
363 MouseWheelDelta horizontalWheelDelta;
365 UINT dpi = USER_DEFAULT_SCREEN_DPI;
366 ReverseArrowCursor reverseArrowCursor;
368 PRectangle rectangleClient;
369 HRGN hRgnUpdate;
371 bool hasOKText;
373 CLIPFORMAT cfColumnSelect;
374 CLIPFORMAT cfBorlandIDEBlockType;
375 CLIPFORMAT cfLineSelect;
376 CLIPFORMAT cfVSLineTag;
378 HRESULT hrOle;
379 DropSource ds;
380 DataObject dob;
381 DropTarget dt;
383 static HINSTANCE hInstance;
384 static ATOM scintillaClassAtom;
385 static ATOM callClassAtom;
387 float deviceScaleFactor = 1.f;
388 int GetFirstIntegralMultipleDeviceScaleFactor() const noexcept {
389 return static_cast<int>(std::ceil(deviceScaleFactor));
392 #if defined(USE_D2D)
393 ID2D1RenderTarget *pRenderTarget;
394 bool renderTargetValid;
395 // rendering parameters for current monitor
396 HMONITOR hCurrentMonitor;
397 std::shared_ptr<RenderingParams> renderingParams;
398 #endif
400 explicit ScintillaWin(HWND hwnd);
401 // Deleted so ScintillaWin objects can not be copied.
402 ScintillaWin(const ScintillaWin &) = delete;
403 ScintillaWin(ScintillaWin &&) = delete;
404 ScintillaWin &operator=(const ScintillaWin &) = delete;
405 ScintillaWin &operator=(ScintillaWin &&) = delete;
406 // ~ScintillaWin() in public section
408 void Finalise() override;
409 #if defined(USE_D2D)
410 bool UpdateRenderingParams(bool force) noexcept;
411 void EnsureRenderTarget(HDC hdc);
412 #endif
413 void DropRenderTarget() noexcept;
414 HWND MainHWND() const noexcept;
416 static sptr_t DirectFunction(
417 sptr_t ptr, UINT iMessage, uptr_t wParam, sptr_t lParam);
418 static sptr_t DirectStatusFunction(
419 sptr_t ptr, UINT iMessage, uptr_t wParam, sptr_t lParam, int *pStatus);
420 static LRESULT PASCAL SWndProc(
421 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
422 static LRESULT PASCAL CTWndProc(
423 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
425 enum : UINT_PTR { invalidTimerID, standardTimerID, idleTimerID, fineTimerStart };
427 void DisplayCursor(Window::Cursor c) override;
428 bool DragThreshold(Point ptStart, Point ptNow) override;
429 void StartDrag() override;
430 static KeyMod MouseModifiers(uptr_t wParam) noexcept;
432 Sci::Position TargetAsUTF8(char *text) const;
433 Sci::Position EncodedFromUTF8(const char *utf8, char *encoded) const;
435 void SetRenderingParams(Surface *psurf) const;
437 bool PaintDC(HDC hdc);
438 sptr_t WndPaint();
440 // DBCS
441 void ImeStartComposition();
442 void ImeEndComposition();
443 LRESULT ImeOnReconvert(LPARAM lParam);
444 LRESULT ImeOnDocumentFeed(LPARAM lParam) const;
445 sptr_t HandleCompositionWindowed(uptr_t wParam, sptr_t lParam);
446 sptr_t HandleCompositionInline(uptr_t wParam, sptr_t lParam);
447 static bool KoreanIME() noexcept;
448 void MoveImeCarets(Sci::Position offset) noexcept;
449 void DrawImeIndicator(int indicator, Sci::Position len);
450 void SetCandidateWindowPos();
451 void SelectionToHangul();
452 void EscapeHanja();
453 void ToggleHanja();
454 void AddWString(std::wstring_view wsv, CharacterSource charSource);
456 UINT CodePageOfDocument() const noexcept;
457 bool ValidCodePage(int codePage) const override;
458 std::string UTF8FromEncoded(std::string_view encoded) const override;
459 std::string EncodedFromUTF8(std::string_view utf8) const override;
461 std::string EncodeWString(std::wstring_view wsv);
462 sptr_t DefWndProc(Message iMessage, uptr_t wParam, sptr_t lParam) override;
463 void IdleWork() override;
464 void QueueIdleWork(WorkItems items, Sci::Position upTo) override;
465 bool SetIdle(bool on) override;
466 UINT_PTR timers[static_cast<int>(TickReason::dwell)+1] {};
467 bool FineTickerRunning(TickReason reason) override;
468 void FineTickerStart(TickReason reason, int millis, int tolerance) override;
469 void FineTickerCancel(TickReason reason) override;
470 void SetMouseCapture(bool on) override;
471 bool HaveMouseCapture() override;
472 void SetTrackMouseLeaveEvent(bool on) noexcept;
473 void HideCursorIfPreferred() noexcept;
474 void UpdateBaseElements() override;
475 bool PaintContains(PRectangle rc) override;
476 void ScrollText(Sci::Line linesToMove) override;
477 void NotifyCaretMove() override;
478 void UpdateSystemCaret() override;
479 void SetVerticalScrollPos() override;
480 void SetHorizontalScrollPos() override;
481 void HorizontalScrollToClamped(int xPos);
482 HorizontalScrollRange GetHorizontalScrollRange() const;
483 bool ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) override;
484 void NotifyChange() override;
485 void NotifyFocus(bool focus) override;
486 void SetCtrlID(int identifier) override;
487 int GetCtrlID() override;
488 void NotifyParent(NotificationData scn) override;
489 virtual void NotifyParent(SCNotification *scn);
490 void NotifyDoubleClick(Point pt, KeyMod modifiers) override;
491 std::unique_ptr<CaseFolder> CaseFolderForEncoding() override;
492 std::string CaseMapString(const std::string &s, CaseMapping caseMapping) override;
493 void Copy() override;
494 bool CanPaste() override;
495 void Paste() override;
496 void CreateCallTipWindow(PRectangle rc) override;
497 void AddToPopUp(const char *label, int cmd = 0, bool enabled = true) override;
498 void ClaimSelection() override;
500 void GetMouseParameters() noexcept;
501 void CopyToGlobal(GlobalMemory &gmUnicode, const SelectionText &selectedText);
502 void CopyToClipboard(const SelectionText &selectedText) override;
503 void ScrollMessage(WPARAM wParam);
504 void HorizontalScrollMessage(WPARAM wParam);
505 void FullPaint();
506 void FullPaintDC(HDC hdc);
507 bool IsCompatibleDC(HDC hOtherDC) noexcept;
508 DWORD EffectFromState(DWORD grfKeyState) const noexcept;
510 bool IsVisible() const noexcept;
511 int SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw) noexcept;
512 bool GetScrollInfo(int nBar, LPSCROLLINFO lpsi) noexcept;
513 bool ChangeScrollRange(int nBar, int nMin, int nMax, UINT nPage) noexcept;
514 void ChangeScrollPos(int barType, Sci::Position pos);
515 sptr_t GetTextLength();
516 sptr_t GetText(uptr_t wParam, sptr_t lParam);
517 Window::Cursor ContextCursor(Point pt);
518 sptr_t ShowContextMenu(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
519 PRectangle GetClientRectangle() const override;
520 void SizeWindow();
521 sptr_t MouseMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
522 sptr_t KeyMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
523 sptr_t FocusMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
524 sptr_t IMEMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
525 sptr_t EditMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
526 sptr_t IdleMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
527 sptr_t SciMessage(Message iMessage, uptr_t wParam, sptr_t lParam);
529 public:
530 ~ScintillaWin() override;
532 // Public for benefit of Scintilla_DirectFunction
533 sptr_t WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) override;
535 /// Implement IUnknown
536 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv);
537 STDMETHODIMP_(ULONG)AddRef();
538 STDMETHODIMP_(ULONG)Release();
540 /// Implement IDropTarget
541 STDMETHODIMP DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
542 POINTL pt, PDWORD pdwEffect);
543 STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect);
544 STDMETHODIMP DragLeave();
545 STDMETHODIMP Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
546 POINTL pt, PDWORD pdwEffect);
548 /// Implement important part of IDataObject
549 STDMETHODIMP GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM);
551 static void Prepare() noexcept;
552 static bool Register(HINSTANCE hInstance_) noexcept;
553 static bool Unregister() noexcept;
555 bool DragIsRectangularOK(CLIPFORMAT fmt) const noexcept {
556 return drag.rectangular && (fmt == cfColumnSelect);
559 private:
560 // For use in creating a system caret
561 bool HasCaretSizeChanged() const noexcept;
562 BOOL CreateSystemCaret();
563 BOOL DestroySystemCaret() noexcept;
564 HBITMAP sysCaretBitmap;
565 int sysCaretWidth;
566 int sysCaretHeight;
567 bool styleIdleInQueue;
570 HINSTANCE ScintillaWin::hInstance {};
571 ATOM ScintillaWin::scintillaClassAtom = 0;
572 ATOM ScintillaWin::callClassAtom = 0;
574 ScintillaWin::ScintillaWin(HWND hwnd) {
576 lastKeyDownConsumed = false;
577 lastHighSurrogateChar = 0;
579 capturedMouse = false;
580 trackedMouseLeave = false;
581 typingWithoutCursor = false;
582 cursorIsHidden = false;
583 SetCoalescableTimerFn = nullptr;
585 linesPerScroll = 0;
586 charsPerScroll = 0;
588 dpi = DpiForWindow(hwnd);
590 hRgnUpdate = {};
592 hasOKText = false;
594 // There does not seem to be a real standard for indicating that the clipboard
595 // contains a rectangular selection, so copy Developer Studio and Borland Delphi.
596 cfColumnSelect = RegisterClipboardType(L"MSDEVColumnSelect");
597 cfBorlandIDEBlockType = RegisterClipboardType(L"Borland IDE Block Type");
599 // Likewise for line-copy or line-cut (copies or cuts a full line when no text is selected)
600 cfLineSelect = RegisterClipboardType(L"MSDEVLineSelect");
601 cfVSLineTag = RegisterClipboardType(L"VisualStudioEditorOperationsLineCutCopyClipboardTag");
602 hrOle = E_FAIL;
604 wMain = hwnd;
606 dob.sci = this;
607 ds.sci = this;
608 dt.sci = this;
610 sysCaretBitmap = {};
611 sysCaretWidth = 0;
612 sysCaretHeight = 0;
614 styleIdleInQueue = false;
616 #if defined(USE_D2D)
617 pRenderTarget = nullptr;
618 renderTargetValid = true;
619 hCurrentMonitor = {};
620 #endif
622 caret.period = ::GetCaretBlinkTime();
623 if (caret.period < 0)
624 caret.period = 0;
626 // Initialize COM. If the app has already done this it will have
627 // no effect. If the app hasn't, we really shouldn't ask them to call
628 // it just so this internal feature works.
629 hrOle = ::OleInitialize(nullptr);
631 // Find SetCoalescableTimer which is only available from Windows 8+
632 HMODULE user32 = ::GetModuleHandleW(L"user32.dll");
633 SetCoalescableTimerFn = DLLFunction<SetCoalescableTimerSig>(user32, "SetCoalescableTimer");
635 vs.indicators[IndicatorUnknown] = Indicator(IndicatorStyle::Hidden, colourIME);
636 vs.indicators[IndicatorInput] = Indicator(IndicatorStyle::Dots, colourIME);
637 vs.indicators[IndicatorConverted] = Indicator(IndicatorStyle::CompositionThick, colourIME);
638 vs.indicators[IndicatorTarget] = Indicator(IndicatorStyle::StraightBox, colourIME);
641 ScintillaWin::~ScintillaWin() {
642 if (sysCaretBitmap) {
643 ::DeleteObject(sysCaretBitmap);
644 sysCaretBitmap = {};
648 void ScintillaWin::Finalise() {
649 ScintillaBase::Finalise();
650 for (TickReason tr = TickReason::caret; tr <= TickReason::dwell;
651 tr = static_cast<TickReason>(static_cast<int>(tr) + 1)) {
652 FineTickerCancel(tr);
654 SetIdle(false);
655 DropRenderTarget();
656 ::RevokeDragDrop(MainHWND());
657 if (SUCCEEDED(hrOle)) {
658 ::OleUninitialize();
662 #if defined(USE_D2D)
664 bool ScintillaWin::UpdateRenderingParams(bool force) noexcept {
665 if (!renderingParams) {
666 try {
667 renderingParams = std::make_shared<RenderingParams>();
668 } catch (const std::bad_alloc &) {
669 return false;
672 const HWND hRootWnd = ::GetAncestor(MainHWND(), GA_ROOT);
673 const HMONITOR monitor = Internal::MonitorFromWindowHandleScaling(hRootWnd);
674 if (!force && monitor == hCurrentMonitor && renderingParams->defaultRenderingParams) {
675 return false;
678 IDWriteRenderingParams *monitorRenderingParams = nullptr;
679 IDWriteRenderingParams *customClearTypeRenderingParams = nullptr;
680 const HRESULT hr = pIDWriteFactory->CreateMonitorRenderingParams(monitor, &monitorRenderingParams);
681 UINT clearTypeContrast = 0;
682 if (SUCCEEDED(hr) && ::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0) != 0) {
683 if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200) {
684 const FLOAT gamma = static_cast<FLOAT>(clearTypeContrast) / 1000.0f;
685 pIDWriteFactory->CreateCustomRenderingParams(gamma,
686 monitorRenderingParams->GetEnhancedContrast(),
687 monitorRenderingParams->GetClearTypeLevel(),
688 monitorRenderingParams->GetPixelGeometry(),
689 monitorRenderingParams->GetRenderingMode(),
690 &customClearTypeRenderingParams);
694 hCurrentMonitor = monitor;
695 deviceScaleFactor = Internal::GetDeviceScaleFactorWhenGdiScalingActive(hRootWnd);
696 renderingParams->defaultRenderingParams.reset(monitorRenderingParams);
697 renderingParams->customRenderingParams.reset(customClearTypeRenderingParams);
698 return true;
701 namespace {
703 D2D1_SIZE_U GetSizeUFromRect(const RECT &rc, const int scaleFactor) noexcept {
704 const long width = rc.right - rc.left;
705 const long height = rc.bottom - rc.top;
706 const UINT32 scaledWidth = width * scaleFactor;
707 const UINT32 scaledHeight = height * scaleFactor;
708 return D2D1::SizeU(scaledWidth, scaledHeight);
713 void ScintillaWin::EnsureRenderTarget(HDC hdc) {
714 if (!renderTargetValid) {
715 DropRenderTarget();
716 renderTargetValid = true;
718 if (!pRenderTarget) {
719 HWND hw = MainHWND();
720 RECT rc;
721 ::GetClientRect(hw, &rc);
723 // Create a Direct2D render target.
724 D2D1_RENDER_TARGET_PROPERTIES drtp {};
725 drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
726 drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE;
727 drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
729 if (technology == Technology::DirectWriteDC) {
730 drtp.dpiX = 96.f;
731 drtp.dpiY = 96.f;
732 // Explicit pixel format needed.
733 drtp.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
734 D2D1_ALPHA_MODE_IGNORE);
736 ID2D1DCRenderTarget *pDCRT = nullptr;
737 const HRESULT hr = pD2DFactory->CreateDCRenderTarget(&drtp, &pDCRT);
738 if (SUCCEEDED(hr)) {
739 pRenderTarget = pDCRT;
740 } else {
741 Platform::DebugPrintf("Failed CreateDCRenderTarget 0x%lx\n", hr);
742 pRenderTarget = nullptr;
745 } else {
746 const int integralDeviceScaleFactor = GetFirstIntegralMultipleDeviceScaleFactor();
747 drtp.dpiX = 96.f * integralDeviceScaleFactor;
748 drtp.dpiY = 96.f * integralDeviceScaleFactor;
749 drtp.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN,
750 D2D1_ALPHA_MODE_UNKNOWN);
752 D2D1_HWND_RENDER_TARGET_PROPERTIES dhrtp {};
753 dhrtp.hwnd = hw;
754 dhrtp.pixelSize = ::GetSizeUFromRect(rc, integralDeviceScaleFactor);
755 dhrtp.presentOptions = (technology == Technology::DirectWriteRetain) ?
756 D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS : D2D1_PRESENT_OPTIONS_NONE;
758 ID2D1HwndRenderTarget *pHwndRenderTarget = nullptr;
759 const HRESULT hr = pD2DFactory->CreateHwndRenderTarget(drtp, dhrtp, &pHwndRenderTarget);
760 if (SUCCEEDED(hr)) {
761 pRenderTarget = pHwndRenderTarget;
762 } else {
763 Platform::DebugPrintf("Failed CreateHwndRenderTarget 0x%lx\n", hr);
764 pRenderTarget = nullptr;
767 // Pixmaps were created to be compatible with previous render target so
768 // need to be recreated.
769 DropGraphics();
772 if ((technology == Technology::DirectWriteDC) && pRenderTarget) {
773 RECT rcWindow;
774 ::GetClientRect(MainHWND(), &rcWindow);
775 const HRESULT hr = static_cast<ID2D1DCRenderTarget*>(pRenderTarget)->BindDC(hdc, &rcWindow);
776 if (FAILED(hr)) {
777 Platform::DebugPrintf("BindDC failed 0x%lx\n", hr);
778 DropRenderTarget();
782 #endif
784 void ScintillaWin::DropRenderTarget() noexcept {
785 #if defined(USE_D2D)
786 ReleaseUnknown(pRenderTarget);
787 #endif
790 HWND ScintillaWin::MainHWND() const noexcept {
791 return HwndFromWindow(wMain);
794 void ScintillaWin::DisplayCursor(Window::Cursor c) {
795 if (cursorMode != CursorShape::Normal) {
796 c = static_cast<Window::Cursor>(cursorMode);
798 if (c == Window::Cursor::reverseArrow) {
799 ::SetCursor(reverseArrowCursor.Load(static_cast<UINT>(dpi * deviceScaleFactor)));
800 } else {
801 wMain.SetCursor(c);
805 bool ScintillaWin::DragThreshold(Point ptStart, Point ptNow) {
806 const Point ptDifference = ptStart - ptNow;
807 const XYPOSITION xMove = std::trunc(std::abs(ptDifference.x));
808 const XYPOSITION yMove = std::trunc(std::abs(ptDifference.y));
809 return (xMove > SystemMetricsForDpi(SM_CXDRAG, dpi)) ||
810 (yMove > SystemMetricsForDpi(SM_CYDRAG, dpi));
813 void ScintillaWin::StartDrag() {
814 inDragDrop = DragDrop::dragging;
815 DWORD dwEffect = 0;
816 dropWentOutside = true;
817 IDataObject *pDataObject = &dob;
818 IDropSource *pDropSource = &ds;
819 //Platform::DebugPrintf("About to DoDragDrop %x %x\n", pDataObject, pDropSource);
820 const HRESULT hr = ::DoDragDrop(
821 pDataObject,
822 pDropSource,
823 DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);
824 //Platform::DebugPrintf("DoDragDrop = %x\n", hr);
825 if (SUCCEEDED(hr)) {
826 if ((hr == DRAGDROP_S_DROP) && (dwEffect == DROPEFFECT_MOVE) && dropWentOutside) {
827 // Remove dragged out text
828 ClearSelection();
831 inDragDrop = DragDrop::none;
832 SetDragPosition(SelectionPosition(Sci::invalidPosition));
835 KeyMod ScintillaWin::MouseModifiers(uptr_t wParam) noexcept {
836 return ModifierFlags(
837 (wParam & MK_SHIFT) != 0,
838 (wParam & MK_CONTROL) != 0,
839 KeyboardIsKeyDown(VK_MENU));
844 namespace {
846 int InputCodePage() noexcept {
847 HKL inputLocale = ::GetKeyboardLayout(0);
848 const LANGID inputLang = LOWORD(inputLocale);
849 char sCodePage[10];
850 const int res = ::GetLocaleInfoA(MAKELCID(inputLang, SORT_DEFAULT),
851 LOCALE_IDEFAULTANSICODEPAGE, sCodePage, sizeof(sCodePage));
852 if (!res)
853 return 0;
854 return atoi(sCodePage);
857 /** Map the key codes to their equivalent Keys:: form. */
858 Keys KeyTranslate(uptr_t keyIn) noexcept {
859 switch (keyIn) {
860 case VK_DOWN: return Keys::Down;
861 case VK_UP: return Keys::Up;
862 case VK_LEFT: return Keys::Left;
863 case VK_RIGHT: return Keys::Right;
864 case VK_HOME: return Keys::Home;
865 case VK_END: return Keys::End;
866 case VK_PRIOR: return Keys::Prior;
867 case VK_NEXT: return Keys::Next;
868 case VK_DELETE: return Keys::Delete;
869 case VK_INSERT: return Keys::Insert;
870 case VK_ESCAPE: return Keys::Escape;
871 case VK_BACK: return Keys::Back;
872 case VK_TAB: return Keys::Tab;
873 case VK_RETURN: return Keys::Return;
874 case VK_ADD: return Keys::Add;
875 case VK_SUBTRACT: return Keys::Subtract;
876 case VK_DIVIDE: return Keys::Divide;
877 case VK_LWIN: return Keys::Win;
878 case VK_RWIN: return Keys::RWin;
879 case VK_APPS: return Keys::Menu;
880 case VK_OEM_2: return static_cast<Keys>('/');
881 case VK_OEM_3: return static_cast<Keys>('`');
882 case VK_OEM_4: return static_cast<Keys>('[');
883 case VK_OEM_5: return static_cast<Keys>('\\');
884 case VK_OEM_6: return static_cast<Keys>(']');
885 default: return static_cast<Keys>(keyIn);
889 bool BoundsContains(PRectangle rcBounds, HRGN hRgnBounds, PRectangle rcCheck) noexcept {
890 bool contains = true;
891 if (!rcCheck.Empty()) {
892 if (!rcBounds.Contains(rcCheck)) {
893 contains = false;
894 } else if (hRgnBounds) {
895 // In bounding rectangle so check more accurately using region
896 const RECT rcw = RectFromPRectangle(rcCheck);
897 HRGN hRgnCheck = ::CreateRectRgnIndirect(&rcw);
898 if (hRgnCheck) {
899 HRGN hRgnDifference = ::CreateRectRgn(0, 0, 0, 0);
900 if (hRgnDifference) {
901 const int combination = ::CombineRgn(hRgnDifference, hRgnCheck, hRgnBounds, RGN_DIFF);
902 if (combination != NULLREGION) {
903 contains = false;
905 ::DeleteRgn(hRgnDifference);
907 ::DeleteRgn(hRgnCheck);
911 return contains;
914 // Simplify calling WideCharToMultiByte and MultiByteToWideChar by providing default parameters and using string view.
916 int MultiByteFromWideChar(UINT codePage, std::wstring_view wsv, LPSTR lpMultiByteStr, ptrdiff_t cbMultiByte) noexcept {
917 return ::WideCharToMultiByte(codePage, 0, wsv.data(), static_cast<int>(wsv.length()), lpMultiByteStr, static_cast<int>(cbMultiByte), nullptr, nullptr);
920 int MultiByteLenFromWideChar(UINT codePage, std::wstring_view wsv) noexcept {
921 return MultiByteFromWideChar(codePage, wsv, nullptr, 0);
924 int WideCharFromMultiByte(UINT codePage, std::string_view sv, LPWSTR lpWideCharStr, ptrdiff_t cchWideChar) noexcept {
925 return ::MultiByteToWideChar(codePage, 0, sv.data(), static_cast<int>(sv.length()), lpWideCharStr, static_cast<int>(cchWideChar));
928 int WideCharLenFromMultiByte(UINT codePage, std::string_view sv) noexcept {
929 return WideCharFromMultiByte(codePage, sv, nullptr, 0);
932 std::string StringEncode(std::wstring_view wsv, int codePage) {
933 const int cchMulti = wsv.length() ? MultiByteLenFromWideChar(codePage, wsv) : 0;
934 std::string sMulti(cchMulti, 0);
935 if (cchMulti) {
936 MultiByteFromWideChar(codePage, wsv, sMulti.data(), cchMulti);
938 return sMulti;
941 std::wstring StringDecode(std::string_view sv, int codePage) {
942 const int cchWide = sv.length() ? WideCharLenFromMultiByte(codePage, sv) : 0;
943 std::wstring sWide(cchWide, 0);
944 if (cchWide) {
945 WideCharFromMultiByte(codePage, sv, sWide.data(), cchWide);
947 return sWide;
950 std::wstring StringMapCase(std::wstring_view wsv, DWORD mapFlags) {
951 const int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
952 wsv.data(), static_cast<int>(wsv.length()), nullptr, 0);
953 std::wstring wsConverted(charsConverted, 0);
954 if (charsConverted) {
955 ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
956 wsv.data(), static_cast<int>(wsv.length()), wsConverted.data(), charsConverted);
958 return wsConverted;
963 // Returns the target converted to UTF8.
964 // Return the length in bytes.
965 Sci::Position ScintillaWin::TargetAsUTF8(char *text) const {
966 const Sci::Position targetLength = targetRange.Length();
967 if (IsUnicodeMode()) {
968 if (text) {
969 pdoc->GetCharRange(text, targetRange.start.Position(), targetLength);
971 } else {
972 // Need to convert
973 const std::string s = RangeText(targetRange.start.Position(), targetRange.end.Position());
974 const std::wstring characters = StringDecode(s, CodePageOfDocument());
975 const int utf8Len = MultiByteLenFromWideChar(CpUtf8, characters);
976 if (text) {
977 MultiByteFromWideChar(CpUtf8, characters, text, utf8Len);
978 text[utf8Len] = '\0';
980 return utf8Len;
982 return targetLength;
985 // Translates a nul terminated UTF8 string into the document encoding.
986 // Return the length of the result in bytes.
987 Sci::Position ScintillaWin::EncodedFromUTF8(const char *utf8, char *encoded) const {
988 const Sci::Position inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
989 if (IsUnicodeMode()) {
990 if (encoded) {
991 memcpy(encoded, utf8, inputLength);
993 return inputLength;
994 } else {
995 // Need to convert
996 const std::string_view utf8Input(utf8, inputLength);
997 const int charsLen = WideCharLenFromMultiByte(CpUtf8, utf8Input);
998 std::wstring characters(charsLen, L'\0');
999 WideCharFromMultiByte(CpUtf8, utf8Input, &characters[0], charsLen);
1001 const int encodedLen = MultiByteLenFromWideChar(CodePageOfDocument(), characters);
1002 if (encoded) {
1003 MultiByteFromWideChar(CodePageOfDocument(), characters, encoded, encodedLen);
1004 encoded[encodedLen] = '\0';
1006 return encodedLen;
1010 void ScintillaWin::SetRenderingParams([[maybe_unused]] Surface *psurf) const {
1011 #if defined(USE_D2D)
1012 if (psurf) {
1013 ISetRenderingParams *setDrawingParams = dynamic_cast<ISetRenderingParams *>(psurf);
1014 if (setDrawingParams) {
1015 setDrawingParams->SetRenderingParams(renderingParams);
1018 #endif
1021 bool ScintillaWin::PaintDC(HDC hdc) {
1022 if (technology == Technology::Default) {
1023 AutoSurface surfaceWindow(hdc, this);
1024 if (surfaceWindow) {
1025 Paint(surfaceWindow, rcPaint);
1026 surfaceWindow->Release();
1028 } else {
1029 #if defined(USE_D2D)
1030 EnsureRenderTarget(hdc);
1031 if (pRenderTarget) {
1032 AutoSurface surfaceWindow(pRenderTarget, this);
1033 if (surfaceWindow) {
1034 SetRenderingParams(surfaceWindow);
1035 pRenderTarget->BeginDraw();
1036 Paint(surfaceWindow, rcPaint);
1037 surfaceWindow->Release();
1038 const HRESULT hr = pRenderTarget->EndDraw();
1039 if (hr == static_cast<HRESULT>(D2DERR_RECREATE_TARGET)) {
1040 DropRenderTarget();
1041 return false;
1045 #endif
1048 return true;
1051 sptr_t ScintillaWin::WndPaint() {
1052 //ElapsedPeriod ep;
1054 // Redirect assertions to debug output and save current state
1055 const bool assertsPopup = Platform::ShowAssertionPopUps(false);
1056 paintState = PaintState::painting;
1057 PAINTSTRUCT ps = {};
1059 // Removed since this interferes with reporting other assertions as it occurs repeatedly
1060 //PLATFORM_ASSERT(hRgnUpdate == NULL);
1061 hRgnUpdate = ::CreateRectRgn(0, 0, 0, 0);
1062 ::GetUpdateRgn(MainHWND(), hRgnUpdate, FALSE);
1063 ::BeginPaint(MainHWND(), &ps);
1064 rcPaint = PRectangle::FromInts(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom);
1065 const PRectangle rcClient = GetClientRectangle();
1066 paintingAllText = BoundsContains(rcPaint, hRgnUpdate, rcClient);
1067 if (!PaintDC(ps.hdc)) {
1068 paintState = PaintState::abandoned;
1070 if (hRgnUpdate) {
1071 ::DeleteRgn(hRgnUpdate);
1072 hRgnUpdate = {};
1075 ::EndPaint(MainHWND(), &ps);
1076 if (paintState == PaintState::abandoned) {
1077 // Painting area was insufficient to cover new styling or brace highlight positions
1078 FullPaint();
1079 ::ValidateRect(MainHWND(), nullptr);
1081 paintState = PaintState::notPainting;
1083 // Restore debug output state
1084 Platform::ShowAssertionPopUps(assertsPopup);
1086 //Platform::DebugPrintf("Paint took %g\n", ep.Duration());
1087 return 0;
1090 sptr_t ScintillaWin::HandleCompositionWindowed(uptr_t wParam, sptr_t lParam) {
1091 if (lParam & GCS_RESULTSTR) {
1092 IMContext imc(MainHWND());
1093 if (imc.hIMC) {
1094 AddWString(imc.GetCompositionString(GCS_RESULTSTR), CharacterSource::ImeResult);
1096 // Set new position after converted
1097 const Point pos = PointMainCaret();
1098 COMPOSITIONFORM CompForm {};
1099 CompForm.dwStyle = CFS_POINT;
1100 CompForm.ptCurrentPos = POINTFromPoint(pos);
1101 ::ImmSetCompositionWindow(imc.hIMC, &CompForm);
1103 return 0;
1105 return ::DefWindowProc(MainHWND(), WM_IME_COMPOSITION, wParam, lParam);
1108 bool ScintillaWin::KoreanIME() noexcept {
1109 const int codePage = InputCodePage();
1110 return codePage == 949 || codePage == 1361;
1113 void ScintillaWin::MoveImeCarets(Sci::Position offset) noexcept {
1114 // Move carets relatively by bytes.
1115 for (size_t r=0; r<sel.Count(); r++) {
1116 const Sci::Position positionInsert = sel.Range(r).Start().Position();
1117 sel.Range(r).caret.SetPosition(positionInsert + offset);
1118 sel.Range(r).anchor.SetPosition(positionInsert + offset);
1122 void ScintillaWin::DrawImeIndicator(int indicator, Sci::Position len) {
1123 // Emulate the visual style of IME characters with indicators.
1124 // Draw an indicator on the character before caret by the character bytes of len
1125 // so it should be called after InsertCharacter().
1126 // It does not affect caret positions.
1127 if (indicator < 8 || indicator > IndicatorMax) {
1128 return;
1130 pdoc->DecorationSetCurrentIndicator(indicator);
1131 for (size_t r=0; r<sel.Count(); r++) {
1132 const Sci::Position positionInsert = sel.Range(r).Start().Position();
1133 pdoc->DecorationFillRange(positionInsert - len, 1, len);
1137 void ScintillaWin::SetCandidateWindowPos() {
1138 IMContext imc(MainHWND());
1139 if (imc.hIMC) {
1140 const Point pos = PointMainCaret();
1141 const PRectangle rcClient = GetTextRectangle();
1142 CANDIDATEFORM CandForm{};
1143 CandForm.dwIndex = 0;
1144 CandForm.dwStyle = CFS_EXCLUDE;
1145 CandForm.ptCurrentPos.x = static_cast<int>(pos.x);
1146 CandForm.ptCurrentPos.y = static_cast<int>(pos.y + std::max(4, vs.lineHeight/4));
1147 // Exclude the area of the whole caret line
1148 CandForm.rcArea.top = static_cast<int>(pos.y);
1149 CandForm.rcArea.bottom = static_cast<int>(pos.y + vs.lineHeight);
1150 CandForm.rcArea.left = static_cast<int>(rcClient.left);
1151 CandForm.rcArea.right = static_cast<int>(rcClient.right);
1152 ::ImmSetCandidateWindow(imc.hIMC, &CandForm);
1156 void ScintillaWin::SelectionToHangul() {
1157 // Convert every hanja to hangul within the main range.
1158 const Sci::Position selStart = sel.RangeMain().Start().Position();
1159 const Sci::Position documentStrLen = sel.RangeMain().Length();
1160 const Sci::Position selEnd = selStart + documentStrLen;
1161 const Sci::Position utf16Len = pdoc->CountUTF16(selStart, selEnd);
1163 if (utf16Len > 0) {
1164 std::string documentStr(documentStrLen, '\0');
1165 pdoc->GetCharRange(&documentStr[0], selStart, documentStrLen);
1167 std::wstring uniStr = StringDecode(documentStr, CodePageOfDocument());
1168 const bool converted = HanjaDict::GetHangulOfHanja(uniStr);
1170 if (converted) {
1171 documentStr = StringEncode(uniStr, CodePageOfDocument());
1172 pdoc->BeginUndoAction();
1173 ClearSelection();
1174 InsertPaste(&documentStr[0], documentStr.size());
1175 pdoc->EndUndoAction();
1180 void ScintillaWin::EscapeHanja() {
1181 // The candidate box pops up to user to select a hanja.
1182 // It comes into WM_IME_COMPOSITION with GCS_RESULTSTR.
1183 // The existing hangul or hanja is replaced with it.
1184 if (sel.Count() > 1) {
1185 return; // Do not allow multi carets.
1187 const Sci::Position currentPos = CurrentPosition();
1188 const int oneCharLen = pdoc->LenChar(currentPos);
1190 if (oneCharLen < 2) {
1191 return; // No need to handle SBCS.
1194 // ImmEscapeW() may overwrite uniChar[] with a null terminated string.
1195 // So enlarge it enough to Maximum 4 as in UTF-8.
1196 constexpr size_t safeLength = UTF8MaxBytes + 1;
1197 std::string oneChar(safeLength, '\0');
1198 pdoc->GetCharRange(&oneChar[0], currentPos, oneCharLen);
1200 std::wstring uniChar = StringDecode(oneChar, CodePageOfDocument());
1202 IMContext imc(MainHWND());
1203 if (imc.hIMC) {
1204 // Set the candidate box position since IME may show it.
1205 SetCandidateWindowPos();
1206 // IME_ESC_HANJA_MODE appears to receive the first character only.
1207 if (::ImmEscapeW(GetKeyboardLayout(0), imc.hIMC, IME_ESC_HANJA_MODE, &uniChar[0])) {
1208 SetSelection(currentPos, currentPos + oneCharLen);
1213 void ScintillaWin::ToggleHanja() {
1214 // If selection, convert every hanja to hangul within the main range.
1215 // If no selection, commit to IME.
1216 if (sel.Count() > 1) {
1217 return; // Do not allow multi carets.
1220 if (sel.Empty()) {
1221 EscapeHanja();
1222 } else {
1223 SelectionToHangul();
1227 namespace {
1229 std::vector<int> MapImeIndicators(std::vector<BYTE> inputStyle) {
1230 std::vector<int> imeIndicator(inputStyle.size(), IndicatorUnknown);
1231 for (size_t i = 0; i < inputStyle.size(); i++) {
1232 switch (static_cast<int>(inputStyle.at(i))) {
1233 case ATTR_INPUT:
1234 imeIndicator[i] = IndicatorInput;
1235 break;
1236 case ATTR_TARGET_NOTCONVERTED:
1237 case ATTR_TARGET_CONVERTED:
1238 imeIndicator[i] = IndicatorTarget;
1239 break;
1240 case ATTR_CONVERTED:
1241 imeIndicator[i] = IndicatorConverted;
1242 break;
1243 default:
1244 imeIndicator[i] = IndicatorUnknown;
1245 break;
1248 return imeIndicator;
1253 void ScintillaWin::AddWString(std::wstring_view wsv, CharacterSource charSource) {
1254 if (wsv.empty())
1255 return;
1257 const int codePage = CodePageOfDocument();
1258 for (size_t i = 0; i < wsv.size(); ) {
1259 const size_t ucWidth = UTF16CharLength(wsv[i]);
1260 const std::string docChar = StringEncode(wsv.substr(i, ucWidth), codePage);
1262 InsertCharacter(docChar, charSource);
1263 i += ucWidth;
1267 sptr_t ScintillaWin::HandleCompositionInline(uptr_t, sptr_t lParam) {
1268 // Copy & paste by johnsonj with a lot of helps of Neil.
1269 // Great thanks for my foreruners, jiniya and BLUEnLIVE.
1271 IMContext imc(MainHWND());
1272 if (!imc.hIMC)
1273 return 0;
1274 if (pdoc->IsReadOnly() || SelectionContainsProtected()) {
1275 ::ImmNotifyIME(imc.hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
1276 return 0;
1279 bool initialCompose = false;
1280 if (pdoc->TentativeActive()) {
1281 pdoc->TentativeUndo();
1282 } else {
1283 // No tentative undo means start of this composition so
1284 // fill in any virtual spaces.
1285 initialCompose = true;
1288 view.imeCaretBlockOverride = false;
1289 HideCursorIfPreferred();
1291 if (lParam & GCS_RESULTSTR) {
1292 AddWString(imc.GetCompositionString(GCS_RESULTSTR), CharacterSource::ImeResult);
1295 if (lParam & GCS_COMPSTR) {
1296 const std::wstring wcs = imc.GetCompositionString(GCS_COMPSTR);
1297 if (wcs.empty()) {
1298 ShowCaretAtCurrentPosition();
1299 return 0;
1302 if (initialCompose) {
1303 ClearBeforeTentativeStart();
1306 // Set candidate window left aligned to beginning of preedit string.
1307 SetCandidateWindowPos();
1308 pdoc->TentativeStart(); // TentativeActive from now on.
1310 std::vector<int> imeIndicator = MapImeIndicators(imc.GetImeAttributes());
1312 const int codePage = CodePageOfDocument();
1313 const std::wstring_view wsv = wcs;
1314 for (size_t i = 0; i < wsv.size(); ) {
1315 const size_t ucWidth = UTF16CharLength(wsv[i]);
1316 const std::string docChar = StringEncode(wsv.substr(i, ucWidth), codePage);
1318 InsertCharacter(docChar, CharacterSource::TentativeInput);
1320 DrawImeIndicator(imeIndicator[i], docChar.size());
1321 i += ucWidth;
1324 // Japanese IME after pressing Tab replaces input string with first candidate item (target string);
1325 // when selecting other candidate item, previous item will be replaced with current one.
1326 // After candidate item been added, it's looks like been full selected, it's better to keep caret
1327 // at end of "selection" (end of input) instead of jump to beginning of input ("selection").
1328 const bool onlyTarget = std::all_of(imeIndicator.begin(), imeIndicator.end(), [](int i) noexcept {
1329 return i == IndicatorTarget;
1331 if (!onlyTarget) {
1332 // CS_NOMOVECARET: keep caret at beginning of composition string which already moved in InsertCharacter().
1333 // GCS_CURSORPOS: current caret position is provided by IME.
1334 Sci::Position imeEndToImeCaretU16 = -static_cast<Sci::Position>(wcs.size());
1335 if (!(lParam & CS_NOMOVECARET) && (lParam & GCS_CURSORPOS)) {
1336 imeEndToImeCaretU16 += imc.GetImeCaretPos();
1338 if (imeEndToImeCaretU16 != 0) {
1339 // Move back IME caret from current last position to imeCaretPos.
1340 const Sci::Position currentPos = CurrentPosition();
1341 const Sci::Position imeCaretPosDoc = pdoc->GetRelativePositionUTF16(currentPos, imeEndToImeCaretU16);
1343 MoveImeCarets(-currentPos + imeCaretPosDoc);
1347 if (KoreanIME()) {
1348 view.imeCaretBlockOverride = true;
1351 EnsureCaretVisible();
1352 ShowCaretAtCurrentPosition();
1353 return 0;
1356 namespace {
1358 // Translate message IDs from WM_* and EM_* to Message::* so can partly emulate Windows Edit control
1359 Message SciMessageFromEM(unsigned int iMessage) noexcept {
1360 switch (iMessage) {
1361 case EM_CANPASTE: return Message::CanPaste;
1362 case EM_CANUNDO: return Message::CanUndo;
1363 case EM_EMPTYUNDOBUFFER: return Message::EmptyUndoBuffer;
1364 case EM_FINDTEXTEX: return Message::FindText;
1365 case EM_FORMATRANGE: return Message::FormatRange;
1366 case EM_GETFIRSTVISIBLELINE: return Message::GetFirstVisibleLine;
1367 case EM_GETLINECOUNT: return Message::GetLineCount;
1368 case EM_GETSELTEXT: return Message::GetSelText;
1369 case EM_GETTEXTRANGE: return Message::GetTextRange;
1370 case EM_HIDESELECTION: return Message::HideSelection;
1371 case EM_LINEINDEX: return Message::PositionFromLine;
1372 case EM_LINESCROLL: return Message::LineScroll;
1373 case EM_REPLACESEL: return Message::ReplaceSel;
1374 case EM_SCROLLCARET: return Message::ScrollCaret;
1375 case EM_SETREADONLY: return Message::SetReadOnly;
1376 case WM_CLEAR: return Message::Clear;
1377 case WM_COPY: return Message::Copy;
1378 case WM_CUT: return Message::Cut;
1379 case WM_SETTEXT: return Message::SetText;
1380 case WM_PASTE: return Message::Paste;
1381 case WM_UNDO: return Message::Undo;
1383 return static_cast<Message>(iMessage);
1388 namespace Scintilla::Internal {
1390 UINT CodePageFromCharSet(CharacterSet characterSet, UINT documentCodePage) noexcept {
1391 if (documentCodePage == CpUtf8) {
1392 return CpUtf8;
1394 switch (characterSet) {
1395 case CharacterSet::Ansi: return 1252;
1396 case CharacterSet::Default: return documentCodePage ? documentCodePage : 1252;
1397 case CharacterSet::Baltic: return 1257;
1398 case CharacterSet::ChineseBig5: return 950;
1399 case CharacterSet::EastEurope: return 1250;
1400 case CharacterSet::GB2312: return 936;
1401 case CharacterSet::Greek: return 1253;
1402 case CharacterSet::Hangul: return 949;
1403 case CharacterSet::Mac: return 10000;
1404 case CharacterSet::Oem: return 437;
1405 case CharacterSet::Russian: return 1251;
1406 case CharacterSet::ShiftJis: return 932;
1407 case CharacterSet::Turkish: return 1254;
1408 case CharacterSet::Johab: return 1361;
1409 case CharacterSet::Hebrew: return 1255;
1410 case CharacterSet::Arabic: return 1256;
1411 case CharacterSet::Vietnamese: return 1258;
1412 case CharacterSet::Thai: return 874;
1413 case CharacterSet::Iso8859_15: return 28605;
1414 // Not supported
1415 case CharacterSet::Cyrillic: return documentCodePage;
1416 case CharacterSet::Symbol: return documentCodePage;
1417 default: break;
1419 return documentCodePage;
1424 UINT ScintillaWin::CodePageOfDocument() const noexcept {
1425 return CodePageFromCharSet(vs.styles[StyleDefault].characterSet, pdoc->dbcsCodePage);
1428 std::string ScintillaWin::EncodeWString(std::wstring_view wsv) {
1429 if (IsUnicodeMode()) {
1430 const size_t len = UTF8Length(wsv);
1431 std::string putf(len, 0);
1432 UTF8FromUTF16(wsv, putf.data(), len);
1433 return putf;
1434 } else {
1435 // Not in Unicode mode so convert from Unicode to current Scintilla code page
1436 return StringEncode(wsv, CodePageOfDocument());
1440 sptr_t ScintillaWin::GetTextLength() {
1441 if (pdoc->dbcsCodePage == 0 || pdoc->dbcsCodePage == CpUtf8) {
1442 return pdoc->CountUTF16(0, pdoc->Length());
1443 } else {
1444 // Count the number of UTF-16 code units line by line
1445 const UINT cpSrc = CodePageOfDocument();
1446 const Sci::Line lines = pdoc->LinesTotal();
1447 Sci::Position codeUnits = 0;
1448 std::string lineBytes;
1449 for (Sci::Line line = 0; line < lines; line++) {
1450 const Sci::Position start = pdoc->LineStart(line);
1451 const Sci::Position width = pdoc->LineStart(line+1) - start;
1452 lineBytes.resize(width);
1453 pdoc->GetCharRange(lineBytes.data(), start, width);
1454 codeUnits += WideCharLenFromMultiByte(cpSrc, lineBytes);
1456 return codeUnits;
1460 sptr_t ScintillaWin::GetText(uptr_t wParam, sptr_t lParam) {
1461 if (lParam == 0) {
1462 return GetTextLength();
1464 if (wParam == 0) {
1465 return 0;
1467 wchar_t *ptr = static_cast<wchar_t *>(PtrFromSPtr(lParam));
1468 if (pdoc->Length() == 0) {
1469 *ptr = L'\0';
1470 return 0;
1472 const Sci::Position lengthWanted = wParam - 1;
1473 if (IsUnicodeMode()) {
1474 Sci::Position sizeRequestedRange = pdoc->GetRelativePositionUTF16(0, lengthWanted);
1475 if (sizeRequestedRange < 0) {
1476 // Requested more text than there is in the document.
1477 sizeRequestedRange = pdoc->Length();
1479 std::string docBytes(sizeRequestedRange, '\0');
1480 pdoc->GetCharRange(&docBytes[0], 0, sizeRequestedRange);
1481 const size_t uLen = UTF16FromUTF8(docBytes, ptr, lengthWanted);
1482 ptr[uLen] = L'\0';
1483 return uLen;
1484 } else {
1485 // Not Unicode mode
1486 // Convert to Unicode using the current Scintilla code page
1487 // Retrieve as UTF-16 line by line
1488 const UINT cpSrc = CodePageOfDocument();
1489 const Sci::Line lines = pdoc->LinesTotal();
1490 Sci::Position codeUnits = 0;
1491 std::string lineBytes;
1492 std::wstring lineAsUTF16;
1493 for (Sci::Line line = 0; line < lines && codeUnits < lengthWanted; line++) {
1494 const Sci::Position start = pdoc->LineStart(line);
1495 const Sci::Position width = pdoc->LineStart(line + 1) - start;
1496 lineBytes.resize(width);
1497 pdoc->GetCharRange(lineBytes.data(), start, width);
1498 const Sci::Position codeUnitsLine = WideCharLenFromMultiByte(cpSrc, lineBytes);
1499 lineAsUTF16.resize(codeUnitsLine);
1500 const Sci::Position lengthLeft = lengthWanted - codeUnits;
1501 WideCharFromMultiByte(cpSrc, lineBytes, lineAsUTF16.data(), lineAsUTF16.length());
1502 const Sci::Position lengthToCopy = std::min(lengthLeft, codeUnitsLine);
1503 lineAsUTF16.copy(ptr + codeUnits, lengthToCopy);
1504 codeUnits += lengthToCopy;
1506 ptr[codeUnits] = L'\0';
1507 return codeUnits;
1511 Window::Cursor ScintillaWin::ContextCursor(Point pt) {
1512 if (inDragDrop == DragDrop::dragging) {
1513 return Window::Cursor::up;
1514 } else {
1515 // Display regular (drag) cursor over selection
1516 if (PointInSelMargin(pt)) {
1517 return GetMarginCursor(pt);
1518 } else if (!SelectionEmpty() && PointInSelection(pt)) {
1519 return Window::Cursor::arrow;
1520 } else if (PointIsHotspot(pt)) {
1521 return Window::Cursor::hand;
1522 } else if (hoverIndicatorPos != Sci::invalidPosition) {
1523 const Sci::Position pos = PositionFromLocation(pt, true, true);
1524 if (pos != Sci::invalidPosition) {
1525 return Window::Cursor::hand;
1529 return Window::Cursor::text;
1532 sptr_t ScintillaWin::ShowContextMenu(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
1533 Point ptScreen = PointFromLParam(lParam);
1534 Point ptClient;
1535 POINT point = POINTFromLParam(lParam);
1536 if ((point.x == -1) && (point.y == -1)) {
1537 // Caused by keyboard so display menu near caret
1538 ptClient = PointMainCaret();
1539 point = POINTFromPoint(ptClient);
1540 ::ClientToScreen(MainHWND(), &point);
1541 ptScreen = PointFromPOINT(point);
1542 } else {
1543 ::ScreenToClient(MainHWND(), &point);
1544 ptClient = PointFromPOINT(point);
1546 if (ShouldDisplayPopup(ptClient)) {
1547 ContextMenu(ptScreen);
1548 return 0;
1550 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1553 PRectangle ScintillaWin::GetClientRectangle() const {
1554 return rectangleClient;
1557 void ScintillaWin::SizeWindow() {
1558 #if defined(USE_D2D)
1559 if (paintState == PaintState::notPainting) {
1560 DropRenderTarget();
1561 } else {
1562 renderTargetValid = false;
1564 #endif
1565 //Platform::DebugPrintf("Scintilla WM_SIZE %d %d\n", LOWORD(lParam), HIWORD(lParam));
1566 rectangleClient = wMain.GetClientPosition();
1567 ChangeSize();
1570 sptr_t ScintillaWin::MouseMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
1571 switch (iMessage) {
1572 case WM_LBUTTONDOWN: {
1573 // For IME, set the composition string as the result string.
1574 IMContext imc(MainHWND());
1575 if (imc.hIMC) {
1576 ::ImmNotifyIME(imc.hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
1579 //Platform::DebugPrintf("Buttdown %d %x %x %x %x %x\n",iMessage, wParam, lParam,
1580 // KeyboardIsKeyDown(VK_SHIFT),
1581 // KeyboardIsKeyDown(VK_CONTROL),
1582 // KeyboardIsKeyDown(VK_MENU));
1583 ::SetFocus(MainHWND());
1584 ButtonDownWithModifiers(PointFromLParam(lParam), ::GetMessageTime(),
1585 MouseModifiers(wParam));
1587 break;
1589 case WM_LBUTTONUP:
1590 ButtonUpWithModifiers(PointFromLParam(lParam),
1591 ::GetMessageTime(), MouseModifiers(wParam));
1592 break;
1594 case WM_RBUTTONDOWN: {
1595 ::SetFocus(MainHWND());
1596 const Point pt = PointFromLParam(lParam);
1597 if (!PointInSelection(pt)) {
1598 CancelModes();
1599 SetEmptySelection(PositionFromLocation(PointFromLParam(lParam)));
1602 RightButtonDownWithModifiers(pt, ::GetMessageTime(), MouseModifiers(wParam));
1604 break;
1606 case WM_MOUSEMOVE: {
1607 cursorIsHidden = false; // to be shown by ButtonMoveWithModifiers
1608 const Point pt = PointFromLParam(lParam);
1610 // Windows might send WM_MOUSEMOVE even though the mouse has not been moved:
1611 // http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx
1612 if (ptMouseLast != pt) {
1613 SetTrackMouseLeaveEvent(true);
1614 ButtonMoveWithModifiers(pt, ::GetMessageTime(), MouseModifiers(wParam));
1617 break;
1619 case WM_MOUSELEAVE:
1620 SetTrackMouseLeaveEvent(false);
1621 MouseLeave();
1622 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1624 case WM_MOUSEWHEEL:
1625 case WM_MOUSEHWHEEL:
1626 if (!mouseWheelCaptures) {
1627 // if the mouse wheel is not captured, test if the mouse
1628 // pointer is over the editor window and if not, don't
1629 // handle the message but pass it on.
1630 RECT rc;
1631 GetWindowRect(MainHWND(), &rc);
1632 const POINT pt = POINTFromLParam(lParam);
1633 if (!PtInRect(&rc, pt))
1634 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1636 // if autocomplete list active then send mousewheel message to it
1637 if (ac.Active()) {
1638 HWND hWnd = HwndFromWindow(*(ac.lb));
1639 ::SendMessage(hWnd, iMessage, wParam, lParam);
1640 break;
1643 // Treat Shift+WM_MOUSEWHEEL as horizontal scrolling, not data-zoom.
1644 if (iMessage == WM_MOUSEHWHEEL || (wParam & MK_SHIFT)) {
1645 if (vs.wrap.state != Wrap::None || charsPerScroll == 0) {
1646 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1649 MouseWheelDelta &wheelDelta = (iMessage == WM_MOUSEHWHEEL) ? horizontalWheelDelta : verticalWheelDelta;
1650 if (wheelDelta.Accumulate(wParam)) {
1651 const int charsToScroll = charsPerScroll * wheelDelta.Actions();
1652 const int widthToScroll = static_cast<int>(std::lround(charsToScroll * vs.aveCharWidth));
1653 HorizontalScrollToClamped(xOffset + widthToScroll);
1655 return 0;
1658 // Either SCROLL vertically or ZOOM. We handle the wheel steppings calculation
1659 if (linesPerScroll != 0 && verticalWheelDelta.Accumulate(wParam)) {
1660 Sci::Line linesToScroll = linesPerScroll;
1661 if (linesPerScroll == WHEEL_PAGESCROLL)
1662 linesToScroll = LinesOnScreen() - 1;
1663 if (linesToScroll == 0) {
1664 linesToScroll = 1;
1666 linesToScroll *= verticalWheelDelta.Actions();
1668 if (wParam & MK_CONTROL) {
1669 // Zoom! We play with the font sizes in the styles.
1670 // Number of steps/line is ignored, we just care if sizing up or down
1671 if (linesToScroll < 0) {
1672 KeyCommand(Message::ZoomIn);
1673 } else {
1674 KeyCommand(Message::ZoomOut);
1676 } else {
1677 // Scroll
1678 ScrollTo(topLine + linesToScroll);
1681 return 0;
1683 return 0;
1686 sptr_t ScintillaWin::KeyMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
1687 switch (iMessage) {
1689 case WM_SYSKEYDOWN:
1690 case WM_KEYDOWN: {
1691 // Platform::DebugPrintf("Keydown %c %c%c%c%c %x %x\n",
1692 // iMessage == WM_KEYDOWN ? 'K' : 'S',
1693 // (lParam & (1 << 24)) ? 'E' : '-',
1694 // KeyboardIsKeyDown(VK_SHIFT) ? 'S' : '-',
1695 // KeyboardIsKeyDown(VK_CONTROL) ? 'C' : '-',
1696 // KeyboardIsKeyDown(VK_MENU) ? 'A' : '-',
1697 // wParam, lParam);
1698 lastKeyDownConsumed = false;
1699 const bool altDown = KeyboardIsKeyDown(VK_MENU);
1700 if (altDown && KeyboardIsNumericKeypadFunction(wParam, lParam)) {
1701 // Don't interpret these as they may be characters entered by number.
1702 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1704 const int ret = KeyDownWithModifiers(
1705 KeyTranslate(wParam),
1706 ModifierFlags(KeyboardIsKeyDown(VK_SHIFT),
1707 KeyboardIsKeyDown(VK_CONTROL),
1708 altDown),
1709 &lastKeyDownConsumed);
1710 if (!ret && !lastKeyDownConsumed) {
1711 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1713 break;
1716 case WM_KEYUP:
1717 //Platform::DebugPrintf("S keyup %d %x %x\n",iMessage, wParam, lParam);
1718 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1720 case WM_CHAR:
1721 HideCursorIfPreferred();
1722 if (((wParam >= 128) || !iscntrl(static_cast<int>(wParam))) || !lastKeyDownConsumed) {
1723 wchar_t wcs[3] = { static_cast<wchar_t>(wParam), 0 };
1724 unsigned int wclen = 1;
1725 if (IS_HIGH_SURROGATE(wcs[0])) {
1726 // If this is a high surrogate character, we need a second one
1727 lastHighSurrogateChar = wcs[0];
1728 return 0;
1729 } else if (IS_LOW_SURROGATE(wcs[0])) {
1730 wcs[1] = wcs[0];
1731 wcs[0] = lastHighSurrogateChar;
1732 lastHighSurrogateChar = 0;
1733 wclen = 2;
1735 AddWString(std::wstring_view(wcs, wclen), CharacterSource::DirectInput);
1737 return 0;
1739 case WM_UNICHAR:
1740 if (wParam == UNICODE_NOCHAR) {
1741 return TRUE;
1742 } else if (lastKeyDownConsumed) {
1743 return 1;
1744 } else {
1745 wchar_t wcs[3] = { 0 };
1746 const size_t wclen = UTF16FromUTF32Character(static_cast<unsigned int>(wParam), wcs);
1747 AddWString(std::wstring_view(wcs, wclen), CharacterSource::DirectInput);
1748 return FALSE;
1752 return 0;
1755 sptr_t ScintillaWin::FocusMessage(unsigned int iMessage, uptr_t wParam, sptr_t) {
1756 switch (iMessage) {
1757 case WM_KILLFOCUS: {
1758 HWND wOther = reinterpret_cast<HWND>(wParam);
1759 HWND wThis = MainHWND();
1760 const HWND wCT = HwndFromWindow(ct.wCallTip);
1761 if (!wParam ||
1762 !(::IsChild(wThis, wOther) || (wOther == wCT))) {
1763 SetFocusState(false);
1764 DestroySystemCaret();
1766 // Explicitly complete any IME composition
1767 IMContext imc(MainHWND());
1768 if (imc.hIMC) {
1769 ::ImmNotifyIME(imc.hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
1771 break;
1774 case WM_SETFOCUS:
1775 SetFocusState(true);
1776 DestroySystemCaret();
1777 CreateSystemCaret();
1778 break;
1780 return 0;
1783 sptr_t ScintillaWin::IMEMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
1784 switch (iMessage) {
1786 case WM_INPUTLANGCHANGE:
1787 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1789 case WM_INPUTLANGCHANGEREQUEST:
1790 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1792 case WM_IME_KEYDOWN: {
1793 if (wParam == VK_HANJA) {
1794 ToggleHanja();
1796 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1799 case WM_IME_REQUEST: {
1800 if (wParam == IMR_RECONVERTSTRING) {
1801 return ImeOnReconvert(lParam);
1803 if (wParam == IMR_DOCUMENTFEED) {
1804 return ImeOnDocumentFeed(lParam);
1806 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1809 case WM_IME_STARTCOMPOSITION:
1810 if (KoreanIME() || imeInteraction == IMEInteraction::Inline) {
1811 return 0;
1812 } else {
1813 ImeStartComposition();
1814 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1817 case WM_IME_ENDCOMPOSITION:
1818 ImeEndComposition();
1819 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1821 case WM_IME_COMPOSITION:
1822 if (KoreanIME() || imeInteraction == IMEInteraction::Inline) {
1823 return HandleCompositionInline(wParam, lParam);
1824 } else {
1825 return HandleCompositionWindowed(wParam, lParam);
1828 case WM_IME_SETCONTEXT:
1829 if (KoreanIME() || imeInteraction == IMEInteraction::Inline) {
1830 if (wParam) {
1831 LPARAM NoImeWin = lParam;
1832 NoImeWin = NoImeWin & (~ISC_SHOWUICOMPOSITIONWINDOW);
1833 return ::DefWindowProc(MainHWND(), iMessage, wParam, NoImeWin);
1836 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1838 case WM_IME_NOTIFY:
1839 return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1842 return 0;
1845 sptr_t ScintillaWin::EditMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
1846 switch (iMessage) {
1848 case EM_LINEFROMCHAR:
1849 if (PositionFromUPtr(wParam) < 0) {
1850 wParam = SelectionStart().Position();
1852 return pdoc->LineFromPosition(wParam);
1854 case EM_EXLINEFROMCHAR:
1855 return pdoc->LineFromPosition(lParam);
1857 case EM_GETSEL:
1858 if (wParam) {
1859 *reinterpret_cast<DWORD *>(wParam) = static_cast<DWORD>(SelectionStart().Position());
1861 if (lParam) {
1862 *reinterpret_cast<DWORD *>(lParam) = static_cast<DWORD>(SelectionEnd().Position());
1864 return MAKELRESULT(SelectionStart().Position(), SelectionEnd().Position());
1866 case EM_EXGETSEL: {
1867 if (lParam == 0) {
1868 return 0;
1870 CHARRANGE *pCR = reinterpret_cast<CHARRANGE *>(lParam);
1871 pCR->cpMin = static_cast<LONG>(SelectionStart().Position());
1872 pCR->cpMax = static_cast<LONG>(SelectionEnd().Position());
1874 break;
1876 case EM_SETSEL: {
1877 Sci::Position nStart = wParam;
1878 Sci::Position nEnd = lParam;
1879 if (nStart == 0 && nEnd == -1) {
1880 nEnd = pdoc->Length();
1882 if (nStart == -1) {
1883 nStart = nEnd; // Remove selection
1885 SetSelection(nEnd, nStart);
1886 EnsureCaretVisible();
1888 break;
1890 case EM_EXSETSEL: {
1891 if (lParam == 0) {
1892 return 0;
1894 const CHARRANGE *pCR = reinterpret_cast<const CHARRANGE *>(lParam);
1895 sel.selType = Selection::SelTypes::stream;
1896 if (pCR->cpMin == 0 && pCR->cpMax == -1) {
1897 SetSelection(pCR->cpMin, pdoc->Length());
1898 } else {
1899 SetSelection(pCR->cpMin, pCR->cpMax);
1901 EnsureCaretVisible();
1902 return pdoc->LineFromPosition(SelectionStart().Position());
1905 return 0;
1908 sptr_t ScintillaWin::IdleMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
1909 switch (iMessage) {
1910 case SC_WIN_IDLE:
1911 // wParam=dwTickCountInitial, or 0 to initialize. lParam=bSkipUserInputTest
1912 if (idler.state) {
1913 if (lParam || (WAIT_TIMEOUT == MsgWaitForMultipleObjects(0, nullptr, 0, 0, QS_INPUT | QS_HOTKEY))) {
1914 if (Idle()) {
1915 // User input was given priority above, but all events do get a turn. Other
1916 // messages, notifications, etc. will get interleaved with the idle messages.
1918 // However, some things like WM_PAINT are a lower priority, and will not fire
1919 // when there's a message posted. So, several times a second, we stop and let
1920 // the low priority events have a turn (after which the timer will fire again).
1922 // Suppress a warning from Code Analysis that the GetTickCount function
1923 // wraps after 49 days. The WM_TIMER will kick off another SC_WIN_IDLE
1924 // after the wrap.
1925 #ifdef _MSC_VER
1926 #pragma warning(suppress: 28159)
1927 #endif
1928 const DWORD dwCurrent = GetTickCount();
1929 const DWORD dwStart = wParam ? static_cast<DWORD>(wParam) : dwCurrent;
1930 constexpr DWORD maxWorkTime = 50;
1932 if (dwCurrent >= dwStart && dwCurrent > maxWorkTime &&dwCurrent - maxWorkTime < dwStart)
1933 PostMessage(MainHWND(), SC_WIN_IDLE, dwStart, 0);
1934 } else {
1935 SetIdle(false);
1939 break;
1941 case SC_WORK_IDLE:
1942 IdleWork();
1943 break;
1945 return 0;
1948 sptr_t ScintillaWin::SciMessage(Message iMessage, uptr_t wParam, sptr_t lParam) {
1949 switch (iMessage) {
1950 case Message::GetDirectFunction:
1951 return reinterpret_cast<sptr_t>(DirectFunction);
1953 case Message::GetDirectStatusFunction:
1954 return reinterpret_cast<sptr_t>(DirectStatusFunction);
1956 case Message::GetDirectPointer:
1957 return reinterpret_cast<sptr_t>(this);
1959 case Message::GrabFocus:
1960 ::SetFocus(MainHWND());
1961 break;
1963 #ifdef INCLUDE_DEPRECATED_FEATURES
1964 case Message::SETKEYSUNICODE:
1965 break;
1967 case Message::GETKEYSUNICODE:
1968 return true;
1969 #endif
1971 case Message::SetTechnology:
1972 if (const Technology technologyNew = static_cast<Technology>(wParam);
1973 (technologyNew == Technology::Default) ||
1974 (technologyNew == Technology::DirectWriteRetain) ||
1975 (technologyNew == Technology::DirectWriteDC) ||
1976 (technologyNew == Technology::DirectWrite)) {
1977 if (technology != technologyNew) {
1978 if (technologyNew > Technology::Default) {
1979 #if defined(USE_D2D)
1980 if (!LoadD2D()) {
1981 // Failed to load Direct2D or DirectWrite so no effect
1982 return 0;
1984 UpdateRenderingParams(true);
1985 #else
1986 return 0;
1987 #endif
1988 } else {
1989 bidirectional = Bidirectional::Disabled;
1991 DropRenderTarget();
1992 technology = technologyNew;
1993 view.bufferedDraw = technologyNew == Technology::Default;
1994 // Invalidate all cached information including layout.
1995 InvalidateStyleRedraw();
1998 break;
2000 case Message::SetBidirectional:
2001 if (technology == Technology::Default) {
2002 bidirectional = Bidirectional::Disabled;
2003 } else if (static_cast<Bidirectional>(wParam) <= Bidirectional::R2L) {
2004 bidirectional = static_cast<Bidirectional>(wParam);
2006 // Invalidate all cached information including layout.
2007 InvalidateStyleRedraw();
2008 break;
2010 case Message::TargetAsUTF8:
2011 return TargetAsUTF8(CharPtrFromSPtr(lParam));
2013 case Message::EncodedFromUTF8:
2014 return EncodedFromUTF8(ConstCharPtrFromUPtr(wParam),
2015 CharPtrFromSPtr(lParam));
2017 default:
2018 break;
2021 return 0;
2024 sptr_t ScintillaWin::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
2025 try {
2026 //Platform::DebugPrintf("S M:%x WP:%x L:%x\n", iMessage, wParam, lParam);
2027 const unsigned int msg = static_cast<unsigned int>(iMessage);
2028 switch (msg) {
2030 case WM_CREATE:
2031 ctrlID = ::GetDlgCtrlID(HwndFromWindow(wMain));
2032 UpdateBaseElements();
2033 // Get Intellimouse scroll line parameters
2034 GetMouseParameters();
2035 ::RegisterDragDrop(MainHWND(), &dt);
2036 break;
2038 case WM_COMMAND:
2039 Command(LOWORD(wParam));
2040 break;
2042 case WM_PAINT:
2043 return WndPaint();
2045 case WM_PRINTCLIENT: {
2046 HDC hdc = reinterpret_cast<HDC>(wParam);
2047 if (!IsCompatibleDC(hdc)) {
2048 return ::DefWindowProc(MainHWND(), msg, wParam, lParam);
2050 FullPaintDC(hdc);
2052 break;
2054 case WM_VSCROLL:
2055 ScrollMessage(wParam);
2056 break;
2058 case WM_HSCROLL:
2059 HorizontalScrollMessage(wParam);
2060 break;
2062 case WM_SIZE:
2063 SizeWindow();
2064 break;
2066 case WM_TIMER:
2067 if (wParam == idleTimerID && idler.state) {
2068 SendMessage(MainHWND(), SC_WIN_IDLE, 0, 1);
2069 } else {
2070 TickFor(static_cast<TickReason>(wParam - fineTimerStart));
2072 break;
2074 case SC_WIN_IDLE:
2075 case SC_WORK_IDLE:
2076 return IdleMessage(msg, wParam, lParam);
2078 case WM_GETMINMAXINFO:
2079 return ::DefWindowProc(MainHWND(), msg, wParam, lParam);
2081 case WM_LBUTTONDOWN:
2082 case WM_LBUTTONUP:
2083 case WM_RBUTTONDOWN:
2084 case WM_MOUSEMOVE:
2085 case WM_MOUSELEAVE:
2086 case WM_MOUSEWHEEL:
2087 case WM_MOUSEHWHEEL:
2088 return MouseMessage(msg, wParam, lParam);
2090 case WM_SETCURSOR:
2091 if (LOWORD(lParam) == HTCLIENT) {
2092 if (!cursorIsHidden) {
2093 POINT pt;
2094 if (::GetCursorPos(&pt)) {
2095 ::ScreenToClient(MainHWND(), &pt);
2096 DisplayCursor(ContextCursor(PointFromPOINT(pt)));
2099 return TRUE;
2100 } else {
2101 return ::DefWindowProc(MainHWND(), msg, wParam, lParam);
2104 case WM_SYSKEYDOWN:
2105 case WM_KEYDOWN:
2106 case WM_KEYUP:
2107 case WM_CHAR:
2108 case WM_UNICHAR:
2109 return KeyMessage(msg, wParam, lParam);
2111 case WM_SETTINGCHANGE:
2112 //Platform::DebugPrintf("Setting Changed\n");
2113 #if defined(USE_D2D)
2114 if (technology != Technology::Default) {
2115 UpdateRenderingParams(true);
2117 #endif
2118 UpdateBaseElements();
2119 // Get Intellimouse scroll line parameters
2120 GetMouseParameters();
2121 InvalidateStyleRedraw();
2122 break;
2124 case WM_GETDLGCODE:
2125 return DLGC_HASSETSEL | DLGC_WANTALLKEYS;
2127 case WM_KILLFOCUS:
2128 case WM_SETFOCUS:
2129 return FocusMessage(msg, wParam, lParam);
2131 case WM_SYSCOLORCHANGE:
2132 //Platform::DebugPrintf("Setting Changed\n");
2133 UpdateBaseElements();
2134 InvalidateStyleData();
2135 break;
2137 case WM_DPICHANGED:
2138 dpi = HIWORD(wParam);
2139 reverseArrowCursor.Invalidate();
2140 InvalidateStyleRedraw();
2141 break;
2143 case WM_DPICHANGED_AFTERPARENT: {
2144 const UINT dpiNow = DpiForWindow(wMain.GetID());
2145 if (dpi != dpiNow) {
2146 dpi = dpiNow;
2147 reverseArrowCursor.Invalidate();
2148 InvalidateStyleRedraw();
2151 break;
2153 case WM_CONTEXTMENU:
2154 return ShowContextMenu(msg, wParam, lParam);
2156 case WM_ERASEBKGND:
2157 return 1; // Avoid any background erasure as whole window painted.
2159 case WM_SETREDRAW:
2160 ::DefWindowProc(MainHWND(), msg, wParam, lParam);
2161 if (wParam) {
2162 SetScrollBars();
2163 SetVerticalScrollPos();
2164 SetHorizontalScrollPos();
2166 return 0;
2168 case WM_CAPTURECHANGED:
2169 capturedMouse = false;
2170 return 0;
2172 // These are not handled in Scintilla and its faster to dispatch them here.
2173 // Also moves time out to here so profile doesn't count lots of empty message calls.
2175 case WM_MOVE:
2176 case WM_MOUSEACTIVATE:
2177 case WM_NCHITTEST:
2178 case WM_NCCALCSIZE:
2179 case WM_NCPAINT:
2180 case WM_NCMOUSEMOVE:
2181 case WM_NCLBUTTONDOWN:
2182 case WM_SYSCOMMAND:
2183 case WM_WINDOWPOSCHANGING:
2184 return ::DefWindowProc(MainHWND(), msg, wParam, lParam);
2186 case WM_WINDOWPOSCHANGED:
2187 #if defined(USE_D2D)
2188 if (technology != Technology::Default) {
2189 if (UpdateRenderingParams(false)) {
2190 DropGraphics();
2191 Redraw();
2194 #endif
2195 return ::DefWindowProc(MainHWND(), msg, wParam, lParam);
2197 case WM_GETTEXTLENGTH:
2198 return GetTextLength();
2200 case WM_GETTEXT:
2201 return GetText(wParam, lParam);
2203 case WM_INPUTLANGCHANGE:
2204 case WM_INPUTLANGCHANGEREQUEST:
2205 case WM_IME_KEYDOWN:
2206 case WM_IME_REQUEST:
2207 case WM_IME_STARTCOMPOSITION:
2208 case WM_IME_ENDCOMPOSITION:
2209 case WM_IME_COMPOSITION:
2210 case WM_IME_SETCONTEXT:
2211 case WM_IME_NOTIFY:
2212 return IMEMessage(msg, wParam, lParam);
2214 case EM_LINEFROMCHAR:
2215 case EM_EXLINEFROMCHAR:
2216 case EM_GETSEL:
2217 case EM_EXGETSEL:
2218 case EM_SETSEL:
2219 case EM_EXSETSEL:
2220 return EditMessage(msg, wParam, lParam);
2223 iMessage = SciMessageFromEM(msg);
2224 switch (iMessage) {
2225 case Message::GetDirectFunction:
2226 case Message::GetDirectStatusFunction:
2227 case Message::GetDirectPointer:
2228 case Message::GrabFocus:
2229 case Message::SetTechnology:
2230 case Message::SetBidirectional:
2231 case Message::TargetAsUTF8:
2232 case Message::EncodedFromUTF8:
2233 return SciMessage(iMessage, wParam, lParam);
2235 default:
2236 return ScintillaBase::WndProc(iMessage, wParam, lParam);
2238 } catch (std::bad_alloc &) {
2239 errorStatus = Status::BadAlloc;
2240 } catch (...) {
2241 errorStatus = Status::Failure;
2243 return 0;
2246 bool ScintillaWin::ValidCodePage(int codePage) const {
2247 return codePage == 0 || codePage == CpUtf8 ||
2248 codePage == 932 || codePage == 936 || codePage == 949 ||
2249 codePage == 950 || codePage == 1361;
2252 std::string ScintillaWin::UTF8FromEncoded(std::string_view encoded) const {
2253 if (IsUnicodeMode()) {
2254 return std::string(encoded);
2255 } else {
2256 // Pivot through wide string
2257 std::wstring ws = StringDecode(encoded, CodePageOfDocument());
2258 return StringEncode(ws, CpUtf8);
2262 std::string ScintillaWin::EncodedFromUTF8(std::string_view utf8) const {
2263 if (IsUnicodeMode()) {
2264 return std::string(utf8);
2265 } else {
2266 // Pivot through wide string
2267 std::wstring ws = StringDecode(utf8, CpUtf8);
2268 return StringEncode(ws, CodePageOfDocument());
2272 sptr_t ScintillaWin::DefWndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
2273 return ::DefWindowProc(MainHWND(), static_cast<unsigned int>(iMessage), wParam, lParam);
2276 bool ScintillaWin::FineTickerRunning(TickReason reason) {
2277 return timers[static_cast<size_t>(reason)] != 0;
2280 void ScintillaWin::FineTickerStart(TickReason reason, int millis, int tolerance) {
2281 FineTickerCancel(reason);
2282 const UINT_PTR reasonIndex = static_cast<UINT_PTR>(reason);
2283 const UINT_PTR eventID = static_cast<UINT_PTR>(fineTimerStart) + reasonIndex;
2284 if (SetCoalescableTimerFn && tolerance) {
2285 timers[reasonIndex] = SetCoalescableTimerFn(MainHWND(), eventID, millis, nullptr, tolerance);
2286 } else {
2287 timers[reasonIndex] = ::SetTimer(MainHWND(), eventID, millis, nullptr);
2291 void ScintillaWin::FineTickerCancel(TickReason reason) {
2292 const UINT_PTR reasonIndex = static_cast<UINT_PTR>(reason);
2293 if (timers[reasonIndex]) {
2294 ::KillTimer(MainHWND(), timers[reasonIndex]);
2295 timers[reasonIndex] = 0;
2300 bool ScintillaWin::SetIdle(bool on) {
2301 // On Win32 the Idler is implemented as a Timer on the Scintilla window. This
2302 // takes advantage of the fact that WM_TIMER messages are very low priority,
2303 // and are only posted when the message queue is empty, i.e. during idle time.
2304 if (idler.state != on) {
2305 if (on) {
2306 idler.idlerID = ::SetTimer(MainHWND(), idleTimerID, 10, nullptr)
2307 ? reinterpret_cast<IdlerID>(idleTimerID) : 0;
2308 } else {
2309 ::KillTimer(MainHWND(), reinterpret_cast<uptr_t>(idler.idlerID));
2310 idler.idlerID = 0;
2312 idler.state = idler.idlerID != 0;
2314 return idler.state;
2317 void ScintillaWin::IdleWork() {
2318 styleIdleInQueue = false;
2319 Editor::IdleWork();
2322 void ScintillaWin::QueueIdleWork(WorkItems items, Sci::Position upTo) {
2323 Editor::QueueIdleWork(items, upTo);
2324 if (!styleIdleInQueue) {
2325 if (PostMessage(MainHWND(), SC_WORK_IDLE, 0, 0)) {
2326 styleIdleInQueue = true;
2331 void ScintillaWin::SetMouseCapture(bool on) {
2332 if (mouseDownCaptures) {
2333 if (on) {
2334 ::SetCapture(MainHWND());
2335 } else {
2336 ::ReleaseCapture();
2339 capturedMouse = on;
2342 bool ScintillaWin::HaveMouseCapture() {
2343 // Cannot just see if GetCapture is this window as the scroll bar also sets capture for the window
2344 return capturedMouse;
2345 //return capturedMouse && (::GetCapture() == MainHWND());
2348 void ScintillaWin::SetTrackMouseLeaveEvent(bool on) noexcept {
2349 if (on && !trackedMouseLeave) {
2350 TRACKMOUSEEVENT tme {};
2351 tme.cbSize = sizeof(tme);
2352 tme.dwFlags = TME_LEAVE;
2353 tme.hwndTrack = MainHWND();
2354 tme.dwHoverTime = HOVER_DEFAULT; // Unused but triggers Dr. Memory if not initialized
2355 TrackMouseEvent(&tme);
2357 trackedMouseLeave = on;
2360 void ScintillaWin::HideCursorIfPreferred() noexcept {
2361 // SPI_GETMOUSEVANISH from OS.
2362 if (typingWithoutCursor && !cursorIsHidden) {
2363 ::SetCursor(NULL);
2364 cursorIsHidden = true;
2368 void ScintillaWin::UpdateBaseElements() {
2369 struct ElementToIndex { Element element; int nIndex; };
2370 const ElementToIndex eti[] = {
2371 { Element::List, COLOR_WINDOWTEXT },
2372 { Element::ListBack, COLOR_WINDOW },
2373 { Element::ListSelected, COLOR_HIGHLIGHTTEXT },
2374 { Element::ListSelectedBack, COLOR_HIGHLIGHT },
2376 bool changed = false;
2377 for (const ElementToIndex &ei : eti) {
2378 changed = vs.SetElementBase(ei.element, ColourRGBA::FromRGB(static_cast<int>(::GetSysColor(ei.nIndex)))) || changed;
2380 if (changed) {
2381 Redraw();
2385 bool ScintillaWin::PaintContains(PRectangle rc) {
2386 if (paintState == PaintState::painting) {
2387 return BoundsContains(rcPaint, hRgnUpdate, rc);
2389 return true;
2392 void ScintillaWin::ScrollText(Sci::Line /* linesToMove */) {
2393 //Platform::DebugPrintf("ScintillaWin::ScrollText %d\n", linesToMove);
2394 //::ScrollWindow(MainHWND(), 0,
2395 // vs.lineHeight * linesToMove, 0, 0);
2396 //::UpdateWindow(MainHWND());
2397 Redraw();
2398 UpdateSystemCaret();
2401 void ScintillaWin::NotifyCaretMove() {
2402 NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, MainHWND(), OBJID_CARET, CHILDID_SELF);
2405 void ScintillaWin::UpdateSystemCaret() {
2406 if (hasFocus) {
2407 if (pdoc->TentativeActive()) {
2408 // ongoing inline mode IME composition, don't inform IME of system caret position.
2409 // fix candidate window for Google Japanese IME moved on typing on Win7.
2410 return;
2412 if (HasCaretSizeChanged()) {
2413 DestroySystemCaret();
2414 CreateSystemCaret();
2416 const Point pos = PointMainCaret();
2417 ::SetCaretPos(static_cast<int>(pos.x), static_cast<int>(pos.y));
2421 bool ScintillaWin::IsVisible() const noexcept {
2422 return GetWindowStyle(MainHWND()) & WS_VISIBLE;
2425 int ScintillaWin::SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw) noexcept {
2426 return ::SetScrollInfo(MainHWND(), nBar, lpsi, bRedraw);
2429 bool ScintillaWin::GetScrollInfo(int nBar, LPSCROLLINFO lpsi) noexcept {
2430 return ::GetScrollInfo(MainHWND(), nBar, lpsi) ? true : false;
2433 // Change the scroll position but avoid repaint if changing to same value
2434 void ScintillaWin::ChangeScrollPos(int barType, Sci::Position pos) {
2435 if (!IsVisible()) {
2436 return;
2439 SCROLLINFO sci = {
2440 sizeof(sci), 0, 0, 0, 0, 0, 0
2442 sci.fMask = SIF_POS;
2443 GetScrollInfo(barType, &sci);
2444 if (sci.nPos != pos) {
2445 DwellEnd(true);
2446 sci.nPos = static_cast<int>(pos);
2447 SetScrollInfo(barType, &sci, TRUE);
2451 void ScintillaWin::SetVerticalScrollPos() {
2452 ChangeScrollPos(SB_VERT, topLine);
2455 void ScintillaWin::SetHorizontalScrollPos() {
2456 ChangeScrollPos(SB_HORZ, xOffset);
2459 bool ScintillaWin::ChangeScrollRange(int nBar, int nMin, int nMax, UINT nPage) noexcept {
2460 SCROLLINFO sci = { sizeof(sci), SIF_PAGE | SIF_RANGE, 0, 0, 0, 0, 0 };
2461 GetScrollInfo(nBar, &sci);
2462 if ((sci.nMin != nMin) || (sci.nMax != nMax) || (sci.nPage != nPage)) {
2463 sci.nMin = nMin;
2464 sci.nMax = nMax;
2465 sci.nPage = nPage;
2466 SetScrollInfo(nBar, &sci, TRUE);
2467 return true;
2469 return false;
2472 void ScintillaWin::HorizontalScrollToClamped(int xPos) {
2473 const HorizontalScrollRange range = GetHorizontalScrollRange();
2474 HorizontalScrollTo(std::clamp(xPos, 0, range.documentWidth - range.pageWidth + 1));
2477 HorizontalScrollRange ScintillaWin::GetHorizontalScrollRange() const {
2478 const PRectangle rcText = GetTextRectangle();
2479 int pageWidth = static_cast<int>(rcText.Width());
2480 const int horizEndPreferred = std::max({ scrollWidth, pageWidth - 1, 0 });
2481 if (!horizontalScrollBarVisible || Wrapping())
2482 pageWidth = horizEndPreferred + 1;
2483 return { pageWidth, horizEndPreferred };
2486 bool ScintillaWin::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) {
2487 if (!IsVisible()) {
2488 return false;
2491 bool modified = false;
2492 const Sci::Line vertEndPreferred = nMax;
2493 if (!verticalScrollBarVisible)
2494 nPage = vertEndPreferred + 1;
2495 if (ChangeScrollRange(SB_VERT, 0, static_cast<int>(vertEndPreferred), static_cast<unsigned int>(nPage))) {
2496 modified = true;
2498 const HorizontalScrollRange range = GetHorizontalScrollRange();
2499 if (ChangeScrollRange(SB_HORZ, 0, range.documentWidth, range.pageWidth)) {
2500 modified = true;
2501 if (scrollWidth < range.pageWidth) {
2502 HorizontalScrollTo(0);
2506 return modified;
2509 void ScintillaWin::NotifyChange() {
2510 ::SendMessage(::GetParent(MainHWND()), WM_COMMAND,
2511 MAKEWPARAM(GetCtrlID(), FocusChange::Change),
2512 reinterpret_cast<LPARAM>(MainHWND()));
2515 void ScintillaWin::NotifyFocus(bool focus) {
2516 if (commandEvents) {
2517 ::SendMessage(::GetParent(MainHWND()), WM_COMMAND,
2518 MAKEWPARAM(GetCtrlID(), focus ? FocusChange::Setfocus : FocusChange::Killfocus),
2519 reinterpret_cast<LPARAM>(MainHWND()));
2521 Editor::NotifyFocus(focus);
2524 void ScintillaWin::SetCtrlID(int identifier) {
2525 ::SetWindowID(HwndFromWindow(wMain), identifier);
2528 int ScintillaWin::GetCtrlID() {
2529 return ::GetDlgCtrlID(HwndFromWindow(wMain));
2532 void ScintillaWin::NotifyParent(NotificationData scn) {
2533 scn.nmhdr.hwndFrom = MainHWND();
2534 scn.nmhdr.idFrom = GetCtrlID();
2535 ::SendMessage(::GetParent(MainHWND()), WM_NOTIFY,
2536 GetCtrlID(), reinterpret_cast<LPARAM>(&scn));
2539 void ScintillaWin::NotifyParent(SCNotification *scn) {
2540 scn->nmhdr.hwndFrom = MainHWND();
2541 scn->nmhdr.idFrom = GetCtrlID();
2542 ::SendMessage(::GetParent(MainHWND()), WM_NOTIFY,
2543 GetCtrlID(), reinterpret_cast<LPARAM>(scn));
2546 void ScintillaWin::NotifyDoubleClick(Point pt, KeyMod modifiers) {
2547 //Platform::DebugPrintf("ScintillaWin Double click 0\n");
2548 ScintillaBase::NotifyDoubleClick(pt, modifiers);
2549 // Send myself a WM_LBUTTONDBLCLK, so the container can handle it too.
2550 const POINT point = POINTFromPoint(pt);
2551 ::SendMessage(MainHWND(),
2552 WM_LBUTTONDBLCLK,
2553 FlagSet(modifiers, KeyMod::Shift) ? MK_SHIFT : 0,
2554 MAKELPARAM(point.x, point.y));
2557 namespace {
2559 class CaseFolderDBCS : public CaseFolderTable {
2560 // Allocate the expandable storage here so that it does not need to be reallocated
2561 // for each call to Fold.
2562 std::vector<wchar_t> utf16Mixed;
2563 std::vector<wchar_t> utf16Folded;
2564 UINT cp;
2565 public:
2566 explicit CaseFolderDBCS(UINT cp_) : cp(cp_) {
2568 size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override {
2569 if ((lenMixed == 1) && (sizeFolded > 0)) {
2570 folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
2571 return 1;
2572 } else {
2573 if (lenMixed > utf16Mixed.size()) {
2574 utf16Mixed.resize(lenMixed + 8);
2576 const size_t nUtf16Mixed = WideCharFromMultiByte(cp,
2577 std::string_view(mixed, lenMixed),
2578 &utf16Mixed[0],
2579 utf16Mixed.size());
2581 if (nUtf16Mixed == 0) {
2582 // Failed to convert -> bad input
2583 folded[0] = '\0';
2584 return 1;
2587 size_t lenFlat = 0;
2588 for (size_t mixIndex=0; mixIndex < nUtf16Mixed; mixIndex++) {
2589 if ((lenFlat + 20) > utf16Folded.size())
2590 utf16Folded.resize(lenFlat + 60);
2591 const char *foldedUTF8 = CaseConvert(utf16Mixed[mixIndex], CaseConversion::fold);
2592 if (foldedUTF8) {
2593 // Maximum length of a case conversion is 6 bytes, 3 characters
2594 wchar_t wFolded[20];
2595 const size_t charsConverted = UTF16FromUTF8(std::string_view(foldedUTF8),
2596 wFolded, std::size(wFolded));
2597 for (size_t j=0; j<charsConverted; j++)
2598 utf16Folded[lenFlat++] = wFolded[j];
2599 } else {
2600 utf16Folded[lenFlat++] = utf16Mixed[mixIndex];
2604 const std::wstring_view wsvFolded(&utf16Folded[0], lenFlat);
2605 const size_t lenOut = MultiByteLenFromWideChar(cp, wsvFolded);
2607 if (lenOut < sizeFolded) {
2608 MultiByteFromWideChar(cp, wsvFolded, folded, lenOut);
2609 return lenOut;
2610 } else {
2611 return 0;
2619 std::unique_ptr<CaseFolder> ScintillaWin::CaseFolderForEncoding() {
2620 const UINT cpDest = CodePageOfDocument();
2621 if (cpDest == CpUtf8) {
2622 return std::make_unique<CaseFolderUnicode>();
2623 } else {
2624 if (pdoc->dbcsCodePage == 0) {
2625 std::unique_ptr<CaseFolderTable> pcf = std::make_unique<CaseFolderTable>();
2626 // Only for single byte encodings
2627 for (int i=0x80; i<0x100; i++) {
2628 char sCharacter[2] = "A";
2629 sCharacter[0] = static_cast<char>(i);
2630 wchar_t wCharacter[20];
2631 const unsigned int lengthUTF16 = WideCharFromMultiByte(cpDest, sCharacter,
2632 wCharacter, std::size(wCharacter));
2633 if (lengthUTF16 == 1) {
2634 const char *caseFolded = CaseConvert(wCharacter[0], CaseConversion::fold);
2635 if (caseFolded) {
2636 wchar_t wLower[20];
2637 const size_t charsConverted = UTF16FromUTF8(std::string_view(caseFolded),
2638 wLower, std::size(wLower));
2639 if (charsConverted == 1) {
2640 char sCharacterLowered[20];
2641 const unsigned int lengthConverted = MultiByteFromWideChar(cpDest,
2642 std::wstring_view(wLower, charsConverted),
2643 sCharacterLowered, std::size(sCharacterLowered));
2644 if ((lengthConverted == 1) && (sCharacter[0] != sCharacterLowered[0])) {
2645 pcf->SetTranslation(sCharacter[0], sCharacterLowered[0]);
2651 return pcf;
2652 } else {
2653 return std::make_unique<CaseFolderDBCS>(cpDest);
2658 std::string ScintillaWin::CaseMapString(const std::string &s, CaseMapping caseMapping) {
2659 if ((s.size() == 0) || (caseMapping == CaseMapping::same))
2660 return s;
2662 const UINT cpDoc = CodePageOfDocument();
2663 if (cpDoc == CpUtf8) {
2664 return CaseConvertString(s, (caseMapping == CaseMapping::upper) ? CaseConversion::upper : CaseConversion::lower);
2667 // Change text to UTF-16
2668 const std::wstring wsText = StringDecode(s, cpDoc);
2670 const DWORD mapFlags = LCMAP_LINGUISTIC_CASING |
2671 ((caseMapping == CaseMapping::upper) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE);
2673 // Change case
2674 const std::wstring wsConverted = StringMapCase(wsText, mapFlags);
2676 // Change back to document encoding
2677 std::string sConverted = StringEncode(wsConverted, cpDoc);
2679 return sConverted;
2682 void ScintillaWin::Copy() {
2683 //Platform::DebugPrintf("Copy\n");
2684 if (!sel.Empty()) {
2685 SelectionText selectedText;
2686 CopySelectionRange(&selectedText);
2687 CopyToClipboard(selectedText);
2691 bool ScintillaWin::CanPaste() {
2692 if (!Editor::CanPaste())
2693 return false;
2694 return ::IsClipboardFormatAvailable(CF_UNICODETEXT) != FALSE;
2697 namespace {
2699 class GlobalMemory {
2700 HGLOBAL hand {};
2701 public:
2702 void *ptr {};
2703 GlobalMemory() noexcept {
2705 explicit GlobalMemory(HGLOBAL hand_) noexcept : hand(hand_) {
2706 if (hand) {
2707 ptr = ::GlobalLock(hand);
2710 // Deleted so GlobalMemory objects can not be copied.
2711 GlobalMemory(const GlobalMemory &) = delete;
2712 GlobalMemory(GlobalMemory &&) = delete;
2713 GlobalMemory &operator=(const GlobalMemory &) = delete;
2714 GlobalMemory &operator=(GlobalMemory &&) = delete;
2715 ~GlobalMemory() {
2716 assert(!ptr);
2717 assert(!hand);
2719 void Allocate(size_t bytes) noexcept {
2720 assert(!hand);
2721 hand = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes);
2722 if (hand) {
2723 ptr = ::GlobalLock(hand);
2726 HGLOBAL Unlock() noexcept {
2727 assert(ptr);
2728 HGLOBAL handCopy = hand;
2729 ::GlobalUnlock(hand);
2730 ptr = nullptr;
2731 hand = {};
2732 return handCopy;
2734 void SetClip(UINT uFormat) noexcept {
2735 ::SetClipboardData(uFormat, Unlock());
2737 operator bool() const noexcept {
2738 return ptr != nullptr;
2740 SIZE_T Size() const noexcept {
2741 return ::GlobalSize(hand);
2745 // OpenClipboard may fail if another application has opened the clipboard.
2746 // Try up to 8 times, with an initial delay of 1 ms and an exponential back off
2747 // for a maximum total delay of 127 ms (1+2+4+8+16+32+64).
2748 bool OpenClipboardRetry(HWND hwnd) noexcept {
2749 for (int attempt=0; attempt<8; attempt++) {
2750 if (attempt > 0) {
2751 ::Sleep(1 << (attempt-1));
2753 if (::OpenClipboard(hwnd)) {
2754 return true;
2757 return false;
2760 // Ensure every successful OpenClipboard is followed by a CloseClipboard.
2761 class Clipboard {
2762 bool opened = false;
2763 public:
2764 Clipboard(HWND hwnd) noexcept : opened(::OpenClipboardRetry(hwnd)) {
2766 // Deleted so Clipboard objects can not be copied.
2767 Clipboard(const Clipboard &) = delete;
2768 Clipboard(Clipboard &&) = delete;
2769 Clipboard &operator=(const Clipboard &) = delete;
2770 Clipboard &operator=(Clipboard &&) = delete;
2771 ~Clipboard() noexcept {
2772 if (opened) {
2773 ::CloseClipboard();
2776 constexpr operator bool() const noexcept {
2777 return opened;
2781 bool IsValidFormatEtc(const FORMATETC *pFE) noexcept {
2782 return pFE->ptd == nullptr &&
2783 (pFE->dwAspect & DVASPECT_CONTENT) != 0 &&
2784 pFE->lindex == -1 &&
2785 (pFE->tymed & TYMED_HGLOBAL) != 0;
2788 bool SupportedFormat(const FORMATETC *pFE) noexcept {
2789 return pFE->cfFormat == CF_UNICODETEXT &&
2790 IsValidFormatEtc(pFE);
2795 void ScintillaWin::Paste() {
2796 Clipboard clipboard(MainHWND());
2797 if (!clipboard) {
2798 return;
2800 UndoGroup ug(pdoc);
2801 const bool isLine = SelectionEmpty() &&
2802 (::IsClipboardFormatAvailable(cfLineSelect) || ::IsClipboardFormatAvailable(cfVSLineTag));
2803 ClearSelection(multiPasteMode == MultiPaste::Each);
2804 bool isRectangular = (::IsClipboardFormatAvailable(cfColumnSelect) != 0);
2806 if (!isRectangular) {
2807 // Evaluate "Borland IDE Block Type" explicitly
2808 GlobalMemory memBorlandSelection(::GetClipboardData(cfBorlandIDEBlockType));
2809 if (memBorlandSelection) {
2810 isRectangular = (memBorlandSelection.Size() == 1) && (static_cast<BYTE *>(memBorlandSelection.ptr)[0] == 0x02);
2811 memBorlandSelection.Unlock();
2814 const PasteShape pasteShape = isRectangular ? PasteShape::rectangular : (isLine ? PasteShape::line : PasteShape::stream);
2816 // Use CF_UNICODETEXT if available
2817 GlobalMemory memUSelection(::GetClipboardData(CF_UNICODETEXT));
2818 if (const wchar_t *uptr = static_cast<const wchar_t *>(memUSelection.ptr)) {
2819 const std::string putf = EncodeWString(uptr);
2820 InsertPasteShape(putf.c_str(), putf.length(), pasteShape);
2821 memUSelection.Unlock();
2823 Redraw();
2826 void ScintillaWin::CreateCallTipWindow(PRectangle) {
2827 if (!ct.wCallTip.Created()) {
2828 HWND wnd = ::CreateWindow(callClassName, TEXT("ACallTip"),
2829 WS_POPUP, 100, 100, 150, 20,
2830 MainHWND(), 0,
2831 GetWindowInstance(MainHWND()),
2832 this);
2833 ct.wCallTip = wnd;
2834 ct.wDraw = wnd;
2838 void ScintillaWin::AddToPopUp(const char *label, int cmd, bool enabled) {
2839 HMENU hmenuPopup = static_cast<HMENU>(popup.GetID());
2840 if (!label[0])
2841 ::AppendMenuA(hmenuPopup, MF_SEPARATOR, 0, "");
2842 else if (enabled)
2843 ::AppendMenuA(hmenuPopup, MF_STRING, cmd, label);
2844 else
2845 ::AppendMenuA(hmenuPopup, MF_STRING | MF_DISABLED | MF_GRAYED, cmd, label);
2848 void ScintillaWin::ClaimSelection() {
2849 // Windows does not have a primary selection
2852 /// Implement IUnknown
2853 STDMETHODIMP FormatEnumerator::QueryInterface(REFIID riid, PVOID *ppv) {
2854 //Platform::DebugPrintf("EFE QI");
2855 *ppv = nullptr;
2856 if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) {
2857 *ppv = this;
2858 } else {
2859 return E_NOINTERFACE;
2861 AddRef();
2862 return S_OK;
2864 STDMETHODIMP_(ULONG)FormatEnumerator::AddRef() {
2865 return ++ref;
2867 STDMETHODIMP_(ULONG)FormatEnumerator::Release() {
2868 const ULONG refs = --ref;
2869 if (refs == 0) {
2870 delete this;
2872 return refs;
2874 /// Implement IEnumFORMATETC
2875 STDMETHODIMP FormatEnumerator::Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) {
2876 if (!rgelt) return E_POINTER;
2877 ULONG putPos = 0;
2878 while ((pos < formats.size()) && (putPos < celt)) {
2879 rgelt->cfFormat = formats[pos];
2880 rgelt->ptd = nullptr;
2881 rgelt->dwAspect = DVASPECT_CONTENT;
2882 rgelt->lindex = -1;
2883 rgelt->tymed = TYMED_HGLOBAL;
2884 rgelt++;
2885 pos++;
2886 putPos++;
2888 if (pceltFetched)
2889 *pceltFetched = putPos;
2890 return putPos ? S_OK : S_FALSE;
2892 STDMETHODIMP FormatEnumerator::Skip(ULONG celt) {
2893 pos += celt;
2894 return S_OK;
2896 STDMETHODIMP FormatEnumerator::Reset() {
2897 pos = 0;
2898 return S_OK;
2900 STDMETHODIMP FormatEnumerator::Clone(IEnumFORMATETC **ppenum) {
2901 FormatEnumerator *pfe;
2902 try {
2903 pfe = new FormatEnumerator(pos, &formats[0], formats.size());
2904 } catch (...) {
2905 return E_OUTOFMEMORY;
2907 return pfe->QueryInterface(IID_IEnumFORMATETC, reinterpret_cast<void **>(ppenum));
2910 FormatEnumerator::FormatEnumerator(ULONG pos_, const CLIPFORMAT formats_[], size_t formatsLen_) {
2911 ref = 0; // First QI adds first reference...
2912 pos = pos_;
2913 formats.insert(formats.begin(), formats_, formats_+formatsLen_);
2916 /// Implement IUnknown
2917 STDMETHODIMP DropSource::QueryInterface(REFIID riid, PVOID *ppv) {
2918 return sci->QueryInterface(riid, ppv);
2920 STDMETHODIMP_(ULONG)DropSource::AddRef() {
2921 return sci->AddRef();
2923 STDMETHODIMP_(ULONG)DropSource::Release() {
2924 return sci->Release();
2927 /// Implement IDropSource
2928 STDMETHODIMP DropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) {
2929 if (fEsc)
2930 return DRAGDROP_S_CANCEL;
2931 if (!(grfKeyState & MK_LBUTTON))
2932 return DRAGDROP_S_DROP;
2933 return S_OK;
2936 STDMETHODIMP DropSource::GiveFeedback(DWORD) {
2937 return DRAGDROP_S_USEDEFAULTCURSORS;
2940 /// Implement IUnkown
2941 STDMETHODIMP DataObject::QueryInterface(REFIID riid, PVOID *ppv) {
2942 //Platform::DebugPrintf("DO QI %p\n", this);
2943 return sci->QueryInterface(riid, ppv);
2945 STDMETHODIMP_(ULONG)DataObject::AddRef() {
2946 return sci->AddRef();
2948 STDMETHODIMP_(ULONG)DataObject::Release() {
2949 return sci->Release();
2951 /// Implement IDataObject
2952 STDMETHODIMP DataObject::GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) {
2953 return sci->GetData(pFEIn, pSTM);
2956 STDMETHODIMP DataObject::GetDataHere(FORMATETC *, STGMEDIUM *) {
2957 //Platform::DebugPrintf("DOB GetDataHere\n");
2958 return E_NOTIMPL;
2961 STDMETHODIMP DataObject::QueryGetData(FORMATETC *pFE) {
2962 if (sci->DragIsRectangularOK(pFE->cfFormat) && IsValidFormatEtc(pFE)) {
2963 return S_OK;
2966 if (SupportedFormat(pFE)) {
2967 return S_OK;
2968 } else {
2969 return S_FALSE;
2973 STDMETHODIMP DataObject::GetCanonicalFormatEtc(FORMATETC *, FORMATETC *pFEOut) {
2974 //Platform::DebugPrintf("DOB GetCanon\n");
2975 pFEOut->cfFormat = CF_UNICODETEXT;
2976 pFEOut->ptd = nullptr;
2977 pFEOut->dwAspect = DVASPECT_CONTENT;
2978 pFEOut->lindex = -1;
2979 pFEOut->tymed = TYMED_HGLOBAL;
2980 return S_OK;
2983 STDMETHODIMP DataObject::SetData(FORMATETC *, STGMEDIUM *, BOOL) {
2984 //Platform::DebugPrintf("DOB SetData\n");
2985 return E_FAIL;
2988 STDMETHODIMP DataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnum) {
2989 try {
2990 //Platform::DebugPrintf("DOB EnumFormatEtc %d\n", dwDirection);
2991 if (dwDirection != DATADIR_GET) {
2992 *ppEnum = nullptr;
2993 return E_FAIL;
2996 const CLIPFORMAT formats[] = {CF_UNICODETEXT};
2997 FormatEnumerator *pfe = new FormatEnumerator(0, formats, std::size(formats));
2998 return pfe->QueryInterface(IID_IEnumFORMATETC, reinterpret_cast<void **>(ppEnum));
2999 } catch (std::bad_alloc &) {
3000 sci->errorStatus = Status::BadAlloc;
3001 return E_OUTOFMEMORY;
3002 } catch (...) {
3003 sci->errorStatus = Status::Failure;
3004 return E_FAIL;
3008 STDMETHODIMP DataObject::DAdvise(FORMATETC *, DWORD, IAdviseSink *, PDWORD) {
3009 //Platform::DebugPrintf("DOB DAdvise\n");
3010 return E_FAIL;
3013 STDMETHODIMP DataObject::DUnadvise(DWORD) {
3014 //Platform::DebugPrintf("DOB DUnadvise\n");
3015 return E_FAIL;
3018 STDMETHODIMP DataObject::EnumDAdvise(IEnumSTATDATA **) {
3019 //Platform::DebugPrintf("DOB EnumDAdvise\n");
3020 return E_FAIL;
3023 /// Implement IUnknown
3024 STDMETHODIMP DropTarget::QueryInterface(REFIID riid, PVOID *ppv) {
3025 //Platform::DebugPrintf("DT QI %p\n", this);
3026 return sci->QueryInterface(riid, ppv);
3028 STDMETHODIMP_(ULONG)DropTarget::AddRef() {
3029 return sci->AddRef();
3031 STDMETHODIMP_(ULONG)DropTarget::Release() {
3032 return sci->Release();
3035 /// Implement IDropTarget by forwarding to Scintilla
3036 STDMETHODIMP DropTarget::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) {
3037 try {
3038 return sci->DragEnter(pIDataSource, grfKeyState, pt, pdwEffect);
3039 } catch (...) {
3040 sci->errorStatus = Status::Failure;
3042 return E_FAIL;
3044 STDMETHODIMP DropTarget::DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) {
3045 try {
3046 return sci->DragOver(grfKeyState, pt, pdwEffect);
3047 } catch (...) {
3048 sci->errorStatus = Status::Failure;
3050 return E_FAIL;
3052 STDMETHODIMP DropTarget::DragLeave() {
3053 try {
3054 return sci->DragLeave();
3055 } catch (...) {
3056 sci->errorStatus = Status::Failure;
3058 return E_FAIL;
3060 STDMETHODIMP DropTarget::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) {
3061 try {
3062 return sci->Drop(pIDataSource, grfKeyState, pt, pdwEffect);
3063 } catch (...) {
3064 sci->errorStatus = Status::Failure;
3066 return E_FAIL;
3070 * DBCS: support Input Method Editor (IME).
3071 * Called when IME Window opened.
3073 void ScintillaWin::ImeStartComposition() {
3074 if (caret.active) {
3075 // Move IME Window to current caret position
3076 IMContext imc(MainHWND());
3077 const Point pos = PointMainCaret();
3078 COMPOSITIONFORM CompForm {};
3079 CompForm.dwStyle = CFS_POINT;
3080 CompForm.ptCurrentPos = POINTFromPoint(pos);
3082 ::ImmSetCompositionWindow(imc.hIMC, &CompForm);
3084 // Set font of IME window to same as surrounded text.
3085 if (stylesValid) {
3086 // Since the style creation code has been made platform independent,
3087 // The logfont for the IME is recreated here.
3088 const int styleHere = pdoc->StyleIndexAt(sel.MainCaret());
3089 LOGFONTW lf = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L""};
3090 int sizeZoomed = vs.styles[styleHere].size + vs.zoomLevel * FontSizeMultiplier;
3091 if (sizeZoomed <= 2 * FontSizeMultiplier) // Hangs if sizeZoomed <= 1
3092 sizeZoomed = 2 * FontSizeMultiplier;
3093 // The negative is to allow for leading
3094 lf.lfHeight = -::MulDiv(sizeZoomed, dpi, 72*FontSizeMultiplier);
3095 lf.lfWeight = static_cast<LONG>(vs.styles[styleHere].weight);
3096 lf.lfItalic = vs.styles[styleHere].italic ? 1 : 0;
3097 lf.lfCharSet = DEFAULT_CHARSET;
3098 lf.lfFaceName[0] = L'\0';
3099 if (vs.styles[styleHere].fontName) {
3100 const char* fontName = vs.styles[styleHere].fontName;
3101 UTF16FromUTF8(std::string_view(fontName), lf.lfFaceName, LF_FACESIZE);
3104 ::ImmSetCompositionFontW(imc.hIMC, &lf);
3106 // Caret is displayed in IME window. So, caret in Scintilla is useless.
3107 DropCaret();
3111 /** Called when IME Window closed. */
3112 void ScintillaWin::ImeEndComposition() {
3113 // clear IME composition state.
3114 view.imeCaretBlockOverride = false;
3115 pdoc->TentativeUndo();
3116 ShowCaretAtCurrentPosition();
3119 LRESULT ScintillaWin::ImeOnReconvert(LPARAM lParam) {
3120 // Reconversion on windows limits within one line without eol.
3121 // Look around: baseStart <-- (|mainStart| -- mainEnd) --> baseEnd.
3122 const Sci::Position mainStart = sel.RangeMain().Start().Position();
3123 const Sci::Position mainEnd = sel.RangeMain().End().Position();
3124 const Sci::Line curLine = pdoc->SciLineFromPosition(mainStart);
3125 if (curLine != pdoc->LineFromPosition(mainEnd))
3126 return 0;
3127 const Sci::Position baseStart = pdoc->LineStart(curLine);
3128 const Sci::Position baseEnd = pdoc->LineEnd(curLine);
3129 if ((baseStart == baseEnd) || (mainEnd > baseEnd))
3130 return 0;
3132 const int codePage = CodePageOfDocument();
3133 const std::wstring rcFeed = StringDecode(RangeText(baseStart, baseEnd), codePage);
3134 const int rcFeedLen = static_cast<int>(rcFeed.length()) * sizeof(wchar_t);
3135 const int rcSize = sizeof(RECONVERTSTRING) + rcFeedLen + sizeof(wchar_t);
3137 RECONVERTSTRING *rc = static_cast<RECONVERTSTRING *>(PtrFromSPtr(lParam));
3138 if (!rc)
3139 return rcSize; // Immediately be back with rcSize of memory block.
3141 wchar_t *rcFeedStart = reinterpret_cast<wchar_t*>(rc + 1);
3142 memcpy(rcFeedStart, &rcFeed[0], rcFeedLen);
3144 std::string rcCompString = RangeText(mainStart, mainEnd);
3145 std::wstring rcCompWstring = StringDecode(rcCompString, codePage);
3146 std::string rcCompStart = RangeText(baseStart, mainStart);
3147 std::wstring rcCompWstart = StringDecode(rcCompStart, codePage);
3149 // Map selection to dwCompStr.
3150 // No selection assumes current caret as rcCompString without length.
3151 rc->dwVersion = 0; // It should be absolutely 0.
3152 rc->dwStrLen = static_cast<DWORD>(rcFeed.length());
3153 rc->dwStrOffset = sizeof(RECONVERTSTRING);
3154 rc->dwCompStrLen = static_cast<DWORD>(rcCompWstring.length());
3155 rc->dwCompStrOffset = static_cast<DWORD>(rcCompWstart.length()) * sizeof(wchar_t);
3156 rc->dwTargetStrLen = rc->dwCompStrLen;
3157 rc->dwTargetStrOffset =rc->dwCompStrOffset;
3159 IMContext imc(MainHWND());
3160 if (!imc.hIMC)
3161 return 0;
3163 if (!::ImmSetCompositionStringW(imc.hIMC, SCS_QUERYRECONVERTSTRING, rc, rcSize, nullptr, 0))
3164 return 0;
3166 // No selection asks IME to fill target fields with its own value.
3167 const int tgWlen = rc->dwTargetStrLen;
3168 const int tgWstart = rc->dwTargetStrOffset / sizeof(wchar_t);
3170 std::string tgCompStart = StringEncode(rcFeed.substr(0, tgWstart), codePage);
3171 std::string tgComp = StringEncode(rcFeed.substr(tgWstart, tgWlen), codePage);
3173 // No selection needs to adjust reconvert start position for IME set.
3174 const int adjust = static_cast<int>(tgCompStart.length() - rcCompStart.length());
3175 const int docCompLen = static_cast<int>(tgComp.length());
3177 // Make place for next composition string to sit in.
3178 for (size_t r=0; r<sel.Count(); r++) {
3179 const Sci::Position rBase = sel.Range(r).Start().Position();
3180 const Sci::Position docCompStart = rBase + adjust;
3182 if (inOverstrike) { // the docCompLen of bytes will be overstriked.
3183 sel.Range(r).caret.SetPosition(docCompStart);
3184 sel.Range(r).anchor.SetPosition(docCompStart);
3185 } else {
3186 // Ensure docCompStart+docCompLen be not beyond lineEnd.
3187 // since docCompLen by byte might break eol.
3188 const Sci::Position lineEnd = pdoc->LineEndPosition(rBase);
3189 const Sci::Position overflow = (docCompStart + docCompLen) - lineEnd;
3190 if (overflow > 0) {
3191 pdoc->DeleteChars(docCompStart, docCompLen - overflow);
3192 } else {
3193 pdoc->DeleteChars(docCompStart, docCompLen);
3197 // Immediately Target Input or candidate box choice with GCS_COMPSTR.
3198 return rcSize;
3201 LRESULT ScintillaWin::ImeOnDocumentFeed(LPARAM lParam) const {
3202 // This is called while typing preedit string in.
3203 // So there is no selection.
3204 // Limit feed within one line without EOL.
3205 // Look around: lineStart |<-- |compStart| - caret - compEnd| -->| lineEnd.
3207 const Sci::Position curPos = CurrentPosition();
3208 const Sci::Line curLine = pdoc->SciLineFromPosition(curPos);
3209 const Sci::Position lineStart = pdoc->LineStart(curLine);
3210 const Sci::Position lineEnd = pdoc->LineEnd(curLine);
3212 const std::wstring rcFeed = StringDecode(RangeText(lineStart, lineEnd), CodePageOfDocument());
3213 const int rcFeedLen = static_cast<int>(rcFeed.length()) * sizeof(wchar_t);
3214 const int rcSize = sizeof(RECONVERTSTRING) + rcFeedLen + sizeof(wchar_t);
3216 RECONVERTSTRING *rc = static_cast<RECONVERTSTRING *>(PtrFromSPtr(lParam));
3217 if (!rc)
3218 return rcSize;
3220 wchar_t *rcFeedStart = reinterpret_cast<wchar_t*>(rc + 1);
3221 memcpy(rcFeedStart, &rcFeed[0], rcFeedLen);
3223 IMContext imc(MainHWND());
3224 if (!imc.hIMC)
3225 return 0;
3227 DWORD compStrLen = 0;
3228 Sci::Position compStart = curPos;
3229 if (pdoc->TentativeActive()) {
3230 // rcFeed contains current composition string
3231 compStrLen = imc.GetCompositionStringLength(GCS_COMPSTR);
3232 const int imeCaretPos = imc.GetImeCaretPos();
3233 compStart = pdoc->GetRelativePositionUTF16(curPos, -imeCaretPos);
3235 const Sci::Position compStrOffset = pdoc->CountUTF16(lineStart, compStart);
3237 // Fill in reconvert structure.
3238 // Let IME to decide what the target is.
3239 rc->dwVersion = 0; //constant
3240 rc->dwStrLen = static_cast<DWORD>(rcFeed.length());
3241 rc->dwStrOffset = sizeof(RECONVERTSTRING); //constant
3242 rc->dwCompStrLen = compStrLen;
3243 rc->dwCompStrOffset = static_cast<DWORD>(compStrOffset) * sizeof(wchar_t);
3244 rc->dwTargetStrLen = rc->dwCompStrLen;
3245 rc->dwTargetStrOffset = rc->dwCompStrOffset;
3247 return rcSize; // MS API says reconv structure to be returned.
3250 void ScintillaWin::GetMouseParameters() noexcept {
3251 // mouse pointer size and colour may changed
3252 reverseArrowCursor.Invalidate();
3253 // This retrieves the number of lines per scroll as configured in the Mouse Properties sheet in Control Panel
3254 ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0);
3255 if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &charsPerScroll, 0)) {
3256 // no horizontal scrolling configuration on Windows XP
3257 charsPerScroll = (linesPerScroll == WHEEL_PAGESCROLL) ? 3 : linesPerScroll;
3259 ::SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &typingWithoutCursor, 0);
3262 void ScintillaWin::CopyToGlobal(GlobalMemory &gmUnicode, const SelectionText &selectedText) {
3263 const std::string_view svSelected(selectedText.Data(), selectedText.LengthWithTerminator());
3264 if (IsUnicodeMode()) {
3265 const size_t uchars = UTF16Length(svSelected);
3266 gmUnicode.Allocate(2 * uchars);
3267 if (gmUnicode) {
3268 UTF16FromUTF8(svSelected,
3269 static_cast<wchar_t *>(gmUnicode.ptr), uchars);
3271 } else {
3272 // Not Unicode mode
3273 // Convert to Unicode using the current Scintilla code page
3274 const UINT cpSrc = CodePageFromCharSet(
3275 selectedText.characterSet, selectedText.codePage);
3276 const size_t uLen = WideCharLenFromMultiByte(cpSrc, svSelected);
3277 gmUnicode.Allocate(2 * uLen);
3278 if (gmUnicode) {
3279 WideCharFromMultiByte(cpSrc, svSelected,
3280 static_cast<wchar_t *>(gmUnicode.ptr), uLen);
3285 void ScintillaWin::CopyToClipboard(const SelectionText &selectedText) {
3286 Clipboard clipboard(MainHWND());
3287 if (!clipboard) {
3288 return;
3290 ::EmptyClipboard();
3292 GlobalMemory uniText;
3293 CopyToGlobal(uniText, selectedText);
3294 if (uniText) {
3295 uniText.SetClip(CF_UNICODETEXT);
3298 if (selectedText.rectangular) {
3299 ::SetClipboardData(cfColumnSelect, 0);
3301 GlobalMemory borlandSelection;
3302 borlandSelection.Allocate(1);
3303 if (borlandSelection) {
3304 static_cast<BYTE *>(borlandSelection.ptr)[0] = 0x02;
3305 borlandSelection.SetClip(cfBorlandIDEBlockType);
3309 if (selectedText.lineCopy) {
3310 ::SetClipboardData(cfLineSelect, 0);
3311 ::SetClipboardData(cfVSLineTag, 0);
3315 void ScintillaWin::ScrollMessage(WPARAM wParam) {
3316 //DWORD dwStart = timeGetTime();
3317 //Platform::DebugPrintf("Scroll %x %d\n", wParam, lParam);
3319 SCROLLINFO sci = {};
3320 sci.cbSize = sizeof(sci);
3321 sci.fMask = SIF_ALL;
3323 GetScrollInfo(SB_VERT, &sci);
3325 //Platform::DebugPrintf("ScrollInfo %d mask=%x min=%d max=%d page=%d pos=%d track=%d\n", b,sci.fMask,
3326 //sci.nMin, sci.nMax, sci.nPage, sci.nPos, sci.nTrackPos);
3327 Sci::Line topLineNew = topLine;
3328 switch (LOWORD(wParam)) {
3329 case SB_LINEUP:
3330 topLineNew -= 1;
3331 break;
3332 case SB_LINEDOWN:
3333 topLineNew += 1;
3334 break;
3335 case SB_PAGEUP:
3336 topLineNew -= LinesToScroll(); break;
3337 case SB_PAGEDOWN: topLineNew += LinesToScroll(); break;
3338 case SB_TOP: topLineNew = 0; break;
3339 case SB_BOTTOM: topLineNew = MaxScrollPos(); break;
3340 case SB_THUMBPOSITION: topLineNew = sci.nTrackPos; break;
3341 case SB_THUMBTRACK: topLineNew = sci.nTrackPos; break;
3343 ScrollTo(topLineNew);
3346 void ScintillaWin::HorizontalScrollMessage(WPARAM wParam) {
3347 int xPos = xOffset;
3348 const PRectangle rcText = GetTextRectangle();
3349 const int pageWidth = static_cast<int>(rcText.Width() * 2 / 3);
3350 switch (LOWORD(wParam)) {
3351 case SB_LINEUP:
3352 xPos -= 20;
3353 break;
3354 case SB_LINEDOWN: // May move past the logical end
3355 xPos += 20;
3356 break;
3357 case SB_PAGEUP:
3358 xPos -= pageWidth;
3359 break;
3360 case SB_PAGEDOWN:
3361 xPos += pageWidth;
3362 break;
3363 case SB_TOP:
3364 xPos = 0;
3365 break;
3366 case SB_BOTTOM:
3367 xPos = scrollWidth;
3368 break;
3369 case SB_THUMBPOSITION:
3370 case SB_THUMBTRACK: {
3371 // 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 =]
3372 SCROLLINFO si {};
3373 si.cbSize = sizeof(si);
3374 si.fMask = SIF_TRACKPOS;
3375 if (GetScrollInfo(SB_HORZ, &si)) {
3376 xPos = si.nTrackPos;
3379 break;
3381 HorizontalScrollToClamped(xPos);
3385 * Redraw all of text area.
3386 * This paint will not be abandoned.
3388 void ScintillaWin::FullPaint() {
3389 if ((technology == Technology::Default) || (technology == Technology::DirectWriteDC)) {
3390 HDC hdc = ::GetDC(MainHWND());
3391 FullPaintDC(hdc);
3392 ::ReleaseDC(MainHWND(), hdc);
3393 } else {
3394 FullPaintDC({});
3399 * Redraw all of text area on the specified DC.
3400 * This paint will not be abandoned.
3402 void ScintillaWin::FullPaintDC(HDC hdc) {
3403 paintState = PaintState::painting;
3404 rcPaint = GetClientRectangle();
3405 paintingAllText = true;
3406 PaintDC(hdc);
3407 paintState = PaintState::notPainting;
3410 namespace {
3412 bool CompareDevCap(HDC hdc, HDC hOtherDC, int nIndex) noexcept {
3413 return ::GetDeviceCaps(hdc, nIndex) == ::GetDeviceCaps(hOtherDC, nIndex);
3418 bool ScintillaWin::IsCompatibleDC(HDC hOtherDC) noexcept {
3419 HDC hdc = ::GetDC(MainHWND());
3420 const bool isCompatible =
3421 CompareDevCap(hdc, hOtherDC, TECHNOLOGY) &&
3422 CompareDevCap(hdc, hOtherDC, LOGPIXELSY) &&
3423 CompareDevCap(hdc, hOtherDC, LOGPIXELSX) &&
3424 CompareDevCap(hdc, hOtherDC, BITSPIXEL) &&
3425 CompareDevCap(hdc, hOtherDC, PLANES);
3426 ::ReleaseDC(MainHWND(), hdc);
3427 return isCompatible;
3430 DWORD ScintillaWin::EffectFromState(DWORD grfKeyState) const noexcept {
3431 // These are the Wordpad semantics.
3432 DWORD dwEffect;
3433 if (inDragDrop == DragDrop::dragging) // Internal defaults to move
3434 dwEffect = DROPEFFECT_MOVE;
3435 else
3436 dwEffect = DROPEFFECT_COPY;
3437 if (grfKeyState & MK_ALT)
3438 dwEffect = DROPEFFECT_MOVE;
3439 if (grfKeyState & MK_CONTROL)
3440 dwEffect = DROPEFFECT_COPY;
3441 return dwEffect;
3444 /// Implement IUnknown
3445 STDMETHODIMP ScintillaWin::QueryInterface(REFIID riid, PVOID *ppv) {
3446 *ppv = nullptr;
3447 if (riid == IID_IUnknown) {
3448 *ppv = &dt;
3449 } else if (riid == IID_IDropSource) {
3450 *ppv = &ds;
3451 } else if (riid == IID_IDropTarget) {
3452 *ppv = &dt;
3453 } else if (riid == IID_IDataObject) {
3454 *ppv = &dob;
3456 if (!*ppv)
3457 return E_NOINTERFACE;
3458 return S_OK;
3461 STDMETHODIMP_(ULONG) ScintillaWin::AddRef() {
3462 return 1;
3465 STDMETHODIMP_(ULONG) ScintillaWin::Release() {
3466 return 1;
3469 /// Implement IDropTarget
3470 STDMETHODIMP ScintillaWin::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
3471 POINTL, PDWORD pdwEffect) {
3472 if (!pIDataSource )
3473 return E_POINTER;
3474 FORMATETC fmtu = {CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
3475 const HRESULT hrHasUText = pIDataSource->QueryGetData(&fmtu);
3476 hasOKText = (hrHasUText == S_OK);
3477 if (hasOKText) {
3478 *pdwEffect = EffectFromState(grfKeyState);
3479 } else {
3480 *pdwEffect = DROPEFFECT_NONE;
3483 return S_OK;
3486 STDMETHODIMP ScintillaWin::DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) {
3487 try {
3488 if (!hasOKText || pdoc->IsReadOnly()) {
3489 *pdwEffect = DROPEFFECT_NONE;
3490 return S_OK;
3493 *pdwEffect = EffectFromState(grfKeyState);
3495 // Update the cursor.
3496 POINT rpt = {pt.x, pt.y};
3497 ::ScreenToClient(MainHWND(), &rpt);
3498 SetDragPosition(SPositionFromLocation(PointFromPOINT(rpt), false, false, UserVirtualSpace()));
3500 return S_OK;
3501 } catch (...) {
3502 errorStatus = Status::Failure;
3504 return E_FAIL;
3507 STDMETHODIMP ScintillaWin::DragLeave() {
3508 try {
3509 SetDragPosition(SelectionPosition(Sci::invalidPosition));
3510 return S_OK;
3511 } catch (...) {
3512 errorStatus = Status::Failure;
3514 return E_FAIL;
3517 STDMETHODIMP ScintillaWin::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
3518 POINTL pt, PDWORD pdwEffect) {
3519 try {
3520 *pdwEffect = EffectFromState(grfKeyState);
3522 if (!pIDataSource)
3523 return E_POINTER;
3525 SetDragPosition(SelectionPosition(Sci::invalidPosition));
3527 std::string putf;
3528 FORMATETC fmtu = {CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
3529 STGMEDIUM medium{};
3530 const HRESULT hr = pIDataSource->GetData(&fmtu, &medium);
3531 if (!SUCCEEDED(hr)) {
3532 return hr;
3534 if (medium.hGlobal) {
3535 GlobalMemory memUDrop(medium.hGlobal);
3536 if (const wchar_t *uptr = static_cast<const wchar_t *>(memUDrop.ptr)) {
3537 putf = EncodeWString(uptr);
3539 memUDrop.Unlock();
3542 if (putf.empty()) {
3543 return S_OK;
3546 FORMATETC fmtr = {cfColumnSelect, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
3547 const bool isRectangular = S_OK == pIDataSource->QueryGetData(&fmtr);
3549 POINT rpt = {pt.x, pt.y};
3550 ::ScreenToClient(MainHWND(), &rpt);
3551 const SelectionPosition movePos = SPositionFromLocation(PointFromPOINT(rpt), false, false, UserVirtualSpace());
3553 DropAt(movePos, putf.c_str(), putf.size(), *pdwEffect == DROPEFFECT_MOVE, isRectangular);
3555 // Free data
3556 ::ReleaseStgMedium(&medium);
3558 return S_OK;
3559 } catch (...) {
3560 errorStatus = Status::Failure;
3562 return E_FAIL;
3565 /// Implement important part of IDataObject
3566 STDMETHODIMP ScintillaWin::GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) {
3567 if (!SupportedFormat(pFEIn)) {
3568 //Platform::DebugPrintf("DOB GetData No %d %x %x fmt=%x\n", lenDrag, pFEIn, pSTM, pFEIn->cfFormat);
3569 return DATA_E_FORMATETC;
3572 pSTM->tymed = TYMED_HGLOBAL;
3573 //Platform::DebugPrintf("DOB GetData OK %d %x %x\n", lenDrag, pFEIn, pSTM);
3575 GlobalMemory uniText;
3576 CopyToGlobal(uniText, drag);
3577 pSTM->hGlobal = uniText ? uniText.Unlock() : 0;
3578 pSTM->pUnkForRelease = nullptr;
3579 return S_OK;
3582 void ScintillaWin::Prepare() noexcept {
3583 Platform_Initialise(hInstance);
3585 // Register the CallTip class
3586 WNDCLASSEX wndclassc{};
3587 wndclassc.cbSize = sizeof(wndclassc);
3588 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
3589 wndclassc.cbWndExtra = sizeof(ScintillaWin *);
3590 wndclassc.hInstance = hInstance;
3591 wndclassc.lpfnWndProc = ScintillaWin::CTWndProc;
3592 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
3593 wndclassc.lpszClassName = callClassName;
3595 callClassAtom = ::RegisterClassEx(&wndclassc);
3598 bool ScintillaWin::Register(HINSTANCE hInstance_) noexcept {
3600 hInstance = hInstance_;
3602 // Register the Scintilla class
3603 // Register Scintilla as a wide character window
3604 WNDCLASSEXW wndclass {};
3605 wndclass.cbSize = sizeof(wndclass);
3606 wndclass.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
3607 wndclass.lpfnWndProc = ScintillaWin::SWndProc;
3608 wndclass.cbWndExtra = sizeof(ScintillaWin *);
3609 wndclass.hInstance = hInstance;
3610 wndclass.lpszClassName = L"Scintilla";
3611 scintillaClassAtom = ::RegisterClassExW(&wndclass);
3612 const bool result = 0 != scintillaClassAtom;
3614 return result;
3617 bool ScintillaWin::Unregister() noexcept {
3618 bool result = true;
3619 if (0 != scintillaClassAtom) {
3620 if (::UnregisterClass(MAKEINTATOM(scintillaClassAtom), hInstance) == 0) {
3621 result = false;
3623 scintillaClassAtom = 0;
3625 if (0 != callClassAtom) {
3626 if (::UnregisterClass(MAKEINTATOM(callClassAtom), hInstance) == 0) {
3627 result = false;
3629 callClassAtom = 0;
3631 return result;
3634 bool ScintillaWin::HasCaretSizeChanged() const noexcept {
3635 if (
3636 ( (0 != vs.caret.width) && (sysCaretWidth != vs.caret.width) )
3637 || ((0 != vs.lineHeight) && (sysCaretHeight != vs.lineHeight))
3639 return true;
3641 return false;
3644 BOOL ScintillaWin::CreateSystemCaret() {
3645 sysCaretWidth = vs.caret.width;
3646 if (0 == sysCaretWidth) {
3647 sysCaretWidth = 1;
3649 sysCaretHeight = vs.lineHeight;
3650 const int bitmapSize = (((sysCaretWidth + 15) & ~15) >> 3) *
3651 sysCaretHeight;
3652 std::vector<BYTE> bits(bitmapSize);
3653 sysCaretBitmap = ::CreateBitmap(sysCaretWidth, sysCaretHeight, 1,
3654 1, &bits[0]);
3655 const BOOL retval = ::CreateCaret(
3656 MainHWND(), sysCaretBitmap,
3657 sysCaretWidth, sysCaretHeight);
3658 if (technology == Technology::Default) {
3659 // System caret interferes with Direct2D drawing so only show it for GDI.
3660 ::ShowCaret(MainHWND());
3662 return retval;
3665 BOOL ScintillaWin::DestroySystemCaret() noexcept {
3666 ::HideCaret(MainHWND());
3667 const BOOL retval = ::DestroyCaret();
3668 if (sysCaretBitmap) {
3669 ::DeleteObject(sysCaretBitmap);
3670 sysCaretBitmap = {};
3672 return retval;
3675 LRESULT PASCAL ScintillaWin::CTWndProc(
3676 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3677 // Find C++ object associated with window.
3678 ScintillaWin *sciThis = static_cast<ScintillaWin *>(PointerFromWindow(hWnd));
3679 try {
3680 // ctp will be zero if WM_CREATE not seen yet
3681 if (sciThis == nullptr) {
3682 if (iMessage == WM_CREATE) {
3683 // Associate CallTip object with window
3684 CREATESTRUCT *pCreate = static_cast<CREATESTRUCT *>(PtrFromSPtr(lParam));
3685 SetWindowPointer(hWnd, pCreate->lpCreateParams);
3686 return 0;
3687 } else {
3688 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3690 } else {
3691 if (iMessage == WM_NCDESTROY) {
3692 SetWindowPointer(hWnd, nullptr);
3693 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3694 } else if (iMessage == WM_PAINT) {
3695 PAINTSTRUCT ps;
3696 ::BeginPaint(hWnd, &ps);
3697 std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(sciThis->technology));
3698 #if defined(USE_D2D)
3699 ID2D1HwndRenderTarget *pCTRenderTarget = nullptr;
3700 #endif
3701 RECT rc;
3702 GetClientRect(hWnd, &rc);
3703 if (sciThis->technology == Technology::Default) {
3704 surfaceWindow->Init(ps.hdc, hWnd);
3705 } else {
3706 #if defined(USE_D2D)
3707 const int scaleFactor = sciThis->GetFirstIntegralMultipleDeviceScaleFactor();
3709 // Create a Direct2D render target.
3710 D2D1_HWND_RENDER_TARGET_PROPERTIES dhrtp {};
3711 dhrtp.hwnd = hWnd;
3712 dhrtp.pixelSize = ::GetSizeUFromRect(rc, scaleFactor);
3713 dhrtp.presentOptions = (sciThis->technology == Technology::DirectWriteRetain) ?
3714 D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS : D2D1_PRESENT_OPTIONS_NONE;
3716 D2D1_RENDER_TARGET_PROPERTIES drtp {};
3717 drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
3718 drtp.pixelFormat.format = DXGI_FORMAT_UNKNOWN;
3719 drtp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN;
3720 drtp.dpiX = 96.f * scaleFactor;
3721 drtp.dpiY = 96.f * scaleFactor;
3722 drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE;
3723 drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
3725 if (!SUCCEEDED(pD2DFactory->CreateHwndRenderTarget(drtp, dhrtp, &pCTRenderTarget))) {
3726 surfaceWindow->Release();
3727 ::EndPaint(hWnd, &ps);
3728 return 0;
3730 // If above SUCCEEDED, then pCTRenderTarget not nullptr
3731 assert(pCTRenderTarget);
3732 if (pCTRenderTarget) {
3733 surfaceWindow->Init(pCTRenderTarget, hWnd);
3734 pCTRenderTarget->BeginDraw();
3736 #endif
3738 surfaceWindow->SetMode(sciThis->CurrentSurfaceMode());
3739 sciThis->SetRenderingParams(surfaceWindow.get());
3740 sciThis->ct.PaintCT(surfaceWindow.get());
3741 #if defined(USE_D2D)
3742 if (pCTRenderTarget)
3743 pCTRenderTarget->EndDraw();
3744 #endif
3745 surfaceWindow->Release();
3746 #if defined(USE_D2D)
3747 ReleaseUnknown(pCTRenderTarget);
3748 #endif
3749 ::EndPaint(hWnd, &ps);
3750 return 0;
3751 } else if ((iMessage == WM_NCLBUTTONDOWN) || (iMessage == WM_NCLBUTTONDBLCLK)) {
3752 POINT pt = POINTFromLParam(lParam);
3753 ::ScreenToClient(hWnd, &pt);
3754 sciThis->ct.MouseClick(PointFromPOINT(pt));
3755 sciThis->CallTipClick();
3756 return 0;
3757 } else if (iMessage == WM_LBUTTONDOWN) {
3758 // This does not fire due to the hit test code
3759 sciThis->ct.MouseClick(PointFromLParam(lParam));
3760 sciThis->CallTipClick();
3761 return 0;
3762 } else if (iMessage == WM_SETCURSOR) {
3763 ::SetCursor(::LoadCursor(NULL, IDC_ARROW));
3764 return 0;
3765 } else if (iMessage == WM_NCHITTEST) {
3766 return HTCAPTION;
3767 } else {
3768 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3771 } catch (...) {
3772 sciThis->errorStatus = Status::Failure;
3774 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3777 sptr_t ScintillaWin::DirectFunction(
3778 sptr_t ptr, UINT iMessage, uptr_t wParam, sptr_t lParam) {
3779 ScintillaWin *sci = reinterpret_cast<ScintillaWin *>(ptr);
3780 PLATFORM_ASSERT(::GetCurrentThreadId() == ::GetWindowThreadProcessId(sci->MainHWND(), nullptr));
3781 return sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3784 sptr_t ScintillaWin::DirectStatusFunction(
3785 sptr_t ptr, UINT iMessage, uptr_t wParam, sptr_t lParam, int *pStatus) {
3786 ScintillaWin *sci = reinterpret_cast<ScintillaWin *>(ptr);
3787 PLATFORM_ASSERT(::GetCurrentThreadId() == ::GetWindowThreadProcessId(sci->MainHWND(), nullptr));
3788 const sptr_t returnValue = sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3789 *pStatus = static_cast<int>(sci->errorStatus);
3790 return returnValue;
3793 namespace Scintilla::Internal {
3795 sptr_t DirectFunction(
3796 ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam) {
3797 return sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3802 LRESULT PASCAL ScintillaWin::SWndProc(
3803 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3804 //Platform::DebugPrintf("S W:%x M:%x WP:%x L:%x\n", hWnd, iMessage, wParam, lParam);
3806 // Find C++ object associated with window.
3807 ScintillaWin *sci = static_cast<ScintillaWin *>(PointerFromWindow(hWnd));
3808 // sci will be zero if WM_CREATE not seen yet
3809 if (sci == nullptr) {
3810 try {
3811 if (iMessage == WM_CREATE) {
3812 static std::once_flag once;
3813 std::call_once(once, Prepare);
3814 // Create C++ object associated with window
3815 sci = new ScintillaWin(hWnd);
3816 SetWindowPointer(hWnd, sci);
3817 return sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3819 } catch (...) {
3821 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3822 } else {
3823 if (iMessage == WM_NCDESTROY) {
3824 try {
3825 sci->Finalise();
3826 delete sci;
3827 } catch (...) {
3829 SetWindowPointer(hWnd, nullptr);
3830 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3831 } else {
3832 return sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3837 // This function is externally visible so it can be called from container when building statically.
3838 // Must be called once only.
3839 extern "C" int Scintilla_RegisterClasses(void *hInstance) {
3840 const bool result = ScintillaWin::Register(static_cast<HINSTANCE>(hInstance));
3841 return result;
3844 namespace Scintilla::Internal {
3846 int ResourcesRelease(bool fromDllMain) noexcept {
3847 const bool result = ScintillaWin::Unregister();
3848 Platform_Finalise(fromDllMain);
3849 return result;
3852 int RegisterClasses(void *hInstance) noexcept {
3853 const bool result = ScintillaWin::Register(static_cast<HINSTANCE>(hInstance));
3854 return result;
3859 // This function is externally visible so it can be called from container when building statically.
3860 extern "C" int Scintilla_ReleaseResources() {
3861 return Scintilla::Internal::ResourcesRelease(false);