From 2c8dd03b6f0ac384593cef5748e48b8b1c8f71e6 Mon Sep 17 00:00:00 2001 From: "METANEOCORTEX\\Kotti" Date: Sun, 29 Aug 2021 00:38:03 +0200 Subject: [PATCH 1/4] +fix: remove unused files --- scintilla/win32/PlatWin_old.cxx | 3706 ----------------------- scintilla/win32/PlatWin_old.h | 143 - scintilla/win32/ScintillaWin_old.cxx | 4200 -------------------------- scintilla/win32/ScintillaWin_old.h | 20 - 4 files changed, 8069 deletions(-) delete mode 100644 scintilla/win32/PlatWin_old.cxx delete mode 100644 scintilla/win32/PlatWin_old.h delete mode 100644 scintilla/win32/ScintillaWin_old.cxx delete mode 100644 scintilla/win32/ScintillaWin_old.h diff --git a/scintilla/win32/PlatWin_old.cxx b/scintilla/win32/PlatWin_old.cxx deleted file mode 100644 index fc9b25029..000000000 --- a/scintilla/win32/PlatWin_old.cxx +++ /dev/null @@ -1,3706 +0,0 @@ -// Scintilla source code edit control -/** @file PlatWin.cxx - ** Implementation of platform facilities on Windows. - **/ -// Copyright 1998-2003 by Neil Hodgson -// The License.txt file describes the conditions under which this software may be distributed. - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -//#include - -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x601 /*_WIN32_WINNT_WIN7*/ -#endif -#ifndef WINVER -#define WINVER 0x601 /*_WIN32_WINNT_WIN7*/ -#endif -#ifndef NTDDI_VERSION -#define NTDDI_VERSION 0x06010000 /*NTDDI_WIN7*/ -#endif - -#define VC_EXTRALEAN 1 -#define WIN32_LEAN_AND_MEAN 1 - -// Want to use std::min and std::max so don't want Windows.h version of min and max -#if !defined(NOMINMAX) -#define NOMINMAX -#endif -#include -#include -#include -#include -#include -#include - -#include "Platform.h" -#include "Scintilla.h" -#include "XPM.h" -//~#include "CharClassify.h" -//~#include "DBCS.h" -#include "UniConversion.h" -#include "FontQuality.h" - -#include "PlatWin.h" - -#ifndef SPI_GETFONTSMOOTHINGCONTRAST -#define SPI_GETFONTSMOOTHINGCONTRAST 0x200C -#endif - -#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 -#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 -#endif - -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 -extern DWORD kSystemLibraryLoadFlags; -#else -#define kSystemLibraryLoadFlags LOAD_LIBRARY_SEARCH_SYSTEM32 -#endif - -extern "C" unsigned g_uSystemDPI = USER_DEFAULT_SCREEN_DPI; - -namespace { - -using GetDpiForWindowSig = UINT (WINAPI *)(HWND hwnd); -GetDpiForWindowSig fnGetDpiForWindow = nullptr; - -#ifndef DPI_ENUMS_DECLARED -#define MDT_EFFECTIVE_DPI 0 -#endif - -using GetDpiForMonitorSig = HRESULT (WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY); -HMODULE hShcoreDLL {}; -GetDpiForMonitorSig fnGetDpiForMonitor = nullptr; - -using GetSystemMetricsForDpiSig = int (WINAPI *)(int nIndex, UINT dpi); -GetSystemMetricsForDpiSig fnGetSystemMetricsForDpi = nullptr; - -using AdjustWindowRectExForDpiSig = BOOL (WINAPI *)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); -AdjustWindowRectExForDpiSig fnAdjustWindowRectExForDpi = nullptr; - -} - -void Scintilla_LoadDpiForWindow(void) { - using Scintilla::DLLFunction; - - HMODULE user32 = ::GetModuleHandleW(L"user32.dll"); - fnGetDpiForWindow = DLLFunction(user32, "GetDpiForWindow"); - fnGetSystemMetricsForDpi = DLLFunction(user32, "GetSystemMetricsForDpi"); - fnAdjustWindowRectExForDpi = DLLFunction(user32, "AdjustWindowRectExForDpi"); - - using GetDpiForSystemSig = UINT (WINAPI *)(void); - GetDpiForSystemSig fnGetDpiForSystem = DLLFunction(user32, "GetDpiForSystem"); - if (fnGetDpiForSystem) { - g_uSystemDPI = fnGetDpiForSystem(); - } else { - HDC hDC = ::GetDC({}); - g_uSystemDPI = ::GetDeviceCaps(hDC, LOGPIXELSY); - ::ReleaseDC({}, hDC); - } - - if (!fnGetDpiForWindow) { - hShcoreDLL = ::LoadLibraryExW(L"shcore.dll", {}, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (hShcoreDLL) { - fnGetDpiForMonitor = DLLFunction(hShcoreDLL, "GetDpiForMonitor"); - } - } -} - -DPI_T GetWindowDPI(HWND hwnd) { - DPI_T _dpi; - _dpi.x = g_uSystemDPI; - _dpi.y = g_uSystemDPI; - if (hwnd) { - if (fnGetDpiForWindow) { - _dpi.y = fnGetDpiForWindow(hwnd); - _dpi.x = _dpi.y; - } - else if (fnGetDpiForMonitor) { - HMONITOR hMonitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - if (FAILED(fnGetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &(_dpi.x), &(_dpi.y)))) { - _dpi.x = g_uSystemDPI; - _dpi.y = g_uSystemDPI; - } - } - } - return _dpi; -} - -int SystemMetricsForDpi(int nIndex, unsigned dpi) { - if (fnGetSystemMetricsForDpi) { - return fnGetSystemMetricsForDpi(nIndex, dpi); - } - int const value = ::GetSystemMetrics(nIndex); - return ((dpi == g_uSystemDPI) ? value : ::MulDiv(value, dpi, g_uSystemDPI)); -} - -BOOL AdjustWindowRectForDpi(LPRECT lpRect, DWORD dwStyle, DWORD dwExStyle, unsigned dpi) { - if (fnAdjustWindowRectExForDpi) { - return fnAdjustWindowRectExForDpi(lpRect, dwStyle, FALSE, dwExStyle, dpi); - } - return ::AdjustWindowRectEx(lpRect, dwStyle, FALSE, dwExStyle); -} - -namespace Scintilla { - -UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) noexcept; - -#if defined(USE_D2D) -IDWriteFactory *pIDWriteFactory = nullptr; -ID2D1Factory *pD2DFactory = nullptr; -IDWriteRenderingParams *defaultRenderingParams = nullptr; -IDWriteRenderingParams *customClearTypeRenderingParams = nullptr; -IDWriteGdiInterop *gdiInterop = nullptr; -D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE; - -static HMODULE hDLLD2D {}; -static HMODULE hDLLDWrite {}; - -static BOOL CALLBACK LoadD2DOnce(PINIT_ONCE initOnce, PVOID parameter, PVOID *lpContext) noexcept -{ - UNREFERENCED_PARAMETER(initOnce); - UNREFERENCED_PARAMETER(parameter); - UNREFERENCED_PARAMETER(lpContext); - - // Availability of SetDefaultDllDirectories implies Windows 8+ or - // that KB2533623 has been installed so LoadLibraryEx can be called - // with LOAD_LIBRARY_SEARCH_SYSTEM32. - - using D2D1CreateFactorySig = HRESULT(WINAPI *)(D2D1_FACTORY_TYPE factoryType, REFIID riid, - CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory); - using DWriteCreateFactorySig = HRESULT(WINAPI *)(DWRITE_FACTORY_TYPE factoryType, REFIID iid, - IUnknown **factory); - - hDLLD2D = ::LoadLibraryEx(L"d2d1.dll", {}, kSystemLibraryLoadFlags); - if (hDLLD2D) { - D2D1CreateFactorySig fnD2DCF = DLLFunction(hDLLD2D, "D2D1CreateFactory"); - if (fnD2DCF) { -#ifdef NDEBUG - // A single threaded factory as Scintilla always draw on the GUI thread - fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED, - __uuidof(ID2D1Factory), - nullptr, - reinterpret_cast(&pD2DFactory)); -#else - D2D1_FACTORY_OPTIONS options = {}; - options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; - fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED, - __uuidof(ID2D1Factory), - &options, - reinterpret_cast(&pD2DFactory)); -#endif - } - } - - hDLLDWrite = ::LoadLibraryEx(L"dwrite.dll", {}, kSystemLibraryLoadFlags); - if (hDLLDWrite) { - DWriteCreateFactorySig fnDWCF = DLLFunction(hDLLDWrite, "DWriteCreateFactory"); - if (fnDWCF) { - const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec - { 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } }; - - const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED, - IID_IDWriteFactory2, - reinterpret_cast(&pIDWriteFactory)); - if (SUCCEEDED(hr)) { - // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT - d2dDrawTextOptions = static_cast(0x00000004); - } else { - fnDWCF(DWRITE_FACTORY_TYPE_SHARED, - __uuidof(IDWriteFactory), - reinterpret_cast(&pIDWriteFactory)); - } - } - } - - if (pIDWriteFactory) { - HRESULT hr = pIDWriteFactory->CreateRenderingParams(&defaultRenderingParams); - if (SUCCEEDED(hr)) { - unsigned int clearTypeContrast; - if (::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0)) { - - FLOAT gamma; - if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200) - gamma = static_cast(clearTypeContrast) / 1000.0f; - else - gamma = defaultRenderingParams->GetGamma(); - - pIDWriteFactory->CreateCustomRenderingParams(gamma, defaultRenderingParams->GetEnhancedContrast(), defaultRenderingParams->GetClearTypeLevel(), - defaultRenderingParams->GetPixelGeometry(), defaultRenderingParams->GetRenderingMode(), &customClearTypeRenderingParams); - } - } - - hr = pIDWriteFactory->GetGdiInterop(&gdiInterop); - if (!SUCCEEDED(hr)) { - ReleaseUnknown(gdiInterop); - } - } - return TRUE; -} - -bool LoadD2D() noexcept { - static INIT_ONCE once = INIT_ONCE_STATIC_INIT; - ::InitOnceExecuteOnce(&once, LoadD2DOnce, nullptr, nullptr); - return (pIDWriteFactory && pD2DFactory); -} -#endif - -struct FormatAndMetrics { - int technology; - LOGFONTW lf; - HFONT hfont; -#if defined(USE_D2D) - IDWriteTextFormat *pTextFormat; -#endif - int extraFontFlag; - int characterSet; - FLOAT yAscent; - FLOAT yDescent; - FLOAT yInternalLeading; - FormatAndMetrics(const LOGFONTW &lf_, HFONT hfont_, int extraFontFlag_, int characterSet_) noexcept : - technology(SCWIN_TECH_GDI), lf(lf_), hfont(hfont_), -#if defined(USE_D2D) - pTextFormat(nullptr), -#endif - extraFontFlag(extraFontFlag_), characterSet(characterSet_), yAscent(2), yDescent(1), yInternalLeading(0) {} -#if defined(USE_D2D) - FormatAndMetrics(const LOGFONTW &lf_, IDWriteTextFormat *pTextFormat_, - int extraFontFlag_, - int characterSet_, - FLOAT yAscent_, - FLOAT yDescent_, - FLOAT yInternalLeading_) noexcept : - technology(SCWIN_TECH_DIRECTWRITE), - lf(lf_), - hfont{}, - pTextFormat(pTextFormat_), - extraFontFlag(extraFontFlag_), - characterSet(characterSet_), - yAscent(yAscent_), - yDescent(yDescent_), - yInternalLeading(yInternalLeading_) {} -#endif - FormatAndMetrics(const FormatAndMetrics &) = delete; - FormatAndMetrics(FormatAndMetrics &&) = delete; - FormatAndMetrics &operator=(const FormatAndMetrics &) = delete; - FormatAndMetrics &operator=(FormatAndMetrics &&) = delete; - - ~FormatAndMetrics() { - if (hfont) - ::DeleteObject(hfont); -#if defined(USE_D2D) - ReleaseUnknown(pTextFormat); -#endif - extraFontFlag = 0; - characterSet = 0; - yAscent = 2; - yDescent = 1; - yInternalLeading = 0; - } - HFONT HFont() const noexcept; -}; - -HFONT FormatAndMetrics::HFont() const noexcept { - return ::CreateFontIndirectW(&lf); -} - -namespace { - -HINSTANCE hinstPlatformRes {}; - -inline FormatAndMetrics *FamFromFontID(void *fid) noexcept { - return static_cast(fid); -} - -constexpr BYTE Win32MapFontQuality(int extraFontFlag) noexcept { - switch (extraFontFlag & SC_EFF_QUALITY_MASK) { - - case SC_EFF_QUALITY_NON_ANTIALIASED: - return NONANTIALIASED_QUALITY; - - case SC_EFF_QUALITY_ANTIALIASED: - return ANTIALIASED_QUALITY; - - case SC_EFF_QUALITY_LCD_OPTIMIZED: - return CLEARTYPE_QUALITY; - - default: - return SC_EFF_QUALITY_DEFAULT; - } -} - -#if defined(USE_D2D) -constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) noexcept { - switch (extraFontFlag & SC_EFF_QUALITY_MASK) { - - case SC_EFF_QUALITY_NON_ANTIALIASED: - return D2D1_TEXT_ANTIALIAS_MODE_ALIASED; - - case SC_EFF_QUALITY_ANTIALIASED: - return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; - - case SC_EFF_QUALITY_LCD_OPTIMIZED: - return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; - - default: - return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; - } -} -#endif - -void SetLogFont(LOGFONTW &lf, const char *faceName, int characterSet, float size, int weight, int stretch, bool italic, int extraFontFlag) { - lf = LOGFONTW(); - // The negative is to allow for leading - lf.lfHeight = -std::abs(std::lround(size)); - lf.lfWidth = 0; // std::abs(std::lround(size * stretch / DWRITE_FONT_STRETCH_NORMAL)); - lf.lfWeight = weight; - lf.lfItalic = italic ? 1 : 0; - lf.lfCharSet = static_cast(characterSet); - lf.lfQuality = Win32MapFontQuality(extraFontFlag); - UTF16FromUTF8(faceName, lf.lfFaceName, LF_FACESIZE); -} - -#if defined(USE_D2D) -bool GetDWriteFontProperties(const LOGFONTW &lf, std::wstring &wsFamily, - DWRITE_FONT_WEIGHT &weight, DWRITE_FONT_STYLE &style, DWRITE_FONT_STRETCH &stretch) { - bool success = false; - if (gdiInterop) { - IDWriteFont *font = nullptr; - HRESULT hr = gdiInterop->CreateFontFromLOGFONT(&lf, &font); - if (SUCCEEDED(hr)) { - weight = font->GetWeight(); - style = font->GetStyle(); - stretch = font->GetStretch(); - - IDWriteFontFamily *family = nullptr; - hr = font->GetFontFamily(&family); - if (SUCCEEDED(hr)) { - IDWriteLocalizedStrings *names = nullptr; - hr = family->GetFamilyNames(&names); - if (SUCCEEDED(hr)) { - UINT32 index = 0; - BOOL exists = false; - names->FindLocaleName(L"en-us", &index, &exists); - if (!exists) { - index = 0; - } - - UINT32 length = 0; - names->GetStringLength(index, &length); - - wsFamily.resize(length + 1); - names->GetString(index, wsFamily.data(), length + 1); - - success = wsFamily[0] != L'\0'; - } - ReleaseUnknown(names); - } - ReleaseUnknown(family); - } - ReleaseUnknown(font); - } - return success; -} -#endif - -FontID CreateFontFromParameters(const FontParameters &fp) { - LOGFONTW lf; - SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.stretch, fp.italic, fp.extraFontFlag); - FontID fid = nullptr; - if (fp.technology == SCWIN_TECH_GDI) { - HFONT hfont = ::CreateFontIndirectW(&lf); - fid = new FormatAndMetrics(lf, hfont, fp.extraFontFlag, fp.characterSet); - } else { -#if defined(USE_D2D) - IDWriteTextFormat *pTextFormat = nullptr; - std::wstring wsFamily; - const FLOAT fHeight = fp.size; - DWRITE_FONT_WEIGHT weight = static_cast(fp.weight); - DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; - DWRITE_FONT_STRETCH stretch = static_cast(fp.stretch); - if (!GetDWriteFontProperties(lf, wsFamily, weight, style, stretch)) { - wsFamily = WStringFromUTF8(fp.faceName); - } - - const std::wstring wsLocale = WStringFromUTF8(fp.localeName); - HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFamily.c_str(), nullptr, - weight, style, stretch, fHeight, wsLocale.c_str(), &pTextFormat); - if (SUCCEEDED(hr)) { - pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); - - FLOAT yAscent = 1.0f; - FLOAT yDescent = 1.0f; - FLOAT yInternalLeading = 0.0f; - IDWriteTextLayout *pTextLayout = nullptr; - hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat, - 100.0f, 100.0f, &pTextLayout); - if (SUCCEEDED(hr) && pTextLayout) { - constexpr int maxLines = 2; - DWRITE_LINE_METRICS lineMetrics[maxLines]{}; - UINT32 lineCount = 0; - hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount); - if (SUCCEEDED(hr)) { - yAscent = lineMetrics[0].baseline; - yDescent = lineMetrics[0].height - lineMetrics[0].baseline; - - FLOAT emHeight; - hr = pTextLayout->GetFontSize(0, &emHeight); - if (SUCCEEDED(hr)) { - yInternalLeading = lineMetrics[0].height - emHeight; - } - } - ReleaseUnknown(pTextLayout); - pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline); - } - fid = new FormatAndMetrics(lf, pTextFormat, fp.extraFontFlag, fp.characterSet, yAscent, yDescent, yInternalLeading); - } -#endif - } - return fid; -} - -} - -Font::Font() noexcept : fid{} { -} - -Font::~Font() = default; - -void Font::Create(const FontParameters &fp) { - Release(); - if (fp.faceName) - fid = CreateFontFromParameters(fp); -} - -void Font::Release() noexcept { - if (fid) - delete FamFromFontID(fid); - fid = nullptr; -} - -// Buffer to hold strings and string position arrays without always allocating on heap. -// May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer -// when less than safe size otherwise allocate on heap and free automatically. -template -class VarBuffer { - T bufferStandard[lengthStandard]; -public: - T * buffer; - explicit VarBuffer(size_t length) : buffer(nullptr) { - if (length > lengthStandard) { - buffer = new T[length]; - } else { - buffer = bufferStandard; - } - } - // Deleted so VarBuffer objects can not be copied. - VarBuffer(const VarBuffer &) = delete; - VarBuffer(VarBuffer &&) = delete; - VarBuffer &operator=(const VarBuffer &) = delete; - VarBuffer &operator=(VarBuffer &&) = delete; - - ~VarBuffer() { - if (buffer != bufferStandard) { - delete[]buffer; - buffer = nullptr; - } - } -}; - -constexpr int stackBufferLength = 1000; -class TextWide : public VarBuffer { -public: - int tlen; // Using int instead of size_t as most Win32 APIs take int. - TextWide(std::string_view text, bool unicodeMode, int codePage = 0) : - VarBuffer(text.length()) { - if (unicodeMode) { - tlen = static_cast(UTF16FromUTF8(text, buffer, text.length())); - } else { - // Support Asian string display in 9x English - tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast(text.length()), - buffer, static_cast(text.length())); - } - } -}; -using TextPositions = VarBuffer; - -class SurfaceGDI final : public Surface { - bool unicodeMode = false; - HDC hdc{}; - bool hdcOwned = false; - HPEN pen{}; - HPEN penOld{}; - HBRUSH brush{}; - HBRUSH brushOld{}; - HFONT fontOld{}; - HBITMAP bitmap{}; - HBITMAP bitmapOld{}; - - int logPixelsY = USER_DEFAULT_SCREEN_DPI; - - int maxWidthMeasure = INT_MAX; - // There appears to be a 16 bit string length limit in GDI on NT. - int maxLenText = 65535; - - int codePage = 0; - - void BrushColour(ColourDesired back) noexcept; - void SetFont(const Font &font_) noexcept; - void Clear() noexcept; - -public: - SurfaceGDI() noexcept = default; - // Deleted so SurfaceGDI objects can not be copied. - SurfaceGDI(const SurfaceGDI &) = delete; - SurfaceGDI(SurfaceGDI &&) = delete; - SurfaceGDI &operator=(const SurfaceGDI &) = delete; - SurfaceGDI &operator=(SurfaceGDI &&) = delete; - - ~SurfaceGDI() noexcept override; - - void Init(WindowID wid) noexcept override; - void Init(SurfaceID sid, WindowID wid, bool printing = false) noexcept override; - void InitPixMap(int width, int height, Surface *surface_, WindowID wid) noexcept override; - - void Release() noexcept override; - bool Initialised() const noexcept override; - void PenColour(ColourDesired fore) noexcept override; - int LogPixelsY() const noexcept override; - int DeviceHeightFont(int points) const noexcept override; - void SCICALL MoveTo(int x_, int y_) noexcept override; - void SCICALL LineTo(int x_, int y_) noexcept override; - void SCICALL Polygon(const Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; - void SCICALL RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) noexcept override; - void SCICALL FillRectangle(PRectangle rc, ColourDesired back) noexcept override; - void SCICALL FillRectangle(PRectangle rc, Surface &surfacePattern) noexcept override; - void SCICALL RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) noexcept override; - void SCICALL AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, - ColourDesired outline, int alphaOutline, int flags) noexcept override; - void SCICALL GradientRectangle(PRectangle rc, const std::vector &stops, GradientOptions options) override; - void SCICALL DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) noexcept override; - void SCICALL Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) noexcept override; - void SCICALL Copy(PRectangle rc, Point from, Surface &surfaceSource) noexcept override; - - std::unique_ptr Layout(const IScreenLine *screenLine) noexcept override; - - void SCICALL DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); - void SCICALL DrawTextNoClip(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; - void SCICALL DrawTextClipped(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; - void SCICALL DrawTextTransparent(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; - void SCICALL MeasureWidths(const Font &font_, std::string_view text, XYPOSITION *positions) override; - XYPOSITION WidthText(const Font &font_, std::string_view text) override; - XYPOSITION Ascent(const Font &font_) noexcept override; - XYPOSITION Descent(const Font &font_) noexcept override; - XYPOSITION InternalLeading(const Font &font_) noexcept override; - XYPOSITION Height(const Font &font_) noexcept override; - XYPOSITION AverageCharWidth(const Font &font_) noexcept override; - - void SCICALL SetClip(PRectangle rc) noexcept override; - void FlushCachedState() noexcept override; - - void SetUnicodeMode(bool unicodeMode_) noexcept override; - void SetDBCSMode(int codePage_) noexcept override; - void SetBidiR2L(bool bidiR2L_) noexcept override; -}; - -SurfaceGDI::~SurfaceGDI() noexcept { - Clear(); -} - -void SurfaceGDI::Clear() noexcept { - if (penOld) { - ::SelectObject(hdc, penOld); - ::DeleteObject(pen); - penOld = {}; - } - pen = {}; - if (brushOld) { - ::SelectObject(hdc, brushOld); - ::DeleteObject(brush); - brushOld = {}; - } - brush = {}; - if (fontOld) { - // Fonts are not deleted as they are owned by a Font object - ::SelectObject(hdc, fontOld); - fontOld = {}; - } - if (bitmapOld) { - ::SelectObject(hdc, bitmapOld); - ::DeleteObject(bitmap); - bitmapOld = {}; - } - bitmap = {}; - if (hdcOwned) { - ::DeleteDC(hdc); - hdc = {}; - hdcOwned = false; - } -} - -void SurfaceGDI::Release() noexcept { - Clear(); -} - -bool SurfaceGDI::Initialised() const noexcept { - return hdc != nullptr; -} - -void SurfaceGDI::Init(WindowID wid) noexcept { - Release(); - logPixelsY = DpiYForWindow(wid); - hdc = ::CreateCompatibleDC({}); - hdcOwned = true; - ::SetTextAlign(hdc, TA_BASELINE); -} - -void SurfaceGDI::Init(SurfaceID sid, WindowID wid, bool printing /*=false*/) noexcept { - Release(); - hdc = static_cast(sid); - // Windows on screen are scaled but printers are not. - //const bool printing = (::GetDeviceCaps(hdc, TECHNOLOGY) != DT_RASDISPLAY); - logPixelsY = printing ? ::GetDeviceCaps(hdc, LOGPIXELSY) : DpiYForWindow(wid); - ::SetTextAlign(hdc, TA_BASELINE); -} - -void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID wid) noexcept { - Release(); - logPixelsY = DpiYForWindow(wid); - SurfaceGDI *psurfOther = down_cast(surface_); - // Should only ever be called with a SurfaceGDI, not a SurfaceD2D - PLATFORM_ASSERT(psurfOther); - hdc = ::CreateCompatibleDC(psurfOther->hdc); - hdcOwned = true; - bitmap = ::CreateCompatibleBitmap(psurfOther->hdc, width, height); - bitmapOld = SelectBitmap(hdc, bitmap); - ::SetTextAlign(hdc, TA_BASELINE); - SetUnicodeMode(psurfOther->unicodeMode); - SetDBCSMode(psurfOther->codePage); -} - -void SurfaceGDI::PenColour(ColourDesired fore) noexcept { - if (pen) { - ::SelectObject(hdc, penOld); - ::DeleteObject(pen); - pen = {}; - penOld = {}; - } - pen = ::CreatePen(PS_SOLID, 1, fore.AsInteger()); - penOld = SelectPen(hdc, pen); -} - -void SurfaceGDI::BrushColour(ColourDesired back) noexcept { - if (brush) { - ::SelectObject(hdc, brushOld); - ::DeleteObject(brush); - brush = {}; - brushOld = {}; - } - // Only ever want pure, non-dithered brushes - //const ColourDesired colourNearest = ColourDesired(::GetNearestColor(hdc, back.AsInteger())); - //brush = ::CreateSolidBrush(colourNearest.AsInteger()); - brush = ::CreateSolidBrush(back.AsInteger()); - brushOld = SelectBrush(hdc, brush); -} - -void SurfaceGDI::SetFont(const Font &font_) noexcept { - const FormatAndMetrics *pfm = FamFromFontID(font_.GetID()); - PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI); - if (fontOld) { - SelectFont(hdc, pfm->hfont); - } else { - fontOld = SelectFont(hdc, pfm->hfont); - } -} - -int SurfaceGDI::LogPixelsY() const noexcept { - return logPixelsY; -} - -int SurfaceGDI::DeviceHeightFont(int points) const noexcept { - return ::MulDiv(points, logPixelsY, 72); -} - -void SurfaceGDI::MoveTo(int x_, int y_) noexcept { - ::MoveToEx(hdc, x_, y_, nullptr); -} - -void SurfaceGDI::LineTo(int x_, int y_) noexcept { - ::LineTo(hdc, x_, y_); -} - -void SurfaceGDI::Polygon(const Point *pts, size_t npts, ColourDesired fore, ColourDesired back) { - PenColour(fore); - BrushColour(back); - std::vector outline; - std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint); - ::Polygon(hdc, outline.data(), static_cast(npts)); -} - -void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) noexcept { - PenColour(fore); - BrushColour(back); - const RECT rcw = RectFromPRectangle(rc); - ::Rectangle(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); -} - -void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) noexcept { - // Using ExtTextOut rather than a FillRect ensures that no dithering occurs. - // There is no need to allocate a brush either. - const RECT rcw = RectFromPRectangle(rc); - ::SetBkColor(hdc, back.AsInteger()); - ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, L"", 0, nullptr); -} - -void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) noexcept { - HBRUSH br; - if (SurfaceGDI *psgdi = down_cast(&surfacePattern); psgdi && psgdi->bitmap) { - br = ::CreatePatternBrush(psgdi->bitmap); - } else { // Something is wrong so display in red - br = ::CreateSolidBrush(RGB(0xff, 0, 0)); - } - const RECT rcw = RectFromPRectangle(rc); - ::FillRect(hdc, &rcw, br); - ::DeleteObject(br); -} - -void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) noexcept { - PenColour(fore); - BrushColour(back); - const RECT rcw = RectFromPRectangle(rc); - ::RoundRect(hdc, - rcw.left + 1, rcw.top, - rcw.right - 1, rcw.bottom, - 8, 8); -} - -namespace { - -constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept { - return (a << 24) | (r << 16) | (g << 8) | b; -} - -constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept { - return static_cast(component * alpha / 255); -} - -constexpr DWORD dwordMultiplied(ColourDesired colour, unsigned int alpha) noexcept { - return dwordFromBGRA( - AlphaScaled(colour.GetBlue(), alpha), - AlphaScaled(colour.GetGreen(), alpha), - AlphaScaled(colour.GetRed(), alpha), - static_cast(alpha)); -} - -class DIBSection { - HDC hMemDC{}; - HBITMAP hbmMem{}; - HBITMAP hbmOld{}; - SIZE size{}; - DWORD *pixels = nullptr; -public: - DIBSection(HDC hdc, SIZE size_) noexcept; - // Deleted so DIBSection objects can not be copied. - DIBSection(const DIBSection&) = delete; - DIBSection(DIBSection&&) = delete; - DIBSection &operator=(const DIBSection&) = delete; - DIBSection &operator=(DIBSection&&) = delete; - ~DIBSection() noexcept; - operator bool() const noexcept { - return hMemDC && hbmMem && pixels; - } - DWORD *Pixels() const noexcept { - return pixels; - } - unsigned char *Bytes() const noexcept { - return reinterpret_cast(pixels); - } - HDC DC() const noexcept { - return hMemDC; - } - void SetPixel(LONG x, LONG y, DWORD value) noexcept { - PLATFORM_ASSERT(x >= 0); - PLATFORM_ASSERT(y >= 0); - PLATFORM_ASSERT(x < size.cx); - PLATFORM_ASSERT(y < size.cy); - pixels[y * size.cx + x] = value; - } - void SetSymmetric(LONG x, LONG y, DWORD value) noexcept; -}; - -DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept { - hMemDC = ::CreateCompatibleDC(hdc); - if (!hMemDC) { - return; - } - - size = size_; - - // -size.y makes bitmap start from top - const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), size.cx, -size.cy, 1, 32, BI_RGB, 0, 0, 0, 0, 0}, - {{0, 0, 0, 0}} }; - void *image = nullptr; - hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, &image, {}, 0); - if (!hbmMem || !image) { - return; - } - pixels = static_cast(image); - hbmOld = SelectBitmap(hMemDC, hbmMem); -} - -DIBSection::~DIBSection() noexcept { - if (hbmOld) { - SelectBitmap(hMemDC, hbmOld); - hbmOld = {}; - } - if (hbmMem) { - ::DeleteObject(hbmMem); - hbmMem = {}; - } - if (hMemDC) { - ::DeleteDC(hMemDC); - hMemDC = {}; - } -} - -void DIBSection::SetSymmetric(LONG x, LONG y, DWORD value) noexcept { - // Plot a point symmetrically to all 4 quadrants - const LONG xSymmetric = size.cx - 1 - x; - const LONG ySymmetric = size.cy - 1 - y; - SetPixel(x, y, value); - SetPixel(xSymmetric, y, value); - SetPixel(x, ySymmetric, value); - SetPixel(xSymmetric, ySymmetric, value); -} - -constexpr unsigned int Proportional(unsigned char a, unsigned char b, float t) noexcept { - return static_cast(a + t * (b - a)); -} - -ColourAlpha Proportional(ColourAlpha a, ColourAlpha b, float t) noexcept { - return ColourAlpha( - Proportional(a.GetRed(), b.GetRed(), t), - Proportional(a.GetGreen(), b.GetGreen(), t), - Proportional(a.GetBlue(), b.GetBlue(), t), - Proportional(a.GetAlpha(), b.GetAlpha(), t)); -} - -ColourAlpha GradientValue(const std::vector &stops, float proportion) noexcept { - for (size_t stop = 0; stop < stops.size() - 1; stop++) { - // Loop through each pair of stops - const float positionStart = stops[stop].position; - const float positionEnd = stops[stop + 1].position; - if ((proportion >= positionStart) && (proportion <= positionEnd)) { - const float proportionInPair = (proportion - positionStart) / - (positionEnd - positionStart); - return Proportional(stops[stop].colour, stops[stop + 1].colour, proportionInPair); - } - } - // Loop should always find a value - return ColourAlpha(); -} - -constexpr SIZE SizeOfRect(RECT rc) noexcept { - return { rc.right - rc.left, rc.bottom - rc.top }; -} - -constexpr BLENDFUNCTION mergeAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; - -} - -void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, - ColourDesired outline, int alphaOutline, int /* flags*/) noexcept { - const RECT rcw = RectFromPRectangle(rc); - const SIZE size = SizeOfRect(rcw); - - if (size.cx > 0) { - DIBSection section(hdc, size); - if (section) { - // Ensure not distorted too much by corners when small - const LONG corner = std::min(cornerSize, (std::min(size.cx, size.cy) / 2) - 2); - - constexpr DWORD valEmpty = dwordFromBGRA(0, 0, 0, 0); - const DWORD valFill = dwordMultiplied(fill, alphaFill); - const DWORD valOutline = dwordMultiplied(outline, alphaOutline); - - // Draw a framed rectangle - for (int y = 0; y < size.cy; y++) { - for (int x = 0; x < size.cx; x++) { - if ((x == 0) || (x == size.cx - 1) || (y == 0) || (y == size.cy - 1)) { - section.SetPixel(x, y, valOutline); - } else { - section.SetPixel(x, y, valFill); - } - } - } - - // Make the corners transparent - for (LONG c = 0; c < corner; c++) { - for (LONG x = 0; x < c + 1; x++) { - section.SetSymmetric(x, c - x, valEmpty); - } - } - - // Draw the corner frame pieces - for (LONG x = 1; x < corner; x++) { - section.SetSymmetric(x, corner - x, valOutline); - } - - GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha); - } - } else { - BrushColour(outline); - FrameRect(hdc, &rcw, brush); - } -} - -void SurfaceGDI::GradientRectangle(PRectangle rc, const std::vector &stops, GradientOptions options) { - const RECT rcw = RectFromPRectangle(rc); - const SIZE size = SizeOfRect(rcw); - - DIBSection section(hdc, size); - if (section) { - if (options == GradientOptions::topToBottom) { - for (LONG y = 0; y < size.cy; y++) { - // Find y/height proportional colour - const float proportion = y / (rc.Height() - 1.0f); - const ColourAlpha mixed = GradientValue(stops, proportion); - const DWORD valFill = dwordMultiplied(mixed, mixed.GetAlpha()); - for (LONG x = 0; x < size.cx; x++) { - section.SetPixel(x, y, valFill); - } - } - } else { - for (LONG x = 0; x < size.cx; x++) { - // Find x/width proportional colour - const float proportion = x / (rc.Width() - 1.0f); - const ColourAlpha mixed = GradientValue(stops, proportion); - const DWORD valFill = dwordMultiplied(mixed, mixed.GetAlpha()); - for (LONG y = 0; y < size.cy; y++) { - section.SetPixel(x, y, valFill); - } - } - } - - GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha); - } -} - -void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) noexcept { - if (rc.Width() > 0) { - if (rc.Width() > width) - rc.left += std::floor((rc.Width() - width) / 2); - rc.right = rc.left + width; - if (rc.Height() > height) - rc.top += std::floor((rc.Height() - height) / 2); - rc.bottom = rc.top + height; - - const SIZE size{ width, height }; - DIBSection section(hdc, size); - if (section) { - RGBAImage::BGRAFromRGBA(section.Bytes(), pixelsImage, width * height); - GdiAlphaBlend(hdc, static_cast(rc.left), static_cast(rc.top), - static_cast(rc.Width()), static_cast(rc.Height()), section.DC(), - 0, 0, width, height, mergeAlpha); - } - } -} - -void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) noexcept { - PenColour(fore); - BrushColour(back); - const RECT rcw = RectFromPRectangle(rc); - ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); -} - -void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) noexcept { - ::BitBlt(hdc, - static_cast(rc.left), static_cast(rc.top), - static_cast(rc.Width()), static_cast(rc.Height()), - dynamic_cast(surfaceSource).hdc, - static_cast(from.x), static_cast(from.y), SRCCOPY); -} - -std::unique_ptr SurfaceGDI::Layout(const IScreenLine *) noexcept { - return {}; -} - -using TextPositionsI = VarBuffer; - -void SurfaceGDI::DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { - SetFont(font_); - const RECT rcw = RectFromPRectangle(rc); - const int x = static_cast(rc.left); - const int yBaseInt = static_cast(ybase); - - if (unicodeMode) { - const TextWide tbuf(text, unicodeMode, codePage); - ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr); - } else { - ::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, text.data(), static_cast(text.length()), nullptr); - } -} - -void SurfaceGDI::DrawTextNoClip(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, - ColourDesired fore, ColourDesired back) { - ::SetTextColor(hdc, fore.AsInteger()); - ::SetBkColor(hdc, back.AsInteger()); - DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE); -} - -void SurfaceGDI::DrawTextClipped(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, - ColourDesired fore, ColourDesired back) { - ::SetTextColor(hdc, fore.AsInteger()); - ::SetBkColor(hdc, back.AsInteger()); - DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED); -} - -void SurfaceGDI::DrawTextTransparent(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, - ColourDesired fore) { - // Avoid drawing spaces in transparent mode - for (const char ch : text) { - if (ch != ' ') { - ::SetTextColor(hdc, fore.AsInteger()); - ::SetBkMode(hdc, TRANSPARENT); - DrawTextCommon(rc, font_, ybase, text, 0); - ::SetBkMode(hdc, OPAQUE); - return; - } - } -} - -XYPOSITION SurfaceGDI::WidthText(const Font &font_, std::string_view text) { - SetFont(font_); - SIZE sz = { 0, 0 }; - if (!unicodeMode) { - ::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast(text.length()), maxLenText), &sz); - } else { - const TextWide tbuf(text, unicodeMode, codePage); - ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz); - } - return static_cast(sz.cx); -} - -void SurfaceGDI::MeasureWidths(const Font &font_, std::string_view text, XYPOSITION *positions) { - // Zero positions to avoid random behaviour on failure. - std::fill(positions, positions + text.length(), 0.0f); - SetFont(font_); - SIZE sz = { 0, 0 }; - int fit = 0; - int i = 0; - const int len = static_cast(text.length()); - if (unicodeMode) { - const TextWide tbuf(text, unicodeMode, codePage); - TextPositionsI poses(tbuf.tlen); - if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) { - // Failure - return; - } - // Map the widths given for UTF-16 characters back onto the UTF-8 input string - for (int ui = 0; ui < fit; ui++) { - const unsigned char uch = text[i]; - const unsigned int byteCount = UTF8BytesOfLead[uch]; - if (byteCount == 4) { // Non-BMP - ui++; - } - for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) { - positions[i++] = static_cast(poses.buffer[ui]); - } - } - } else { - TextPositionsI poses(len); - if (!::GetTextExtentExPointA(hdc, text.data(), len, maxWidthMeasure, &fit, poses.buffer, &sz)) { - // Eeek - a NULL DC or other foolishness could cause this. - return; - } - while (i < fit) { - positions[i] = static_cast(poses.buffer[i]); - i++; - } - } - // If any positions not filled in then use the last position for them - const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f; - std::fill(positions + i, positions + text.length(), lastPos); -} - -XYPOSITION SurfaceGDI::Ascent(const Font &font_) noexcept { - SetFont(font_); - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - return static_cast(tm.tmAscent); -} - -XYPOSITION SurfaceGDI::Descent(const Font &font_) noexcept { - SetFont(font_); - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - return static_cast(tm.tmDescent); -} - -XYPOSITION SurfaceGDI::InternalLeading(const Font &font_) noexcept { - SetFont(font_); - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - return static_cast(tm.tmInternalLeading); -} - -XYPOSITION SurfaceGDI::Height(const Font &font_) noexcept { - SetFont(font_); - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - return static_cast(tm.tmHeight); -} - -XYPOSITION SurfaceGDI::AverageCharWidth(const Font &font_) noexcept { - SetFont(font_); - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - return static_cast(tm.tmAveCharWidth); -} - -void SurfaceGDI::SetClip(PRectangle rc) noexcept { - ::IntersectClipRect(hdc, static_cast(rc.left), static_cast(rc.top), - static_cast(rc.right), static_cast(rc.bottom)); -} - -void SurfaceGDI::FlushCachedState() noexcept { - pen = {}; - brush = {}; -} - -void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) noexcept { - unicodeMode = unicodeMode_; -} - -void SurfaceGDI::SetDBCSMode(int codePage_) noexcept { - // No action on window as automatically handled by system. - codePage = codePage_; -} - -void SurfaceGDI::SetBidiR2L(bool) noexcept { -} - -#if defined(USE_D2D) - -namespace { - -constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept { - return { rc.left, rc.top, rc.right, rc.bottom }; -} - -} - -class BlobInline; - -class SurfaceD2D final : public Surface { - bool unicodeMode = false; - int x = 0; - int y = 0; - - int codePage = 0; - int codePageText =0; - - ID2D1RenderTarget *pRenderTarget = nullptr; - ID2D1BitmapRenderTarget *pBitmapRenderTarget = nullptr; - bool ownRenderTarget = false; - int clipsActive = 0; - - // From selected font - IDWriteTextFormat *pTextFormat = nullptr; - FLOAT yAscent = 2; - FLOAT yDescent = 1; - FLOAT yInternalLeading = 0; - - ID2D1SolidColorBrush *pBrush = nullptr; - - int logPixelsY = USER_DEFAULT_SCREEN_DPI; - - void Clear() noexcept; - void SetFont(const Font &font_) noexcept; - HRESULT GetBitmap(ID2D1Bitmap **ppBitmap); - -public: - SurfaceD2D() noexcept = default; - // Deleted so SurfaceD2D objects can not be copied. - SurfaceD2D(const SurfaceD2D &) = delete; - SurfaceD2D(SurfaceD2D &&) = delete; - SurfaceD2D &operator=(const SurfaceD2D &) = delete; - SurfaceD2D &operator=(SurfaceD2D &&) = delete; - ~SurfaceD2D() noexcept override; - - void Init(WindowID wid) noexcept override; - void Init(SurfaceID sid, WindowID wid, bool printing = false) noexcept override; - void InitPixMap(int width, int height, Surface *surface_, WindowID wid) noexcept override; - - void Release() noexcept override; - bool Initialised() const noexcept override; - - HRESULT FlushDrawing() const noexcept; - - void PenColour(ColourDesired fore) override; - void D2DPenColour(ColourDesired fore, int alpha = 255); - int LogPixelsY() const noexcept override; - int DeviceHeightFont(int points) const noexcept override; - void SCICALL MoveTo(int x_, int y_) noexcept override; - void SCICALL LineTo(int x_, int y_) noexcept override; - void SCICALL Polygon(const Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; - void SCICALL RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; - void SCICALL FillRectangle(PRectangle rc, ColourDesired back) override; - void SCICALL FillRectangle(PRectangle rc, Surface &surfacePattern) override; - void SCICALL RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; - void SCICALL AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, - ColourDesired outline, int alphaOutline, int flags) override; - void SCICALL GradientRectangle(PRectangle rc, const std::vector &stops, GradientOptions options) override; - void SCICALL DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; - void SCICALL Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; - void SCICALL Copy(PRectangle rc, Point from, Surface &surfaceSource) override; - - std::unique_ptr Layout(const IScreenLine *screenLine) override; - - void SCICALL DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); - void SCICALL DrawTextNoClip(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; - void SCICALL DrawTextClipped(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; - void SCICALL DrawTextTransparent(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; - void SCICALL MeasureWidths(const Font &font_, std::string_view text, XYPOSITION *positions) override; - XYPOSITION WidthText(const Font &font_, std::string_view text) override; - XYPOSITION Ascent(const Font &font_) noexcept override; - XYPOSITION Descent(const Font &font_) noexcept override; - XYPOSITION InternalLeading(const Font &font_) noexcept override; - XYPOSITION Height(const Font &font_) noexcept override; - XYPOSITION AverageCharWidth(const Font &font_) override; - - void SCICALL SetClip(PRectangle rc) noexcept override; - void FlushCachedState() noexcept override; - - void SetUnicodeMode(bool unicodeMode_) noexcept override; - void SetDBCSMode(int codePage_) noexcept override; - void SetBidiR2L(bool bidiR2L_) noexcept override; -}; - -SurfaceD2D::~SurfaceD2D() noexcept { - Clear(); -} - -void SurfaceD2D::Clear() noexcept { - ReleaseUnknown(pBrush); - if (pRenderTarget) { - while (clipsActive) { - pRenderTarget->PopAxisAlignedClip(); - clipsActive--; - } - if (ownRenderTarget) { - pRenderTarget->EndDraw(); - ReleaseUnknown(pRenderTarget); - ownRenderTarget = false; - } - pRenderTarget = nullptr; - } - pBitmapRenderTarget = nullptr; -} - -void SurfaceD2D::Release() noexcept { - Clear(); -} - -bool SurfaceD2D::Initialised() const noexcept { - return pRenderTarget != nullptr; -} - -HRESULT SurfaceD2D::FlushDrawing() const noexcept { - return pRenderTarget->Flush(); -} - -void SurfaceD2D::Init(WindowID wid) noexcept { - Release(); - logPixelsY = DpiYForWindow(wid); -} - -void SurfaceD2D::Init(SurfaceID sid, WindowID wid, bool printing /*=false*/) noexcept { - Release(); - // printing always using GDI technology - logPixelsY = DpiYForWindow(wid); - pRenderTarget = static_cast(sid); -} - -void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID wid) noexcept { - Release(); - logPixelsY = DpiYForWindow(wid); - SurfaceD2D *psurfOther = down_cast(surface_); - // Should only ever be called with a SurfaceD2D, not a SurfaceGDI - PLATFORM_ASSERT(psurfOther); - const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast(width), static_cast(height)); - D2D1_PIXEL_FORMAT desiredFormat; -#ifdef __MINGW32__ - desiredFormat.format = DXGI_FORMAT_UNKNOWN; -#else - desiredFormat = psurfOther->pRenderTarget->GetPixelFormat(); -#endif - desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; - const HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget( - &desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pBitmapRenderTarget); - if (SUCCEEDED(hr)) { - pRenderTarget = pBitmapRenderTarget; - pRenderTarget->BeginDraw(); - ownRenderTarget = true; - } - SetUnicodeMode(psurfOther->unicodeMode); - SetDBCSMode(psurfOther->codePage); -} - -HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) { - PLATFORM_ASSERT(pBitmapRenderTarget); - return pBitmapRenderTarget->GetBitmap(ppBitmap); -} - -void SurfaceD2D::PenColour(ColourDesired fore) { - D2DPenColour(fore); -} - -void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) { - if (pRenderTarget) { - D2D_COLOR_F col; - col.r = fore.GetRedComponent(); - col.g = fore.GetGreenComponent(); - col.b = fore.GetBlueComponent(); - col.a = alpha / 255.0f; - if (pBrush) { - pBrush->SetColor(col); - } else { - const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush); - if (!SUCCEEDED(hr)) { - ReleaseUnknown(pBrush); - } - } - } -} - -void SurfaceD2D::SetFont(const Font &font_) noexcept { - const FormatAndMetrics *pfm = FamFromFontID(font_.GetID()); - PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE); - pTextFormat = pfm->pTextFormat; - yAscent = pfm->yAscent; - yDescent = pfm->yDescent; - yInternalLeading = pfm->yInternalLeading; - codePageText = codePage; - if (!unicodeMode && pfm->characterSet) { - codePageText = Scintilla::CodePageFromCharSet(pfm->characterSet, codePage); - } - if (pRenderTarget) { - D2D1_TEXT_ANTIALIAS_MODE aaMode; - aaMode = DWriteMapFontQuality(pfm->extraFontFlag); - - if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && customClearTypeRenderingParams) - pRenderTarget->SetTextRenderingParams(customClearTypeRenderingParams); - else if (defaultRenderingParams) - pRenderTarget->SetTextRenderingParams(defaultRenderingParams); - - pRenderTarget->SetTextAntialiasMode(aaMode); - } -} - -int SurfaceD2D::LogPixelsY() const noexcept { - return logPixelsY; -} - -int SurfaceD2D::DeviceHeightFont(int points) const noexcept { - return ::MulDiv(points, logPixelsY, 72); -} - -void SurfaceD2D::MoveTo(int x_, int y_) noexcept { - x = x_; - y = y_; -} - -static constexpr int Delta(int difference) noexcept { - if (difference < 0) - return -1; - else if (difference > 0) - return 1; - else - return 0; -} - -void SurfaceD2D::LineTo(int x_, int y_) noexcept { - if (pRenderTarget) { - const int xDiff = x_ - x; - const int xDelta = Delta(xDiff); - const int yDiff = y_ - y; - const int yDelta = Delta(yDiff); - if ((xDiff == 0) || (yDiff == 0)) { - // Horizontal or vertical lines can be more precisely drawn as a filled rectangle - const int xEnd = x_ - xDelta; - const int left = std::min(x, xEnd); - const int width = std::abs(x - xEnd) + 1; - const int yEnd = y_ - yDelta; - const int top = std::min(y, yEnd); - const int height = std::abs(y - yEnd) + 1; - const D2D1_RECT_F rectangle1 = D2D1::RectF(static_cast(left), static_cast(top), - static_cast(left + width), static_cast(top + height)); - pRenderTarget->FillRectangle(&rectangle1, pBrush); - } else if ((std::abs(xDiff) == std::abs(yDiff))) { - // 45 degree slope - pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f), - D2D1::Point2F(x_ + 0.5f - xDelta, y_ + 0.5f - yDelta), pBrush); - } else { - // Line has a different slope so difficult to avoid last pixel - pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f), - D2D1::Point2F(x_ + 0.5f, y_ + 0.5f), pBrush); - } - x = x_; - y = y_; - } -} - -void SurfaceD2D::Polygon(const Point *pts, size_t npts, ColourDesired fore, ColourDesired back) { - PLATFORM_ASSERT(pRenderTarget && (npts > 2)); - if (pRenderTarget) { - ID2D1PathGeometry *geometry = nullptr; - HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry); - PLATFORM_ASSERT(geometry); - if (SUCCEEDED(hr) && geometry) { - ID2D1GeometrySink *sink = nullptr; - hr = geometry->Open(&sink); - if (SUCCEEDED(hr) && sink) { - sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED); - for (size_t i = 1; i < npts; i++) { - sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f)); - } - sink->EndFigure(D2D1_FIGURE_END_CLOSED); - sink->Close(); - ReleaseUnknown(sink); - - D2DPenColour(back); - pRenderTarget->FillGeometry(geometry, pBrush); - D2DPenColour(fore); - pRenderTarget->DrawGeometry(geometry, pBrush); - } - } - ReleaseUnknown(geometry); - } -} - -void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { - if (pRenderTarget) { - const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left) + 0.5f, rc.top + 0.5f, std::round(rc.right) - 0.5f, rc.bottom - 0.5f); - D2DPenColour(back); - pRenderTarget->FillRectangle(&rectangle1, pBrush); - D2DPenColour(fore); - pRenderTarget->DrawRectangle(&rectangle1, pBrush); - } -} - -void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) { - if (pRenderTarget) { - D2DPenColour(back); - const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left), rc.top, std::round(rc.right), rc.bottom); - pRenderTarget->FillRectangle(&rectangle1, pBrush); - } -} - -void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) { - SurfaceD2D *psurfOther = down_cast(&surfacePattern); - PLATFORM_ASSERT(psurfOther); - psurfOther->FlushDrawing(); - ID2D1Bitmap *pBitmap = nullptr; - HRESULT hr = psurfOther->GetBitmap(&pBitmap); - if (SUCCEEDED(hr) && pBitmap) { - ID2D1BitmapBrush *pBitmapBrush = nullptr; - const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties = - D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, - D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); - // Create the bitmap brush. - hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush); - if (SUCCEEDED(hr) && pBitmapBrush) { - pRenderTarget->FillRectangle( - D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), - pBitmapBrush); - } - ReleaseUnknown(pBitmapBrush); - } - ReleaseUnknown(pBitmap); -} - -void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) { - if (pRenderTarget) { - D2D1_ROUNDED_RECT roundedRectFill = { - D2D1::RectF(rc.left + 1.0f, rc.top + 1.0f, rc.right - 1.0f, rc.bottom - 1.0f), - 4, 4 }; - D2DPenColour(back); - pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush); - - D2D1_ROUNDED_RECT roundedRect = { - D2D1::RectF(rc.left + 0.5f, rc.top + 0.5f, rc.right - 0.5f, rc.bottom - 0.5f), - 4, 4 }; - D2DPenColour(fore); - pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush); - } -} - -void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, - ColourDesired outline, int alphaOutline, int /* flags*/) { - if (pRenderTarget) { - float const left = std::round(rc.left); - float const right = std::round(rc.right); - if (cornerSize == 0) { - // When corner size is zero, draw square rectangle to prevent blurry pixels at corners - const D2D1_RECT_F rectFill = D2D1::RectF(left + 1.0f, rc.top + 1.0f, right - 1.0f, rc.bottom - 1.0f); - D2DPenColour(fill, alphaFill); - pRenderTarget->FillRectangle(rectFill, pBrush); - - const D2D1_RECT_F rectOutline = D2D1::RectF(left + 0.5f, rc.top + 0.5f, right - 0.5f, rc.bottom - 0.5f); - D2DPenColour(outline, alphaOutline); - pRenderTarget->DrawRectangle(rectOutline, pBrush); - } else { - const float cornerSizeF = static_cast(cornerSize); - D2D1_ROUNDED_RECT roundedRectFill = { - D2D1::RectF(left + 1.0f, rc.top + 1.0f, right - 1.0f, rc.bottom - 1.0f), - cornerSizeF - 1.0f, cornerSizeF - 1.0f }; - D2DPenColour(fill, alphaFill); - pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush); - - D2D1_ROUNDED_RECT roundedRect = { - D2D1::RectF(left + 0.5f, rc.top + 0.5f, right - 0.5f, rc.bottom - 0.5f), - cornerSizeF, cornerSizeF }; - D2DPenColour(outline, alphaOutline); - pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush); - } - } -} - -namespace { - -constexpr D2D_COLOR_F ColorFromColourAlpha(ColourAlpha colour) noexcept { - D2D_COLOR_F col = { - colour.GetRedComponent(), - colour.GetGreenComponent(), - colour.GetBlueComponent(), - colour.GetAlphaComponent() - }; - return col; -} - -} - -void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector &stops, GradientOptions options) { - if (pRenderTarget) { - D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp; - lgbp.startPoint = D2D1::Point2F(rc.left, rc.top); - switch (options) { - case GradientOptions::leftToRight: - lgbp.endPoint = D2D1::Point2F(rc.right, rc.top); - break; - case GradientOptions::topToBottom: - default: - lgbp.endPoint = D2D1::Point2F(rc.left, rc.bottom); - break; - } - - std::vector gradientStops; - gradientStops.reserve(stops.size()); - for (const ColourStop &stop : stops) { - gradientStops.push_back({ stop.position, ColorFromColourAlpha(stop.colour) }); - } - ID2D1GradientStopCollection *pGradientStops = nullptr; - HRESULT hr = pRenderTarget->CreateGradientStopCollection( - gradientStops.data(), static_cast(gradientStops.size()), &pGradientStops); - if (FAILED(hr) || !pGradientStops) { - return; - } - ID2D1LinearGradientBrush *pBrushLinear = nullptr; - hr = pRenderTarget->CreateLinearGradientBrush( - lgbp, pGradientStops, &pBrushLinear); - if (SUCCEEDED(hr) && pBrushLinear) { - const D2D1_RECT_F rectangle = D2D1::RectF(std::round(rc.left), rc.top, std::round(rc.right), rc.bottom); - pRenderTarget->FillRectangle(&rectangle, pBrushLinear); - ReleaseUnknown(pBrushLinear); - } - ReleaseUnknown(pGradientStops); - } -} - -void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) { - if (pRenderTarget) { - if (rc.Width() > width) - rc.left += std::floor((rc.Width() - width) / 2); - rc.right = rc.left + width; - if (rc.Height() > height) - rc.top += std::floor((rc.Height() - height) / 2); - rc.bottom = rc.top + height; - - std::vector image(RGBAImage::bytesPerPixel * height * width); - RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast(height) * width); - - ID2D1Bitmap *bitmap = nullptr; - const D2D1_SIZE_U size = D2D1::SizeU(width, height); - D2D1_BITMAP_PROPERTIES props = { {DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0 }; - const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(), - width * 4, &props, &bitmap); - if (SUCCEEDED(hr)) { - const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); - pRenderTarget->DrawBitmap(bitmap, rcDestination); - } - ReleaseUnknown(bitmap); - } -} - -void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) { - if (pRenderTarget) { - const FLOAT radius = rc.Width() / 2.0f; - D2D1_ELLIPSE ellipse = { - D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f), - radius, radius }; - - PenColour(back); - pRenderTarget->FillEllipse(ellipse, pBrush); - PenColour(fore); - pRenderTarget->DrawEllipse(ellipse, pBrush); - } -} - -void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { - SurfaceD2D &surfOther = dynamic_cast(surfaceSource); - surfOther.FlushDrawing(); - ID2D1Bitmap *pBitmap = nullptr; - HRESULT hr = surfOther.GetBitmap(&pBitmap); - if (SUCCEEDED(hr) && pBitmap) { - const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); - D2D1_RECT_F rcSource = { from.x, from.y, from.x + rc.Width(), from.y + rc.Height() }; - pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f, - D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource); - hr = pRenderTarget->Flush(); - if (FAILED(hr)) { - //Platform::DebugPrintf("Failed Flush 0x%lx\n", hr); - } - } - ReleaseUnknown(pBitmap); -} - -class BlobInline : public IDWriteInlineObject { - XYPOSITION width; - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) noexcept override; - STDMETHODIMP_(ULONG)AddRef() noexcept override; - STDMETHODIMP_(ULONG)Release() noexcept override; - - // IDWriteInlineObject - COM_DECLSPEC_NOTHROW STDMETHODIMP Draw( - void *clientDrawingContext, - IDWriteTextRenderer *renderer, - FLOAT originX, - FLOAT originY, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown *clientDrawingEffect - ) override; - COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override; - COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override; - COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions( - DWRITE_BREAK_CONDITION *breakConditionBefore, - DWRITE_BREAK_CONDITION *breakConditionAfter) override; -public: - explicit BlobInline(XYPOSITION width_ = 0.0f) noexcept : width(width_) {} - virtual ~BlobInline() = default; -}; - -/// Implement IUnknown -STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) noexcept { - if (!ppv) { - return E_POINTER; - } - // Never called so not checked. - *ppv = nullptr; - if (riid == IID_IUnknown) - *ppv = this; - if (riid == __uuidof(IDWriteInlineObject)) - *ppv = this; - if (!*ppv) - return E_NOINTERFACE; - return S_OK; -} - -STDMETHODIMP_(ULONG) BlobInline::AddRef() noexcept { - // Lifetime tied to Platform methods so ignore any reference operations. - return 1; -} - -STDMETHODIMP_(ULONG) BlobInline::Release() noexcept { - // Lifetime tied to Platform methods so ignore any reference operations. - return 1; -} - -/// Implement IDWriteInlineObject -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw( - void*, - IDWriteTextRenderer*, - FLOAT, - FLOAT, - BOOL, - BOOL, - IUnknown*) { - // Since not performing drawing, not necessary to implement - // Could be implemented by calling back into platform-independent code. - // This would allow more of the drawing to be mediated through DirectWrite. - return S_OK; -} - -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics( - DWRITE_INLINE_OBJECT_METRICS *metrics -) { - if (!metrics) { - return E_POINTER; - } - metrics->width = width; - metrics->height = 2; - metrics->baseline = 1; - metrics->supportsSideways = FALSE; - return S_OK; -} - -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics( - DWRITE_OVERHANG_METRICS *overhangs -) { - if (!overhangs) { - return E_POINTER; - } - overhangs->left = 0; - overhangs->top = 0; - overhangs->right = 0; - overhangs->bottom = 0; - return S_OK; -} - -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions( - DWRITE_BREAK_CONDITION *breakConditionBefore, - DWRITE_BREAK_CONDITION *breakConditionAfter -) { - if (!breakConditionBefore || !breakConditionAfter) { - return E_POINTER; - } - // Since not performing 2D layout, not necessary to implement - *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; - *breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; - return S_OK; -} - -class ScreenLineLayout : public IScreenLineLayout { - IDWriteTextLayout *textLayout = nullptr; - std::string text; - std::wstring buffer; - std::vector blobs; - static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector &blobs); - static std::wstring ReplaceRepresentation(std::string_view text); - static size_t GetPositionInLayout(std::string_view text, size_t position) noexcept; -public: - explicit ScreenLineLayout(const IScreenLine *screenLine); - // Deleted so ScreenLineLayout objects can not be copied - ScreenLineLayout(const ScreenLineLayout &) = delete; - ScreenLineLayout(ScreenLineLayout &&) = delete; - ScreenLineLayout &operator=(const ScreenLineLayout &) = delete; - ScreenLineLayout &operator=(ScreenLineLayout &&) = delete; - ~ScreenLineLayout() override; - size_t PositionFromX(XYPOSITION xDistance, bool charPosition) noexcept override; - XYPOSITION XFromPosition(size_t caretPosition) noexcept override; - std::vector FindRangeIntervals(size_t start, size_t end) override; -}; - -// Each char can have its own style, so we fill the textLayout with the textFormat of each char - -void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector &blobs) { - // Reserve enough entries up front so they are not moved and the pointers handed - // to textLayout remain valid. - const ptrdiff_t numRepresentations = screenLine->RepresentationCount(); - std::string_view text = screenLine->Text(); - const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t'); - blobs.reserve(numRepresentations + numTabs); - - UINT32 layoutPosition = 0; - - for (size_t bytePosition = 0; bytePosition < screenLine->Length();) { - const unsigned char uch = screenLine->Text()[bytePosition]; - const unsigned int byteCount = UTF8BytesOfLead[uch]; - const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount); - const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits }; - - XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition); - if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) { - Point realPt; - DWRITE_HIT_TEST_METRICS realCaretMetrics {}; - textLayout->HitTestTextPosition( - layoutPosition, - false, // trailing if false, else leading edge - &realPt.x, - &realPt.y, - &realCaretMetrics - ); - - const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x); - representationWidth = nextTab - realPt.x; - } - if (representationWidth > 0.0f) { - blobs.emplace_back(representationWidth); - textLayout->SetInlineObject(&blobs.back(), textRange); - }; - - FormatAndMetrics *pfm = - static_cast(screenLine->FontOfPosition(bytePosition)->GetID()); - - const UINT32 fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength(); - std::wstring fontFamilyName(fontFamilyNameSize + 1, L'\0'); - - const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1); - if (SUCCEEDED(hrFamily)) { - textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange); - } - - textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange); - textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange); - textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange); - - const UINT32 localeNameSize = pfm->pTextFormat->GetLocaleNameLength(); - std::wstring localeName(localeNameSize + 1, L'\0'); - - const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize); - if (SUCCEEDED(hrLocale)) { - textLayout->SetLocaleName(localeName.c_str(), textRange); - } - - textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange); - - IDWriteFontCollection *fontCollection = nullptr; - if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) { - textLayout->SetFontCollection(fontCollection, textRange); - } - - bytePosition += byteCount; - layoutPosition += codeUnits; - } - -} - -/* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */ - -std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) { - const TextWide wideText(text, true); - std::wstring ws(wideText.buffer, wideText.tlen); - std::replace(ws.begin(), ws.end(), L'\t', L'X'); - return ws; -} - -// Finds the position in the wide character version of the text. - -size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) noexcept { - const std::string_view textUptoPosition = text.substr(0, position); - return UTF16Length(textUptoPosition); -} - -ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) { - // If the text is empty, then no need to go through this function - if (!screenLine || !screenLine->Length()) { - return; - } - - text = screenLine->Text(); - - // Get textFormat - FormatAndMetrics *pfm = static_cast(screenLine->FontOfPosition(0)->GetID()); - - if (!pIDWriteFactory || !pfm->pTextFormat) { - return; - } - - // Convert the string to wstring and replace the original control characters with their representative chars. - buffer = ReplaceRepresentation(screenLine->Text()); - - // Create a text layout - const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(buffer.c_str(), static_cast(buffer.length()), - pfm->pTextFormat, screenLine->Width(), screenLine->Height(), &textLayout); - if (!SUCCEEDED(hrCreate)) { - return; - } - - // Fill the textLayout chars with their own formats - FillTextLayoutFormats(screenLine, textLayout, blobs); -} - -ScreenLineLayout::~ScreenLineLayout() { - ReleaseUnknown(textLayout); -} - -// Get the position from the provided x - -size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) noexcept { - if (!textLayout) { - return 0; - } - - // Returns the text position corresponding to the mouse (x, y). - // If hitting the trailing side of a cluster, return the - // leading edge of the following text position. - - BOOL isTrailingHit = FALSE; - BOOL isInside = FALSE; - DWRITE_HIT_TEST_METRICS caretMetrics {}; - - textLayout->HitTestPoint( - xDistance, - 0.0f, - &isTrailingHit, - &isInside, - &caretMetrics - ); - - DWRITE_HIT_TEST_METRICS hitTestMetrics {}; - if (isTrailingHit) { - FLOAT caretX = 0.0f; - FLOAT caretY = 0.0f; - - // Uses hit-testing to align the current caret position to a whole cluster, - // rather than residing in the middle of a base character + diacritic, - // surrogate pair, or character + UVS. - - // Align the caret to the nearest whole cluster. - textLayout->HitTestTextPosition( - caretMetrics.textPosition, - false, - &caretX, - &caretY, - &hitTestMetrics - ); - } - - size_t pos; - if (charPosition) { - pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition; - } else { - pos = isTrailingHit ? hitTestMetrics.textPosition + hitTestMetrics.length : caretMetrics.textPosition; - } - - // Get the character position in original string - return UTF8PositionFromUTF16Position(text, pos); -} - -// Finds the point of the caret position - -XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) noexcept { - if (!textLayout) { - return 0.0; - } - // Convert byte positions to wchar_t positions - const size_t position = GetPositionInLayout(text, caretPosition); - - // Translate text character offset to point (x, y). - DWRITE_HIT_TEST_METRICS caretMetrics {}; - Point pt {}; - - textLayout->HitTestTextPosition( - static_cast(position), - false, // trailing if false, else leading edge - &pt.x, - &pt.y, - &caretMetrics - ); - - return pt.x; -} - -// Find the selection range rectangles - -std::vector ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) { - std::vector ret; - - if (!textLayout || (start == end)) { - return ret; - } - - // Convert byte positions to wchar_t positions - const size_t startPos = GetPositionInLayout(text, start); - const size_t endPos = GetPositionInLayout(text, end); - - // Find selection range length - const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos); - - // Determine actual number of hit-test ranges - UINT32 actualHitTestCount = 0; - - // First try with 2 elements and if more needed, allocate. - std::vector hitTestMetrics(2); - textLayout->HitTestTextRange( - static_cast(startPos), - static_cast(rangeLength), - 0, // x - 0, // y - hitTestMetrics.data(), - static_cast(hitTestMetrics.size()), - &actualHitTestCount - ); - - if (actualHitTestCount == 0) { - return ret; - } - - if (hitTestMetrics.size() < actualHitTestCount) { - // Allocate enough room to return all hit-test metrics. - hitTestMetrics.resize(actualHitTestCount); - textLayout->HitTestTextRange( - static_cast(startPos), - static_cast(rangeLength), - 0, // x - 0, // y - hitTestMetrics.data(), - static_cast(hitTestMetrics.size()), - &actualHitTestCount - ); - } - - // Get the selection ranges behind the text. - ret.reserve(actualHitTestCount); - for (size_t i = 0; i < actualHitTestCount; ++i) { - // Store selection rectangle - const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i]; - Interval selectionInterval; - - selectionInterval.left = htm.left; - selectionInterval.right = htm.left + htm.width; - - ret.push_back(selectionInterval); - } - - return ret; -} - -std::unique_ptr SurfaceD2D::Layout(const IScreenLine *screenLine) { - return std::make_unique(screenLine); -} - -void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { - SetFont(font_); - - // Use Unicode calls - const TextWide tbuf(text, unicodeMode, codePageText); - if (pRenderTarget && pTextFormat && pBrush) { - if (fuOptions & ETO_CLIPPED) { - const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); - pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); - } - - // Explicitly creating a text layout appears a little faster - IDWriteTextLayout *pTextLayout = nullptr; - const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, - rc.Width(), rc.Height(), &pTextLayout); - if (SUCCEEDED(hr)) { - D2D1_POINT_2F origin = { rc.left, ybase - yAscent }; - pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, d2dDrawTextOptions); - ReleaseUnknown(pTextLayout); - } - - if (fuOptions & ETO_CLIPPED) { - pRenderTarget->PopAxisAlignedClip(); - } - } -} - -void SurfaceD2D::DrawTextNoClip(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, - ColourDesired fore, ColourDesired back) { - if (pRenderTarget) { - FillRectangle(rc, back); - D2DPenColour(fore); - DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE); - } -} - -void SurfaceD2D::DrawTextClipped(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, - ColourDesired fore, ColourDesired back) { - if (pRenderTarget) { - FillRectangle(rc, back); - D2DPenColour(fore); - DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED); - } -} - -void SurfaceD2D::DrawTextTransparent(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, - ColourDesired fore) { - // Avoid drawing spaces in transparent mode - for (const char ch : text) { - if (ch != ' ') { - if (pRenderTarget) { - D2DPenColour(fore); - DrawTextCommon(rc, font_, ybase, text, 0); - } - return; - } - } -} - -XYPOSITION SurfaceD2D::WidthText(const Font &font_, std::string_view text) { - FLOAT width = 1.0; - SetFont(font_); - const TextWide tbuf(text, unicodeMode, codePageText); - if (pIDWriteFactory && pTextFormat) { - // Create a layout - IDWriteTextLayout *pTextLayout = nullptr; - const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout); - if (SUCCEEDED(hr) && pTextLayout) { - DWRITE_TEXT_METRICS textMetrics; - if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) - width = textMetrics.widthIncludingTrailingWhitespace; - ReleaseUnknown(pTextLayout); - } - } - return width; -} - -void SurfaceD2D::MeasureWidths(const Font &font_, std::string_view text, XYPOSITION *positions) { - SetFont(font_); - if (!pIDWriteFactory || !pTextFormat) { - // SetFont failed or no access to DirectWrite so give up. - return; - } - const TextWide tbuf(text, unicodeMode, codePageText); - TextPositions poses(tbuf.tlen); - // Initialize poses for safety. - std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f); - // Create a layout - IDWriteTextLayout *pTextLayout = nullptr; - const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout); - if (!SUCCEEDED(hrCreate) || !pTextLayout) { - return; - } - constexpr int clusters = stackBufferLength; - DWRITE_CLUSTER_METRICS clusterMetrics[clusters]; - UINT32 count = 0; - const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count); - ReleaseUnknown(pTextLayout); - if (!SUCCEEDED(hrGetCluster)) { - return; - } - // A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font - FLOAT position = 0.0f; - int ti = 0; - for (unsigned int ci = 0; ci < count; ci++) { - const FLOAT width = clusterMetrics[ci].width; - const UINT16 length = clusterMetrics[ci].length; - for (UINT16 inCluster = 0; inCluster < length; inCluster++) { - poses.buffer[ti++] = position + width * (inCluster + 1) / length; - } - position += width; - } - PLATFORM_ASSERT(ti == tbuf.tlen); - if (unicodeMode) { - // Map the widths given for UTF-16 characters back onto the UTF-8 input string - int ui = 0; - size_t i = 0; - while (ui < tbuf.tlen) { - const unsigned char uch = text[i]; - const unsigned int byteCount = UTF8BytesOfLead[uch]; - if (byteCount == 4) { // Non-BMP - ui++; - } - for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < text.length()) && (ui < tbuf.tlen); bytePos++) { - positions[i++] = poses.buffer[ui]; - } - ui++; - } - XYPOSITION lastPos = 0.0f; - if (i > 0) - lastPos = positions[i - 1]; - while (i < text.length()) { - positions[i++] = lastPos; - } - } else { -#if 0 - const DBCSCharClassify *dbcs = DBCSCharClassify::Get(codePageText); - if (dbcs) { - // May be one or two bytes per position - int ui = 0; - for (size_t i = 0; i < text.length() && ui < tbuf.tlen;) { - positions[i] = poses.buffer[ui]; - if (dbcs->IsLeadByte(text[i])) { - positions[i + 1] = poses.buffer[ui]; - i += 2; - } else { - i++; - } - - ui++; - } - } else -#endif - { - // One char per position - PLATFORM_ASSERT(text.length() == static_cast(tbuf.tlen)); - for (int kk = 0; kk < tbuf.tlen; kk++) { - positions[kk] = poses.buffer[kk]; - } - } - } -} - -XYPOSITION SurfaceD2D::Ascent(const Font &font_) noexcept { - SetFont(font_); - return std::ceil(yAscent); -} - -XYPOSITION SurfaceD2D::Descent(const Font &font_) noexcept { - SetFont(font_); - return std::ceil(yDescent); -} - -XYPOSITION SurfaceD2D::InternalLeading(const Font &font_) noexcept { - SetFont(font_); - return std::floor(yInternalLeading); -} - -XYPOSITION SurfaceD2D::Height(const Font &font_) noexcept { - return Ascent(font_) + Descent(font_); -} - -XYPOSITION SurfaceD2D::AverageCharWidth(const Font &font_) { - FLOAT width = 1.0; - SetFont(font_); - if (pIDWriteFactory && pTextFormat) { - // Create a layout - IDWriteTextLayout *pTextLayout = nullptr; - const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - constexpr size_t lenAllAlpha = (sizeof(wszAllAlpha) / sizeof(WCHAR)) - 1; - const HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast(lenAllAlpha), - pTextFormat, 1000.0, 1000.0, &pTextLayout); - if (SUCCEEDED(hr) && pTextLayout) { - DWRITE_TEXT_METRICS textMetrics; - if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) - width = textMetrics.width / lenAllAlpha; - ReleaseUnknown(pTextLayout); - } - } - return width; -} - -void SurfaceD2D::SetClip(PRectangle rc) noexcept { - if (pRenderTarget) { - const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); - pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); - clipsActive++; - } -} - -void SurfaceD2D::FlushCachedState() noexcept { -} - -void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) noexcept { - unicodeMode = unicodeMode_; -} - -void SurfaceD2D::SetDBCSMode(int codePage_) noexcept { - // No action on window as automatically handled by system. - codePage = codePage_; -} - -void SurfaceD2D::SetBidiR2L(bool) noexcept { -} - -#endif - -Surface *Surface::Allocate(int technology) { -#if defined(USE_D2D) - if (technology == SCWIN_TECH_GDI) - return new SurfaceGDI; - else - return new SurfaceD2D; -#else - return new SurfaceGDI; -#endif -} - -Window::~Window() = default; - -void Window::Destroy() noexcept { - if (wid) - ::DestroyWindow(HwndFromWindowID(wid)); - wid = nullptr; -} - -PRectangle Window::GetPosition() const noexcept { - RECT rc; - ::GetWindowRect(HwndFromWindowID(wid), &rc); - return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom); -} - -void Window::SetPosition(PRectangle rc) noexcept { - ::SetWindowPos(HwndFromWindowID(wid), - nullptr, static_cast(rc.left), static_cast(rc.top), - static_cast(rc.Width()), static_cast(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE); -} - -namespace { - -RECT RectFromMonitor(HMONITOR hMonitor) noexcept { - MONITORINFO mi = {}; - mi.cbSize = sizeof(mi); - if (GetMonitorInfo(hMonitor, &mi)) { - return mi.rcWork; - } - RECT rc = { 0, 0, 0, 0 }; - if (::SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0) == 0) { - rc.left = 0; - rc.top = 0; - rc.right = 0; - rc.bottom = 0; - } - return rc; -} - -} - -void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) noexcept { - const DWORD style = GetWindowStyle(HwndFromWindowID(wid)); - if (style & WS_POPUP) { - POINT ptOther = { 0, 0 }; - ::ClientToScreen(HwndFromWindow(*relativeTo), &ptOther); - rc.Move(static_cast(ptOther.x), static_cast(ptOther.y)); - - const RECT rcMonitor = RectFromPRectangle(rc); - - HMONITOR hMonitor = MonitorFromRect(&rcMonitor, MONITOR_DEFAULTTONEAREST); - // If hMonitor is NULL, that's just the main screen anyways. - const RECT rcWork = RectFromMonitor(hMonitor); - - if (rcWork.left < rcWork.right) { - // Now clamp our desired rectangle to fit inside the work area - // This way, the menu will fit wholly on one screen. An improvement even - // if you don't have a second monitor on the left... Menu's appears half on - // one screen and half on the other are just U.G.L.Y.! - if (rc.right > rcWork.right) - rc.Move(rcWork.right - rc.right, 0); - if (rc.bottom > rcWork.bottom) - rc.Move(0, rcWork.bottom - rc.bottom); - if (rc.left < rcWork.left) - rc.Move(rcWork.left - rc.left, 0); - if (rc.top < rcWork.top) - rc.Move(0, rcWork.top - rc.top); - } - } - SetPosition(rc); -} - -PRectangle Window::GetClientPosition() const noexcept { - RECT rc = { 0, 0, 0, 0 }; - if (wid) - ::GetClientRect(HwndFromWindowID(wid), &rc); - return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom); -} - -void Window::Show(bool show) const noexcept { - if (show) - ::ShowWindow(HwndFromWindowID(wid), SW_SHOWNOACTIVATE); - else - ::ShowWindow(HwndFromWindowID(wid), SW_HIDE); -} - -void Window::InvalidateAll() noexcept { - ::InvalidateRect(HwndFromWindowID(wid), nullptr, FALSE); -} - -void Window::InvalidateRectangle(PRectangle rc) noexcept { - const RECT rcw = RectFromPRectangle(rc); - ::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE); -} - -void Window::SetFont(const Font &font) noexcept { - SetWindowFont(HwndFromWindowID(wid), font.GetID(), FALSE); -} - -namespace { - -void FlipBitmap(HBITMAP bitmap, int width, int height) noexcept { - HDC hdc = ::CreateCompatibleDC({}); - if (hdc) { - HBITMAP prevBmp = SelectBitmap(hdc, bitmap); - ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY); - SelectBitmap(hdc, prevBmp); - ::DeleteDC(hdc); - } -} - -} - -HCURSOR LoadReverseArrowCursor(DPI_T dpi) noexcept { - HCURSOR reverseArrowCursor{}; - - bool created = false; - HCURSOR cursor = ::LoadCursor({}, IDC_ARROW); - - if (dpi.y != g_uSystemDPI) { - const int width = SystemMetricsForDpi(SM_CXCURSOR, dpi.x); - const int height = SystemMetricsForDpi(SM_CYCURSOR, dpi.y); - HCURSOR copy = static_cast(::CopyImage(cursor, IMAGE_CURSOR, width, height, LR_COPYFROMRESOURCE | LR_COPYRETURNORG)); - if (copy) { - created = copy != cursor; - cursor = copy; - } - } - - ICONINFO info; - if (::GetIconInfo(cursor, &info)) { - BITMAP bmp; - if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) { - FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight); - if (info.hbmColor) - FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight); - info.xHotspot = bmp.bmWidth - 1 - info.xHotspot; - - reverseArrowCursor = ::CreateIconIndirect(&info); - } - - ::DeleteObject(info.hbmMask); - if (info.hbmColor) - ::DeleteObject(info.hbmColor); - } - - if (created) { - ::DestroyCursor(cursor); - } - return reverseArrowCursor; -} - -void Window::SetCursor(Cursor curs) noexcept { - switch (curs) { - case Cursor::cursorText: - ::SetCursor(::LoadCursor({}, IDC_IBEAM)); - break; - case Cursor::cursorUp: - ::SetCursor(::LoadCursor({}, IDC_UPARROW)); - break; - case Cursor::cursorWait: - ::SetCursor(::LoadCursor({}, IDC_WAIT)); - break; - case Cursor::cursorHoriz: - ::SetCursor(::LoadCursor({}, IDC_SIZEWE)); - break; - case Cursor::cursorVert: - ::SetCursor(::LoadCursor({}, IDC_SIZENS)); - break; - case Cursor::cursorHand: - ::SetCursor(::LoadCursor({}, IDC_HAND)); - break; - case Cursor::cursorReverseArrow: - case Cursor::cursorArrow: - case Cursor::cursorInvalid: // Should not occur, but just in case. - ::SetCursor(::LoadCursor({}, IDC_ARROW)); - break; - } -} - -/* Returns rectangle of monitor pt is on, both rect and pt are in Window's - coordinates */ -PRectangle Window::GetMonitorRect(Point pt) const noexcept { - const PRectangle rcPosition = GetPosition(); - POINT ptDesktop = { static_cast(pt.x + rcPosition.left), - static_cast(pt.y + rcPosition.top) }; - HMONITOR hMonitor = MonitorFromPoint(ptDesktop, MONITOR_DEFAULTTONEAREST); - - const RECT rcWork = RectFromMonitor(hMonitor); - if (rcWork.left < rcWork.right) { - const PRectangle rcMonitor( - rcWork.left - rcPosition.left, - rcWork.top - rcPosition.top, - rcWork.right - rcPosition.left, - rcWork.bottom - rcPosition.top); - return rcMonitor; - } else { - return PRectangle(); - } -} - -struct ListItemData { - const char *text; - int pixId; -}; - -class LineToItem { - std::vector words; - - std::vector data; - -public: - void Clear() noexcept { - words.clear(); - data.clear(); - } - - ListItemData Get(size_t index) const noexcept { - if (index < data.size()) { - return data[index]; - } else { - ListItemData missing = { "", -1 }; - return missing; - } - } - int Count() const noexcept { - return static_cast(data.size()); - } - - void AllocItem(const char *text, int pixId) { - ListItemData lid = { text, pixId }; - data.push_back(lid); - } - - char *SetWords(const char *s, size_t length) { - words = std::vector(s, s + length + 1); - return words.data(); - } -}; - -static const TCHAR *ListBoxX_ClassName = L"ListBoxX"; -#define LISTBOXX_USE_THICKFRAME 0 -#define LISTBOXX_USE_BORDER 1 -#define LISTBOXX_USE_FAKE_FRAME 0 - -ListBox::ListBox() noexcept = default; - -ListBox::~ListBox() = default; - -class ListBoxX final : public ListBox { - int lineHeight; - FontID fontCopy; - int technology; - RGBAImageSet images; - LineToItem lti; - HWND lb; - bool unicodeMode; - int desiredVisibleRows; - unsigned int maxItemCharacters; - unsigned int aveCharWidth; - COLORREF colorText; - COLORREF colorBackground; - HBRUSH hbrBackground; - Window *parent; - int ctrlID; - DPI_T dpi; - IListBoxDelegate *delegate; - const char *widestItem; - unsigned int maxCharWidth; - WPARAM resizeHit; - PRectangle rcPreSize; - Point dragOffset; - Point location; // Caret location at which the list is opened - int wheelDelta; // mouse wheel residue - - HWND GetHWND() const noexcept; - void AppendListItem(const char *text, const char *numword); - static void AdjustWindowRect(PRectangle *rc, UINT dpi) noexcept; - int ItemHeight() const; - int MinClientWidth() const noexcept; - int TextOffset() const; - POINT GetClientExtent() const noexcept; - POINT MinTrackSize() const; - POINT MaxTrackSize() const; - void SetRedraw(bool on) noexcept; - void OnDoubleClick(); - void OnSelChange(); - void ResizeToCursor(); - void StartResize(WPARAM) noexcept; - LRESULT NcHitTest(WPARAM, LPARAM) const noexcept; - void CentreItem(int n); - void Paint(HDC) noexcept; - static LRESULT CALLBACK ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); - - static constexpr Point ItemInset {0, 0}; // Padding around whole item - static constexpr Point TextInset {2, 0}; // Padding around text - static constexpr Point ImageInset {1, 0}; // Padding around image - -public: - ListBoxX() noexcept : lineHeight(10), fontCopy{}, technology(0), lb{}, unicodeMode(false), - desiredVisibleRows(9), maxItemCharacters(0), aveCharWidth(8), - colorText(0), colorBackground(0), hbrBackground{}, - parent(nullptr), ctrlID(0), dpi({ USER_DEFAULT_SCREEN_DPI, USER_DEFAULT_SCREEN_DPI }), - delegate(nullptr), - widestItem(nullptr), maxCharWidth(1), resizeHit(0), wheelDelta(0) {} - ~ListBoxX() override { - if (fontCopy) { - ::DeleteObject(fontCopy); - fontCopy = nullptr; - } - if (hbrBackground) { - ::DeleteObject(hbrBackground); - hbrBackground = nullptr; - } - } - void SetFont(const Font &font) noexcept override; - void SetColour(ColourDesired fore, ColourDesired back) noexcept override; - void SCICALL Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) noexcept override; - void SetAverageCharWidth(int width) noexcept override; - void SetVisibleRows(int rows) noexcept override; - int GetVisibleRows() const noexcept override; - PRectangle GetDesiredRect() override; - int CaretFromEdge() const override; - void Clear() noexcept override; - void Append(const char *s, int type = -1) const noexcept override; - int Length() const noexcept override; - void Select(int n) override; - int GetSelection() const noexcept override; - int Find(const char *prefix) const noexcept override; - void GetValue(int n, char *value, int len) const noexcept override; - void RegisterImage(int type, const char *xpm_data) override; - void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override; - void ClearRegisteredImages() noexcept override; - void SetDelegate(IListBoxDelegate *lbDelegate) noexcept override; - void SetList(const char *list, char separator, char typesep) override; - void Draw(const DRAWITEMSTRUCT *pDrawItem); - LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); - static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); -}; - -#if LISTBOXX_USE_FAKE_FRAME -constexpr int ListBoxXFakeFrameSize = 4; -#endif - -ListBox *ListBox::Allocate() { - ListBoxX *lb = new ListBoxX(); - return lb; -} - -void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) noexcept { - parent = &parent_; - ctrlID = ctrlID_; - location = location_; - lineHeight = lineHeight_; - unicodeMode = unicodeMode_; - technology = technology_; - HWND hwndParent = HwndFromWindow(*parent); - HINSTANCE hinstanceParent = GetWindowInstance(hwndParent); - // Window created as popup so not clipped within parent client area - wid = ::CreateWindowEx( - WS_EX_WINDOWEDGE, ListBoxX_ClassName, L"", -#if LISTBOXX_USE_THICKFRAME - WS_POPUP | WS_THICKFRAME, -#elif LISTBOXX_USE_BORDER - WS_POPUP | WS_BORDER, -#else - WS_POPUP, -#endif - 100, 100, 150, 80, hwndParent, - {}, - hinstanceParent, - this); - - dpi = GetWindowDPI(hwndParent); - POINT locationw = POINTFromPoint(location); - ::MapWindowPoints(hwndParent, {}, &locationw, 1); - location = PointFromPOINT(locationw); -} - -void ListBoxX::SetFont(const Font &font) noexcept { - if (font.GetID()) { - if (fontCopy) { - ::DeleteObject(fontCopy); - fontCopy = {}; - } - const FormatAndMetrics *pfm = static_cast(font.GetID()); - fontCopy = pfm->HFont(); - SetWindowFont(lb, fontCopy, FALSE); - } -} - -void ListBoxX::SetColour(ColourDesired fore, ColourDesired back) noexcept { - if (hbrBackground) { - ::DeleteObject(hbrBackground); - hbrBackground = {}; - } - colorText = fore.AsInteger(); - colorBackground = back.AsInteger(); - hbrBackground = ::CreateSolidBrush(colorBackground); -} - -void ListBoxX::SetAverageCharWidth(int width) noexcept { - aveCharWidth = width; -} - -void ListBoxX::SetVisibleRows(int rows) noexcept { - desiredVisibleRows = rows; -} - -int ListBoxX::GetVisibleRows() const noexcept { - return desiredVisibleRows; -} - -HWND ListBoxX::GetHWND() const noexcept { - return HwndFromWindowID(GetID()); -} - -PRectangle ListBoxX::GetDesiredRect() { - PRectangle rcDesired = GetPosition(); - - int rows = Length(); - if ((rows == 0) || (rows > desiredVisibleRows)) - rows = desiredVisibleRows; - rcDesired.bottom = rcDesired.top + ItemHeight() * rows; - - int width = MinClientWidth(); - HDC hdc = ::GetDC(lb); - HFONT oldFont = SelectFont(hdc, fontCopy); - SIZE textSize = { 0, 0 }; - int len = 0; - if (widestItem) { - len = static_cast(strlen(widestItem)); - if (unicodeMode) { - const TextWide tbuf(widestItem, unicodeMode); - ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize); - } else { - ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize); - } - } - - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - maxCharWidth = tm.tmMaxCharWidth; - SelectFont(hdc, oldFont); - ::ReleaseDC(lb, hdc); - - const int widthDesired = std::max(textSize.cx, (len + 1) * tm.tmAveCharWidth); - if (width < widthDesired) - width = widthDesired; - - rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2); - if (Length() > rows) { - rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi.x); - } - - AdjustWindowRect(&rcDesired, dpi.y); - return rcDesired; -} - -int ListBoxX::TextOffset() const { - const int pixWidth = images.GetWidth(); - return static_cast(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2)); -} - -int ListBoxX::CaretFromEdge() const { - PRectangle rc; - AdjustWindowRect(&rc, dpi.y); - return TextOffset() + static_cast(TextInset.x + (0 - rc.left) - 1); -} - -void ListBoxX::Clear() noexcept { - ListBox_ResetContent(lb); - maxItemCharacters = 0; - widestItem = nullptr; - lti.Clear(); -} - -void ListBoxX::Append(const char *, int) const noexcept { - // This method is no longer called in Scintilla - PLATFORM_ASSERT(false); -} - -int ListBoxX::Length() const noexcept { - return lti.Count(); -} - -void ListBoxX::Select(int n) { - // We are going to scroll to centre on the new selection and then select it, so disable - // redraw to avoid flicker caused by a painting new selection twice in unselected and then - // selected states - SetRedraw(false); - CentreItem(n); - ListBox_SetCurSel(lb, n); - OnSelChange(); - SetRedraw(true); -} - -int ListBoxX::GetSelection() const noexcept { - return ListBox_GetCurSel(lb); -} - -// This is not actually called at present -int ListBoxX::Find(const char *) const noexcept { - return LB_ERR; -} - -void ListBoxX::GetValue(int n, char *value, int len) const noexcept { - const ListItemData item = lti.Get(n); - strncpy_s(value, len, item.text, len); - value[len - 1] = '\0'; -} - -void ListBoxX::RegisterImage(int type, const char *xpm_data) { - XPM xpmImage(xpm_data); - images.Add(type, new RGBAImage(xpmImage)); -} - -void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) { - images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage)); -} - -void ListBoxX::ClearRegisteredImages() noexcept { - images.Clear(); -} - -void ListBoxX::Draw(const DRAWITEMSTRUCT *pDrawItem) { - if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) { - RECT rcBox = pDrawItem->rcItem; - rcBox.left += TextOffset(); - if (pDrawItem->itemState & ODS_SELECTED) { - RECT rcImage = pDrawItem->rcItem; - rcImage.right = rcBox.left; - // The image is not highlighted - ::FillRect(pDrawItem->hDC, &rcImage, hbrBackground); - ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast(COLOR_HIGHLIGHT + 1)); - ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT)); - ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT)); - } else { - ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, hbrBackground); - ::SetBkColor(pDrawItem->hDC, colorBackground); - ::SetTextColor(pDrawItem->hDC, colorText); - } - - const ListItemData item = lti.Get(pDrawItem->itemID); - const int pixId = item.pixId; - const char *text = item.text; - const int len = static_cast(strlen(text)); - - RECT rcText = rcBox; - ::InsetRect(&rcText, static_cast(TextInset.x), static_cast(TextInset.y)); - - if (unicodeMode) { - const TextWide tbuf(text, unicodeMode); - ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOCLIP); - } else { - ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOCLIP); - } - - // Draw the image, if any - const RGBAImage *pimage = images.Get(pixId); - if (pimage) { - std::unique_ptr surfaceItem(Surface::Allocate(technology)); - if (technology == SCWIN_TECH_GDI) { - surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem); - const long left = pDrawItem->rcItem.left + static_cast(ItemInset.x + ImageInset.x); - const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top, - left + images.GetWidth(), pDrawItem->rcItem.bottom); - surfaceItem->DrawRGBAImage(rcImage, - pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); - ::SetTextAlign(pDrawItem->hDC, TA_TOP); - } else { -#if defined(USE_D2D) - const D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat( - DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_IGNORE), - 0, - 0, - D2D1_RENDER_TARGET_USAGE_NONE, - D2D1_FEATURE_LEVEL_DEFAULT - ); - ID2D1DCRenderTarget *pDCRT = nullptr; - HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT); - if (SUCCEEDED(hr) && pDCRT) { - RECT rcWindow; - GetClientRect(pDrawItem->hwndItem, &rcWindow); - hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow); - if (SUCCEEDED(hr)) { - surfaceItem->Init(pDCRT, pDrawItem->hwndItem); - pDCRT->BeginDraw(); - const long left = pDrawItem->rcItem.left + static_cast(ItemInset.x + ImageInset.x); - const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top, - left + images.GetWidth(), pDrawItem->rcItem.bottom); - surfaceItem->DrawRGBAImage(rcImage, - pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); - pDCRT->EndDraw(); - } - } - ReleaseUnknown(pDCRT); -#endif - } - } - } -} - -void ListBoxX::AppendListItem(const char *text, const char *numword) { - int pixId = -1; - if (numword) { - pixId = 0; - char ch; - while ((ch = *++numword) != '\0') { - pixId = 10 * pixId + (ch - '0'); - } - } - - lti.AllocItem(text, pixId); - const unsigned int len = static_cast(strlen(text)); - if (maxItemCharacters < len) { - maxItemCharacters = len; - widestItem = text; - } -} - -void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) noexcept { - delegate = lbDelegate; -} - -void ListBoxX::SetList(const char *list, const char separator, const char typesep) { - // Turn off redraw while populating the list - this has a significant effect, even if - // the listbox is not visible. - SetRedraw(false); - Clear(); - const size_t size = strlen(list); - char *words = lti.SetWords(list, size); - char *startword = words; - char *numword = nullptr; - for (size_t i = 0; i < size; i++) { - if (words[i] == separator) { - words[i] = '\0'; - if (numword) - *numword = '\0'; - AppendListItem(startword, numword); - startword = words + i + 1; - numword = nullptr; - } else if (words[i] == typesep) { - numword = words + i; - } - } - if (startword) { - if (numword) - *numword = '\0'; - AppendListItem(startword, numword); - } - - // Finally populate the listbox itself with the correct number of items - const int count = lti.Count(); - ::SendMessage(lb, LB_INITSTORAGE, count, 0); - for (intptr_t j = 0; j < count; j++) { - ListBox_AddItemData(lb, j + 1); - } - SetRedraw(true); -} - -void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpi) noexcept { - RECT rcw = RectFromPRectangle(*rc); -#if LISTBOXX_USE_THICKFRAME - AdjustWindowRectForDpi(&rcw, WS_THICKFRAME, WS_EX_WINDOWEDGE, dpi); -#elif LISTBOXX_USE_BORDER - AdjustWindowRectForDpi(&rcw, WS_BORDER, WS_EX_WINDOWEDGE, dpi); -#else - AdjustWindowRectForDpi(&rcw, 0, WS_EX_WINDOWEDGE, dpi); -#endif - *rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom); -#if LISTBOXX_USE_FAKE_FRAME - *rc = rc->Inflate(ListBoxXFakeFrameSize, ListBoxXFakeFrameSize); -#endif -} - -int ListBoxX::ItemHeight() const { - int itemHeight = lineHeight + (static_cast(TextInset.y) * 2); - const int pixHeight = images.GetHeight() + (static_cast(ImageInset.y) * 2); - if (itemHeight < pixHeight) { - itemHeight = pixHeight; - } - return itemHeight; -} - -int ListBoxX::MinClientWidth() const noexcept { - return 12 * (aveCharWidth + aveCharWidth / 3); -} - -POINT ListBoxX::MinTrackSize() const { - PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight()); - AdjustWindowRect(&rc, dpi.y); - POINT ret = { static_cast(rc.Width()), static_cast(rc.Height()) }; - return ret; -} - -POINT ListBoxX::MaxTrackSize() const { - const int width = maxCharWidth * maxItemCharacters + static_cast(TextInset.x) * 2 + - TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi.x); - PRectangle rc = PRectangle::FromInts(0, 0, - std::max(MinClientWidth(), width), - ItemHeight() * lti.Count()); - AdjustWindowRect(&rc, dpi.y); - POINT ret = { static_cast(rc.Width()), static_cast(rc.Height()) }; - return ret; -} - -void ListBoxX::SetRedraw(bool on) noexcept { - ::SendMessage(lb, WM_SETREDRAW, on, 0); - if (on) - ::InvalidateRect(lb, nullptr, TRUE); -} - -void ListBoxX::ResizeToCursor() { - PRectangle rc = GetPosition(); - POINT ptw; - ::GetCursorPos(&ptw); - const Point pt = PointFromPOINT(ptw) + dragOffset; - - switch (resizeHit) { - case HTLEFT: - rc.left = pt.x; - break; - case HTRIGHT: - rc.right = pt.x; - break; - case HTTOP: - rc.top = pt.y; - break; - case HTTOPLEFT: - rc.top = pt.y; - rc.left = pt.x; - break; - case HTTOPRIGHT: - rc.top = pt.y; - rc.right = pt.x; - break; - case HTBOTTOM: - rc.bottom = pt.y; - break; - case HTBOTTOMLEFT: - rc.bottom = pt.y; - rc.left = pt.x; - break; - case HTBOTTOMRIGHT: - rc.bottom = pt.y; - rc.right = pt.x; - break; - } - - const POINT ptMin = MinTrackSize(); - const POINT ptMax = MaxTrackSize(); - // We don't allow the left edge to move at present, but just in case - rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x); - rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y); - rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x); - rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y); - - SetPosition(rc); -} - -void ListBoxX::StartResize(WPARAM hitCode) noexcept { - rcPreSize = GetPosition(); - POINT cursorPos; - ::GetCursorPos(&cursorPos); - - switch (hitCode) { - case HTRIGHT: - case HTBOTTOM: - case HTBOTTOMRIGHT: - dragOffset.x = rcPreSize.right - cursorPos.x; - dragOffset.y = rcPreSize.bottom - cursorPos.y; - break; - - case HTTOPRIGHT: - dragOffset.x = rcPreSize.right - cursorPos.x; - dragOffset.y = rcPreSize.top - cursorPos.y; - break; - - // Note that the current hit test code prevents the left edge cases ever firing - // as we don't want the left edge to be movable - case HTLEFT: - case HTTOP: - case HTTOPLEFT: - dragOffset.x = rcPreSize.left - cursorPos.x; - dragOffset.y = rcPreSize.top - cursorPos.y; - break; - case HTBOTTOMLEFT: - dragOffset.x = rcPreSize.left - cursorPos.x; - dragOffset.y = rcPreSize.bottom - cursorPos.y; - break; - - default: - return; - } - - ::SetCapture(GetHWND()); - resizeHit = hitCode; -} - -LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const noexcept { - const PRectangle rc = GetPosition(); - - LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam); - // There is an apparent bug in the DefWindowProc hit test code whereby it will - // return HTTOPXXX if the window in question is shorter than the default - // window caption height + frame, even if one is hovering over the bottom edge of - // the frame, so workaround that here - if (hit >= HTTOP && hit <= HTTOPRIGHT) { - const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi.y); - const int yPos = GET_Y_LPARAM(lParam); - if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom) / 2))) { - hit += HTBOTTOM - HTTOP; - } - } -#if LISTBOXX_USE_BORDER || LISTBOXX_USE_FAKE_FRAME - else if (hit < HTSIZEFIRST || hit > HTSIZELAST) { - const int cx = SystemMetricsForDpi(SM_CXVSCROLL, dpi.x); -#if LISTBOXX_USE_BORDER - const PRectangle rcInner = rc.Deflate(SystemMetricsForDpi(SM_CXBORDER, dpi.x), SystemMetricsForDpi(SM_CYBORDER, dpi.y)); -#else - const PRectangle rcInner = rc.Deflate(ListBoxXFakeFrameSize, ListBoxXFakeFrameSize); -#endif - const int xPos = GET_X_LPARAM(lParam); - const int yPos = GET_Y_LPARAM(lParam); - /* - 13 | 12 | 14 4 | 3 | 5 - 10 | | 11 => 1 | 0 | 2 - 16 | 15 | 17 7 | 6 | 8 - */ - const int x = (xPos <= rcInner.left) ? 1 : ((xPos >= rcInner.right - cx) ? 2 : 0); - int y = (yPos <= rcInner.top) ? 3 : ((yPos >= rcInner.bottom) ? 6 : 0); - if (y == 0 && x == 2) { - if (location.y < rc.top) { - y = (yPos >= rcInner.bottom - cx) ? 6 : 0; - } else { - y = (yPos <= rcInner.top + cx) ? 3 : 0; - } - } - const int h = x + y; - hit = h ? (9 + h) : HTERROR; - } -#endif - - // Never permit resizing that moves the left edge. Allow movement of top or bottom edge - // depending on whether the list is above or below the caret - switch (hit) { - case HTLEFT: - case HTTOPLEFT: - case HTBOTTOMLEFT: - hit = HTERROR; - break; - - case HTTOP: - case HTTOPRIGHT: { - // Valid only if caret below list - if (location.y < rc.top) - hit = HTERROR; - } - break; - - case HTBOTTOM: - case HTBOTTOMRIGHT: { - // Valid only if caret above list - if (rc.bottom <= location.y) - hit = HTERROR; - } - break; - } - - return hit; -} - -void ListBoxX::OnDoubleClick() { - if (delegate) { - ListBoxEvent event(ListBoxEvent::EventType::doubleClick); - delegate->ListNotify(&event); - } -} - -void ListBoxX::OnSelChange() { - if (delegate) { - ListBoxEvent event(ListBoxEvent::EventType::selectionChange); - delegate->ListNotify(&event); - } -} - -POINT ListBoxX::GetClientExtent() const noexcept { - RECT rc; - ::GetWindowRect(HwndFromWindowID(wid), &rc); - POINT ret{ rc.right - rc.left, rc.bottom - rc.top }; - return ret; -} - -void ListBoxX::CentreItem(int n) { - // If below mid point, scroll up to centre, but with more items below if uneven - if (n >= 0) { - const POINT extent = GetClientExtent(); - const int visible = extent.y / ItemHeight(); - if (visible < Length()) { - const int top = ListBox_GetTopIndex(lb); - const int half = (visible - 1) / 2; - if (n > (top + half)) - ListBox_SetTopIndex(lb, n - half); - } - } -} - -// Performs a double-buffered paint operation to avoid flicker -void ListBoxX::Paint(HDC hDC) noexcept { - const POINT extent = GetClientExtent(); - HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y); - HDC bitmapDC = ::CreateCompatibleDC(hDC); - HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap); - // The list background is mainly erased during painting, but can be a small - // unpainted area when at the end of a non-integrally sized list with a - // vertical scroll bar - const RECT rc = { 0, 0, extent.x, extent.y }; - ::FillRect(bitmapDC, &rc, hbrBackground); - // Paint the entire client area and vertical scrollbar - ::SendMessage(lb, WM_PRINT, reinterpret_cast(bitmapDC), PRF_CLIENT | PRF_NONCLIENT); - ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY); - // Select a stock brush to prevent warnings from BoundsChecker - SelectBrush(bitmapDC, GetStockBrush(WHITE_BRUSH)); - SelectBitmap(bitmapDC, hBitmapOld); - ::DeleteDC(bitmapDC); - ::DeleteObject(hBitmap); -} - -LRESULT CALLBACK ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR /*dwRefData*/) { - try { - ListBoxX *lbx = static_cast(PointerFromWindow(::GetParent(hWnd))); - switch (iMessage) { - case WM_ERASEBKGND: - return TRUE; - - case WM_PAINT: { - PAINTSTRUCT ps; - HDC hDC = ::BeginPaint(hWnd, &ps); - if (lbx) { - lbx->Paint(hDC); - } - ::EndPaint(hWnd, &ps); - } - return 0; - - case WM_MOUSEACTIVATE: - // This prevents the view activating when the scrollbar is clicked - return MA_NOACTIVATE; - - case WM_LBUTTONDOWN: { - // We must take control of selection to prevent the ListBox activating - // the popup - const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam); - const int item = LOWORD(lResult); - if (HIWORD(lResult) == 0 && item >= 0) { - ListBox_SetCurSel(hWnd, item); - if (lbx) { - lbx->OnSelChange(); - } - } - } - return 0; - - case WM_LBUTTONUP: - return 0; - - case WM_LBUTTONDBLCLK: { - if (lbx) { - lbx->OnDoubleClick(); - } - } - return 0; - - case WM_MBUTTONDOWN: - // disable the scroll wheel button click action - return 0; - - case WM_NCDESTROY: - RemoveWindowSubclass(hWnd, ControlWndProc, uIdSubclass); - break; - } - } catch (...) { - } - return ::DefSubclassProc(hWnd, iMessage, wParam, lParam); -} - -LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - switch (iMessage) { - case WM_CREATE: { - HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent)); - // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list - // but has useful side effect of speeding up list population significantly - lb = ::CreateWindowEx( - 0, L"listbox", L"", - WS_CHILD | WS_VSCROLL | WS_VISIBLE | - LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT, - 0, 0, 150, 80, hWnd, - reinterpret_cast(static_cast(ctrlID)), - hinstanceParent, - nullptr); - ::SetWindowSubclass(lb, ControlWndProc, 0, 0); - } - break; - - case WM_SIZE: - if (lb) { - SetRedraw(false); - ::SetWindowPos(lb, nullptr, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); - // Ensure the selection remains visible - CentreItem(GetSelection()); - SetRedraw(true); - } - break; - - case WM_PAINT: { - PAINTSTRUCT ps; - ::BeginPaint(hWnd, &ps); - ::EndPaint(hWnd, &ps); - } - break; - - case WM_COMMAND: - // This is not actually needed now - the registered double click action is used - // directly to action a choice from the list. - ::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam); - break; - - case WM_MEASUREITEM: { - MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast(lParam); - pMeasureItem->itemHeight = ItemHeight(); - } - break; - - case WM_DRAWITEM: - Draw(reinterpret_cast(lParam)); - break; - - case WM_DESTROY: - lb = nullptr; - SetWindowPointer(hWnd, nullptr); - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - - case WM_ERASEBKGND: - // To reduce flicker we can elide background erasure since this window is - // completely covered by its child. - return TRUE; - - case WM_GETMINMAXINFO: { - MINMAXINFO *minMax = reinterpret_cast(lParam); - minMax->ptMaxTrackSize = MaxTrackSize(); - minMax->ptMinTrackSize = MinTrackSize(); - } - break; - - case WM_MOUSEACTIVATE: - return MA_NOACTIVATE; - -#if LISTBOXX_USE_FAKE_FRAME - case WM_NCPAINT: { - HDC hDC = ::GetWindowDC(hWnd); - RECT rect; - ::GetClientRect(hWnd, &rect); - - // outer frame - rect.right += 2*ListBoxXFakeFrameSize; - rect.bottom += 2*ListBoxXFakeFrameSize; - const int width = rect.right - rect.left; - const int height = rect.bottom - rect.top; - - // inner border - RECT client = rect; - ::InflateRect(&client, -ListBoxXFakeFrameSize + 1, -ListBoxXFakeFrameSize + 1); - - HDC hMemDC = CreateCompatibleDC(hDC); - const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}, - {{0, 0, 0, 0}} }; - HBITMAP hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, nullptr, nullptr, 0); - - if (hbmMem) { - HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem); - BLENDFUNCTION merge = { AC_SRC_OVER, 0, 0, AC_SRC_ALPHA }; - - GdiAlphaBlend(hDC, rect.left, rect.top, width, height, hMemDC, 0, 0, width, height, merge); - - SelectBitmap(hMemDC, hbmOld); - ::DeleteObject(hbmMem); - } - ::DeleteDC(hMemDC); - - //HPEN hPen = ::CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_WINDOWFRAME)); - HPEN hPen = ::CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); - HPEN hPenOld = SelectPen(hDC, hPen); - ::Rectangle(hDC, client.left, client.top, client.right, client.bottom); - ::SelectObject(hDC, hPenOld); - ::DeleteObject(hPen); - - ::ReleaseDC(hWnd, hDC); - return 0; - } - - case WM_NCCALCSIZE: { - LPRECT rect = reinterpret_cast(lParam); - ::InflateRect(rect, -ListBoxXFakeFrameSize, -ListBoxXFakeFrameSize); - return 0; - } -#endif - - case WM_NCHITTEST: - return NcHitTest(wParam, lParam); - - case WM_NCLBUTTONDOWN: - // We have to implement our own window resizing because the DefWindowProc - // implementation insists on activating the resized window - StartResize(wParam); - return 0; - - case WM_MOUSEMOVE: { - if (resizeHit == 0) { - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } else { - ResizeToCursor(); - } - } - break; - - case WM_LBUTTONUP: - case WM_CANCELMODE: - if (resizeHit != 0) { - resizeHit = 0; - ::ReleaseCapture(); - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - - case WM_MOUSEWHEEL: - wheelDelta -= GET_WHEEL_DELTA_WPARAM(wParam); - if (std::abs(wheelDelta) >= WHEEL_DELTA) { - const int nRows = GetVisibleRows(); - int linesToScroll = std::clamp(nRows - 1, 1, 3); - linesToScroll *= (wheelDelta / WHEEL_DELTA); - int top = ListBox_GetTopIndex(lb) + linesToScroll; - if (top < 0) { - top = 0; - } - ListBox_SetTopIndex(lb, top); - // update wheel delta residue - if (wheelDelta >= 0) - wheelDelta = wheelDelta % WHEEL_DELTA; - else - wheelDelta = -(-wheelDelta % WHEEL_DELTA); - } - break; - - default: - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } - - return 0; -} - -LRESULT CALLBACK ListBoxX::StaticWndProc( - HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - if (iMessage == WM_CREATE) { - CREATESTRUCT *pCreate = reinterpret_cast(lParam); - SetWindowPointer(hWnd, pCreate->lpCreateParams); - } - // Find C++ object associated with window. - ListBoxX *lbx = static_cast(PointerFromWindow(hWnd)); - if (lbx) { - return lbx->WndProc(hWnd, iMessage, wParam, lParam); - } else { - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } -} - -namespace { - -bool ListBoxX_Register() noexcept { - WNDCLASSEX wndclassc {}; - wndclassc.cbSize = sizeof(wndclassc); - // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for - // truncated items in the list and the appearance/disappearance of the vertical scroll bar. - // The list repaint is double-buffered to avoid the flicker this would otherwise cause. - wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; - wndclassc.cbWndExtra = sizeof(ListBoxX *); - wndclassc.hInstance = hinstPlatformRes; - wndclassc.lpfnWndProc = ListBoxX::StaticWndProc; - wndclassc.hCursor = ::LoadCursor({}, IDC_ARROW); - wndclassc.lpszClassName = ListBoxX_ClassName; - - return ::RegisterClassEx(&wndclassc) != 0; -} - -void ListBoxX_Unregister() noexcept { - if (hinstPlatformRes) { - ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes); - } -} - -} - -Menu::Menu() noexcept : mid{} { -} - -void Menu::CreatePopUp() noexcept { - Destroy(); - mid = ::CreatePopupMenu(); -} - -void Menu::Destroy() noexcept { - if (mid) - ::DestroyMenu(static_cast(mid)); - mid = nullptr; -} - -void Menu::Show(Point pt, const Window &w) noexcept { - ::TrackPopupMenu(static_cast(mid), - TPM_RIGHTBUTTON, static_cast(pt.x - 4), static_cast(pt.y), 0, - HwndFromWindow(w), nullptr); - Destroy(); -} - -class DynamicLibraryImpl : public DynamicLibrary { -protected: - HMODULE h; -public: - explicit DynamicLibraryImpl(const char* modulePath) noexcept { - h = ::LoadLibraryA(modulePath); - } - - ~DynamicLibraryImpl() override { - if (h) - ::FreeLibrary(h); - } - - // Use GetProcAddress to get a pointer to the relevant function. - Function FindFunction(const char* name) noexcept override { - if (h) { - // C++ standard doesn't like casts between function pointers and void pointers so use a union - union { - FARPROC fp; - Function f; - } fnConv; - fnConv.fp = ::GetProcAddress(h, name); - return fnConv.f; - } - else { - return nullptr; - } - } - - bool IsValid() noexcept override { - return h != nullptr; - } -}; - -DynamicLibrary* DynamicLibrary::Load(const char* modulePath) { - return static_cast(new DynamicLibraryImpl(modulePath)); -} - -ColourDesired Platform::Chrome() noexcept { - return ColourDesired(::GetSysColor(COLOR_3DFACE)); -} - -ColourDesired Platform::ChromeHighlight() noexcept { - return ColourDesired(::GetSysColor(COLOR_3DHIGHLIGHT)); -} - -const char *Platform::DefaultFont() noexcept { - return "Verdana"; -} - -int Platform::DefaultFontSize() noexcept { - return 10; -} - -unsigned int Platform::DoubleClickTime() noexcept { - return ::GetDoubleClickTime(); -} - -//#define TRACE - -#ifdef TRACE -void Platform::DebugDisplay(const char *s) noexcept { - ::OutputDebugStringA(s); -} -#else -void Platform::DebugDisplay(const char *) noexcept { -} -#endif - -#ifdef TRACE -void Platform::DebugPrintf(const char *format, ...) noexcept { - char buffer[2000]; - va_list pArguments; - va_start(pArguments, format); - vsprintf(buffer, format, pArguments); - va_end(pArguments); - Platform::DebugDisplay(buffer); -} -#else -void Platform::DebugPrintf(const char *, ...) noexcept { -} -#endif - -#ifdef TRACE -static bool assertionPopUps = true; - -bool Platform::ShowAssertionPopUps(bool assertionPopUps_) noexcept { - const bool ret = assertionPopUps; - assertionPopUps = assertionPopUps_; - return ret; -} -#else -bool Platform::ShowAssertionPopUps(bool) noexcept { - return false; -} -#endif - -#ifdef TRACE -void Platform::Assert(const char *c, const char *file, int line) noexcept { - char buffer[2000]{}; - sprintf(buffer, "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n"); - if (assertionPopUps) { - const int idButton = ::MessageBoxA({}, buffer, "Assertion failure", - MB_ABORTRETRYIGNORE | MB_ICONHAND | MB_SETFOREGROUND | MB_TASKMODAL); - if (idButton == IDRETRY) { - ::DebugBreak(); - } else if (idButton == IDIGNORE) { - // all OK - } else { - abort(); - } - } else { - Platform::DebugDisplay(buffer); - ::DebugBreak(); - abort(); - } -} -#else -void Platform::Assert(const char *, const char *, int) noexcept { -} -#endif - -void Platform_Initialise(void *hInstance) noexcept { - hinstPlatformRes = static_cast(hInstance); - //LoadDpiForWindow() is moved into wWinMain(). - ListBoxX_Register(); -} - -void Platform_Finalise(bool fromDllMain) noexcept { -#if defined(USE_D2D) - if (!fromDllMain) { - ReleaseUnknown(defaultRenderingParams); - ReleaseUnknown(customClearTypeRenderingParams); - ReleaseUnknown(gdiInterop); - ReleaseUnknown(pIDWriteFactory); - ReleaseUnknown(pD2DFactory); - if (hDLLDWrite) { - FreeLibrary(hDLLDWrite); - hDLLDWrite = {}; - } - if (hDLLD2D) { - FreeLibrary(hDLLD2D); - hDLLD2D = {}; - } - } -#endif - if (!fromDllMain && hShcoreDLL) { - FreeLibrary(hShcoreDLL); - hShcoreDLL = {}; - } - ListBoxX_Unregister(); -} - -} // namespace Scintilla diff --git a/scintilla/win32/PlatWin_old.h b/scintilla/win32/PlatWin_old.h deleted file mode 100644 index a5fdb441c..000000000 --- a/scintilla/win32/PlatWin_old.h +++ /dev/null @@ -1,143 +0,0 @@ -// Scintilla source code edit control -/** @file PlatWin.h - ** Implementation of platform facilities on Windows. - **/ -// Copyright 1998-2011 by Neil Hodgson -// The License.txt file describes the conditions under which this software may be distributed. -#pragma once - -// sdkddkver.h -#ifndef _WIN32_WINNT_VISTA -#define _WIN32_WINNT_VISTA 0x0600 -#endif -#ifndef _WIN32_WINNT_WIN7 -#define _WIN32_WINNT_WIN7 0x0601 -#endif -#ifndef _WIN32_WINNT_WIN8 -#define _WIN32_WINNT_WIN8 0x0602 -#endif -#ifndef _WIN32_WINNT_WINBLUE -#define _WIN32_WINNT_WINBLUE 0x0603 -#endif -#ifndef _WIN32_WINNT_WIN10 -#define _WIN32_WINNT_WIN10 0x0A00 -#endif - -#ifndef USER_DEFAULT_SCREEN_DPI -#define USER_DEFAULT_SCREEN_DPI 96 -#endif - -#if !defined(DISABLE_D2D) -#define USE_D2D 1 -#endif - -#if defined(USE_D2D) -#if defined(_MSC_BUILD) && (VER_PRODUCTVERSION_W <= _WIN32_WINNT_WIN7) -#pragma warning(push) -#pragma warning(disable: 4458) -// d2d1helper.h(677,19): warning C4458: declaration of 'a' hides class member -#endif -#include -#include -#if defined(_MSC_BUILD) && (VER_PRODUCTVERSION_W <= _WIN32_WINNT_WIN7) -#pragma warning(pop) -#endif -#endif - -#ifdef __cplusplus -DPI_T GetWindowDPI(HWND hwnd); -int SystemMetricsForDpi(int nIndex, unsigned dpi); -BOOL AdjustWindowRectForDpi(LPRECT lpRect, DWORD dwStyle, DWORD dwExStyle, unsigned dpi); -#else -extern "C" DPI_T GetWindowDPI(HWND hwnd); -extern "C" int SystemMetricsForDpi(int nIndex, unsigned dpi); -extern "C" BOOL AdjustWindowRectForDpi(LPRECT lpRect, DWORD dwStyle, DWORD dwExStyle, unsigned dpi); -#endif // !__cplusplus - - -namespace Scintilla { - -extern void Platform_Initialise(void *hInstance) noexcept; -extern void Platform_Finalise(bool fromDllMain) noexcept; - -constexpr RECT RectFromPRectangle(PRectangle prc) noexcept { - RECT rc = { static_cast(prc.left), static_cast(prc.top), - static_cast(prc.right), static_cast(prc.bottom) }; - return rc; -} - -constexpr POINT POINTFromPoint(Point pt) noexcept { - return POINT { static_cast(pt.x), static_cast(pt.y) }; -} - -constexpr Point PointFromPOINT(POINT pt) noexcept { - return Point::FromInts(pt.x, pt.y); -} - -constexpr HWND HwndFromWindowID(WindowID wid) noexcept { - return static_cast(wid); -} - -inline HWND HwndFromWindow(const Window &w) noexcept { - return HwndFromWindowID(w.GetID()); -} - -inline void *PointerFromWindow(HWND hWnd) noexcept { - return reinterpret_cast(::GetWindowLongPtr(hWnd, 0)); -} - -inline void SetWindowPointer(HWND hWnd, void *ptr) noexcept { - ::SetWindowLongPtr(hWnd, 0, reinterpret_cast(ptr)); -} - -/// Find a function in a DLL and convert to a function pointer. -/// This avoids undefined and conditionally defined behaviour. -template -inline T DLLFunction(HMODULE hModule, LPCSTR lpProcName) noexcept { -#if 1 - return reinterpret_cast(::GetProcAddress(hModule, lpProcName)); -#else - if (!hModule) { - return nullptr; - } - FARPROC function = ::GetProcAddress(hModule, lpProcName); - static_assert(sizeof(T) == sizeof(function)); - T fp; - memcpy(&fp, &function, sizeof(T)); - return fp; -#endif -} - -template -inline T DLLFunctionEx(LPCWSTR lpDllName, LPCSTR lpProcName) noexcept { - return DLLFunction(::GetModuleHandleW(lpDllName), lpProcName); -} - -// Release an IUnknown* and set to nullptr. -// While IUnknown::Release must be noexcept, it isn't marked as such so produces -// warnings which are avoided by the catch. -template -inline void ReleaseUnknown(T *&ppUnknown) noexcept { - if (ppUnknown) { - try { - ppUnknown->Release(); - } catch (...) { - // Never occurs - } - ppUnknown = nullptr; - } -} - -inline UINT DpiYForWindow(WindowID wid) noexcept { - return GetWindowDPI(HwndFromWindowID(wid)).y; -} - -HCURSOR LoadReverseArrowCursor(DPI_T dpi) noexcept; - -#if defined(USE_D2D) -extern bool LoadD2D() noexcept; -extern ID2D1Factory *pD2DFactory; -extern IDWriteFactory *pIDWriteFactory; -#endif - -} diff --git a/scintilla/win32/ScintillaWin_old.cxx b/scintilla/win32/ScintillaWin_old.cxx deleted file mode 100644 index 04510a30e..000000000 --- a/scintilla/win32/ScintillaWin_old.cxx +++ /dev/null @@ -1,4200 +0,0 @@ -// Scintilla source code edit control -/** @file ScintillaWin.cxx - ** Windows specific subclass of ScintillaBase. - **/ -// Copyright 1998-2003 by Neil Hodgson -// The License.txt file describes the conditions under which this software may be distributed. - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -//#include - -// Want to use std::min and std::max so don't want Windows.h version of min and max -#if !defined(NOMINMAX) -#define NOMINMAX -#endif - -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x601 /*_WIN32_WINNT_WIN7*/ -#endif -#ifndef WINVER -#define WINVER 0x601 /*_WIN32_WINNT_WIN7*/ -#endif -#ifndef NTDDI_VERSION -#define NTDDI_VERSION 0x06010000 /*NTDDI_WIN7*/ -#endif - -#define VC_EXTRALEAN 1 -#define WIN32_LEAN_AND_MEAN 1 - -#include -#include -#include -#include -#include -#include - -#if !defined(DISABLE_D2D) -#define USE_D2D 1 -#endif - -#if defined(USE_D2D) -#include -#include -#endif - -#include "Debugging.h" -#include "Geometry.h" - -#include -#include -#include -#include - -#define DebugDragAndDropDataFormat 0 -#define MaxDragAndDropDataFormatCount 6 -/* -CF_VSSTGPROJECTITEMS, CF_VSREFPROJECTITEMS -https://docs.microsoft.com/en-us/visualstudio/extensibility/ux-guidelines/application-patterns-for-visual-studio -*/ -#define EnableDrop_VisualStudioProjectItem 1 -/* -Chromium Web Custom MIME Data Format -Used by VSCode, Atom etc. -*/ -#define Enable_ChromiumWebCustomMIMEDataFormat 0 - -#include "Platform.h" - -#include "ILoader.h" -#include "ILexer.h" -#include "Scintilla.h" - -//~#include "CharacterCategory.h" -#include "Position.h" -#include "UniqueString.h" -#include "SplitVector.h" -#include "Partitioning.h" -#include "RunStyles.h" -#include "ContractionState.h" -#include "CellBuffer.h" -#include "CallTip.h" -#include "KeyMap.h" -#include "Indicator.h" -#include "LineMarker.h" -#include "Style.h" -#include "ViewStyle.h" -#include "CharClassify.h" -#include "Decoration.h" -#include "CaseFolder.h" -#include "Document.h" -#include "CaseConvert.h" -#include "UniConversion.h" -#include "Selection.h" -#include "PositionCache.h" -#include "EditModel.h" -#include "MarginView.h" -#include "EditView.h" -#include "Editor.h" -#include "ElapsedPeriod.h" - -#include "AutoComplete.h" -#include "ScintillaBase.h" - -#include "PlatWin.h" -#include "HanjaDic.h" -#include "ScintillaWin.h" - -#ifndef SPI_GETWHEELSCROLLLINES -#define SPI_GETWHEELSCROLLLINES 104 -#endif - -#ifndef WM_UNICHAR -#define WM_UNICHAR 0x0109 -#endif - -#ifndef WM_DPICHANGED -#define WM_DPICHANGED 0x02E0 -#endif -#ifndef WM_DPICHANGED_AFTERPARENT -#define WM_DPICHANGED_AFTERPARENT 0x02E3 -#endif - -#ifndef UNICODE_NOCHAR -#define UNICODE_NOCHAR 0xFFFF -#endif - -#ifndef IS_HIGH_SURROGATE -#define IS_HIGH_SURROGATE(x) ((x) >= SURROGATE_LEAD_FIRST && (x) <= SURROGATE_LEAD_LAST) -#endif - -#ifndef IS_LOW_SURROGATE -#define IS_LOW_SURROGATE(x) ((x) >= SURROGATE_TRAIL_FIRST && (x) <= SURROGATE_TRAIL_LAST) -#endif - -#ifndef MK_ALT -#define MK_ALT 32 -#endif - -// Two idle messages SC_WIN_IDLE and SC_WORK_IDLE. - -// SC_WIN_IDLE is low priority so should occur after the next WM_PAINT -// It is for lengthy actions like wrapping and background styling -constexpr UINT SC_WIN_IDLE = 5001; -// SC_WORK_IDLE is high priority and should occur before the next WM_PAINT -// It is for shorter actions like restyling the text just inserted -// and delivering SCN_UPDATEUI -constexpr UINT SC_WORK_IDLE = 5002; - -#define SC_INDICATOR_INPUT INDICATOR_IME -#define SC_INDICATOR_TARGET (INDICATOR_IME + 1) -#define SC_INDICATOR_CONVERTED (INDICATOR_IME + 2) -#define SC_INDICATOR_UNKNOWN INDICATOR_IME_MAX - -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 -DWORD kSystemLibraryLoadFlags = 0; -using SetCoalescableTimerSig = UINT_PTR (WINAPI *)(HWND hwnd, UINT_PTR nIDEvent, - UINT uElapse, TIMERPROC lpTimerFunc, ULONG uToleranceDelay); -#endif - -using namespace Scintilla; - -namespace { - -const TCHAR *callClassName = L"CallTip"; - -inline void SetWindowID(HWND hWnd, int identifier) noexcept { - ::SetWindowLongPtr(hWnd, GWLP_ID, identifier); -} - -constexpr Point PointFromLParam(sptr_t lpoint) noexcept { - return Point::FromInts(GET_X_LPARAM(lpoint), GET_Y_LPARAM(lpoint)); -} - -inline bool KeyboardIsKeyDown(int key) noexcept { - // the return value is a SHORT (16 bits), not a 32 bit value - // (in other words, 0x80000000 is not a valid bit mask) - return (::GetKeyState(key) & 0x8000) != 0; -} - -constexpr bool KeyboardIsNumericKeypadFunction(uptr_t wParam, sptr_t lParam) noexcept { - // Bit 24 is the extended keyboard flag and the numeric keypad is non-extended - if ((lParam & (1 << 24)) != 0) { - // Not from the numeric keypad - return false; - } - - switch (wParam) { - case VK_INSERT: // 0 - case VK_END: // 1 - case VK_DOWN: // 2 - case VK_NEXT: // 3 - case VK_LEFT: // 4 - case VK_CLEAR: // 5 - case VK_RIGHT: // 6 - case VK_HOME: // 7 - case VK_UP: // 8 - case VK_PRIOR: // 9 - return true; - default: - return false; - } -} - -inline CLIPFORMAT GetClipboardFormat(LPCWSTR name) noexcept { - return static_cast(::RegisterClipboardFormat(name)); -} - -#if 0 -inline void LazyGetClipboardFormat(UINT &fmt, LPCWSTR name) noexcept { - if (fmt == 0) { - fmt = ::RegisterClipboardFormat(name); - } -} -#endif - -} - -class ScintillaWin; // Forward declaration for COM interface subobjects - -/** - */ -class FormatEnumerator final : public IEnumFORMATETC { - ULONG ref; - ULONG pos; - std::vector formats; - -public: - FormatEnumerator(ULONG pos_, const CLIPFORMAT formats_[], size_t formatsLen_); - virtual ~FormatEnumerator() = default; - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) noexcept override; - STDMETHODIMP_(ULONG)AddRef() noexcept override; - STDMETHODIMP_(ULONG)Release() noexcept override; - - // IEnumFORMATETC - STDMETHODIMP Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) noexcept override; - STDMETHODIMP Skip(ULONG celt) noexcept override; - STDMETHODIMP Reset() noexcept override; - STDMETHODIMP Clone(IEnumFORMATETC **ppenum) override; -}; - -/** - */ -class DropSource final : public IDropSource { -public: - ScintillaWin *sci = nullptr; - DropSource() noexcept = default; - virtual ~DropSource() = default; - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) noexcept override; - STDMETHODIMP_(ULONG)AddRef() noexcept override; - STDMETHODIMP_(ULONG)Release() noexcept override; - - // IDropSource - STDMETHODIMP QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) noexcept override; - STDMETHODIMP GiveFeedback(DWORD) noexcept override; -}; - -/** - */ -class DataObject final : public IDataObject { -public: - ScintillaWin *sci = nullptr; - DataObject() noexcept = default; - virtual ~DataObject() = default; - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) noexcept override; - STDMETHODIMP_(ULONG)AddRef() noexcept override; - STDMETHODIMP_(ULONG)Release() noexcept override; - - // IDataObject - STDMETHODIMP GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) override; - STDMETHODIMP GetDataHere(FORMATETC *, STGMEDIUM *) noexcept override; - STDMETHODIMP QueryGetData(FORMATETC *pFE) noexcept override; - STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *, FORMATETC *pFEOut) noexcept override; - STDMETHODIMP SetData(FORMATETC *, STGMEDIUM *, BOOL) noexcept override; - STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnum) override; - STDMETHODIMP DAdvise(FORMATETC *, DWORD, IAdviseSink *, PDWORD) noexcept override; - STDMETHODIMP DUnadvise(DWORD) noexcept override; - STDMETHODIMP EnumDAdvise(IEnumSTATDATA **) noexcept override; -}; - -/** - */ -class DropTarget final : public IDropTarget { -public: - ScintillaWin *sci = nullptr; - DropTarget() noexcept = default; - virtual ~DropTarget() = default; - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) noexcept override; - STDMETHODIMP_(ULONG)AddRef() noexcept override; - STDMETHODIMP_(ULONG)Release() noexcept override; - - // IDropTarget - STDMETHODIMP DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) override; - STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) override; - STDMETHODIMP DragLeave() override; - STDMETHODIMP Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) override; -}; - -namespace { - -// InputLanguage() and SetCandidateWindowPos() are based on Chromium's IMM32Manager and InputMethodWinImm32. -// https://github.com/chromium/chromium/blob/master/ui/base/ime/win/imm32_manager.cc -// See License.txt or https://github.com/chromium/chromium/blob/master/LICENSE for license details. - -// See Chromium's IMM32Manager::SetInputLanguage() -LANGID InputLanguage() noexcept { - // Retrieve the current input language from the system's keyboard layout. - // Using GetKeyboardLayoutName instead of GetKeyboardLayout, because - // the language from GetKeyboardLayout is the language under where the - // keyboard layout is installed. And the language from GetKeyboardLayoutName - // indicates the language of the keyboard layout itself. - // See crbug.com/344834. - LANGID inputLang; - WCHAR keyboard_layout[KL_NAMELENGTH]; - if (::GetKeyboardLayoutNameW(keyboard_layout)) { - inputLang = static_cast(wcstol(&keyboard_layout[KL_NAMELENGTH >> 1], nullptr, 16)); - } else { - /// TODO: Fallback to en-US? - HKL inputLocale = ::GetKeyboardLayout(0); - inputLang = LOWORD(inputLocale); - } - //Platform::DebugPrintf("InputLanguage(): %04X\n", inputLang); - return inputLang; -} - -class IMContext { - HWND hwnd; -public: - HIMC hIMC; - explicit IMContext(HWND hwnd_) noexcept : - hwnd(hwnd_), hIMC(::ImmGetContext(hwnd_)) {} - // Deleted so IMContext objects can not be copied. - IMContext(const IMContext &) = delete; - IMContext(IMContext &&) = delete; - IMContext &operator=(const IMContext &) = delete; - IMContext &operator=(IMContext &&) = delete; - ~IMContext() { - if (hIMC) - ::ImmReleaseContext(hwnd, hIMC); - } - - operator bool() const noexcept { - return hIMC != nullptr; - } - - LONG GetImeCaretPos() const noexcept { - return ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, nullptr, 0); - } - - std::vector GetImeAttributes() const { - const LONG attrLen = ::ImmGetCompositionStringW(hIMC, GCS_COMPATTR, nullptr, 0); - std::vector attr(attrLen, 0); - ::ImmGetCompositionStringW(hIMC, GCS_COMPATTR, attr.data(), static_cast(attr.size())); - return attr; - } - - LONG HasCompositionString(DWORD dwIndex) const noexcept { - return hIMC ? ::ImmGetCompositionStringW(hIMC, dwIndex, nullptr, 0) : 0; - } - - std::wstring GetCompositionString(DWORD dwIndex) const { - const LONG byteLen = ::ImmGetCompositionStringW(hIMC, dwIndex, nullptr, 0); - std::wstring wcs(byteLen / sizeof(wchar_t), 0); - ::ImmGetCompositionStringW(hIMC, dwIndex, wcs.data(), byteLen); - return wcs; - } -}; - -class GlobalMemory; - -class ReverseArrowCursor { - DPI_T dpi = { USER_DEFAULT_SCREEN_DPI, USER_DEFAULT_SCREEN_DPI }; - HCURSOR cursor {}; - -public: - ReverseArrowCursor() noexcept = default; - // Deleted so ReverseArrowCursor objects can not be copied. - ReverseArrowCursor(const ReverseArrowCursor &) = delete; - ReverseArrowCursor(ReverseArrowCursor &&) = delete; - ReverseArrowCursor &operator=(const ReverseArrowCursor &) = delete; - ReverseArrowCursor &operator=(ReverseArrowCursor &&) = delete; - ~ReverseArrowCursor() { - if (cursor) { - ::DestroyCursor(cursor); - } - } - - HCURSOR Load(DPI_T dpi_) noexcept { - if (cursor) { - if ((dpi.x == dpi_.x) && (dpi.y == dpi_.y)) { - return cursor; - } - ::DestroyCursor(cursor); - } - - dpi = dpi_; - cursor = LoadReverseArrowCursor(dpi_); - return cursor ? cursor : ::LoadCursor({}, IDC_ARROW); - } -}; - -} - -/** - */ -class ScintillaWin final : - public ScintillaBase { - - bool lastKeyDownConsumed; - wchar_t lastHighSurrogateChar; - - bool capturedMouse; - bool trackedMouseLeave; -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 - SetCoalescableTimerSig SetCoalescableTimerFn; -#endif - - UINT linesPerScroll; ///< Intellimouse support - UINT charsPerScroll; ///< Intellimouse support - int wheelDelta; ///< Wheel delta from roll - int wheelDeltaH; ///< Wheel delta from horizontal wheel roll - - DPI_T dpi = { USER_DEFAULT_SCREEN_DPI, USER_DEFAULT_SCREEN_DPI }; - ReverseArrowCursor reverseArrowCursor; - - HRGN hRgnUpdate; - - bool hasOKText; - - CLIPFORMAT cfColumnSelect; - UINT cfBorlandIDEBlockType; - UINT cfLineSelect; - UINT cfVSLineTag; - -#if EnableDrop_VisualStudioProjectItem - CLIPFORMAT cfVSStgProjectItem; - CLIPFORMAT cfVSRefProjectItem; -#endif -#if Enable_ChromiumWebCustomMIMEDataFormat - CLIPFORMAT cfChromiumCustomMIME; -#endif - - // supported drag & drop format - CLIPFORMAT dropFormat[MaxDragAndDropDataFormatCount]; - UINT dropFormatCount; - - //HRESULT hrOle; - DropSource ds; - DataObject dob; - DropTarget dt; - - static HINSTANCE hInstance; - static ATOM scintillaClassAtom; - static ATOM callClassAtom; - - // The current input Language ID. - LANGID inputLang; - -#if defined(USE_D2D) - ID2D1RenderTarget *pRenderTarget; - bool renderTargetValid; -#endif - - explicit ScintillaWin(HWND hwnd) noexcept; - // virtual ~ScintillaWin() in public section - - void Init() noexcept; - void Finalise() noexcept override; -#if defined(USE_D2D) - void EnsureRenderTarget(HDC hdc) noexcept; - void DropRenderTarget() noexcept; -#endif - HWND MainHWND() const noexcept; -#if DebugDragAndDropDataFormat - void EnumDataSourceFormat(const char *tag, LPDATAOBJECT pIDataSource); - void EnumAllClipboardFormat(const char *tag); -#else - #define EnumDataSourceFormat(tag, pIDataSource) - #define EnumAllClipboardFormat(tag) -#endif - - static sptr_t DirectFunction( - sptr_t ptr, UINT iMessage, uptr_t wParam, sptr_t lParam); - static LRESULT CALLBACK SWndProc( - HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); - static LRESULT CALLBACK CTWndProc( - HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); - - enum : UINT_PTR { - invalidTimerID, standardTimerID, idleTimerID, fineTimerStart - }; - - void DisplayCursor(Window::Cursor c) noexcept override; - bool DragThreshold(Point ptStart, Point ptNow) noexcept override; - void StartDrag() override; - static int MouseModifiers(uptr_t wParam) noexcept; - - Sci::Position TargetAsUTF8(char *text) const; - Sci::Position EncodedFromUTF8(const char *utf8, char *encoded) const; - - bool PaintDC(HDC hdc); - sptr_t WndPaint(); - - sptr_t HandleCompositionWindowed(uptr_t wParam, sptr_t lParam); - sptr_t HandleCompositionInline(uptr_t wParam, sptr_t lParam); - static bool KoreanIME() noexcept; -// >>>>>>>>>>>>>>> BEG NON STD SCI PATCH >>>>>>>>>>>>>>> - bool IsIMEOpen(); - DWORD GetIMEInputMode(); -// <<<<<<<<<<<<<<< END NON STD SCI PATCH <<<<<<<<<<<<<<< - void MoveImeCarets(Sci::Position offset) noexcept; - void DrawImeIndicator(int indicator, Sci::Position len); - void SetCandidateWindowPos(); - void SelectionToHangul(); - void EscapeHanja(); - void ToggleHanja(); - void AddWString(std::wstring_view wsv, CharacterSource charSource); - - UINT CodePageOfDocument() const noexcept; - bool ValidCodePage(int codePage) const noexcept override; - std::string UTF8FromEncoded(std::string_view encoded) const override; - std::string EncodedFromUTF8(std::string_view utf8) const override; - - std::string EncodeWString(std::wstring_view wsv); - sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) noexcept override; - void IdleWork() override; - void QueueIdleWork(WorkItems items, Sci::Position upTo) noexcept override; - bool SetIdle(bool on) noexcept override; - UINT_PTR timers[static_cast(TickReason::dwell)+1] {}; - bool FineTickerRunning(TickReason reason) noexcept override; - void FineTickerStart(TickReason reason, int millis, int tolerance) noexcept override; - void FineTickerCancel(TickReason reason) noexcept override; - void SetMouseCapture(bool on) noexcept override; - bool HaveMouseCapture() noexcept override; - void SetTrackMouseLeaveEvent(bool on) noexcept; - bool PaintContains(PRectangle rc) const noexcept override; - void ScrollText(Sci::Line linesToMove) override; - void NotifyCaretMove() noexcept override; - void UpdateSystemCaret() override; - void SetVerticalScrollPos() override; - void SetHorizontalScrollPos() override; - bool ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) override; - void NotifyChange() noexcept override; - void NotifyFocus(bool focus) override; - void SetCtrlID(int identifier) noexcept override; - int GetCtrlID() const noexcept override; - void NotifyParent(SCNotification scn) noexcept override; - void NotifyDoubleClick(Point pt, int modifiers) override; - void NotifyURIDropped(const char *list) noexcept; - std::unique_ptr CaseFolderForEncoding() override; - std::string CaseMapString(const std::string &s, CaseMapping caseMapping) override; - void Copy(bool asBinary) override; - bool CanPaste() noexcept override; - void Paste(bool asBinary) override; - void CreateCallTipWindow(PRectangle rc) noexcept override; -#if SCI_EnablePopupMenu - void AddToPopUp(const char *label, int cmd = 0, bool enabled = true) noexcept override; -#endif - void ClaimSelection() noexcept override; - - // DBCS - void ImeStartComposition(); - void ImeEndComposition(); - LRESULT ImeOnReconvert(LPARAM lParam); - - enum class CopyEncoding { - Unicode, // used in Copy & Paste, Drag & Drop - Ansi, // used in Drag & Drop for CF_TEXT - Binary, // used in Copy & Paste for asBinary - }; - - void GetIntelliMouseParameters() noexcept; - void CopyToGlobal(GlobalMemory &gmUnicode, const SelectionText &selectedText, CopyEncoding encoding); - void CopyToClipboard(const SelectionText &selectedText) override; - void ScrollMessage(WPARAM wParam); - void HorizontalScrollMessage(WPARAM wParam); - void FullPaint(); - void FullPaintDC(HDC hdc); - bool IsCompatibleDC(HDC hOtherDC) const noexcept; - DWORD EffectFromState(DWORD grfKeyState) const noexcept; - - int SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw) const noexcept; - bool GetScrollInfo(int nBar, LPSCROLLINFO lpsi) const noexcept; - void ChangeScrollPos(int barType, Sci::Position pos); - sptr_t GetTextLength() const noexcept; - sptr_t GetText(uptr_t wParam, sptr_t lParam) const; - Window::Cursor SCICALL ContextCursor(Point pt); - sptr_t ShowContextMenu(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - - void SizeWindow(); - sptr_t MouseMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - sptr_t KeyMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - sptr_t FocusMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - sptr_t IMEMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - sptr_t EditMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - sptr_t IdleMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - sptr_t SciMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam); - -public: - ~ScintillaWin() override; - - // Deleted so ScintillaWin objects can not be copied. - ScintillaWin(const ScintillaWin &) = delete; - ScintillaWin(ScintillaWin &&) = delete; - ScintillaWin &operator=(const ScintillaWin &) = delete; - ScintillaWin &operator=(ScintillaWin &&) = delete; - // Public for benefit of Scintilla_DirectFunction - sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) override; - - /// Implement IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) noexcept; - STDMETHODIMP_(ULONG)AddRef() noexcept; - STDMETHODIMP_(ULONG)Release() noexcept; - - /// Implement IDropTarget - STDMETHODIMP DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, - POINTL pt, PDWORD pdwEffect); - STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect); - STDMETHODIMP DragLeave(); - STDMETHODIMP Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, - POINTL pt, PDWORD pdwEffect); - - /// Implement important part of IDataObject - STDMETHODIMP GetData(const FORMATETC *pFEIn, STGMEDIUM *pSTM); - - static BOOL CALLBACK PrepareOnce(PINIT_ONCE initOnce, PVOID parameter, PVOID *lpContext) noexcept; - static bool Register(HINSTANCE hInstance_) noexcept; - static bool Unregister() noexcept; - - friend class DropSource; - friend class DataObject; - friend class DropTarget; - bool DragIsRectangularOK(UINT fmt) const noexcept { - return drag.rectangular && (fmt == cfColumnSelect); - } - -private: - // For use in creating a system caret - bool HasCaretSizeChanged() const noexcept; - BOOL CreateSystemCaret(); - BOOL DestroySystemCaret() noexcept; - HBITMAP sysCaretBitmap; - int sysCaretWidth; - int sysCaretHeight; - bool styleIdleInQueue; -}; - -HINSTANCE ScintillaWin::hInstance {}; -ATOM ScintillaWin::scintillaClassAtom = 0; -ATOM ScintillaWin::callClassAtom = 0; - -ScintillaWin::ScintillaWin(HWND hwnd) noexcept { - - lastKeyDownConsumed = false; - lastHighSurrogateChar = 0; - - capturedMouse = false; - trackedMouseLeave = false; -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 - SetCoalescableTimerFn = nullptr; -#endif - - linesPerScroll = 0; - charsPerScroll = 0; - wheelDelta = 0; // Wheel delta from roll - wheelDeltaH = 0; // H-Wheel delta from roll - - dpi = GetWindowDPI(hwnd); - - hRgnUpdate = {}; - - hasOKText = false; - - // There does not seem to be a real standard for indicating that the clipboard - // contains a rectangular selection, so copy Developer Studio and Borland Delphi. - cfColumnSelect = GetClipboardFormat(L"MSDEVColumnSelect"); - cfBorlandIDEBlockType = ::RegisterClipboardFormat(L"Borland IDE Block Type"); - - // Likewise for line-copy (copies a full line when no text is selected) - cfLineSelect = ::RegisterClipboardFormat(L"MSDEVLineSelect"); - cfVSLineTag = ::RegisterClipboardFormat(L"VisualStudioEditorOperationsLineCutCopyClipboardTag"); - -#if EnableDrop_VisualStudioProjectItem - cfVSStgProjectItem = GetClipboardFormat(L"CF_VSSTGPROJECTITEMS"); - cfVSRefProjectItem = GetClipboardFormat(L"CF_VSREFPROJECTITEMS"); -#endif -#if Enable_ChromiumWebCustomMIMEDataFormat - cfChromiumCustomMIME = GetClipboardFormat(L"Chromium Web Custom MIME Data Format"); -#endif - - UINT index = 0; - dropFormat[index++] = CF_HDROP; -#if EnableDrop_VisualStudioProjectItem - dropFormat[index++] = cfVSStgProjectItem; - dropFormat[index++] = cfVSRefProjectItem; -#endif -#if Enable_ChromiumWebCustomMIMEDataFormat - dropFormat[index++] = cfChromiumCustomMIME; -#endif - // text format comes last - dropFormat[index++] = CF_UNICODETEXT; - dropFormat[index++] = CF_TEXT; - dropFormatCount = index; - - //hrOle = E_FAIL; - wMain = hwnd; - - dob.sci = this; - ds.sci = this; - dt.sci = this; - - sysCaretBitmap = {}; - sysCaretWidth = 0; - sysCaretHeight = 0; - inputLang = LANG_USER_DEFAULT; - - styleIdleInQueue = false; - -#if defined(USE_D2D) - pRenderTarget = nullptr; - renderTargetValid = true; -#endif - - caret.period = ::GetCaretBlinkTime(); - if (caret.period < 0) - caret.period = 0; - - Init(); -} - -ScintillaWin::~ScintillaWin() { Finalise(); } - -void ScintillaWin::Init() noexcept { - // Initialize COM. If the app has already done this it will have - // no effect. If the app hasn't, we really shouldn't ask them to call - // it just so this internal feature works. - //hrOle = ::OleInitialize(nullptr); - - // Find SetCoalescableTimer which is only available from Windows 8+ -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 - HMODULE user32 = ::GetModuleHandleW(L"user32.dll"); - SetCoalescableTimerFn = DLLFunction(user32, "SetCoalescableTimer"); -#endif - - vs.indicators[SC_INDICATOR_UNKNOWN] = Indicator(INDIC_HIDDEN, ColourDesired(0, 0, 0xff)); - vs.indicators[SC_INDICATOR_INPUT] = Indicator(INDIC_DOTS, ColourDesired(0, 0, 0xff)); - vs.indicators[SC_INDICATOR_CONVERTED] = Indicator(INDIC_COMPOSITIONTHICK, ColourDesired(0, 0, 0xff)); - vs.indicators[SC_INDICATOR_TARGET] = Indicator(INDIC_STRAIGHTBOX, ColourDesired(0, 0, 0xff)); -} - -void ScintillaWin::Finalise() noexcept { - ScintillaBase::Finalise(); - for (TickReason tr = TickReason::caret; tr <= TickReason::dwell; - tr = static_cast(static_cast(tr) + 1)) { - FineTickerCancel(tr); - } - SetIdle(false); -#if defined(USE_D2D) - DropRenderTarget(); -#endif - ::RevokeDragDrop(MainHWND()); - //if (SUCCEEDED(hrOle)) { - // ::OleUninitialize(); - //} -} - -#if defined(USE_D2D) - -void ScintillaWin::EnsureRenderTarget(HDC hdc) noexcept { - if (!renderTargetValid) { - DropRenderTarget(); - renderTargetValid = true; - } - if (pD2DFactory && !pRenderTarget) { - HWND hw = MainHWND(); - RECT rc; - GetClientRect(hw, &rc); - - const D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); - - // Create a Direct2D render target. -#if 1 - D2D1_RENDER_TARGET_PROPERTIES drtp; - ZeroMemory(&drtp, sizeof(D2D1_RENDER_TARGET_PROPERTIES)); - drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; - drtp.pixelFormat.format = DXGI_FORMAT_UNKNOWN; - drtp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN; - drtp.dpiX = 96.0; - drtp.dpiY = 96.0; - drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE; - drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; - - if (technology == SC_TECHNOLOGY_DIRECTWRITEDC) { - // Explicit pixel format needed. - drtp.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_IGNORE); - - ID2D1DCRenderTarget *pDCRT = nullptr; - const HRESULT hr = pD2DFactory->CreateDCRenderTarget(&drtp, &pDCRT); - if (SUCCEEDED(hr)) { - pRenderTarget = pDCRT; - } else { - //Platform::DebugPrintf("Failed CreateDCRenderTarget 0x%lx\n", hr); - ReleaseUnknown(pDCRT); - pRenderTarget = nullptr; - } - - } else { - D2D1_HWND_RENDER_TARGET_PROPERTIES dhrtp; - ZeroMemory(&dhrtp, sizeof(D2D1_HWND_RENDER_TARGET_PROPERTIES)); - dhrtp.hwnd = hw; - dhrtp.pixelSize = size; - dhrtp.presentOptions = (technology == SC_TECHNOLOGY_DIRECTWRITERETAIN) ? - D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS : D2D1_PRESENT_OPTIONS_NONE; - - ID2D1HwndRenderTarget *pHwndRenderTarget = nullptr; - const HRESULT hr = pD2DFactory->CreateHwndRenderTarget(drtp, dhrtp, &pHwndRenderTarget); - if (SUCCEEDED(hr)) { - pRenderTarget = pHwndRenderTarget; - } else { - //Platform::DebugPrintf("Failed CreateHwndRenderTarget 0x%lx\n", hr); - ReleaseUnknown(pHwndRenderTarget); - pRenderTarget = nullptr; - } - } -#else - pD2DFactory->CreateHwndRenderTarget( - D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), - 96.0f, 96.0f, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT), - D2D1::HwndRenderTargetProperties(hw, size), - &pRenderTarget); -#endif - // Pixmaps were created to be compatible with previous render target so - // need to be recreated. - DropGraphics(); - } - - if ((technology == SC_TECHNOLOGY_DIRECTWRITEDC) && pRenderTarget) { - RECT rcWindow; - GetClientRect(MainHWND(), &rcWindow); - const HRESULT hr = static_cast(pRenderTarget)->BindDC(hdc, &rcWindow); - if (FAILED(hr)) { - //Platform::DebugPrintf("BindDC failed 0x%lx\n", hr); - DropRenderTarget(); - } - } -} - -void ScintillaWin::DropRenderTarget() noexcept { - ReleaseUnknown(pRenderTarget); -} - -#endif - -HWND ScintillaWin::MainHWND() const noexcept { - return HwndFromWindow(wMain); -} - -void ScintillaWin::DisplayCursor(Window::Cursor c) noexcept { - if (cursorMode != SC_CURSORNORMAL) { - c = static_cast(cursorMode); - } - if (c == Window::Cursor::reverseArrow) { - ::SetCursor(reverseArrowCursor.Load(dpi)); - } else { - wMain.SetCursor(c); - } -} - -bool ScintillaWin::DragThreshold(Point ptStart, Point ptNow) noexcept { - const Point ptDifference = ptStart - ptNow; - const XYPOSITION xMove = std::trunc(std::abs(ptDifference.x)); - const XYPOSITION yMove = std::trunc(std::abs(ptDifference.y)); - return (xMove > SystemMetricsForDpi(SM_CXDRAG, dpi.x)) || - (yMove > SystemMetricsForDpi(SM_CYDRAG, dpi.y)); -} - -void ScintillaWin::StartDrag() { - inDragDrop = DragDrop::dragging; - DWORD dwEffect = 0; - dropWentOutside = true; - IDataObject *pDataObject = reinterpret_cast(&dob); - IDropSource *pDropSource = reinterpret_cast(&ds); - //Platform::DebugPrintf("About to DoDragDrop %p %p\n", pDataObject, pDropSource); - const HRESULT hr = ::DoDragDrop( - pDataObject, - pDropSource, - DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect); - //Platform::DebugPrintf("DoDragDrop = %lx\n", hr); - if (SUCCEEDED(hr)) { - if ((hr == DRAGDROP_S_DROP) && (dwEffect == DROPEFFECT_MOVE) && dropWentOutside) { - // Remove dragged out text - ClearSelection(); - } - } - inDragDrop = DragDrop::none; - SetDragPosition(SelectionPosition(Sci::invalidPosition)); -} - -int ScintillaWin::MouseModifiers(uptr_t wParam) noexcept { - return ModifierFlags((wParam & MK_SHIFT) != 0, - (wParam & MK_CONTROL) != 0, - KeyboardIsKeyDown(VK_MENU)); -} - -namespace { - -int InputCodePage() noexcept { - HKL const inputLocale = ::GetKeyboardLayout(0); - const LANGID inputLang = LOWORD(inputLocale); - WCHAR sCodePage[32]; - const int res = ::GetLocaleInfo(MAKELCID(inputLang, SORT_DEFAULT), - LOCALE_IDEFAULTANSICODEPAGE, sCodePage, sizeof(sCodePage) / sizeof(WCHAR)); - if (!res) - return 0; - return static_cast(wcstol(sCodePage, nullptr, 32)); -} - -/** Map the key codes to their equivalent SCK_ form. */ -int KeyTranslate(int keyIn) noexcept { - //PLATFORM_ASSERT(!keyIn); - switch (keyIn) { - case VK_DOWN: return SCK_DOWN; - case VK_UP: return SCK_UP; - case VK_LEFT: return SCK_LEFT; - case VK_RIGHT: return SCK_RIGHT; - case VK_HOME: return SCK_HOME; - case VK_END: return SCK_END; - case VK_PRIOR: return SCK_PRIOR; - case VK_NEXT: return SCK_NEXT; - case VK_DELETE: return SCK_DELETE; - case VK_INSERT: return SCK_INSERT; - case VK_ESCAPE: return SCK_ESCAPE; - case VK_BACK: return SCK_BACK; - case VK_TAB: return SCK_TAB; - case VK_RETURN: return SCK_RETURN; - case VK_ADD: return SCK_ADD; - case VK_SUBTRACT: return SCK_SUBTRACT; - case VK_DIVIDE: return SCK_DIVIDE; - case VK_LWIN: return SCK_WIN; - case VK_RWIN: return SCK_RWIN; - case VK_APPS: return SCK_MENU; - case VK_OEM_2: return '/'; - case VK_OEM_3: return '`'; - case VK_OEM_4: return '['; - case VK_OEM_5: return '\\'; - case VK_OEM_6: return ']'; - default: return keyIn; - } -} - -bool BoundsContains(PRectangle rcBounds, HRGN hRgnBounds, PRectangle rcCheck) noexcept { - bool contains = true; - if (!rcCheck.Empty()) { - if (!rcBounds.Contains(rcCheck)) { - contains = false; - } else if (hRgnBounds) { - // In bounding rectangle so check more accurately using region - const RECT rcw = RectFromPRectangle(rcCheck); - HRGN hRgnCheck = ::CreateRectRgnIndirect(&rcw); - if (hRgnCheck) { - HRGN hRgnDifference = ::CreateRectRgn(0, 0, 0, 0); - if (hRgnDifference) { - const int combination = ::CombineRgn(hRgnDifference, hRgnCheck, hRgnBounds, RGN_DIFF); - if (combination != NULLREGION) { - contains = false; - } - ::DeleteRgn(hRgnDifference); - } - ::DeleteRgn(hRgnCheck); - } - } - } - return contains; -} - -// Simplify calling WideCharToMultiByte and MultiByteToWideChar by providing default parameters and using string view. - -inline int MultiByteFromWideChar(UINT codePage, std::wstring_view wsv, LPSTR lpMultiByteStr, ptrdiff_t cbMultiByte) noexcept { - return ::WideCharToMultiByte(codePage, 0, wsv.data(), static_cast(wsv.length()), lpMultiByteStr, static_cast(cbMultiByte), nullptr, nullptr); -} - -inline int MultiByteLenFromWideChar(UINT codePage, std::wstring_view wsv) noexcept { - return MultiByteFromWideChar(codePage, wsv, nullptr, 0); -} - -inline int WideCharFromMultiByte(UINT codePage, std::string_view sv, LPWSTR lpWideCharStr, ptrdiff_t cchWideChar) noexcept { - return ::MultiByteToWideChar(codePage, 0, sv.data(), static_cast(sv.length()), lpWideCharStr, static_cast(cchWideChar)); -} - -inline int WideCharLenFromMultiByte(UINT codePage, std::string_view sv) noexcept { - return WideCharFromMultiByte(codePage, sv, nullptr, 0); -} - -std::string StringEncode(const std::wstring_view wsv, int codePage) { - const int cchMulti = wsv.length() ? MultiByteLenFromWideChar(codePage, wsv) : 0; - std::string sMulti(cchMulti, 0); - if (cchMulti) { - MultiByteFromWideChar(codePage, wsv, sMulti.data(), cchMulti); - } - return sMulti; -} - -std::wstring StringDecode(const std::string_view sv, int codePage) { - const int cchWide = sv.length() ? WideCharLenFromMultiByte(codePage, sv) : 0; - std::wstring sWide(cchWide, 0); - if (cchWide) { - WideCharFromMultiByte(codePage, sv, sWide.data(), cchWide); - } - return sWide; -} - -std::wstring StringMapCase(const std::wstring_view wsv, DWORD mapFlags) { - const int charsConverted = ::LCMapStringW(LOCALE_USER_DEFAULT, mapFlags, - wsv.data(), static_cast(wsv.length()), nullptr, 0); - std::wstring wsConverted(charsConverted, 0); - if (charsConverted) { - ::LCMapStringW(LOCALE_USER_DEFAULT, mapFlags, - wsv.data(), static_cast(wsv.length()), wsConverted.data(), charsConverted); - } - return wsConverted; -} - -} - -// Returns the target converted to UTF8. -// Return the length in bytes. -Sci::Position ScintillaWin::TargetAsUTF8(char *text) const { - const Sci::Position targetLength = targetRange.Length(); - if (IsUnicodeMode()) { - if (text) { - pdoc->GetCharRange(text, targetRange.start.Position(), targetLength); - } - } else { - // Need to convert - const std::string s = RangeText(targetRange.start.Position(), targetRange.end.Position()); - const std::wstring characters = StringDecode(s, CodePageOfDocument()); - const int utf8Len = MultiByteLenFromWideChar(CP_UTF8, characters); - if (text) { - MultiByteFromWideChar(CP_UTF8, characters, text, utf8Len); - text[utf8Len] = '\0'; - } - return utf8Len; - } - return targetLength; -} - -// Translates a nul terminated UTF8 string into the document encoding. -// Return the length of the result in bytes. -Sci::Position ScintillaWin::EncodedFromUTF8(const char *utf8, char *encoded) const { - const Sci::Position inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8); - if (IsUnicodeMode()) { - if (encoded) { - memcpy(encoded, utf8, inputLength); - } - return inputLength; - } else { - // Need to convert - const std::string_view utf8Input(utf8, inputLength); - const int charsLen = WideCharLenFromMultiByte(CP_UTF8, utf8Input); - std::wstring characters(charsLen, L'\0'); - WideCharFromMultiByte(CP_UTF8, utf8Input, characters.data(), charsLen); - - const UINT codePage = CodePageOfDocument(); - const int encodedLen = MultiByteLenFromWideChar(codePage, characters); - if (encoded) { - MultiByteFromWideChar(codePage, characters, encoded, encodedLen); - encoded[encodedLen] = '\0'; - } - return encodedLen; - } -} - -bool ScintillaWin::PaintDC(HDC hdc) { - if (technology == SC_TECHNOLOGY_DEFAULT) { - AutoSurface surfaceWindow(hdc, this); - if (surfaceWindow) { - Paint(surfaceWindow, rcPaint); - surfaceWindow->Release(); - } - } else { -#if defined(USE_D2D) - EnsureRenderTarget(hdc); - if (pRenderTarget) { - AutoSurface surfaceWindow(pRenderTarget, this); - if (surfaceWindow) { - pRenderTarget->BeginDraw(); - Paint(surfaceWindow, rcPaint); - surfaceWindow->Release(); - const HRESULT hr = pRenderTarget->EndDraw(); - if (hr == static_cast(D2DERR_RECREATE_TARGET)) { - DropRenderTarget(); - return false; - } - } - } -#endif - } - - return true; -} - -sptr_t ScintillaWin::WndPaint() { - //ElapsedPeriod ep; - - // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - if (paintState != PaintState::notPainting) { return 0; } // prevent recursion loop - // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - // Redirect assertions to debug output and save current state - //const bool assertsPopup = Platform::ShowAssertionPopUps(false); - paintState = PaintState::painting; - PAINTSTRUCT ps = {}; - - // Removed since this interferes with reporting other assertions as it occurs repeatedly - //PLATFORM_ASSERT(hRgnUpdate == nullptr); - hRgnUpdate = ::CreateRectRgn(0, 0, 0, 0); - ::GetUpdateRgn(MainHWND(), hRgnUpdate, FALSE); - ::BeginPaint(MainHWND(), &ps); - rcPaint = PRectangle::FromInts(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom); - const PRectangle rcClient = GetClientRectangle(); - paintingAllText = BoundsContains(rcPaint, hRgnUpdate, rcClient); - if (!PaintDC(ps.hdc)) { - paintState = PaintState::abandoned; - } - if (hRgnUpdate) { - ::DeleteRgn(hRgnUpdate); - hRgnUpdate = {}; - } - - ::EndPaint(MainHWND(), &ps); - if (paintState == PaintState::abandoned) { - // Painting area was insufficient to cover new styling or brace highlight positions - FullPaint(); - ::ValidateRect(MainHWND(), nullptr); - } - paintState = PaintState::notPainting; - - // Restore debug output state - //Platform::ShowAssertionPopUps(assertsPopup); - - //Platform::DebugPrintf("Paint took %g\n", ep.Duration()); - return 0; -} - -sptr_t ScintillaWin::HandleCompositionWindowed(uptr_t wParam, sptr_t lParam) { - if (lParam & GCS_RESULTSTR) { - IMContext imc(MainHWND()); - if (imc.hIMC) { - AddWString(imc.GetCompositionString(GCS_RESULTSTR), CharacterSource::imeResult); - - // Set new position after converted - const Point pos = PointMainCaret(); - COMPOSITIONFORM CompForm; - CompForm.dwStyle = CFS_POINT; - CompForm.ptCurrentPos = POINTFromPoint(pos); - ::ImmSetCompositionWindow(imc.hIMC, &CompForm); - } - return 0; - } - return ::DefWindowProc(MainHWND(), WM_IME_COMPOSITION, wParam, lParam); -} - -bool ScintillaWin::KoreanIME() noexcept { - const int codePage = InputCodePage(); - return codePage == 949 || codePage == 1361; -} - -// >>>>>>>>>>>>>>> BEG NON STD SCI PATCH >>>>>>>>>>>>>>> -bool ScintillaWin::IsIMEOpen() { - IMContext imc(MainHWND()); - if (imc.hIMC) { - if (ImmGetOpenStatus(imc.hIMC)) { - return true; - } - } - return false; -} - -DWORD ScintillaWin::GetIMEInputMode() { - IMContext imc(MainHWND()); - if (imc.hIMC && ImmGetOpenStatus(imc.hIMC)) { - DWORD dwConversion = IME_CMODE_ALPHANUMERIC, dwSentence = IME_SMODE_NONE; - if (ImmGetConversionStatus(imc.hIMC, &dwConversion, &dwSentence)) { - return dwConversion; - } - } - return 0; -} -// <<<<<<<<<<<<<<< END NON STD SCI PATCH <<<<<<<<<<<<<<< - -void ScintillaWin::MoveImeCarets(Sci::Position offset) noexcept { - // Move carets relatively by bytes. - for (size_t r = 0; r < sel.Count(); r++) { - const Sci::Position positionInsert = sel.Range(r).Start().Position(); - sel.Range(r).caret.SetPosition(positionInsert + offset); - sel.Range(r).anchor.SetPosition(positionInsert + offset); - } -} - -void ScintillaWin::DrawImeIndicator(int indicator, Sci::Position len) { - // Emulate the visual style of IME characters with indicators. - // Draw an indicator on the character before caret by the character bytes of len - // so it should be called after InsertCharacter(). - // It does not affect caret positions. - if (indicator < 8 || indicator > INDICATOR_MAX) { - return; - } - pdoc->DecorationSetCurrentIndicator(indicator); - for (size_t r = 0; r < sel.Count(); r++) { - const Sci::Position positionInsert = sel.Range(r).Start().Position(); - pdoc->DecorationFillRange(positionInsert - len, 1, len); - } -} - -void ScintillaWin::SetCandidateWindowPos() { - IMContext imc(MainHWND()); - if (imc.hIMC) { - const Point pos = PointMainCaret(); - const PRectangle rcClient = GetTextRectangle(); - CANDIDATEFORM CandForm{}; - CandForm.dwIndex = 0; - CandForm.dwStyle = CFS_EXCLUDE; - CandForm.ptCurrentPos.x = static_cast(pos.x); - CandForm.ptCurrentPos.y = static_cast(pos.y + std::max(4, vs.lineHeight/4)); - // Exclude the area of the whole caret line - CandForm.rcArea.top = static_cast(pos.y); - CandForm.rcArea.bottom = static_cast(pos.y + vs.lineHeight); - CandForm.rcArea.left = static_cast(rcClient.left); - CandForm.rcArea.right = static_cast(rcClient.right); - ::ImmSetCandidateWindow(imc.hIMC, &CandForm); - } -} - -void ScintillaWin::SelectionToHangul() { - // Convert every hanja to hangul within the main range. - const Sci::Position selStart = sel.RangeMain().Start().Position(); - const Sci::Position documentStrLen = sel.RangeMain().Length(); - const Sci::Position selEnd = selStart + documentStrLen; - const Sci::Position utf16Len = pdoc->CountUTF16(selStart, selEnd); - - if (utf16Len > 0) { - std::string documentStr(documentStrLen, '\0'); - pdoc->GetCharRange(documentStr.data(), selStart, documentStrLen); - const UINT codePage = CodePageOfDocument(); - - std::wstring uniStr = StringDecode(documentStr, codePage); - const int converted = HanjaDict::GetHangulOfHanja(uniStr.data()); - documentStr = StringEncode(uniStr, codePage); - - if (converted > 0) { - pdoc->BeginUndoAction(); - ClearSelection(); - InsertPaste(documentStr.data(), documentStr.size()); - pdoc->EndUndoAction(); - } - } -} - -void ScintillaWin::EscapeHanja() { - // The candidate box pops up to user to select a hanja. - // It comes into WM_IME_COMPOSITION with GCS_RESULTSTR. - // The existing hangul or hanja is replaced with it. - if (sel.Count() > 1) { - return; // Do not allow multi carets. - } - const Sci::Position currentPos = CurrentPosition(); - const int oneCharLen = pdoc->LenChar(currentPos); - - if (oneCharLen < 2) { - return; // No need to handle SBCS. - } - - // ImmEscapeW() may overwrite uniChar[] with a null terminated string. - // So enlarge it enough to Maximum 4 as in UTF-8. - constexpr size_t safeLength = UTF8MaxBytes + 1; - std::string oneChar(safeLength, '\0'); - pdoc->GetCharRange(oneChar.data(), currentPos, oneCharLen); - - std::wstring uniChar = StringDecode(oneChar, CodePageOfDocument()); - - IMContext imc(MainHWND()); - if (imc.hIMC) { - // Set the candidate box position since IME may show it. - SetCandidateWindowPos(); - // IME_ESC_HANJA_MODE appears to receive the first character only. - if (::ImmEscapeW(GetKeyboardLayout(0), imc.hIMC, IME_ESC_HANJA_MODE, uniChar.data())) { - SetSelection(currentPos, currentPos + oneCharLen); - } - } -} - -void ScintillaWin::ToggleHanja() { - // If selection, convert every hanja to hangul within the main range. - // If no selection, commit to IME. - if (sel.Count() > 1) { - return; // Do not allow multi carets. - } - - if (sel.Empty()) { - EscapeHanja(); - } else { - SelectionToHangul(); - } -} - -namespace { - -// https://docs.microsoft.com/en-us/windows/desktop/Intl/composition-string -std::vector MapImeIndicators(const std::vector &inputStyle) { - std::vector imeIndicator(inputStyle.size(), SC_INDICATOR_UNKNOWN); - for (size_t i = 0; i < inputStyle.size(); i++) { - switch (static_cast(inputStyle.at(i))) { - case ATTR_INPUT: - imeIndicator[i] = SC_INDICATOR_INPUT; - break; - case ATTR_TARGET_NOTCONVERTED: - case ATTR_TARGET_CONVERTED: - imeIndicator[i] = SC_INDICATOR_TARGET; - break; - case ATTR_CONVERTED: - imeIndicator[i] = SC_INDICATOR_CONVERTED; - break; - default: - imeIndicator[i] = SC_INDICATOR_UNKNOWN; - break; - } - } - return imeIndicator; -} - -} - -void ScintillaWin::AddWString(std::wstring_view wsv, CharacterSource charSource) { - if (wsv.empty()) - return; - - const UINT codePage = CodePageOfDocument(); - char inBufferCP[16]; - for (size_t i = 0; i < wsv.size(); ) { - const size_t ucWidth = UTF16CharLength(wsv[i]); - - const int size = MultiByteFromWideChar(codePage, wsv.substr(i, ucWidth), inBufferCP, sizeof(inBufferCP) - 1); - inBufferCP[size] = '\0'; - InsertCharacter(std::string_view(inBufferCP, size), charSource); - i += ucWidth; - } -} - -sptr_t ScintillaWin::HandleCompositionInline(uptr_t, sptr_t lParam) { - // Copy & paste by johnsonj with a lot of helps of Neil. - // Great thanks for my foreruners, jiniya and BLUEnLIVE. - - IMContext imc(MainHWND()); - if (!imc.hIMC) - return 0; - if (pdoc->IsReadOnly() || SelectionContainsProtected()) { - ::ImmNotifyIME(imc.hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0); - return 0; - } - - bool initialCompose = false; - if (pdoc->TentativeActive()) { - pdoc->TentativeUndo(); - } else { - // No tentative undo means start of this composition so - // fill in any virtual spaces. - initialCompose = true; - } - - view.imeCaretBlockOverride = false; - - if (lParam & GCS_RESULTSTR) { - AddWString(imc.GetCompositionString(GCS_RESULTSTR), CharacterSource::imeResult); - } - - if (lParam & GCS_COMPSTR) { - const std::wstring wcs = imc.GetCompositionString(GCS_COMPSTR); - if (wcs.empty()) { - ShowCaretAtCurrentPosition(); - return 0; - } - - if (initialCompose) { - ClearBeforeTentativeStart(); - } - - // Set candidate window left aligned to beginning of preedit string. - SetCandidateWindowPos(); - pdoc->TentativeStart(); // TentativeActive from now on. - - std::vector imeIndicator = MapImeIndicators(imc.GetImeAttributes()); - - const UINT codePage = CodePageOfDocument(); - char inBufferCP[16]; - const std::wstring_view wsv = wcs; - - for (size_t i = 0; i < wsv.size(); ) { - const size_t ucWidth = UTF16CharLength(wsv[i]); - const int size = MultiByteFromWideChar(codePage, wsv.substr(i, ucWidth), inBufferCP, sizeof(inBufferCP) - 1); - inBufferCP[size] = '\0'; - InsertCharacter(std::string_view(inBufferCP, size), CharacterSource::tentativeInput); - - DrawImeIndicator(imeIndicator[i], size); - i += ucWidth; - } - - // Move IME caret from current last position to imeCaretPos. - const int imeEndToImeCaretU16 = imc.GetImeCaretPos() - static_cast(wcs.size()); - const Sci::Position imeCaretPosDoc = pdoc->GetRelativePositionUTF16(CurrentPosition(), imeEndToImeCaretU16); - - MoveImeCarets(-CurrentPosition() + imeCaretPosDoc); - - if (std::find(imeIndicator.begin(), imeIndicator.end(), SC_INDICATOR_TARGET) != imeIndicator.end()) { - // set candidate window left aligned to beginning of target string. - SetCandidateWindowPos(); - } - - if (KoreanIME()) { - view.imeCaretBlockOverride = true; - } - } - EnsureCaretVisible(); - ShowCaretAtCurrentPosition(); - return 0; -} - -namespace { - -// Translate message IDs from WM_* and EM_* to SCI_* so can partly emulate Windows Edit control -unsigned int SciMessageFromEM(unsigned int iMessage) noexcept { - switch (iMessage) { - case EM_CANPASTE: return SCI_CANPASTE; - case EM_CANREDO: return SCI_CANREDO; - case EM_CANUNDO: return SCI_CANUNDO; - case EM_EMPTYUNDOBUFFER: return SCI_EMPTYUNDOBUFFER; - case EM_FINDTEXTEX: return SCI_FINDTEXT; - case EM_FORMATRANGE: return SCI_FORMATRANGE; - case EM_GETFIRSTVISIBLELINE: return SCI_GETFIRSTVISIBLELINE; - case EM_GETLINE: return SCI_GETLINE; - case EM_GETLINECOUNT: return SCI_GETLINECOUNT; - case EM_GETSELTEXT: return SCI_GETSELTEXT; - case EM_GETTEXTRANGE: return SCI_GETTEXTRANGE; - case EM_HIDESELECTION: return SCI_HIDESELECTION; - case EM_LINEINDEX: return SCI_POSITIONFROMLINE; - case EM_LINESCROLL: return SCI_LINESCROLL; - case EM_REDO: return SCI_REDO; - case EM_REPLACESEL: return SCI_REPLACESEL; - case EM_SCROLL: return WM_VSCROLL; - case EM_SCROLLCARET: return SCI_SCROLLCARET; - case EM_SETREADONLY: return SCI_SETREADONLY; - case EM_UNDO: return SCI_UNDO; - case WM_CLEAR: return SCI_CLEAR; - case WM_COPY: return SCI_COPY; - case WM_CUT: return SCI_CUT; - case WM_PASTE: return SCI_PASTE; - case WM_SETTEXT: return SCI_SETTEXT; - case WM_UNDO: return SCI_UNDO; - } - return iMessage; -} - -} - -namespace Scintilla { - -UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) noexcept { - // UTF-8 and DBCS ANSI code pages - if (documentCodePage) { - return SC_CP_UTF8; // we only use UTF-8 - } - // SBCS code pages: zero / CP_ACP - switch (characterSet) { - case SC_CHARSET_ANSI: return 1252; - //case SC_CHARSET_DEFAULT: return documentCodePage ? documentCodePage : 1252; // SCI orig - case SC_CHARSET_DEFAULT: return documentCodePage; - case SC_CHARSET_BALTIC: return 1257; - case SC_CHARSET_CHINESEBIG5: return 950; - case SC_CHARSET_EASTEUROPE: return 1250; - case SC_CHARSET_GB2312: return 936; - case SC_CHARSET_GREEK: return 1253; - case SC_CHARSET_HANGUL: return 949; - case SC_CHARSET_MAC: return 10000; - case SC_CHARSET_OEM: return 437; - case SC_CHARSET_RUSSIAN: return 1251; - case SC_CHARSET_SHIFTJIS: return 932; - case SC_CHARSET_TURKISH: return 1254; - case SC_CHARSET_JOHAB: return 1361; - case SC_CHARSET_HEBREW: return 1255; - case SC_CHARSET_ARABIC: return 1256; - case SC_CHARSET_VIETNAMESE: return 1258; - case SC_CHARSET_THAI: return 874; - case SC_CHARSET_8859_15: return 28605; - // Not supported - case SC_CHARSET_CYRILLIC: return documentCodePage; - case SC_CHARSET_SYMBOL: return documentCodePage; - } - return documentCodePage; -} - -} - -UINT ScintillaWin::CodePageOfDocument() const noexcept { - return pdoc->dbcsCodePage; // see SCI_GETCODEPAGE in Editor.cxx - //return CodePageFromCharSet(vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage); -} - -std::string ScintillaWin::EncodeWString(std::wstring_view wsv) { - if (IsUnicodeMode()) { - const size_t len = UTF8Length(wsv); - std::string putf(len, 0); - UTF8FromUTF16(wsv, putf.data(), len); - return putf; - } else { - // Not in Unicode mode so convert from Unicode to current Scintilla code page - return StringEncode(wsv, CodePageOfDocument()); - } -} - -sptr_t ScintillaWin::GetTextLength() const noexcept { - return pdoc->CountUTF16(0, pdoc->Length()); -} - -sptr_t ScintillaWin::GetText(uptr_t wParam, sptr_t lParam) const { - if (lParam == 0) { - return pdoc->CountUTF16(0, pdoc->Length()); - } - if (wParam == 0) { - return 0; - } - wchar_t *ptr = static_cast(PtrFromSPtr(lParam)); - if (pdoc->Length() == 0) { - *ptr = L'\0'; - return 0; - } - const Sci::Position lengthWanted = wParam - 1; - Sci::Position sizeRequestedRange = pdoc->GetRelativePositionUTF16(0, lengthWanted); - if (sizeRequestedRange < 0) { - // Requested more text than there is in the document. - sizeRequestedRange = pdoc->Length(); - } - std::string docBytes(sizeRequestedRange, '\0'); - pdoc->GetCharRange(&docBytes[0], 0, sizeRequestedRange); - if (IsUnicodeMode()) { - const size_t uLen = UTF16FromUTF8(docBytes, ptr, lengthWanted); - ptr[uLen] = L'\0'; - return uLen; - } else { - // Not Unicode mode - // Convert to Unicode using the current Scintilla code page - const UINT cpSrc = CodePageOfDocument(); - int lengthUTF16 = WideCharLenFromMultiByte(cpSrc, docBytes); - if (lengthUTF16 > lengthWanted) - lengthUTF16 = static_cast(lengthWanted); - WideCharFromMultiByte(cpSrc, docBytes, ptr, lengthUTF16); - ptr[lengthUTF16] = L'\0'; - return lengthUTF16; - } -} - -Window::Cursor ScintillaWin::ContextCursor(Point pt) { - if (inDragDrop == DragDrop::dragging) { - return Window::Cursor::up; - } else { - // Display regular (drag) cursor over selection - if (PointInSelMargin(pt)) { - return GetMarginCursor(pt); - } else if (!SelectionEmpty() && PointInSelection(pt)) { - return Window::Cursor::arrow; - } else if (PointIsHotspot(pt)) { - return Window::Cursor::hand; - } else if (hoverIndicatorPos != Sci::invalidPosition) { - const Sci::Position pos = PositionFromLocation(pt, true, true); - if (pos != Sci::invalidPosition) { - return Window::Cursor::hand; - } - } - } - return Window::Cursor::text; -} - -sptr_t ScintillaWin::ShowContextMenu(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - Point pt = PointFromLParam(lParam); - POINT rpt = POINTFromPoint(pt); - ::ScreenToClient(MainHWND(), &rpt); - const Point ptClient = PointFromPOINT(rpt); - if (ShouldDisplayPopup(ptClient)) { -#if SCI_EnablePopupMenu - if ((pt.x == -1) && (pt.y == -1)) { - // Caused by keyboard so display menu near caret - pt = PointMainCaret(); - POINT spt = POINTFromPoint(pt); - ::ClientToScreen(MainHWND(), &spt); - pt = PointFromPOINT(spt); - } - ContextMenu(pt); - return 0; -#else - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); -#endif - } -#if SCI_EnablePopupMenu - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); -#else - return 0; -#endif -} - -void ScintillaWin::SizeWindow() { -#if defined(USE_D2D) - if (paintState == PaintState::notPainting) { - DropRenderTarget(); - } else { - renderTargetValid = false; - } -#endif - //Platform::DebugPrintf("Scintilla WM_SIZE %d %d\n", LOWORD(lParam), HIWORD(lParam)); - ChangeSize(); -} - -sptr_t ScintillaWin::MouseMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - switch (iMessage) { - case WM_LBUTTONDOWN: { - // For IME, set the composition string as the result string. - IMContext imc(MainHWND()); - if (imc.hIMC) { - ::ImmNotifyIME(imc.hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - } - // - //Platform::DebugPrintf("Buttdown %d %x %x %x %x %x\n",iMessage, wParam, lParam, - // KeyboardIsKeyDown(VK_SHIFT), - // KeyboardIsKeyDown(VK_CONTROL), - // KeyboardIsKeyDown(VK_MENU)); - ::SetFocus(MainHWND()); - ButtonDownWithModifiers(PointFromLParam(lParam), ::GetMessageTime(), - MouseModifiers(wParam)); - } - break; - - case WM_LBUTTONUP: - ButtonUpWithModifiers(PointFromLParam(lParam), - ::GetMessageTime(), MouseModifiers(wParam)); - break; - - case WM_RBUTTONDOWN: { - ::SetFocus(MainHWND()); - const Point pt = PointFromLParam(lParam); - if (!PointInSelection(pt)) { - CancelModes(); - SetEmptySelection(PositionFromLocation(PointFromLParam(lParam))); - } - - RightButtonDownWithModifiers(pt, ::GetMessageTime(), MouseModifiers(wParam)); - } - break; - - // >>>>>>>>>>>>>>> BEG NON STD SCI PATCH >>>>>>>>>>>>>>> - case WM_MBUTTONDOWN: - // send to main window - ::SetFocus(MainHWND()); - //::DefWindowProc(MainHWND(), iMessage, wParam, lParam); // does not propagate - filter msg ? - ::SendMessage(GetParent(MainHWND()), WM_MBUTTONDOWN, wParam, lParam); - break; - // <<<<<<<<<<<<<<< END NON STD SCI PATCH <<<<<<<<<<<<<<< - - case WM_MOUSEMOVE: { - const Point pt = PointFromLParam(lParam); - - // Windows might send WM_MOUSEMOVE even though the mouse has not been moved: - // http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx - if (ptMouseLast != pt) { - SetTrackMouseLeaveEvent(true); - ButtonMoveWithModifiers(pt, ::GetMessageTime(), MouseModifiers(wParam)); - } - } - break; - - case WM_MOUSELEAVE: - SetTrackMouseLeaveEvent(false); - MouseLeave(); - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_MOUSEWHEEL: - if (!mouseWheelCaptures) { - // if the mouse wheel is not captured, test if the mouse - // pointer is over the editor window and if not, don't - // handle the message but pass it on. - RECT rc; - GetWindowRect(MainHWND(), &rc); - const POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - if (!PtInRect(&rc, pt)) { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - } - - // if autocomplete list active then send mousewheel message to it - if (ac.Active()) { - HWND hWnd = HwndFromWindow(*(ac.lb)); - ::SendMessage(hWnd, iMessage, wParam, lParam); - break; - } - - // Don't handle datazoom. - // (A good idea for datazoom would be to "fold" or "unfold" details. - // i.e. if datazoomed out only class structures are visible, when datazooming in the control - // structures appear, then eventually the individual statements...) - //@@@if (wParam & (MK_SHIFT | MK_RBUTTON)) { - if (wParam & MK_SHIFT) { - if (vs.wrapState != WrapMode::none || charsPerScroll == 0) { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - } - - // Either SCROLL or ZOOM. We handle the wheel steppings calculation - wheelDelta -= GET_WHEEL_DELTA_WPARAM(wParam); - if (std::abs(wheelDelta) < WHEEL_DELTA) { - return 0; - } - if (wParam & MK_SHIFT) { - int charsToScroll = charsPerScroll; - if (charsPerScroll == WHEEL_PAGESCROLL) { - const PRectangle rcText = GetTextRectangle(); - const int pageWidth = static_cast(rcText.Width() * 2 / 3); - charsToScroll = pageWidth; - } else { - charsToScroll = 1 + static_cast(std::max(charsToScroll, 1) * vs.aveCharWidth); - } - charsToScroll *= (wheelDelta / WHEEL_DELTA); - if (wheelDelta >= 0) { - wheelDelta = wheelDelta % WHEEL_DELTA; - } else { - wheelDelta = -(-wheelDelta % WHEEL_DELTA); - } - HorizontalScrollTo(xOffset + charsToScroll); - } else if (linesPerScroll > 0) { - Sci::Line linesToScroll = linesPerScroll; - if (linesPerScroll == WHEEL_PAGESCROLL) { - linesToScroll = LinesOnScreen() - 1; - } - linesToScroll = std::max(linesToScroll, 1); - linesToScroll *= (wheelDelta / WHEEL_DELTA); - if (wheelDelta >= 0) { - wheelDelta = wheelDelta % WHEEL_DELTA; - } else { - wheelDelta = -(-wheelDelta % WHEEL_DELTA); - } - - if (wParam & MK_CONTROL) { - // Zoom! We play with the font sizes in the styles. - // Number of steps/line is ignored, we just care if sizing up or down - if (linesToScroll < 0) { - KeyCommand(SCI_ZOOMIN); - } else { - KeyCommand(SCI_ZOOMOUT); - } - // send to main window too (trigger Zoom CallTip) ! - ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } else { - // Scroll - ScrollTo(topLine + linesToScroll); - } - } - return 0; - - case WM_MOUSEHWHEEL: - if (!mouseWheelCaptures) { - // if the mouse wheel is not captured, test if the mouse - // pointer is over the editor window and if not, don't - // handle the message but pass it on. - RECT rc; - GetWindowRect(MainHWND(), &rc); - const POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - if (!PtInRect(&rc, pt)) { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - } - - wheelDeltaH += GET_WHEEL_DELTA_WPARAM(wParam); - if (std::abs(wheelDeltaH) < WHEEL_DELTA) { - return 0; - } - - if (vs.wrapState != WrapMode::none || charsPerScroll == 0) { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - else { - int charsToScroll = charsPerScroll; - if (charsPerScroll == WHEEL_PAGESCROLL) { - const PRectangle rcText = GetTextRectangle(); - const int pageWidth = static_cast(rcText.Width() * 2 / 3); - charsToScroll = pageWidth; - } - else { - charsToScroll = 1 + static_cast(std::max(charsToScroll, 1) * vs.aveCharWidth); - } - charsToScroll *= (wheelDeltaH / WHEEL_DELTA); - if (wheelDeltaH >= 0) { - wheelDeltaH = wheelDeltaH % WHEEL_DELTA; - } - else { - wheelDeltaH = -(-wheelDeltaH % WHEEL_DELTA); - } - HorizontalScrollTo(xOffset + charsToScroll); - } - return 0; - - } - return 0; -} - -sptr_t ScintillaWin::KeyMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - switch (iMessage) { - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: { - // Platform::DebugPrintf("Keydown %c %c%c%c%c %x %x\n", - // iMessage == WM_KEYDOWN ? 'K' : 'S', - // (lParam & (1 << 24)) ? 'E' : '-', - // KeyboardIsKeyDown(VK_SHIFT) ? 'S' : '-', - // KeyboardIsKeyDown(VK_CONTROL) ? 'C' : '-', - // KeyboardIsKeyDown(VK_MENU) ? 'A' : '-', - // wParam, lParam); - lastKeyDownConsumed = false; - const bool altDown = KeyboardIsKeyDown(VK_MENU); - if (altDown && KeyboardIsNumericKeypadFunction(wParam, lParam)) { - // Don't interpret these as they may be characters entered by number. - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - const int ret = KeyDownWithModifiers(KeyTranslate(static_cast(wParam)), - ModifierFlags(KeyboardIsKeyDown(VK_SHIFT), KeyboardIsKeyDown(VK_CONTROL), altDown), - &lastKeyDownConsumed); - if (!ret && !lastKeyDownConsumed) { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - break; - } - - case WM_KEYUP: - //Platform::DebugPrintf("S keyup %d %x %x\n",iMessage, wParam, lParam); - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_CHAR: - if (!lastKeyDownConsumed) { - wchar_t wcs[3] = { static_cast(wParam), 0 }; - unsigned int wclen = 1; - if (IS_HIGH_SURROGATE(wcs[0])) { - // If this is a high surrogate character, we need a second one - lastHighSurrogateChar = wcs[0]; - return 0; - } else if (IS_LOW_SURROGATE(wcs[0])) { - wcs[1] = wcs[0]; - wcs[0] = lastHighSurrogateChar; - lastHighSurrogateChar = 0; - wclen = 2; - } - AddWString(std::wstring_view(wcs, wclen), CharacterSource::directInput); - } - return 0; - - case WM_UNICHAR: - if (wParam == UNICODE_NOCHAR) { - return TRUE; - } else if (lastKeyDownConsumed) { - return 1; - } else { - wchar_t wcs[3] = { 0 }; - const size_t wclen = UTF16FromUTF32Character(static_cast(wParam), wcs); - AddWString(std::wstring_view(wcs, wclen), CharacterSource::directInput); - return FALSE; - } - } - - return 0; -} - -sptr_t ScintillaWin::FocusMessage(unsigned int iMessage, uptr_t wParam, sptr_t) { - switch (iMessage) { - case WM_KILLFOCUS: { - HWND wOther = reinterpret_cast(wParam); - HWND wThis = MainHWND(); - HWND wCT = HwndFromWindow(ct.wCallTip); - if (!wParam || - !(::IsChild(wThis, wOther) || (wOther == wCT))) { - SetFocusState(false); - DestroySystemCaret(); - } - // Explicitly complete any IME composition - IMContext imc(MainHWND()); - if (imc.hIMC) { - ::ImmNotifyIME(imc.hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - } - break; - } - - case WM_SETFOCUS: - SetFocusState(true); - DestroySystemCaret(); - CreateSystemCaret(); - break; - } - return 0; -} - -sptr_t ScintillaWin::IMEMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - switch (iMessage) { - - case WM_INPUTLANGCHANGE: - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_INPUTLANGCHANGEREQUEST: - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_IME_KEYDOWN: { - if (wParam == VK_HANJA) { - ToggleHanja(); - } - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - - case WM_IME_REQUEST: { - if (wParam == IMR_RECONVERTSTRING) { - return ImeOnReconvert(lParam); - } - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - - case WM_IME_STARTCOMPOSITION: - if (KoreanIME() || imeInteraction == IMEInteraction::internal) { - return 0; - } else { - ImeStartComposition(); - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - - case WM_IME_ENDCOMPOSITION: - ImeEndComposition(); - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_IME_COMPOSITION: - if (KoreanIME() || imeInteraction == IMEInteraction::internal) { - return HandleCompositionInline(wParam, lParam); - } else { - return HandleCompositionWindowed(wParam, lParam); - } - - case WM_IME_SETCONTEXT: - if (KoreanIME() || imeInteraction == IMEInteraction::internal) { - if (wParam) { - LPARAM NoImeWin = lParam; - NoImeWin = NoImeWin & (~ISC_SHOWUICOMPOSITIONWINDOW); - return ::DefWindowProc(MainHWND(), iMessage, wParam, NoImeWin); - } - } - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - // >>>>>>>>>>>>>>> BEG NON STD SCI PATCH >>>>>>>>>>>>>>> - case WM_IME_NOTIFY: - if (wParam == IMN_SETOPENSTATUS) { - imeIsOpen = IsIMEOpen(); - } - if (wParam == IMN_SETOPENSTATUS || wParam == IMN_SETCONVERSIONMODE) { - imeIsInModeCJK = (GetIMEInputMode() != IME_CMODE_ALPHANUMERIC); - } - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - // <<<<<<<<<<<<<<< END NON STD SCI PATCH <<<<<<<<<<<<<<< - -} - return 0; -} - -sptr_t ScintillaWin::EditMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - switch (iMessage) { - - case EM_FINDTEXT: - if (lParam == 0) { - return -1; - } else { - const FINDTEXTA *pFT = reinterpret_cast(lParam); - Sci_TextToFind tt = { { pFT->chrg.cpMin, pFT->chrg.cpMax }, pFT->lpstrText, {} }; - return ScintillaBase::WndProc(SCI_FINDTEXT, wParam, reinterpret_cast(&tt)); - } - - case EM_FINDTEXTEX: - if (lParam == 0) { - return -1; - } else { - FINDTEXTEXA *pFT = reinterpret_cast(lParam); - Sci_TextToFind tt = { { pFT->chrg.cpMin, pFT->chrg.cpMax }, pFT->lpstrText, {} }; - const Sci::Position pos =ScintillaBase::WndProc(SCI_FINDTEXT, wParam, reinterpret_cast(&tt)); - pFT->chrgText.cpMin = (pos == -1)? -1 : static_cast(tt.chrgText.cpMin); - pFT->chrgText.cpMax = (pos == -1)? -1 : static_cast(tt.chrgText.cpMax); - return pos; - } - - case EM_FORMATRANGE: - if (lParam) { - const FORMATRANGE *pFR = reinterpret_cast(lParam); - const Sci_RangeToFormat fr = { pFR->hdcTarget, pFR->hdc, - { pFR->rc.left, pFR->rc.top, pFR->rc.right, pFR->rc.bottom }, - { pFR->rcPage.left, pFR->rcPage.top, pFR->rcPage.right, pFR->rcPage.bottom }, - { pFR->chrg.cpMin, pFR->chrg.cpMax }, - }; - return ScintillaBase::WndProc(SCI_FORMATRANGE, wParam, reinterpret_cast(&fr)); - } - break; - - case EM_GETTEXTRANGE: - if (lParam) { - TEXTRANGEA *pTR = reinterpret_cast(lParam); - Sci_TextRange tr = { { pTR->chrg.cpMin, pTR->chrg.cpMax }, pTR->lpstrText }; - return ScintillaBase::WndProc(SCI_GETTEXTRANGE, 0, reinterpret_cast(&tr)); - } - break; - - case EM_LINEFROMCHAR: - if (static_cast(wParam) < 0) { - wParam = SelectionStart().Position(); - } - return pdoc->LineFromPosition(wParam); - - case EM_EXLINEFROMCHAR: - return pdoc->LineFromPosition(lParam); - - case EM_GETSEL: - if (wParam) { - *reinterpret_cast(wParam) = static_cast(SelectionStart().Position()); - } - if (lParam) { - *reinterpret_cast(lParam) = static_cast(SelectionEnd().Position()); - } - return MAKELRESULT(SelectionStart().Position(), SelectionEnd().Position()); - - case EM_EXGETSEL: { - if (lParam == 0) { - return 0; - } - CHARRANGE *pCR = reinterpret_cast(lParam); - pCR->cpMin = static_cast(SelectionStart().Position()); - pCR->cpMax = static_cast(SelectionEnd().Position()); - } - break; - - case EM_SETSEL: { - Sci::Position nStart = wParam; - Sci::Position nEnd = lParam; - if (nStart == 0 && nEnd == -1) { - nEnd = pdoc->Length(); - } - if (nStart == -1) { - nStart = nEnd; // Remove selection - } - SetSelection(nEnd, nStart); - EnsureCaretVisible(); - } - break; - - case EM_EXSETSEL: { - if (lParam == 0) { - return 0; - } - const CHARRANGE *pCR = reinterpret_cast(lParam); - sel.selType = Selection::SelTypes::stream; - if (pCR->cpMin == 0 && pCR->cpMax == -1) { - SetSelection(pCR->cpMin, pdoc->Length()); - } else { - SetSelection(pCR->cpMin, pCR->cpMax); - } - EnsureCaretVisible(); - return pdoc->LineFromPosition(SelectionStart().Position()); - } - - case EM_LINELENGTH: - return ScintillaBase::WndProc(SCI_LINELENGTH, pdoc->LineFromPosition(wParam), lParam); - - case EM_POSFROMCHAR: - if (wParam) { - const Point pt = LocationFromPosition(lParam); - POINTL *ptw = reinterpret_cast(wParam); - ptw->x = static_cast(pt.x - vs.textStart + vs.fixedColumnWidth); // SCI_POINTXFROMPOSITION - ptw->y = static_cast(pt.y); - } - break; - - case EM_GETZOOM: - if (wParam && lParam) { - *reinterpret_cast(wParam) = 16*vs.zoomLevel/25; - *reinterpret_cast(lParam) = 64; - return TRUE; - } - break; - - case EM_SETZOOM: { - int level = 0; - if (wParam == 0 && lParam == 0) { - level = 100; - } else if (wParam != 0 && lParam > 0) { - level = static_cast(wParam/lParam); - } - if (level != 0) { - ScintillaBase::WndProc(SCI_SETZOOM, level, 0); - return TRUE; - } - } - break; - - } - return 0; -} - -sptr_t ScintillaWin::IdleMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - switch (iMessage) { - case SC_WIN_IDLE: - // wParam=dwTickCountInitial, or 0 to initialize. lParam=bSkipUserInputTest - if (idler.state) { - if (lParam || (WAIT_TIMEOUT == MsgWaitForMultipleObjects(0, nullptr, 0, 0, QS_INPUT | QS_HOTKEY))) { - if (Idle()) { - // User input was given priority above, but all events do get a turn. Other - // messages, notifications, etc. will get interleaved with the idle messages. - - // However, some things like WM_PAINT are a lower priority, and will not fire - // when there's a message posted. So, several times a second, we stop and let - // the low priority events have a turn (after which the timer will fire again). - - // Suppress a warning from Code Analysis that the GetTickCount function - // wraps after 49 days. The WM_TIMER will kick off another SC_WIN_IDLE - // after the wrap. - - #pragma warning( disable : 28159 ) - const DWORD dwCurrent = GetTickCount(); - const DWORD dwStart = wParam ? static_cast(wParam) : dwCurrent; - constexpr DWORD maxWorkTime = 50; - - if (dwCurrent >= dwStart && dwCurrent > maxWorkTime &&dwCurrent - maxWorkTime < dwStart) { - PostMessage(MainHWND(), SC_WIN_IDLE, dwStart, 0); - } - } else { - SetIdle(false); - } - } - } - break; - - case SC_WORK_IDLE: - IdleWork(); - break; - } - return 0; -} - -sptr_t ScintillaWin::SciMessage(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - switch (iMessage) { - case SCI_GETDIRECTFUNCTION: - return reinterpret_cast(DirectFunction); - - case SCI_GETDIRECTPOINTER: - return reinterpret_cast(this); - - case SCI_GRABFOCUS: - ::SetFocus(MainHWND()); - break; - -#ifdef INCLUDE_DEPRECATED_FEATURES - case SCI_SETKEYSUNICODE: - break; - - case SCI_GETKEYSUNICODE: - return true; -#endif - - case SCI_SETTECHNOLOGY: - if ((wParam == SC_TECHNOLOGY_DEFAULT) || - (wParam == SC_TECHNOLOGY_DIRECTWRITERETAIN) || - (wParam == SC_TECHNOLOGY_DIRECTWRITEDC) || - (wParam == SC_TECHNOLOGY_DIRECTWRITE)) { - const int technologyNew = static_cast(wParam); - if (technology != technologyNew) { - if (technologyNew != SC_TECHNOLOGY_DEFAULT) { -#if defined(USE_D2D) - if (!LoadD2D()) - // Failed to load Direct2D or DirectWrite so no effect - return 0; -#else - return 0; -#endif - } else { - bidirectional = EditModel::Bidirectional::bidiDisabled; - } -#if defined(USE_D2D) - DropRenderTarget(); - view.bufferedDraw = technologyNew == SC_TECHNOLOGY_DEFAULT; -#endif - technology = technologyNew; - // Invalidate all cached information including layout. - vs.fontsValid = false; - DropGraphics(); - InvalidateStyleRedraw(); - } - } - break; - - case SCI_SETBIDIRECTIONAL: - if (technology == SC_TECHNOLOGY_DEFAULT) { - bidirectional = EditModel::Bidirectional::bidiDisabled; - } else if (wParam <= SC_BIDIRECTIONAL_R2L) { - bidirectional = static_cast(wParam); - } - // Invalidate all cached information including layout. - DropGraphics(); - InvalidateStyleRedraw(); - break; - - case SCI_TARGETASUTF8: - return TargetAsUTF8(CharPtrFromSPtr(lParam)); - - case SCI_ENCODEDFROMUTF8: - return EncodedFromUTF8(ConstCharPtrFromUPtr(wParam), - CharPtrFromSPtr(lParam)); - - } - return 0; -} - -sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { - try { - //Platform::DebugPrintf("S M:%x WP:%x L:%x\n", iMessage, wParam, lParam); - iMessage = SciMessageFromEM(iMessage); - switch (iMessage) { - - case WM_CREATE: - ctrlID = ::GetDlgCtrlID(HwndFromWindow(wMain)); - // Get Intellimouse scroll line parameters - GetIntelliMouseParameters(); - ::RegisterDragDrop(MainHWND(), reinterpret_cast(&dt)); - break; - - case WM_COMMAND: - Command(LOWORD(wParam)); - break; - - case WM_PAINT: - return WndPaint(); - - case WM_PRINTCLIENT: { - HDC hdc = reinterpret_cast(wParam); - if (!IsCompatibleDC(hdc)) { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - } - FullPaintDC(hdc); - } - break; - - case WM_VSCROLL: - ScrollMessage(wParam); - break; - - case WM_HSCROLL: - HorizontalScrollMessage(wParam); - break; - - case WM_SIZE: - SizeWindow(); - break; - - case WM_TIMER: - if (wParam == idleTimerID && idler.state) { - SendMessage(MainHWND(), SC_WIN_IDLE, 0, 1); - } else { - TickFor(static_cast(wParam - fineTimerStart)); - } - break; - - case SC_WIN_IDLE: - case SC_WORK_IDLE: - return IdleMessage(iMessage, wParam, lParam); - - case WM_GETMINMAXINFO: - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_RBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_MOUSEMOVE: - case WM_MOUSELEAVE: - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - return MouseMessage(iMessage, wParam, lParam); - - case WM_SETCURSOR: - if (LOWORD(lParam) == HTCLIENT) { - POINT pt; - if (::GetCursorPos(&pt)) { - ::ScreenToClient(MainHWND(), &pt); - DisplayCursor(ContextCursor(PointFromPOINT(pt))); - } - return TRUE; - } - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - case WM_KEYUP: - case WM_CHAR: - case WM_UNICHAR: - return KeyMessage(iMessage, wParam, lParam); - - case WM_SETTINGCHANGE: - //Platform::DebugPrintf("Setting Changed\n"); - InvalidateStyleData(); - // Get Intellimouse scroll line parameters - GetIntelliMouseParameters(); - break; - - case WM_GETDLGCODE: - return DLGC_HASSETSEL | DLGC_WANTALLKEYS; - - case WM_KILLFOCUS: - case WM_SETFOCUS: - return FocusMessage(iMessage, wParam, lParam); - - case WM_SYSCOLORCHANGE: - //Platform::DebugPrintf("Setting Changed\n"); - InvalidateStyleData(); - break; - - case WM_DPICHANGED: - dpi.x = LOWORD(wParam); - dpi.y = HIWORD(wParam); - vs.fontsValid = false; - InvalidateStyleRedraw(); - break; - - case WM_DPICHANGED_AFTERPARENT: { - DPI_T const dpiNow = GetWindowDPI(MainHWND()); - if ((dpi.x != dpiNow.x) || (dpi.y != dpiNow.y)) { - dpi = dpiNow; - vs.fontsValid = false; - InvalidateStyleRedraw(); - } - } - break; - - case WM_CONTEXTMENU: - return ShowContextMenu(iMessage, wParam, lParam); - - case WM_ERASEBKGND: - return 1; // Avoid any background erasure as whole window painted. - - case WM_CAPTURECHANGED: - capturedMouse = false; - return 0; - - // These are not handled in Scintilla and its faster to dispatch them here. - // Also moves time out to here so profile doesn't count lots of empty message calls. - - case WM_MOVE: - case WM_MOUSEACTIVATE: - case WM_NCHITTEST: - case WM_NCCALCSIZE: - case WM_NCPAINT: - case WM_NCMOUSEMOVE: - case WM_NCLBUTTONDOWN: - case WM_SYSCOMMAND: - case WM_WINDOWPOSCHANGING: - case WM_WINDOWPOSCHANGED: - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); - - case WM_GETTEXTLENGTH: - return GetTextLength(); - - case WM_GETTEXT: - return GetText(wParam, lParam); - - case WM_INPUTLANGCHANGE: - case WM_INPUTLANGCHANGEREQUEST: - case WM_IME_KEYDOWN: - case WM_IME_REQUEST: - case WM_IME_STARTCOMPOSITION: - case WM_IME_ENDCOMPOSITION: - case WM_IME_COMPOSITION: - case WM_IME_SETCONTEXT: - case WM_IME_NOTIFY: - return IMEMessage(iMessage, wParam, lParam); - - case EM_LINEFROMCHAR: - case EM_EXLINEFROMCHAR: - case EM_FINDTEXT: - case EM_FINDTEXTEX: - case EM_FORMATRANGE: - case EM_GETTEXTRANGE: - case EM_GETSEL: - case EM_EXGETSEL: - case EM_SETSEL: - case EM_EXSETSEL: - case EM_LINELENGTH: - case EM_POSFROMCHAR: - case EM_GETZOOM: - case EM_SETZOOM: - return EditMessage(iMessage, wParam, lParam); - - case SCI_GETDIRECTFUNCTION: - case SCI_GETDIRECTPOINTER: - case SCI_GRABFOCUS: -#ifdef INCLUDE_DEPRECATED_FEATURES - case SCI_SETKEYSUNICODE: - case SCI_GETKEYSUNICODE: -#endif - case SCI_SETTECHNOLOGY: - case SCI_SETBIDIRECTIONAL: - case SCI_TARGETASUTF8: - case SCI_ENCODEDFROMUTF8: - return SciMessage(iMessage, wParam, lParam); - - default: - return ScintillaBase::WndProc(iMessage, wParam, lParam); - } - } catch (std::bad_alloc &) { - errorStatus = SC_STATUS_BADALLOC; - } catch (...) { - errorStatus = SC_STATUS_FAILURE; - } - return 0; -} - -bool ScintillaWin::ValidCodePage(int codePage) const noexcept { - return codePage == 0 || codePage == SC_CP_UTF8 || - codePage == 932 || codePage == 936 || codePage == 949 || - codePage == 950 || codePage == 1361; -} - -std::string ScintillaWin::UTF8FromEncoded(std::string_view encoded) const { - if (IsUnicodeMode()) { - return std::string(encoded); - } else { - // Pivot through wide string - std::wstring ws = StringDecode(encoded, CodePageOfDocument()); - return StringEncode(ws, SC_CP_UTF8); - } -} - -std::string ScintillaWin::EncodedFromUTF8(std::string_view utf8) const { - if (IsUnicodeMode()) { - return std::string(utf8); - } else { - // Pivot through wide string - std::wstring ws = StringDecode(utf8, SC_CP_UTF8); - return StringEncode(ws, CodePageOfDocument()); - } -} - -sptr_t ScintillaWin::DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) noexcept { - return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam); -} - -bool ScintillaWin::FineTickerRunning(TickReason reason) noexcept { - return timers[static_cast(reason)] != 0; -} - -void ScintillaWin::FineTickerStart(TickReason reason, int millis, int tolerance) noexcept { - FineTickerCancel(reason); - const UINT_PTR reasonIndex = static_cast(reason); - const UINT_PTR eventID = static_cast(fineTimerStart) + reasonIndex; -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 - if (SetCoalescableTimerFn && tolerance) { - timers[reasonIndex] = SetCoalescableTimerFn(MainHWND(), eventID, millis, nullptr, tolerance); - } else { - timers[reasonIndex] = ::SetTimer(MainHWND(), eventID, millis, nullptr); - } -#else - if (tolerance) { - timers[reason] = ::SetCoalescableTimer(MainHWND(), eventID, millis, nullptr, tolerance); - } else { - timers[reason] = ::SetTimer(MainHWND(), eventID, millis, nullptr); - } -#endif -} - -void ScintillaWin::FineTickerCancel(TickReason reason) noexcept { - const UINT_PTR reasonIndex = static_cast(reason); - if (timers[reasonIndex]) { - ::KillTimer(MainHWND(), timers[reasonIndex]); - timers[reasonIndex] = 0; - } -} - -bool ScintillaWin::SetIdle(bool on) noexcept { - // On Win32 the Idler is implemented as a Timer on the Scintilla window. This - // takes advantage of the fact that WM_TIMER messages are very low priority, - // and are only posted when the message queue is empty, i.e. during idle time. - if (idler.state != on) { - if (on) { - idler.idlerID = ::SetTimer(MainHWND(), idleTimerID, 10, nullptr) - ? reinterpret_cast(idleTimerID) : nullptr; - } else { - ::KillTimer(MainHWND(), reinterpret_cast(idler.idlerID)); - idler.idlerID = nullptr; - } - idler.state = idler.idlerID != nullptr; - } - return idler.state; -} - -void ScintillaWin::IdleWork() { - styleIdleInQueue = false; - Editor::IdleWork(); -} - -void ScintillaWin::QueueIdleWork(WorkItems items, Sci::Position upTo) noexcept { - Editor::QueueIdleWork(items, upTo); - if (!styleIdleInQueue) { - if (PostMessage(MainHWND(), SC_WORK_IDLE, 0, 0)) { - styleIdleInQueue = true; - } - } -} - -void ScintillaWin::SetMouseCapture(bool on) noexcept { - if (mouseDownCaptures) { - if (on) { - ::SetCapture(MainHWND()); - } else { - ::ReleaseCapture(); - } - } - capturedMouse = on; -} - -bool ScintillaWin::HaveMouseCapture() noexcept { - // Cannot just see if GetCapture is this window as the scroll bar also sets capture for the window - return capturedMouse; - //return capturedMouse && (::GetCapture() == MainHWND()); -} - -void ScintillaWin::SetTrackMouseLeaveEvent(bool on) noexcept { - if (on && !trackedMouseLeave) { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = MainHWND(); - tme.dwHoverTime = HOVER_DEFAULT; // Unused but triggers Dr. Memory if not initialized - TrackMouseEvent(&tme); - } - trackedMouseLeave = on; -} - -bool ScintillaWin::PaintContains(PRectangle rc) const noexcept { - if (paintState == PaintState::painting) { - return BoundsContains(rcPaint, hRgnUpdate, rc); - } - return true; -} - -void ScintillaWin::ScrollText(Sci::Line /* linesToMove */) { - //Platform::DebugPrintf("ScintillaWin::ScrollText %d\n", linesToMove); - //::ScrollWindow(MainHWND(), 0, - // vs.lineHeight * linesToMove, 0, 0); - //::UpdateWindow(MainHWND()); - Redraw(); - UpdateSystemCaret(); -} - -void ScintillaWin::NotifyCaretMove() noexcept { - NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, MainHWND(), OBJID_CARET, CHILDID_SELF); -} - -void ScintillaWin::UpdateSystemCaret() { - if (hasFocus) { - if (pdoc->TentativeActive()) { - // ongoing inline mode IME composition, don't inform IME of system caret position. - // fix candidate window for Google Japanese IME moved on typing on Win7. - return; - } - if (HasCaretSizeChanged()) { - DestroySystemCaret(); - CreateSystemCaret(); - } - const Point pos = PointMainCaret(); - ::SetCaretPos(static_cast(pos.x), static_cast(pos.y)); - } -} - -int ScintillaWin::SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw) const noexcept { - return ::SetScrollInfo(MainHWND(), nBar, lpsi, bRedraw); -} - -bool ScintillaWin::GetScrollInfo(int nBar, LPSCROLLINFO lpsi) const noexcept { - return ::GetScrollInfo(MainHWND(), nBar, lpsi) != 0; -} - -// Change the scroll position but avoid repaint if changing to same value -void ScintillaWin::ChangeScrollPos(int barType, Sci::Position pos) { - SCROLLINFO sci = { - sizeof(sci), 0, 0, 0, 0, 0, 0 - }; - sci.fMask = SIF_POS; - GetScrollInfo(barType, &sci); - if (sci.nPos != pos) { - DwellEnd(true); - sci.nPos = static_cast(pos); - SetScrollInfo(barType, &sci, TRUE); - } -} - -void ScintillaWin::SetVerticalScrollPos() { - ChangeScrollPos(SB_VERT, topLine); -} - -void ScintillaWin::SetHorizontalScrollPos() { - ChangeScrollPos(SB_HORZ, xOffset); -} - -bool ScintillaWin::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) { - bool modified = false; - SCROLLINFO sci = { - sizeof(sci), 0, 0, 0, 0, 0, 0 - }; - sci.fMask = SIF_PAGE | SIF_RANGE; - GetScrollInfo(SB_VERT, &sci); - const Sci::Line vertEndPreferred = nMax; - if (!verticalScrollBarVisible) - nPage = vertEndPreferred + 1; - if ((sci.nMin != 0) || - (sci.nMax != vertEndPreferred) || - (sci.nPage != static_cast(nPage)) || - (sci.nPos != 0)) { - sci.fMask = SIF_PAGE | SIF_RANGE; - sci.nMin = 0; - sci.nMax = static_cast(vertEndPreferred); - sci.nPage = static_cast(nPage); - sci.nPos = 0; - sci.nTrackPos = 1; - SetScrollInfo(SB_VERT, &sci, TRUE); - modified = true; - } - - const PRectangle rcText = GetTextRectangle(); - int horizEndPreferred = scrollWidth; - if (horizEndPreferred < 0) - horizEndPreferred = 0; - int pageWidth = static_cast(rcText.Width()); - if (!horizontalScrollBarVisible || Wrapping()) - pageWidth = horizEndPreferred + 1; - sci.fMask = SIF_PAGE | SIF_RANGE; - GetScrollInfo(SB_HORZ, &sci); - if ((sci.nMin != 0) || - (sci.nMax != horizEndPreferred) || - (sci.nPage != static_cast(pageWidth)) || - (sci.nPos != 0)) { - sci.fMask = SIF_PAGE | SIF_RANGE; - sci.nMin = 0; - sci.nMax = horizEndPreferred; - sci.nPage = pageWidth; - sci.nPos = 0; - sci.nTrackPos = 1; - SetScrollInfo(SB_HORZ, &sci, TRUE); - modified = true; - if (scrollWidth < pageWidth) { - HorizontalScrollTo(0); - } - } - return modified; -} - -void ScintillaWin::NotifyChange() noexcept { - ::SendMessage(::GetParent(MainHWND()), WM_COMMAND, - MAKEWPARAM(GetCtrlID(), SCEN_CHANGE), - reinterpret_cast(MainHWND())); -} - -void ScintillaWin::NotifyFocus(bool focus) { - if (commandEvents) { - ::SendMessage(::GetParent(MainHWND()), WM_COMMAND, - MAKEWPARAM(GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), - reinterpret_cast(MainHWND())); - } - Editor::NotifyFocus(focus); -} - -void ScintillaWin::SetCtrlID(int identifier) noexcept { - ::SetWindowID(HwndFromWindow(wMain), identifier); -} - -int ScintillaWin::GetCtrlID() const noexcept { - return ::GetDlgCtrlID(HwndFromWindow(wMain)); -} - -void ScintillaWin::NotifyParent(SCNotification scn) noexcept { - scn.nmhdr.hwndFrom = MainHWND(); - scn.nmhdr.idFrom = GetCtrlID(); - ::SendMessage(::GetParent(MainHWND()), WM_NOTIFY, - GetCtrlID(), reinterpret_cast(&scn)); -} - -void ScintillaWin::NotifyDoubleClick(Point pt, int modifiers) { - //Platform::DebugPrintf("ScintillaWin Double click 0\n"); - ScintillaBase::NotifyDoubleClick(pt, modifiers); - // Send myself a WM_LBUTTONDBLCLK, so the container can handle it too. - ::SendMessage(::GetParent(MainHWND()), - WM_LBUTTONDBLCLK, - (modifiers & SCI_SHIFT) ? MK_SHIFT : 0, - MAKELPARAM(pt.x, pt.y)); -} - -void ScintillaWin::NotifyURIDropped(const char *list) noexcept { - SCNotification scn = {}; - scn.nmhdr.code = SCN_URIDROPPED; - scn.text = list; - - NotifyParent(scn); -} - -class CaseFolderDBCS final : public CaseFolderTable { - // Allocate the expandable storage here so that it does not need to be reallocated - // for each call to Fold. - std::vector utf16Mixed; - std::vector utf16Folded; - UINT cp; -public: - explicit CaseFolderDBCS(UINT cp_) noexcept : cp(cp_) { } - size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override { - if ((lenMixed == 1) && (sizeFolded > 0)) { - folded[0] = mapping[static_cast(mixed[0])]; - return 1; - } else { - if (lenMixed > utf16Mixed.size()) { - utf16Mixed.resize(lenMixed + 8); - } - const size_t nUtf16Mixed = WideCharFromMultiByte(cp, - std::string_view(mixed, lenMixed), - utf16Mixed.data(), - utf16Mixed.size()); - - if (nUtf16Mixed == 0) { - // Failed to convert -> bad input - folded[0] = '\0'; - return 1; - } - - size_t lenFlat = 0; - for (size_t mixIndex = 0; mixIndex < nUtf16Mixed; mixIndex++) { - if ((lenFlat + 20) > utf16Folded.size()) - utf16Folded.resize(lenFlat + 60); - const char *foldedUTF8 = CaseConvert(utf16Mixed[mixIndex], CaseConversion::fold); - if (foldedUTF8) { - // Maximum length of a case conversion is 6 bytes, 3 characters - wchar_t wFolded[20]; - const size_t charsConverted = UTF16FromUTF8(std::string_view(foldedUTF8), - wFolded, std::size(wFolded)); - for (size_t j = 0; j < charsConverted; j++) { - utf16Folded[lenFlat++] = wFolded[j]; - } - } else { - utf16Folded[lenFlat++] = utf16Mixed[mixIndex]; - } - } - - const std::wstring_view wsvFolded(utf16Folded.data(), lenFlat); - const size_t lenOut = MultiByteLenFromWideChar(cp, wsvFolded); - - if (lenOut < sizeFolded) { - MultiByteFromWideChar(cp, wsvFolded, folded, lenOut); - return lenOut; - } else { - return 0; - } - } - } -}; - -std::unique_ptr ScintillaWin::CaseFolderForEncoding() { - const UINT cpDest = CodePageOfDocument(); - if (cpDest == SC_CP_UTF8) { - return std::make_unique(); - } else { - if (pdoc->dbcsCodePage == 0) { - std::unique_ptr pcf = std::make_unique(); - pcf->StandardASCII(); - // Only for single byte encodings - for (int i = 0x80; i < 0x100; i++) { - char sCharacter[2] = "A"; - sCharacter[0] = static_cast(i); - wchar_t wCharacter[20]; - const unsigned int lengthUTF16 = WideCharFromMultiByte(cpDest, sCharacter, - wCharacter, std::size(wCharacter)); - if (lengthUTF16 == 1) { - const char *caseFolded = CaseConvert(wCharacter[0], CaseConversion::fold); - if (caseFolded) { - wchar_t wLower[20]; - const size_t charsConverted = UTF16FromUTF8(std::string_view(caseFolded), - wLower, std::size(wLower)); - if (charsConverted == 1) { - char sCharacterLowered[20]; - const unsigned int lengthConverted = MultiByteFromWideChar(cpDest, - std::wstring_view(wLower, charsConverted), - sCharacterLowered, std::size(sCharacterLowered)); - if ((lengthConverted == 1) && (sCharacter[0] != sCharacterLowered[0])) { - pcf->SetTranslation(sCharacter[0], sCharacterLowered[0]); - } - } - } - } - } - return pcf; - } else { - return std::make_unique(cpDest); - } - } -} - -std::string ScintillaWin::CaseMapString(const std::string &s, CaseMapping caseMapping) { - if ((s.empty()) || (caseMapping == CaseMapping::same)) - return s; - - const UINT cpDoc = CodePageOfDocument(); - if (cpDoc == SC_CP_UTF8) { - return CaseConvertString(s, (caseMapping == CaseMapping::upper) ? CaseConversion::upper : CaseConversion::lower); - } - - // Change text to UTF-16 - const std::wstring wsText = StringDecode(s, cpDoc); - - const DWORD mapFlags = LCMAP_LINGUISTIC_CASING | - ((caseMapping == CaseMapping::upper) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE); - - // Change case - const std::wstring wsConverted = StringMapCase(wsText, mapFlags); - - // Change back to document encoding - std::string sConverted = StringEncode(wsConverted, cpDoc); - - return sConverted; -} - -void ScintillaWin::Copy(bool asBinary) { - //Platform::DebugPrintf("Copy\n"); - if (!sel.Empty()) { - SelectionText selectedText; - selectedText.asBinary = asBinary; - CopySelectionRange(&selectedText); - CopyToClipboard(selectedText); - } -} - -bool ScintillaWin::CanPaste() noexcept { - if (!Editor::CanPaste()) - return false; - return ::IsClipboardFormatAvailable(CF_UNICODETEXT); -} - -namespace { - -class GlobalMemory { - HGLOBAL hand {}; -public: - void *ptr {}; - GlobalMemory() noexcept = default; - explicit GlobalMemory(HGLOBAL hand_) noexcept : hand(hand_) { - if (hand) { - ptr = ::GlobalLock(hand); - } - } - // Deleted so GlobalMemory objects can not be copied. - GlobalMemory(const GlobalMemory &) = delete; - GlobalMemory(GlobalMemory &&) = delete; - GlobalMemory &operator=(const GlobalMemory &) = delete; - GlobalMemory &operator=(GlobalMemory &&) = delete; - ~GlobalMemory() { - assert(!ptr); - assert(!hand); - } - void Allocate(size_t bytes) noexcept { - assert(!hand); - hand = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes); - if (hand) { - ptr = ::GlobalLock(hand); - } - } - HGLOBAL Unlock() noexcept { - assert(ptr); - HGLOBAL handCopy = hand; - ::GlobalUnlock(hand); - ptr = nullptr; - hand = nullptr; - return handCopy; - } - void SetClip(UINT uFormat) noexcept { - ::SetClipboardData(uFormat, Unlock()); - } - operator bool() const noexcept { - return ptr != nullptr; - } - SIZE_T Size() noexcept { - return ::GlobalSize(hand); - } -}; - -// OpenClipboard may fail if another application has opened the clipboard. -// Try up to 8 times, with an initial delay of 1 ms and an exponential back off -// for a maximum total delay of 127 ms (1+2+4+8+16+32+64). -bool OpenClipboardRetry(HWND hwnd) noexcept { - for (int attempt = 0; attempt < 8; attempt++) { - if (attempt > 0) { - ::Sleep(1 << (attempt - 1)); - } - if (::OpenClipboard(hwnd)) { - return true; - } - } - return false; -} - -inline bool IsValidFormatEtc(const FORMATETC *pFE) noexcept { - return pFE->ptd == nullptr - && (pFE->dwAspect & DVASPECT_CONTENT) != 0 - && pFE->lindex == -1 - && (pFE->tymed & TYMED_HGLOBAL) != 0; -} - -inline bool SupportedFormat(const FORMATETC *pFE) noexcept { - return (pFE->cfFormat == CF_UNICODETEXT || pFE->cfFormat == CF_TEXT) - && IsValidFormatEtc(pFE); -} - -} - -void ScintillaWin::Paste(bool asBinary) { - if (!::OpenClipboardRetry(MainHWND())) { - return; - } - - UndoGroup ug(pdoc); - //EnumAllClipboardFormat("Paste"); - const bool isLine = SelectionEmpty() && - (::IsClipboardFormatAvailable(cfLineSelect) || ::IsClipboardFormatAvailable(cfVSLineTag)); - ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH); - bool isRectangular = (::IsClipboardFormatAvailable(cfColumnSelect) != 0); - - if (!isRectangular) { - // Evaluate "Borland IDE Block Type" explicitly - GlobalMemory memBorlandSelection(::GetClipboardData(cfBorlandIDEBlockType)); - if (memBorlandSelection) { - isRectangular = (memBorlandSelection.Size() == 1) && (static_cast(memBorlandSelection.ptr)[0] == 0x02); - memBorlandSelection.Unlock(); - } - } - const PasteShape pasteShape = isRectangular ? PasteShape::rectangular : (isLine ? PasteShape::line : PasteShape::stream); - - if (asBinary) { - // get data with CF_TEXT, decode and verify length information - if (!asBinary) { - ::CloseClipboard(); - Redraw(); - return; - } - } - - // Use CF_UNICODETEXT if available - GlobalMemory memUSelection(::GetClipboardData(CF_UNICODETEXT)); - if (const wchar_t *uptr = static_cast(memUSelection.ptr)) { - const std::string putf = EncodeWString(uptr); - InsertPasteShape(putf.c_str(), putf.length(), pasteShape); - memUSelection.Unlock(); - } - ::CloseClipboard(); - Redraw(); -} - -void ScintillaWin::CreateCallTipWindow(PRectangle) noexcept { - if (!ct.wCallTip.Created()) { - HWND wnd = ::CreateWindow(callClassName, L"ACallTip", - WS_POPUP, 100, 100, 150, 20, - MainHWND(), nullptr, - GetWindowInstance(MainHWND()), - this); - ct.wCallTip = wnd; - ct.wDraw = wnd; - } -} - -#if SCI_EnablePopupMenu -void ScintillaWin::AddToPopUp(const char *label, int cmd, bool enabled) noexcept { - HMENU hmenuPopup = static_cast(popup.GetID()); - if (!label[0]) - ::AppendMenuA(hmenuPopup, MF_SEPARATOR, 0, ""); - else if (enabled) - ::AppendMenuA(hmenuPopup, MF_STRING, cmd, label); - else - ::AppendMenuA(hmenuPopup, MF_STRING | MF_DISABLED | MF_GRAYED, cmd, label); -} -#endif - -void ScintillaWin::ClaimSelection() noexcept { - // Windows does not have a primary selection -} - -/// Implement IUnknown -STDMETHODIMP FormatEnumerator::QueryInterface(REFIID riid, PVOID *ppv) noexcept { - //Platform::DebugPrintf("EFE QI"); - *ppv = nullptr; - if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) { - *ppv = this; - } else { - return E_NOINTERFACE; - } - AddRef(); - return S_OK; -} -STDMETHODIMP_(ULONG)FormatEnumerator::AddRef() noexcept { - return ++ref; -} -STDMETHODIMP_(ULONG)FormatEnumerator::Release() noexcept { - const ULONG refs = --ref; - if (refs == 0) { - delete this; - } - return refs; -} - -/// Implement IEnumFORMATETC -STDMETHODIMP FormatEnumerator::Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) noexcept { - if (!rgelt) return E_POINTER; - ULONG putPos = 0; - while ((pos < formats.size()) && (putPos < celt)) { - rgelt->cfFormat = formats[pos]; - rgelt->ptd = nullptr; - rgelt->dwAspect = DVASPECT_CONTENT; - rgelt->lindex = -1; - rgelt->tymed = TYMED_HGLOBAL; - rgelt++; - pos++; - putPos++; - } - if (pceltFetched) - *pceltFetched = putPos; - return putPos ? S_OK : S_FALSE; -} -STDMETHODIMP FormatEnumerator::Skip(ULONG celt) noexcept { - pos += celt; - return S_OK; -} -STDMETHODIMP FormatEnumerator::Reset() noexcept { - pos = 0; - return S_OK; -} -STDMETHODIMP FormatEnumerator::Clone(IEnumFORMATETC **ppenum) { - FormatEnumerator *pfe; - try { - pfe = new FormatEnumerator(pos, formats.data(), formats.size()); - } catch (...) { - return E_OUTOFMEMORY; - } - return pfe->QueryInterface(IID_IEnumFORMATETC, reinterpret_cast(ppenum)); -} - -FormatEnumerator::FormatEnumerator(ULONG pos_, const CLIPFORMAT formats_[], size_t formatsLen_) { - ref = 0; // First QI adds first reference... - pos = pos_; - formats.insert(formats.begin(), formats_, formats_ + formatsLen_); -} - -/// Implement IUnknown -STDMETHODIMP DropSource::QueryInterface(REFIID riid, PVOID *ppv) noexcept { - return sci->QueryInterface(riid, ppv); -} -STDMETHODIMP_(ULONG)DropSource::AddRef() noexcept { - return sci->AddRef(); -} -STDMETHODIMP_(ULONG)DropSource::Release() noexcept { - return sci->Release(); -} - -/// Implement IDropSource -STDMETHODIMP DropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) noexcept { - if (fEsc) - return DRAGDROP_S_CANCEL; - if (!(grfKeyState & MK_LBUTTON)) - return DRAGDROP_S_DROP; - return S_OK; -} -STDMETHODIMP DropSource::GiveFeedback(DWORD) noexcept { - return DRAGDROP_S_USEDEFAULTCURSORS; -} - -/// Implement IUnkown -STDMETHODIMP DataObject::QueryInterface(REFIID riid, PVOID *ppv) noexcept { - //Platform::DebugPrintf("DO QI %p\n", this); - return sci->QueryInterface(riid, ppv); -} -STDMETHODIMP_(ULONG)DataObject::AddRef() noexcept { - return sci->AddRef(); -} -STDMETHODIMP_(ULONG)DataObject::Release() noexcept { - return sci->Release(); -} - -/// Implement IDataObject -STDMETHODIMP DataObject::GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) { - return sci->GetData(pFEIn, pSTM); -} - -STDMETHODIMP DataObject::GetDataHere(FORMATETC *, STGMEDIUM *) noexcept { - //Platform::DebugPrintf("DOB GetDataHere\n"); - return E_NOTIMPL; -} - -STDMETHODIMP DataObject::QueryGetData(FORMATETC *pFE) noexcept { - if (sci->DragIsRectangularOK(pFE->cfFormat) && IsValidFormatEtc(pFE)) { - return S_OK; - } - - return SupportedFormat(pFE)? S_OK : S_FALSE; -} - -STDMETHODIMP DataObject::GetCanonicalFormatEtc(FORMATETC *, FORMATETC *pFEOut) noexcept { - //Platform::DebugPrintf("DOB GetCanon\n"); - pFEOut->cfFormat = CF_UNICODETEXT; - pFEOut->ptd = nullptr; - pFEOut->dwAspect = DVASPECT_CONTENT; - pFEOut->lindex = -1; - pFEOut->tymed = TYMED_HGLOBAL; - return S_OK; -} - -STDMETHODIMP DataObject::SetData(FORMATETC *, STGMEDIUM *, BOOL) noexcept { - //Platform::DebugPrintf("DOB SetData\n"); - return E_FAIL; -} - -STDMETHODIMP DataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnum) { - try { - //Platform::DebugPrintf("DOB EnumFormatEtc %lu\n", dwDirection); - if (dwDirection != DATADIR_GET) { - *ppEnum = nullptr; - return E_FAIL; - } - - const CLIPFORMAT formats[] = { CF_UNICODETEXT, CF_TEXT }; - FormatEnumerator *pfe = new FormatEnumerator(0, formats, std::size(formats)); - return pfe->QueryInterface(IID_IEnumFORMATETC, reinterpret_cast(ppEnum)); - } catch (std::bad_alloc &) { - sci->errorStatus = SC_STATUS_BADALLOC; - return E_OUTOFMEMORY; - } catch (...) { - sci->errorStatus = SC_STATUS_FAILURE; - return E_FAIL; - } -} - -STDMETHODIMP DataObject::DAdvise(FORMATETC *, DWORD, IAdviseSink *, PDWORD) noexcept { - //Platform::DebugPrintf("DOB DAdvise\n"); - return E_FAIL; -} - -STDMETHODIMP DataObject::DUnadvise(DWORD) noexcept { - //Platform::DebugPrintf("DOB DUnadvise\n"); - return E_FAIL; -} - -STDMETHODIMP DataObject::EnumDAdvise(IEnumSTATDATA **) noexcept { - //Platform::DebugPrintf("DOB EnumDAdvise\n"); - return E_FAIL; -} - -/// Implement IUnknown -STDMETHODIMP DropTarget::QueryInterface(REFIID riid, PVOID *ppv) noexcept { - //Platform::DebugPrintf("DT QI %p\n", this); - return sci->QueryInterface(riid, ppv); -} -STDMETHODIMP_(ULONG)DropTarget::AddRef() noexcept { - return sci->AddRef(); -} -STDMETHODIMP_(ULONG)DropTarget::Release() noexcept { - return sci->Release(); -} - -/// Implement IDropTarget by forwarding to Scintilla -STDMETHODIMP DropTarget::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { - return sci->DragEnter(pIDataSource, grfKeyState, pt, pdwEffect); -} -STDMETHODIMP DropTarget::DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { - return sci->DragOver(grfKeyState, pt, pdwEffect); -} -STDMETHODIMP DropTarget::DragLeave() { - return sci->DragLeave(); -} -STDMETHODIMP DropTarget::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { - return sci->Drop(pIDataSource, grfKeyState, pt, pdwEffect); -} - -/** - * DBCS: support Input Method Editor (IME). - * Called when IME Window opened. - */ -void ScintillaWin::ImeStartComposition() { - if (caret.active) { - // Move IME Window to current caret position - IMContext imc(MainHWND()); - const Point pos = PointMainCaret(); - COMPOSITIONFORM CompForm; - CompForm.dwStyle = CFS_POINT; - CompForm.ptCurrentPos = POINTFromPoint(pos); - - ::ImmSetCompositionWindow(imc.hIMC, &CompForm); - - // Set font of IME window to same as surrounded text. - if (stylesValid) { - // Since the style creation code has been made platform independent, - // The logfont for the IME is recreated here. - const int styleHere = pdoc->StyleIndexAt(sel.MainCaret()); - LOGFONTW lf {}; - const int sizeZoomed = GetFontSizeZoomed(vs.styles[styleHere].size, vs.zoomLevel); - // The negative is to allow for leading - lf.lfHeight = -::MulDiv(sizeZoomed, dpi.y, 72 * SC_FONT_SIZE_MULTIPLIER); - lf.lfWidth = 0; // vs.styles[styleHere].stretch ? - lf.lfWeight = vs.styles[styleHere].weight; - lf.lfItalic = vs.styles[styleHere].italic ? TRUE : FALSE; - lf.lfCharSet = DEFAULT_CHARSET; - lf.lfFaceName[0] = L'\0'; - if (vs.styles[styleHere].fontName) { - const char* fontName = vs.styles[styleHere].fontName; - UTF16FromUTF8(std::string_view(fontName), lf.lfFaceName, LF_FACESIZE); - } - - ::ImmSetCompositionFontW(imc.hIMC, &lf); - } - // Caret is displayed in IME window. So, caret in Scintilla is useless. - DropCaret(); - } -} - -/** Called when IME Window closed. -* TODO: see Chromium's InputMethodWinImm32::OnImeEndComposition(). -*/ -void ScintillaWin::ImeEndComposition() { - // clear IME composition state. - view.imeCaretBlockOverride = false; - pdoc->TentativeUndo(); - ShowCaretAtCurrentPosition(); -} - -LRESULT ScintillaWin::ImeOnReconvert(LPARAM lParam) { - // Reconversion on windows limits within one line without eol. - // Look around: baseStart <-- (|mainStart| -- mainEnd) --> baseEnd. - const Sci::Position mainStart = sel.RangeMain().Start().Position(); - const Sci::Position mainEnd = sel.RangeMain().End().Position(); - const Sci::Line curLine = pdoc->SciLineFromPosition(mainStart); - if (curLine != pdoc->LineFromPosition(mainEnd)) - return 0; - const Sci::Position baseStart = pdoc->LineStart(curLine); - const Sci::Position baseEnd = pdoc->LineEnd(curLine); - if ((baseStart == baseEnd) || (mainEnd > baseEnd)) - return 0; - - const UINT codePage = CodePageOfDocument(); - const std::wstring rcFeed = StringDecode(RangeText(baseStart, baseEnd), codePage); - const DWORD rcFeedLen = static_cast(rcFeed.length()) * sizeof(wchar_t); - const DWORD rcSize = sizeof(RECONVERTSTRING) + rcFeedLen + sizeof(wchar_t); - - RECONVERTSTRING *rc = static_cast(PtrFromSPtr(lParam)); - if (!rc) - return rcSize; // Immediately be back with rcSize of memory block. - - wchar_t *rcFeedStart = reinterpret_cast(rc + 1); - memcpy(rcFeedStart, rcFeed.data(), rcFeedLen); - - std::string rcCompString = RangeText(mainStart, mainEnd); - std::wstring rcCompWstring = StringDecode(rcCompString, codePage); - std::string rcCompStart = RangeText(baseStart, mainStart); - std::wstring rcCompWstart = StringDecode(rcCompStart, codePage); - - // Map selection to dwCompStr. - // No selection assumes current caret as rcCompString without length. - rc->dwVersion = 0; // It should be absolutely 0. - rc->dwStrLen = static_cast(rcFeed.length()); - rc->dwStrOffset = sizeof(RECONVERTSTRING); - rc->dwCompStrLen = static_cast(rcCompWstring.length()); - rc->dwCompStrOffset = static_cast(rcCompWstart.length()) * sizeof(wchar_t); - rc->dwTargetStrLen = rc->dwCompStrLen; - rc->dwTargetStrOffset = rc->dwCompStrOffset; - - IMContext imc(MainHWND()); - if (!imc.hIMC) - return 0; - - if (!::ImmSetCompositionStringW(imc.hIMC, SCS_QUERYRECONVERTSTRING, rc, rcSize, nullptr, 0)) - return 0; - - // No selection asks IME to fill target fields with its own value. - const DWORD tgWlen = rc->dwTargetStrLen; - const DWORD tgWstart = rc->dwTargetStrOffset / sizeof(wchar_t); - - std::string tgCompStart = StringEncode(rcFeed.substr(0, tgWstart), codePage); - std::string tgComp = StringEncode(rcFeed.substr(tgWstart, tgWlen), codePage); - - // No selection needs to adjust reconvert start position for IME set. - const Sci::Position adjust = tgCompStart.length() - rcCompStart.length(); - const Sci::Position docCompLen = tgComp.length(); - - // Make place for next composition string to sit in. - for (size_t r = 0; r < sel.Count(); r++) { - const Sci::Position rBase = sel.Range(r).Start().Position(); - const Sci::Position docCompStart = rBase + adjust; - - if (inOverstrike) { // the docCompLen of bytes will be overstriked. - sel.Range(r).caret.SetPosition(docCompStart); - sel.Range(r).anchor.SetPosition(docCompStart); - } else { - // Ensure docCompStart+docCompLen be not beyond lineEnd. - // since docCompLen by byte might break eol. - const Sci::Position lineEnd = pdoc->LineEnd(pdoc->LineFromPosition(rBase)); - const Sci::Position overflow = (docCompStart + docCompLen) - lineEnd; - if (overflow > 0) { - pdoc->DeleteChars(docCompStart, docCompLen - overflow); - } else { - pdoc->DeleteChars(docCompStart, docCompLen); - } - } - } - // Immediately Target Input or candidate box choice with GCS_COMPSTR. - return rcSize; -} - -void ScintillaWin::GetIntelliMouseParameters() noexcept { - // This retrieves the number of lines per scroll as configured in the Mouse Properties sheet in Control Panel - ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0); - ::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &charsPerScroll, 0); -} - -void ScintillaWin::CopyToGlobal(GlobalMemory &gmUnicode, const SelectionText &selectedText, CopyEncoding encoding) { - const std::string_view svSelected(selectedText.Data(), selectedText.LengthWithTerminator()); - switch (encoding) { - case CopyEncoding::Unicode: - if (IsUnicodeMode()) { - const size_t uchars = UTF16Length(svSelected); - gmUnicode.Allocate(2 * uchars); - if (gmUnicode) { - UTF16FromUTF8(svSelected, static_cast(gmUnicode.ptr), uchars); - } - } else { - // Not Unicode mode - // Convert to Unicode using the current Scintilla code page - const UINT cpSrc = CodePageFromCharSet(selectedText.characterSet, selectedText.codePage); - const size_t uLen = WideCharLenFromMultiByte(cpSrc, svSelected); - gmUnicode.Allocate(2 * uLen); - if (gmUnicode) { - WideCharFromMultiByte(cpSrc, svSelected, static_cast(gmUnicode.ptr), uLen); - } - } - break; - - case CopyEncoding::Ansi: { - std::string s; - if (IsUnicodeMode()) { - const std::wstring wsv = StringDecode(svSelected, CP_UTF8); - s = StringEncode(wsv, CP_ACP); - } else { - // no need to convert selectedText to CP_ACP - s = svSelected; - } - gmUnicode.Allocate(s.size() + 1); - if (gmUnicode) { - memcpy(gmUnicode.ptr, s.c_str(), s.size()); - } - } - break; - - case CopyEncoding::Binary: - gmUnicode.Allocate(svSelected.size()); - if (gmUnicode) { - memcpy(gmUnicode.ptr, svSelected.data(), svSelected.size() - 1); - } - break; - } -} - -void ScintillaWin::CopyToClipboard(const SelectionText &selectedText) { - if (!::OpenClipboardRetry(MainHWND())) { - return; - } - ::EmptyClipboard(); - - GlobalMemory uniText; - CopyToGlobal(uniText, selectedText, selectedText.asBinary ? CopyEncoding::Binary : CopyEncoding::Unicode); - - if (uniText) { - uniText.SetClip(selectedText.asBinary ? CF_TEXT : CF_UNICODETEXT); - - if (selectedText.asBinary) { - // encode length information - } - } - - if (selectedText.rectangular) { - ::SetClipboardData(cfColumnSelect, nullptr); - - GlobalMemory borlandSelection; - borlandSelection.Allocate(1); - if (borlandSelection) { - static_cast(borlandSelection.ptr)[0] = 0x02; - borlandSelection.SetClip(cfBorlandIDEBlockType); - } - } - - if (selectedText.lineCopy) { - ::SetClipboardData(cfLineSelect, nullptr); - ::SetClipboardData(cfVSLineTag, nullptr); - } - - ::CloseClipboard(); - - // TODO: notify data loss - //if (!selectedText.asBinary && ) { - //} -} - -void ScintillaWin::ScrollMessage(WPARAM wParam) { - //DWORD dwStart = GetTickCount(); - //Platform::DebugPrintf("Scroll %x %d\n", wParam, lParam); - - SCROLLINFO sci = {}; - sci.cbSize = sizeof(sci); - sci.fMask = SIF_ALL; - - GetScrollInfo(SB_VERT, &sci); - - //Platform::DebugPrintf("ScrollInfo %d mask=%x min=%d max=%d page=%d pos=%d track=%d\n", b, sci.fMask, - //sci.nMin, sci.nMax, sci.nPage, sci.nPos, sci.nTrackPos); - Sci::Line topLineNew = topLine; - switch (LOWORD(wParam)) { - case SB_LINEUP: - topLineNew -= 1; - break; - case SB_LINEDOWN: - topLineNew += 1; - break; - case SB_PAGEUP: - topLineNew -= LinesToScroll(); break; - case SB_PAGEDOWN: topLineNew += LinesToScroll(); break; - case SB_TOP: topLineNew = 0; break; - case SB_BOTTOM: topLineNew = MaxScrollPos(); break; - case SB_THUMBPOSITION: topLineNew = sci.nTrackPos; break; - case SB_THUMBTRACK: topLineNew = sci.nTrackPos; break; - } - ScrollTo(topLineNew); -} - -void ScintillaWin::HorizontalScrollMessage(WPARAM wParam) { - int xPos = xOffset; - const PRectangle rcText = GetTextRectangle(); - const int pageWidth = static_cast(rcText.Width() * 2 / 3); - switch (LOWORD(wParam)) { - case SB_LINEUP: - xPos -= 20; - break; - case SB_LINEDOWN: // May move past the logical end - xPos += 20; - break; - case SB_PAGEUP: - xPos -= pageWidth; - break; - case SB_PAGEDOWN: - xPos += pageWidth; - if (xPos > scrollWidth - rcText.Width()) { // Hit the end exactly - xPos = scrollWidth - static_cast(rcText.Width()); - } - break; - case SB_TOP: - xPos = 0; - break; - case SB_BOTTOM: - xPos = scrollWidth; - break; - case SB_THUMBPOSITION: - case SB_THUMBTRACK: { - // Do NOT use wParam, its 16 bit and not enough for very long lines. Its still possible to overflow the 32 bit but you have to try harder =] - SCROLLINFO si; - si.cbSize = sizeof(si); - si.fMask = SIF_TRACKPOS; - if (GetScrollInfo(SB_HORZ, &si)) { - xPos = si.nTrackPos; - } - } - break; - } - HorizontalScrollTo(xPos); -} - -/** - * Redraw all of text area. - * This paint will not be abandoned. - */ -void ScintillaWin::FullPaint() { - if ((technology == SC_TECHNOLOGY_DEFAULT) || (technology == SC_TECHNOLOGY_DIRECTWRITEDC)) { - HDC hdc = ::GetDC(MainHWND()); - FullPaintDC(hdc); - ::ReleaseDC(MainHWND(), hdc); - } else { - FullPaintDC({}); - } -} - -/** - * Redraw all of text area on the specified DC. - * This paint will not be abandoned. - */ -void ScintillaWin::FullPaintDC(HDC hdc) { - paintState = PaintState::painting; - rcPaint = GetClientRectangle(); - paintingAllText = true; - PaintDC(hdc); - paintState = PaintState::notPainting; -} - -namespace { - -inline bool CompareDevCap(HDC hdc, HDC hOtherDC, int nIndex) noexcept { - return ::GetDeviceCaps(hdc, nIndex) == ::GetDeviceCaps(hOtherDC, nIndex); -} - -} - -bool ScintillaWin::IsCompatibleDC(HDC hOtherDC) const noexcept { - HDC hdc = ::GetDC(MainHWND()); - const bool isCompatible = - CompareDevCap(hdc, hOtherDC, TECHNOLOGY) && - CompareDevCap(hdc, hOtherDC, LOGPIXELSY) && - CompareDevCap(hdc, hOtherDC, LOGPIXELSX) && - CompareDevCap(hdc, hOtherDC, BITSPIXEL) && - CompareDevCap(hdc, hOtherDC, PLANES); - ::ReleaseDC(MainHWND(), hdc); - return isCompatible; -} - -// https://docs.microsoft.com/en-us/windows/desktop/api/oleidl/nf-oleidl-idroptarget-dragenter -DWORD ScintillaWin::EffectFromState(DWORD grfKeyState) const noexcept { - // These are the Wordpad semantics. - // DROPEFFECT_COPY not works for some applications like Github Atom. - DWORD dwEffect = DROPEFFECT_MOVE; -#if 0 - if (inDragDrop == DragDrop::dragging) // Internal defaults to move - dwEffect = DROPEFFECT_MOVE; - else - dwEffect = DROPEFFECT_COPY; - if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) - dwEffect = DROPEFFECT_LINK; - else - if (grfKeyState & (MK_ALT | MK_SHIFT)) - dwEffect = DROPEFFECT_MOVE; - else -#endif - if (grfKeyState & MK_CONTROL) - dwEffect = DROPEFFECT_COPY; - return dwEffect; -} - -/// Implement IUnknown -STDMETHODIMP ScintillaWin::QueryInterface(REFIID riid, PVOID *ppv) noexcept { - *ppv = nullptr; - if (riid == IID_IUnknown) { - *ppv = &dt; - } else if (riid == IID_IDropSource) { - *ppv = &ds; - } else if (riid == IID_IDropTarget) { - *ppv = &dt; - } else if (riid == IID_IDataObject) { - *ppv = &dob; - } - if (!*ppv) { - return E_NOINTERFACE; - } - return S_OK; -} - -STDMETHODIMP_(ULONG) ScintillaWin::AddRef() noexcept { - return 1; -} - -STDMETHODIMP_(ULONG) ScintillaWin::Release() noexcept { - return 1; -} - -#if DebugDragAndDropDataFormat - -namespace { - -const char* GetStorageMediumType(DWORD tymed) noexcept { - switch (tymed) { - case TYMED_HGLOBAL: - return "TYMED_HGLOBAL"; - case TYMED_FILE: - return "TYMED_FILE"; - case TYMED_ISTREAM: - return "TYMED_ISTREAM"; - case TYMED_ISTORAGE: - return "TYMED_ISTORAGE"; - default: - return "Unknown"; - } -} - -const char* GetSourceFormatName(UINT fmt, char name[], int cchName) noexcept { - const int len = GetClipboardFormatNameA(fmt, name, cchName); - if (len <= 0) { - switch (fmt) { - case CF_TEXT: - return "CF_TEXT"; - case CF_UNICODETEXT: - return "CF_UNICODETEXT"; - case CF_HDROP: - return "CF_HDROP"; - case CF_LOCALE: - return "CF_LOCALE"; - case CF_OEMTEXT: - return "CF_OEMTEXT"; - default: - return "Unknown"; - } - } - - return name; -} - -} - -// https://docs.microsoft.com/en-us/windows/desktop/api/objidl/nf-objidl-idataobject-enumformatetc -void ScintillaWin::EnumDataSourceFormat(const char *tag, LPDATAOBJECT pIDataSource) { - IEnumFORMATETC *fmtEnum = nullptr; - HRESULT hr = pIDataSource->EnumFormatEtc(DATADIR_GET, &fmtEnum); - if (hr == S_OK && fmtEnum) { - FORMATETC fetc[32] = {}; - ULONG fetched = 0; - hr = fmtEnum->Next(sizeof(fetc) / sizeof(fetc[0]), fetc, &fetched); - if (fetched > 0) { - char name[1024]; - char buf[2048]; - for (ULONG i = 0; i < fetched; i++) { - const CLIPFORMAT fmt = fetc[i].cfFormat; - const DWORD tymed = fetc[i].tymed; - const char *typeName = GetStorageMediumType(tymed); - const char *fmtName = GetSourceFormatName(fmt, name, sizeof(name)); - const int len = sprintf(buf, "%s: fmt[%lu]=%u, 0x%04X; tymed=%lu, %s; name=%s\n", - tag, i, fmt, fmt, tymed, typeName, fmtName); - InsertCharacter(std::string_view(buf, len), CharacterSource::tentativeInput); - } - } - } - if (fmtEnum) { - fmtEnum->Release(); - } -} - -// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumclipboardformats -void ScintillaWin::EnumAllClipboardFormat(const char *tag) { - UINT fmt = 0; - unsigned int i = 0; - char name[1024]; - char buf[2048]; - while ((fmt = ::EnumClipboardFormats(fmt)) != 0) { - const char *fmtName = GetSourceFormatName(fmt, name, sizeof(name)); - const int len = sprintf(buf, "%s: fmt[%u]=%u, 0x%04X; name=%s\n", - tag, i, fmt, fmt, fmtName); - InsertCharacter(std::string_view(buf, len), CharacterSource::tentativeInput); - i++; - } -} - -#endif - -/// Implement IDropTarget -STDMETHODIMP ScintillaWin::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL, PDWORD pdwEffect) { - if (!pIDataSource) { - return E_POINTER; - } - - hasOKText = false; - try { - //EnumDataSourceFormat("DragEnter", pIDataSource); - - for (UINT fmtIndex = 0; fmtIndex < dropFormatCount; fmtIndex++) { - const CLIPFORMAT fmt = dropFormat[fmtIndex]; - FORMATETC fmtu = { fmt, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - const HRESULT hrHasUText = pIDataSource->QueryGetData(&fmtu); - hasOKText = (hrHasUText == S_OK); - if (hasOKText) { - break; - } - } - } catch (...) { - errorStatus = SC_STATUS_FAILURE; - } - - *pdwEffect = hasOKText? EffectFromState(grfKeyState) : DROPEFFECT_NONE; - return S_OK; -} - -STDMETHODIMP ScintillaWin::DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { - try { - if (!hasOKText || pdoc->IsReadOnly()) { - *pdwEffect = DROPEFFECT_NONE; - return S_OK; - } - - *pdwEffect = EffectFromState(grfKeyState); - - // Update the cursor. - POINT rpt = { pt.x, pt.y }; - ::ScreenToClient(MainHWND(), &rpt); - SetDragPosition(SPositionFromLocation(PointFromPOINT(rpt), false, false, UserVirtualSpace())); - - return S_OK; - } catch (...) { - errorStatus = SC_STATUS_FAILURE; - } - return E_FAIL; -} - -STDMETHODIMP ScintillaWin::DragLeave() { - try { - SetDragPosition(SelectionPosition(Sci::invalidPosition)); - return S_OK; - } catch (...) { - errorStatus = SC_STATUS_FAILURE; - } - return E_FAIL; -} - -STDMETHODIMP ScintillaWin::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) { - try { - *pdwEffect = EffectFromState(grfKeyState); - - if (!pIDataSource) { - return E_POINTER; - } - - SetDragPosition(SelectionPosition(Sci::invalidPosition)); - - std::string putf; - bool fileDrop = false; - HRESULT hr = DV_E_FORMATETC; - - //EnumDataSourceFormat("Drop", pIDataSource); - for (UINT fmtIndex = 0; fmtIndex < dropFormatCount; fmtIndex++) { - const CLIPFORMAT fmt = dropFormat[fmtIndex]; - FORMATETC fmtu = { fmt, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - STGMEDIUM medium {}; - hr = pIDataSource->GetData(&fmtu, &medium); - - if (SUCCEEDED(hr) && medium.hGlobal) { - // File Drop - if (fmt == CF_HDROP -#if EnableDrop_VisualStudioProjectItem - || fmt == cfVSStgProjectItem || fmt == cfVSRefProjectItem -#endif - ) { - WCHAR pathDropped[1024]; - HDROP hDrop = static_cast(medium.hGlobal); - if (::DragQueryFileW(hDrop, 0, pathDropped, sizeof(pathDropped)/sizeof(WCHAR)) > 0) { - WCHAR *p = pathDropped; -#if EnableDrop_VisualStudioProjectItem - if (fmt == cfVSStgProjectItem || fmt == cfVSRefProjectItem) { - // {UUID}|Solution\Project.[xx]proj|path - WCHAR *t = StrRChrW(p, nullptr, L'|'); - if (t) { - p = t + 1; - } - } -#endif - putf = StringEncode(p, CP_UTF8); - fileDrop = true; - } - // TODO: This seems not required, MSDN only says it need be called in WM_DROPFILES - ::DragFinish(hDrop); - } -#if Enable_ChromiumWebCustomMIMEDataFormat - else if (fmt == cfChromiumCustomMIME) { - GlobalMemory memUDrop(medium.hGlobal); - if (const wchar_t *uptr = static_cast(memUDrop.ptr)) { - const std::wstring_view wsv(uptr, memUDrop.Size() / 2); - // parse file url: "resource":"file:///path" - const size_t dataLen = UTF8Length(wsv); - } - memUDrop.Unlock(); - } -#endif - // Unicode Text - else if (fmt == CF_UNICODETEXT) { - GlobalMemory memUDrop(medium.hGlobal); - if (const wchar_t *uptr = static_cast(memUDrop.ptr)) { - putf = EncodeWString(uptr); - } - memUDrop.Unlock(); - } - // ANSI Text - else if (fmt == CF_TEXT) { - GlobalMemory memDrop(medium.hGlobal); - if (const char *ptr = static_cast(memDrop.ptr)) { - const std::string_view sv(ptr, strnlen(ptr, memDrop.Size())); - // In Unicode mode, convert text to UTF-8 - if (IsUnicodeMode()) { - const std::wstring wsv = StringDecode(sv, CP_ACP); - putf = StringEncode(wsv, CP_UTF8); - } else { - // no need to convert ptr from CP_ACP to CodePageOfDocument(). - putf = sv; - } - } - memDrop.Unlock(); - } - } - - ::ReleaseStgMedium(&medium); - if (!putf.empty()) { - break; - } - } - - if (!SUCCEEDED(hr)) { - return hr; - } - if (putf.empty()) { - return S_OK; - } - - if (fileDrop) { - NotifyURIDropped(putf.c_str()); - } else { - FORMATETC fmtr = { cfColumnSelect, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - const bool isRectangular = S_OK == pIDataSource->QueryGetData(&fmtr); - - POINT rpt = { pt.x, pt.y }; - ::ScreenToClient(MainHWND(), &rpt); - const SelectionPosition movePos = SPositionFromLocation(PointFromPOINT(rpt), false, false, UserVirtualSpace()); - - DropAt(movePos, putf.c_str(), putf.size(), *pdwEffect == DROPEFFECT_MOVE, isRectangular); - } - - return S_OK; - } catch (...) { - errorStatus = SC_STATUS_FAILURE; - } - return E_FAIL; -} - -/// Implement important part of IDataObject -STDMETHODIMP ScintillaWin::GetData(const FORMATETC *pFEIn, STGMEDIUM *pSTM) { - if (!SupportedFormat(pFEIn)) { - //Platform::DebugPrintf("DOB GetData No %d %x %x fmt=%x\n", lenDrag, pFEIn, pSTM, pFEIn->cfFormat); - return DATA_E_FORMATETC; - } - - pSTM->tymed = TYMED_HGLOBAL; - //Platform::DebugPrintf("DOB GetData OK %d %x %x\n", lenDrag, pFEIn, pSTM); - - GlobalMemory uniText; - CopyToGlobal(uniText, drag, (pFEIn->cfFormat == CF_TEXT)? CopyEncoding::Ansi : CopyEncoding::Unicode); - pSTM->hGlobal = uniText ? uniText.Unlock() : nullptr; - pSTM->pUnkForRelease = nullptr; - return S_OK; -} - -BOOL CALLBACK ScintillaWin::PrepareOnce(PINIT_ONCE initOnce, PVOID parameter, PVOID *lpContext) noexcept -{ -#if USE_WIN32_INIT_ONCE - UNREFERENCED_PARAMETER(initOnce); - UNREFERENCED_PARAMETER(parameter); - UNREFERENCED_PARAMETER(lpContext); -#endif - - Platform_Initialise(hInstance); - //~CharClassify::InitUnicodeData(); - - // Register the CallTip class - WNDCLASSEX wndclassc {}; - wndclassc.cbSize = sizeof(wndclassc); - wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; - wndclassc.cbWndExtra = sizeof(ScintillaWin *); - wndclassc.hInstance = hInstance; - wndclassc.lpfnWndProc = ScintillaWin::CTWndProc; - wndclassc.hCursor = ::LoadCursor({}, IDC_ARROW); - wndclassc.lpszClassName = callClassName; - - callClassAtom = ::RegisterClassEx(&wndclassc); - return TRUE; -} - -bool ScintillaWin::Register(HINSTANCE hInstance_) noexcept { - hInstance = hInstance_; - - // Register the Scintilla class - // Register Scintilla as a wide character window - WNDCLASSEXW wndclass {}; - wndclass.cbSize = sizeof(wndclass); - wndclass.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; - wndclass.lpfnWndProc = ScintillaWin::SWndProc; - wndclass.cbWndExtra = sizeof(ScintillaWin *); - wndclass.hInstance = hInstance; - wndclass.lpszClassName = L"Scintilla"; - scintillaClassAtom = ::RegisterClassExW(&wndclass); - - const bool result = 0 != scintillaClassAtom; - return result; -} - -bool ScintillaWin::Unregister() noexcept { - bool result = true; - if (0 != scintillaClassAtom) { - if (::UnregisterClass(MAKEINTATOM(scintillaClassAtom), hInstance) == 0) { - result = false; - } - scintillaClassAtom = 0; - } - if (0 != callClassAtom) { - if (::UnregisterClass(MAKEINTATOM(callClassAtom), hInstance) == 0) { - result = false; - } - callClassAtom = 0; - } - return result; -} - -bool ScintillaWin::HasCaretSizeChanged() const noexcept { - if ( - ((0 != vs.caretWidth) && (sysCaretWidth != vs.caretWidth)) - || ((0 != vs.lineHeight) && (sysCaretHeight != vs.lineHeight)) - ) { - return true; - } - return false; -} - -BOOL ScintillaWin::CreateSystemCaret() { - sysCaretWidth = vs.caretWidth; - if (0 == sysCaretWidth) { - sysCaretWidth = 1; - } - sysCaretHeight = vs.lineHeight; - const int bitmapSize = (((sysCaretWidth + 15) & ~15) >> 3) * - sysCaretHeight; - std::vector bits(bitmapSize); - sysCaretBitmap = ::CreateBitmap(sysCaretWidth, sysCaretHeight, 1, - 1, bits.data()); - const BOOL retval = ::CreateCaret( - MainHWND(), sysCaretBitmap, - sysCaretWidth, sysCaretHeight); - if (technology == SC_TECHNOLOGY_DEFAULT) { - // System caret interferes with Direct2D drawing so only show it for GDI. - ::ShowCaret(MainHWND()); - } - return retval; -} - -BOOL ScintillaWin::DestroySystemCaret() noexcept { - ::HideCaret(MainHWND()); - const BOOL retval = ::DestroyCaret(); - if (sysCaretBitmap) { - ::DeleteObject(sysCaretBitmap); - sysCaretBitmap = {}; - } - return retval; -} - -LRESULT CALLBACK ScintillaWin::CTWndProc( - HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - // Find C++ object associated with window. - ScintillaWin *sciThis = static_cast(PointerFromWindow(hWnd)); - try { - // ctp will be zero if WM_CREATE not seen yet - if (sciThis == nullptr) { - if (iMessage == WM_CREATE) { - // Associate CallTip object with window - CREATESTRUCT *pCreate = static_cast(PtrFromSPtr(lParam)); - SetWindowPointer(hWnd, pCreate->lpCreateParams); - return 0; - } else { - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } - } else { - if (iMessage == WM_NCDESTROY) { - SetWindowPointer(hWnd, nullptr); - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } else if (iMessage == WM_PAINT) { - PAINTSTRUCT ps; - ::BeginPaint(hWnd, &ps); - std::unique_ptr surfaceWindow(Surface::Allocate(sciThis->technology)); -#if defined(USE_D2D) - ID2D1HwndRenderTarget *pCTRenderTarget = nullptr; -#endif - RECT rc; - GetClientRect(hWnd, &rc); - // Create a Direct2D render target. - if (sciThis->technology == SC_TECHNOLOGY_DEFAULT) { - surfaceWindow->Init(ps.hdc, hWnd); - } else { -#if defined(USE_D2D) - D2D1_HWND_RENDER_TARGET_PROPERTIES dhrtp; - dhrtp.hwnd = hWnd; - dhrtp.pixelSize = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); - dhrtp.presentOptions = (sciThis->technology == SC_TECHNOLOGY_DIRECTWRITERETAIN) ? - D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS : D2D1_PRESENT_OPTIONS_NONE; - - D2D1_RENDER_TARGET_PROPERTIES drtp; - drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; - drtp.pixelFormat.format = DXGI_FORMAT_UNKNOWN; - drtp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN; - drtp.dpiX = 96.0; - drtp.dpiY = 96.0; - drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE; - drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; - - if (!SUCCEEDED(pD2DFactory->CreateHwndRenderTarget(drtp, dhrtp, &pCTRenderTarget))) { - surfaceWindow->Release(); - ::EndPaint(hWnd, &ps); - ReleaseUnknown(pCTRenderTarget); - return 0; - } - // If above SUCCEEDED, then pCTRenderTarget not nullptr - assert(pCTRenderTarget); - if (pCTRenderTarget) { - surfaceWindow->Init(pCTRenderTarget, hWnd); - pCTRenderTarget->BeginDraw(); - } -#endif - } - surfaceWindow->SetMode(SurfaceMode(sciThis->ct.codePage, sciThis->BidirectionalR2L())); - sciThis->ct.PaintCT(surfaceWindow.get()); -#if defined(USE_D2D) - if (pCTRenderTarget) - pCTRenderTarget->EndDraw(); -#endif - surfaceWindow->Release(); -#if defined(USE_D2D) - ReleaseUnknown(pCTRenderTarget); -#endif - ::EndPaint(hWnd, &ps); - return 0; - } else if ((iMessage == WM_NCLBUTTONDOWN) || (iMessage == WM_NCLBUTTONDBLCLK)) { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - sciThis->ct.MouseClick(PointFromPOINT(pt)); - sciThis->CallTipClick(); - return 0; - } else if (iMessage == WM_LBUTTONDOWN) { - // This does not fire due to the hit test code - sciThis->ct.MouseClick(PointFromLParam(lParam)); - sciThis->CallTipClick(); - return 0; - } else if (iMessage == WM_SETCURSOR) { - ::SetCursor(::LoadCursor({}, IDC_ARROW)); - return 0; - } else if (iMessage == WM_NCHITTEST) { - return HTCAPTION; - } else { - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } - } - } catch (...) { - sciThis->errorStatus = SC_STATUS_FAILURE; - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); -} - -sptr_t ScintillaWin::DirectFunction( - sptr_t ptr, UINT iMessage, uptr_t wParam, sptr_t lParam) { - PLATFORM_ASSERT(::GetCurrentThreadId() == ::GetWindowThreadProcessId(reinterpret_cast(ptr)->MainHWND(), nullptr)); - return reinterpret_cast(ptr)->WndProc(iMessage, wParam, lParam); -} - -#ifdef SCINTILLA_DLL -namespace Scintilla { - sptr_t DirectFunction(ScintillaWin* sci, UINT iMessage, uptr_t wParam, sptr_t lParam) { - return sci->WndProc(iMessage, wParam, lParam); - } -} -#else -extern "C" -sptr_t SCI_METHOD Scintilla_DirectFunction( - ScintillaWin* sci, UINT iMessage, uptr_t wParam, sptr_t lParam) { - return sci->WndProc(iMessage, wParam, lParam); -} -#endif - -LRESULT CALLBACK ScintillaWin::SWndProc( - HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - //Platform::DebugPrintf("S W:%x M:%x WP:%x L:%x\n", hWnd, iMessage, wParam, lParam); - - // Find C++ object associated with window. - ScintillaWin *sci = static_cast(PointerFromWindow(hWnd)); - // sci will be zero if WM_CREATE not seen yet - if (sci == nullptr) { - try { - if (iMessage == WM_CREATE) { - static INIT_ONCE once = INIT_ONCE_STATIC_INIT; - ::InitOnceExecuteOnce(&once, PrepareOnce, nullptr, nullptr); - // Create C++ object associated with window - sci = new ScintillaWin(hWnd); - SetWindowPointer(hWnd, sci); - return sci->WndProc(iMessage, wParam, lParam); - } - } catch (...) { - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } else { - if (iMessage == WM_NCDESTROY) { - try { - //sci->Finalise(); - delete sci; // Finalise() in d'tor - } catch (...) { - } - SetWindowPointer(hWnd, nullptr); - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } else { - return sci->WndProc(iMessage, wParam, lParam); - } - } -} - -namespace Scintilla { - -// This function is externally visible so it can be called from container when building statically. -// Must be called once only. -extern "C" __declspec(dllexport) -int Scintilla_RegisterClasses(void *hInstance) { - -#if _WIN32_WINNT < _WIN32_WINNT_WIN8 - // see LoadD2D() in PlatWin.cxx - kSystemLibraryLoadFlags = (IsWindows8Point1OrGreater() || GetProcAddress(GetModuleHandle(L"kernel32.dll"), "SetDefaultDllDirectories")) ? LOAD_LIBRARY_SEARCH_SYSTEM32 : 0; -#endif - - Platform_Initialise(hInstance); - - const bool result = ScintillaWin::Register(static_cast(hInstance)); - //@@@CharClassify::InitUnicodeData(); - - return result; -} - - int ResourcesRelease(bool fromDllMain) noexcept { - const bool result = ScintillaWin::Unregister(); - Platform_Finalise(fromDllMain); - return result; - } - - -// This function is externally visible so it can be called from container when building statically. -extern "C" __declspec(dllexport) -int Scintilla_ReleaseResources(void) { - return Scintilla::ResourcesRelease(false); -} - -extern "C" __declspec(dllexport) -int Scintilla_InputCodePage(void) { - return InputCodePage(); -} - -extern "C" __declspec(dllexport) -DPI_T Scintilla_GetWindowDPI(void* hwnd) { - return GetWindowDPI(static_cast(hwnd)); -} - -extern "C" __declspec(dllexport) -int Scintilla_GetSystemMetricsForDpi(int nIndex, DPI_T dpi) { - return SystemMetricsForDpi(nIndex, dpi.y); -} - -extern "C" __declspec(dllexport) -int Scintilla_AdjustWindowRectForDpi(WRCT_T* lpRect, unsigned long dwStyle, unsigned long dwExStyle, DPI_T dpi) { - RECT rc; - rc.left = lpRect->left; rc.top = lpRect->top; rc.right = lpRect->right; rc.bottom = lpRect->bottom; - BOOL const res = AdjustWindowRectForDpi(&rc, dwStyle, dwExStyle, dpi.y); - lpRect->left = rc.left; lpRect->top = rc.top; lpRect->right = rc.right; lpRect->bottom = rc.bottom; - return (int)res; -} - -} // namespace Scintilla diff --git a/scintilla/win32/ScintillaWin_old.h b/scintilla/win32/ScintillaWin_old.h deleted file mode 100644 index cce72587d..000000000 --- a/scintilla/win32/ScintillaWin_old.h +++ /dev/null @@ -1,20 +0,0 @@ -// Scintilla source code edit control -/** @file ScintillaWin.h - ** Define functions from ScintillaWin.cxx that can be called from ScintillaDLL.cxx. - **/ -// Copyright 1998-2018 by Neil Hodgson -// The License.txt file describes the conditions under which this software may be distributed. - -#ifndef SCINTILLAWIN_H -#define SCINTILLAWIN_H - -class ScintillaWin; - -namespace Scintilla { - -int ResourcesRelease(bool fromDllMain) noexcept; -sptr_t DirectFunction(ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam); - -} - -#endif From 810b236d6dce73111eaa561ef6ce312aa7e7403f Mon Sep 17 00:00:00 2001 From: "METANEOCORTEX\\Kotti" Date: Mon, 30 Aug 2021 12:10:03 +0200 Subject: [PATCH 2/4] +chg: .clang-format, minor fix --- src/.clang-format | 18 +++++++++++++++++- src/Helpers.c | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/.clang-format b/src/.clang-format index 2d22a9b81..8bb378e91 100644 --- a/src/.clang-format +++ b/src/.clang-format @@ -8,12 +8,28 @@ TabWidth: 4 Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign +AlignConsecutiveDeclarations: true +PointerAlignment: Left AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: true ColumnLimit: 0 +#BraceWrapping: +# AfterClass: true +# AfterControlStatement: true +# AfterEnum: false +# AfterFunction: true +# AfterNamespace: false +# AfterObjCDeclaration: true +# AfterStruct: true +# AfterUnion: true +# BeforeCatch: true +# BeforeElse: false +# IndentBraces: false +#BreakBeforeBraces: Custom +BreakBeforeBraces: Stroustrup +#BreakBeforeBraces: Allman Cpp11BracedListStyle: false FixNamespaceComments: false SortIncludes: false UseTab: Never - --- diff --git a/src/Helpers.c b/src/Helpers.c index 5b128bd84..6b09bdeb9 100644 --- a/src/Helpers.c +++ b/src/Helpers.c @@ -1625,8 +1625,8 @@ DWORD NormalizePathEx(LPWSTR lpszPath, DWORD cchBuffer, bool bRealPath, bool bSe StringCchCopy(lpszPath, cchBuffer, p); } } + CloseHandle(hFile); } - CloseHandle(hFile); } return (DWORD)StringCchLen(lpszPath, cchBuffer); From 2b56f11dbf15a10ba6b70ee4cb34bf1d4efb1c2d Mon Sep 17 00:00:00 2001 From: "METANEOCORTEX\\Kotti" Date: Thu, 2 Sep 2021 19:21:29 +0200 Subject: [PATCH 3/4] +fix: short review of Changes.txt for Release package --- Build/Changes.txt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Build/Changes.txt b/Build/Changes.txt index f05c96869..4dbfbd100 100644 --- a/Build/Changes.txt +++ b/Build/Changes.txt @@ -44,13 +44,12 @@ NEW: [.811.1]- Add "Notepad3 Replacement" on Windows 11 Insider Preview which works almost like with Windows 10: - Open (ext: .inf, .ini, .log, .ps1, .psd1, .psm1, .scp, .txt, .wtx, .compositefont, .css, .sct, .wsc). - Edit (ext: .bat, .cmd, .jse, .reg, .text, .vbe, .wsf). -[.809.1]- Configure app to open hyperlink (instead of default browser). +[.809.1]- Configure other application to open hyperlink (instead of default browser). [.805.1]- File URL forced to open in new window by (Ctrl+Alt+Click). [.805.1]- File change notification, if removed current file is recreated. [.805.1]- Allow definition of line number for file:/// URL (separator is ':' ). [.804.2]- File change notification Dlg: add "Indicator (silent) option. -[.804.2]- Configurable File Change/Deleted indicators. -[.804.1]- File on disk changed notification in title bar. +[.804.2]- Configurable File on disk Change/Deleted indicators for title bar. [.726.1]- "grepWinNP3" (current grepWin dev) allow multiple search paths (GRE). [.709.2]- Add "KiXtart" lexer (new in Lexilla) (LEX). [.709.1]- Integrating style theme "Sombra" as factory default Dark-Mode theme. @@ -75,7 +74,6 @@ NEW: [.324.1]- Exclusive Lock (write) Mode shown in windows title. [.323.1]- Menu item for excusive File Locking (write, shared read). [.323.1]- Decorator styling for Python files. -[.321.1]- Experimental DarkMode support for Win10 Insider 21Hx Dev-Preview. [.321.1]- Exclusive File Lock option. [.301.1]- About Dlg: version info for split-off Lexilla component. [.301.1]- About Dlg: Copy-Ver-Info-Btn : add Dark-Mode information. @@ -90,9 +88,6 @@ CHANGES: [.811.1]- Current Lexilla dev (chg: HTML(PHP) Lexer) (LEX). [.811.1]- Scintilla Python generator methods for embeddable Python (SCI). [.811.1]- MUI base language definition moved to solution's preprocessors (C/C++ & RC) defines. -[.810.1]- Some refactorings and code cleanup. -[.804.1]- Minor changed regarding file changed indicator. -[.803.1]- New Sombra scheme as default Dark Mode coloring theme. [.803.1]- Optimized default style handling for settings file (Dark Mode). [.803.1]- Adapted other Dark Mode Theme Files. [.802.1]- Default FileWatchingMode from "don't care" to "notify by MsgBox". @@ -181,7 +176,6 @@ FIXES: [.807.1]- RegEx line begin pattern stuck after zero-length replacement. [.806.1]- Launching file URL (Alt+Click) with respect to flags "Reuse Window" and "Only one Instance per File". [.805.1]- File URL tooltip. -[.804.1]- Incomplete default Dark Mode Scheme. [.804.1]- Config version dependent "FileWatchingMode" settings value. [.804.1]- File change notification in Title Bar. [.804.1]- Trigger reset file changed on disc flag. @@ -224,9 +218,8 @@ FIXES: [.416.2]- Workaround Scintilla text-rendering bug, if line-number margin width set to 0 (so use 1 instead). [.416.1]- Save Schema config in case of inifile from scratch. [.415.1]- Small correction to Line Cut feature (thin selection). -[.414.2]- Minipath uses NP§ preferred language, if not configured. +[.414.2]- Minipath uses NP3 preferred language, if not configured. [.414.2]- Compiling mono-language compiler switch version. -[.414.2]- Some minor bugs around MUI Language selection. [.414.1]- Std windows behavior for cascading new instances (if configured). [.413.2]- Sticky Window Flag on launching new instance. [.413.1]- Prevent duplicate instance, if setting does not allow. @@ -240,7 +233,7 @@ FIXES: [.406.1]- Center dialog in parent. [.406.1]- "grepWinNP3": fix missing search flags (GRE). [.326.1]- TinyExpr regresion inserting newline after equal sign. -[.325.3]- Possible busy-loop in Kotlin and Dard Lexer. +[.325.3]- Possible busy-loop in Kotlin and Dart Lexer. [.325.2]- Don't keep window top-most after returning from full-screen mode (F11 toggle). [.325.1]- Compiler switch to allow mono language binary. [.324.1]- Read-Only-Attribute cooperating with Exclusive-Write-Lock. From 9981cdd68aef61b17b50f9bcf5abf29c5133766c Mon Sep 17 00:00:00 2001 From: "METANEOCORTEX\\Kotti" Date: Fri, 3 Sep 2021 19:28:56 +0200 Subject: [PATCH 4/4] +fix: broken relative paths in file history selection --- src/Dialogs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dialogs.c b/src/Dialogs.c index 7e9f78d40..4c9be564a 100644 --- a/src/Dialogs.c +++ b/src/Dialogs.c @@ -2495,7 +2495,6 @@ CASE_WM_CTLCOLOR_SET: WCHAR tch[MAX_PATH] = { L'\0' }; for (int i = 0; i < MRU_Count(Globals.pFileMRU); i++) { MRU_Enum(Globals.pFileMRU, i, tch, COUNTOF(tch)); - PathAbsoluteFromApp(tch, NULL, 0, true); // SendDlgItemMessage(hwnd,IDC_FILEMRU,LB_ADDSTRING,0,(LPARAM)tch); } // SendDlgItemMessage(hwnd,IDC_FILEMRU,LB_SETCARETINDEX,0,false); lvi.iItem = i; @@ -2546,6 +2545,7 @@ CASE_WM_CTLCOLOR_SET: ListView_GetItem(hwndLV, &lvi); PathUnquoteSpaces(tchFileName); + PathAbsoluteFromApp(tchFileName, NULL, COUNTOF(tchFileName), true); if (!PathIsExistingFile(tchFileName) || (LOWORD(wParam) == IDC_REMOVE)) { // don't remove myself