aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2025-03-18 12:09:17 +1100
committerNeil <nyamatongwe@gmail.com>2025-03-18 12:09:17 +1100
commit4eb2079f2ba24ea5727fb89857b235b34643cf30 (patch)
tree0267ed085cc0b95660990fda2125d9aaeef1ded9
parentf5abf444eea42122e9837d8fc8fb094d91c8da62 (diff)
downloadscintilla-mirror-4eb2079f2ba24ea5727fb89857b235b34643cf30.tar.gz
Use DirectWrite for autocompletion lists when DirectWrite chosen for document
text.
-rw-r--r--doc/ScintillaHistory.html3
-rw-r--r--win32/PlatWin.cxx339
2 files changed, 207 insertions, 135 deletions
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index a5640ea70..01429dc42 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -602,6 +602,9 @@
<a href="https://sourceforge.net/p/scintilla/bugs/2463/">Bug #2463</a>.
</li>
<li>
+ On Win32, use DirectWrite for autocompletion lists when DirectWrite chosen for document text.
+ </li>
+ <li>
On Qt, draw clipped UTF-8 text correctly.
<a href="https://sourceforge.net/p/scintilla/bugs/2464/">Bug #2464</a>.
</li>
diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx
index e24f82675..8dae4867d 100644
--- a/win32/PlatWin.cxx
+++ b/win32/PlatWin.cxx
@@ -388,6 +388,8 @@ constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(FontQuality extraFontFla
// Both GDI and DirectWrite can produce a HFONT for use in list boxes
struct FontWin : public Font {
[[nodiscard]] virtual HFONT HFont() const noexcept = 0;
+ [[nodiscard]] virtual std::unique_ptr<FontWin> Duplicate() const = 0;
+ [[nodiscard]] virtual CharacterSet GetCharacterSet() const noexcept = 0;
};
void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, XYPOSITION size, FontWeight weight, bool italic, FontQuality extraFontFlag) {
@@ -403,7 +405,11 @@ void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, X
struct FontGDI : public FontWin {
HFONT hfont = {};
- explicit FontGDI(const FontParameters &fp) {
+ CharacterSet characterSet = CharacterSet::Ansi;
+ FontGDI(HFONT hfont_, CharacterSet characterSet_) noexcept : hfont(hfont_), characterSet(characterSet_) {
+ // Takes ownership and deletes the font
+ }
+ explicit FontGDI(const FontParameters &fp) : characterSet(fp.characterSet) {
LOGFONTW lf;
SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
hfont = ::CreateFontIndirectW(&lf);
@@ -425,6 +431,13 @@ struct FontGDI : public FontWin {
}
return ::CreateFontIndirectW(&lf);
}
+ [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override {
+ HFONT hfontCopy = HFont();
+ return std::make_unique<FontGDI>(hfontCopy, characterSet);
+ }
+ [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override {
+ return characterSet;
+ }
};
#if defined(USE_D2D)
@@ -478,8 +491,16 @@ struct FontDirectWrite : public FontWin {
}
}
}
+ // Allow copy constructor. Has to explicitly copy each field since can't use =default as deleted in Font.
+ FontDirectWrite(const FontDirectWrite &other) noexcept {
+ pTextFormat = other.pTextFormat;
+ extraFontFlag = other.extraFontFlag;
+ characterSet = other.characterSet;
+ yAscent = other.yAscent;
+ yDescent = other.yDescent;
+ yInternalLeading = other.yInternalLeading;
+ }
// Deleted so FontDirectWrite objects can not be copied.
- FontDirectWrite(const FontDirectWrite &) = delete;
FontDirectWrite(FontDirectWrite &&) = delete;
FontDirectWrite &operator=(const FontDirectWrite &) = delete;
FontDirectWrite &operator=(FontDirectWrite &&) = delete;
@@ -511,6 +532,14 @@ struct FontDirectWrite : public FontWin {
}
return pfm;
}
+
+ [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override {
+ return std::make_unique<FontDirectWrite>(*this);
+ }
+
+ [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override {
+ return characterSet;
+ }
};
#endif
@@ -973,7 +1002,9 @@ HBITMAP BitMapSection(HDC hdc, int width, int height, DWORD **pixels) noexcept {
const BITMAPV5HEADER bi = BitMapHeader(width, height);
void *image = nullptr;
HBITMAP hbm = ::CreateDIBSection(hdc, reinterpret_cast<const BITMAPINFO *>(&bi), DIB_RGB_COLORS, &image, {}, 0);
- *pixels = static_cast<DWORD *>(image);
+ if (pixels) {
+ *pixels = static_cast<DWORD *>(image);
+ }
return hbm;
}
@@ -3236,14 +3267,61 @@ ListBox::ListBox() noexcept = default;
ListBox::~ListBox() noexcept = default;
+namespace {
+
+ColourRGBA ColourElement(std::optional<ColourRGBA> colour, int nIndex) {
+ if (colour.has_value()) {
+ return colour.value();
+ }
+ const DWORD colourValue = ::GetSysColor(nIndex);
+ return ColourRGBA(colourValue);
+}
+
+struct LBGraphics {
+ HDC hMemDC{};
+ HBITMAP hBitmap{};
+ HBITMAP hOldBitmap{};
+ std::unique_ptr<Surface> pixmapLine;
+#if defined(USE_D2D)
+ DCRenderTarget pBMDCTarget;
+#endif
+
+ ~LBGraphics() {
+ Release();
+ }
+
+ void Release() noexcept {
+ pixmapLine.reset();
+#if defined(USE_D2D)
+ pBMDCTarget = nullptr;
+#endif
+ if (hOldBitmap) {
+ SelectBitmap(hMemDC, hOldBitmap);
+ }
+ hOldBitmap = {};
+ if (hBitmap) {
+ ::DeleteObject(hBitmap);
+ }
+ hBitmap = {};
+ if (hMemDC) {
+ ::DeleteDC(hMemDC);
+ }
+ hMemDC = {};
+ }
+};
+
+}
+
class ListBoxX : public ListBox {
int lineHeight = 10;
HFONT fontCopy {};
+ std::unique_ptr<FontWin> fontWin;
Technology technology = Technology::Default;
RGBAImageSet images;
LineToItem lti;
HWND lb {};
bool unicodeMode = false;
+ int codePage = 0;
int desiredVisibleRows = 9;
unsigned int maxItemCharacters = 0;
unsigned int aveCharWidth = 8;
@@ -3261,6 +3339,8 @@ class ListBoxX : public ListBox {
ListOptions options;
DWORD frameStyle = WS_THICKFRAME;
+ LBGraphics graphics;
+
HWND GetHWND() const noexcept;
void AppendListItem(const char *text, const char *numword);
void AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept;
@@ -3277,7 +3357,7 @@ class ListBoxX : public ListBox {
void StartResize(WPARAM);
LRESULT NcHitTest(WPARAM, LPARAM) const;
void CentreItem(int n);
- void Paint(HDC);
+ void AllocateBitMap();
static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
static constexpr POINT ItemInset {0, 0}; // Padding around whole item
@@ -3295,6 +3375,7 @@ public:
::DeleteObject(fontCopy);
fontCopy = {};
}
+ graphics.Release();
}
void SetFont(const Font *font) override;
void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override;
@@ -3331,6 +3412,7 @@ void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHei
location = location_;
lineHeight = lineHeight_;
unicodeMode = unicodeMode_;
+ codePage = unicodeMode ? CpUtf8 : 0;
technology = technology_;
HWND hwndParent = HwndFromWindow(*parent);
HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
@@ -3358,6 +3440,9 @@ void ListBoxX::SetFont(const Font *font) {
}
fontCopy = pfm->HFont();
SetWindowFont(lb, fontCopy, 0);
+ fontWin = pfm->Duplicate();
+ codePage = unicodeMode ? CpUtf8 : CodePageFromCharSet(fontWin->GetCharacterSet(), 1252);
+ graphics.Release();
}
}
@@ -3386,26 +3471,21 @@ PRectangle ListBoxX::GetDesiredRect() {
rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
int width = MinClientWidth();
- HDC hdc = ::GetDC(lb);
- HFONT oldFont = SelectFont(hdc, fontCopy);
- SIZE textSize = {0, 0};
+ int textSize = 0;
int len = 0;
+ int averageCharWidth = 8;
if (widestItem) {
len = static_cast<int>(strlen(widestItem));
- if (unicodeMode) {
- const TextWide tbuf(widestItem, CpUtf8);
- ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
- } else {
- ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
- }
+ // Make a measuring surface
+ std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology));
+ surfaceItem->Init(GetID());
+ surfaceItem->SetMode(SurfaceMode(codePage, false));
+ textSize = static_cast<int>(std::ceil(surfaceItem->WidthText(fontWin.get(), widestItem)));
+ maxCharWidth = static_cast<int>(std::ceil(surfaceItem->WidthText(fontWin.get(), "W")));
+ averageCharWidth = static_cast<int>(surfaceItem->AverageCharWidth(fontWin.get()));
}
- 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);
+ const int widthDesired = std::max(textSize, (len + 1) * averageCharWidth);
if (width < widthDesired)
width = widthDesired;
@@ -3482,103 +3562,76 @@ void ListBoxX::ClearRegisteredImages() {
images.Clear();
}
-namespace {
-
-int ColourOfElement(std::optional<ColourRGBA> colour, int nIndex) {
- if (colour.has_value()) {
- return colour.value().OpaqueRGB();
+void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
+ if ((pDrawItem->itemAction != ODA_SELECT) && (pDrawItem->itemAction != ODA_DRAWENTIRE)) {
+ return;
}
- return ::GetSysColor(nIndex);
-}
-
-void FillRectColour(HDC hdc, const RECT *lprc, int colour) noexcept {
- const HBRUSH brush = ::CreateSolidBrush(colour);
- ::FillRect(hdc, lprc, brush);
- ::DeleteObject(brush);
-}
+ if (!graphics.pixmapLine) {
+ AllocateBitMap();
+ if (!graphics.pixmapLine) {
+ // Failed to allocate, so release fully and give up
+ graphics.Release();
+ return;
+ }
+ }
+#if defined(USE_D2D)
+ if (graphics.pBMDCTarget) {
+ graphics.pBMDCTarget->BeginDraw();
+ }
+#endif
-}
+ const PRectangle rcItemBase = PRectangleFromRECT(pDrawItem->rcItem);
+ const PRectangle rcItem(0, 0, rcItemBase.Width(), rcItemBase.Height());
+ PRectangle rcBox = rcItem;
+ rcBox.left += TextOffset();
+ ColourRGBA colourFore;
+ ColourRGBA colourBack;
+ if (pDrawItem->itemState & ODS_SELECTED) {
+ PRectangle rcImage = rcItem;
+ rcImage.right = rcBox.left;
+ // The image is not highlighted
+ graphics.pixmapLine->FillRectangle(rcImage, ColourElement(options.back, COLOR_WINDOW));
+ colourBack = ColourElement(options.backSelected, COLOR_HIGHLIGHT);
+ graphics.pixmapLine->FillRectangle(rcBox, colourBack);
+ colourFore = ColourElement(options.foreSelected, COLOR_HIGHLIGHTTEXT);
+ } else {
+ colourBack = ColourElement(options.back, COLOR_WINDOW);
+ graphics.pixmapLine->FillRectangle(rcItem, colourBack);
+ colourFore = ColourElement(options.fore, COLOR_WINDOWTEXT);
+ }
-void ListBoxX::Draw(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
- FillRectColour(pDrawItem->hDC, &rcImage, ColourOfElement(options.back, COLOR_WINDOW));
- FillRectColour(pDrawItem->hDC, &rcBox, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT));
- ::SetBkColor(pDrawItem->hDC, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT));
- ::SetTextColor(pDrawItem->hDC, ColourOfElement(options.foreSelected, COLOR_HIGHLIGHTTEXT));
- } else {
- FillRectColour(pDrawItem->hDC, &pDrawItem->rcItem, ColourOfElement(options.back, COLOR_WINDOW));
- ::SetBkColor(pDrawItem->hDC, ColourOfElement(options.back, COLOR_WINDOW));
- ::SetTextColor(pDrawItem->hDC, ColourOfElement(options.fore, COLOR_WINDOWTEXT));
- }
+ const ListItemData item = lti.Get(pDrawItem->itemID);
+ const int pixId = item.pixId;
+ const char *text = item.text;
- const ListItemData item = lti.Get(pDrawItem->itemID);
- const int pixId = item.pixId;
- const char *text = item.text;
- const int len = static_cast<int>(strlen(text));
+ const PRectangle rcText = rcBox.Inset(Point(TextInset.x, TextInset.y));
- RECT rcText = rcBox;
- ::InsetRect(&rcText, TextInset.x, TextInset.y);
+ const double ascent = graphics.pixmapLine->Ascent(fontWin.get());
+ graphics.pixmapLine->DrawTextClipped(rcText, fontWin.get(), rcText.top + ascent, text, colourFore, colourBack);
- if (unicodeMode) {
- const TextWide tbuf(text, CpUtf8);
- ::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) {
+ const XYPOSITION left = rcItem.left + ItemInset.x + ImageInset.x;
+ PRectangle rcImage = rcItem;
+ rcImage.left = left;
+ rcImage.right = rcImage.left + images.GetWidth();
+ graphics.pixmapLine->DrawRGBAImage(rcImage,
+ pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
+ }
- // Draw the image, if any
- const RGBAImage *pimage = images.Get(pixId);
- if (pimage) {
- std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology));
- if (technology == Technology::Default) {
- surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
- const long left = pDrawItem->rcItem.left + 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
- );
- DCRenderTarget pDCRT;
- HRESULT hr = CreateDCRenderTarget(&props, pDCRT);
- if (SUCCEEDED(hr) && pDCRT) {
- const long left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
-
- RECT rcItem = pDrawItem->rcItem;
- rcItem.left = left;
- rcItem.right = rcItem.left + images.GetWidth();
-
- hr = pDCRT->BindDC(pDrawItem->hDC, &rcItem);
- if (SUCCEEDED(hr)) {
- surfaceItem->Init(pDCRT.Get(), pDrawItem->hwndItem);
- pDCRT->BeginDraw();
- const PRectangle rcImage = PRectangle::FromInts(0, 0, images.GetWidth(), rcItem.bottom - rcItem.top);
- surfaceItem->DrawRGBAImage(rcImage,
- pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
- pDCRT->EndDraw();
- }
- }
-#endif
- }
+ if (graphics.pBMDCTarget) {
+ const HRESULT hrEnd = graphics.pBMDCTarget->EndDraw();
+ if (FAILED(hrEnd)) {
+ return;
}
}
+#endif
+
+ // Blit from hMemDC to hDC
+ const SIZE extent = SizeOfRect(pDrawItem->rcItem);
+ ::BitBlt(pDrawItem->hDC, pDrawItem->rcItem.left, pDrawItem->rcItem.top, extent.cx, extent.cy, graphics.hMemDC, 0, 0, SRCCOPY);
}
void ListBoxX::AppendListItem(const char *text, const char *numword) {
@@ -3863,25 +3916,50 @@ void ListBoxX::CentreItem(int n) {
}
}
-// Performs a double-buffered paint operation to avoid flicker
-void ListBoxX::Paint(HDC hDC) {
- 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 };
- FillRectColour(bitmapDC, &rc, ColourOfElement(options.back, COLOR_WINDOWTEXT));
- // Paint the entire client area and vertical scrollbar
- ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(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);
+void ListBoxX::AllocateBitMap() {
+ graphics.hMemDC = ::CreateCompatibleDC({});
+ if (!graphics.hMemDC) {
+ return;
+ }
+
+ const SIZE extent { GetClientExtent().x, lineHeight };
+
+ graphics.hBitmap = BitMapSection(graphics.hMemDC, extent.cx, -extent.cy, nullptr);
+ if (!graphics.hBitmap) {
+ return;
+ }
+ graphics.hOldBitmap = SelectBitmap(graphics.hMemDC, graphics.hBitmap);
+
+ // Make a surface
+ graphics.pixmapLine = Surface::Allocate(technology);
+ graphics.pixmapLine->SetMode(SurfaceMode(codePage, false));
+
+#if defined(USE_D2D)
+ if (technology != Technology::Default) {
+ if (!LoadD2D()) {
+ return;
+ }
+
+ const D2D1_RENDER_TARGET_PROPERTIES drtp = D2D1::RenderTargetProperties(
+ D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED });
+
+ HRESULT hr = CreateDCRenderTarget(&drtp, graphics.pBMDCTarget);
+ if (FAILED(hr) || !graphics.pBMDCTarget) {
+ return;
+ }
+
+ const RECT rcExtent = { 0, 0, extent.cx, extent.cy };
+ hr = graphics.pBMDCTarget->BindDC(graphics.hMemDC, &rcExtent);
+ if (SUCCEEDED(hr)) {
+ graphics.pixmapLine->Init(graphics.pBMDCTarget.Get(), GetID());
+ }
+ return;
+ }
+#endif
+
+ // Either technology == Technology::Default or USE_D2D turned off
+ graphics.pixmapLine->Init(graphics.hMemDC, GetID());
}
LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
@@ -3891,16 +3969,6 @@ LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam,
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;
@@ -3967,6 +4035,7 @@ LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam
case WM_SIZE:
if (lb) {
+ graphics.Release(); // Bitmap must be reallocated to new size.
SetRedraw(false);
::SetWindowPos(lb, {}, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);
// Ensure the selection remains visible