Update Scintilla to version 3.5.7
[TortoiseGit.git] / ext / scintilla / win32 / PlatWin.cxx
blob76c88cb9f0c7abfba42385a651f2e5251f676cd4
1 // Scintilla source code edit control
2 /** @file PlatWin.cxx
3 ** Implementation of platform facilities on Windows.
4 **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <time.h>
13 #include <math.h>
14 #include <ctype.h>
15 #include <limits.h>
17 #include <vector>
18 #include <map>
20 #undef _WIN32_WINNT
21 #define _WIN32_WINNT 0x0500
22 #undef WINVER
23 #define WINVER 0x0500
24 #include <windows.h>
25 #include <commctrl.h>
26 #include <richedit.h>
27 #include <windowsx.h>
29 #if defined(NTDDI_WIN7) && !defined(DISABLE_D2D)
30 #define USE_D2D 1
31 #endif
33 #if defined(USE_D2D)
34 #include <d2d1.h>
35 #include <dwrite.h>
36 #endif
38 #include "Platform.h"
39 #include "StringCopy.h"
40 #include "XPM.h"
41 #include "UniConversion.h"
42 #include "FontQuality.h"
44 #ifndef IDC_HAND
45 #define IDC_HAND MAKEINTRESOURCE(32649)
46 #endif
48 #ifndef SPI_GETFONTSMOOTHINGCONTRAST
49 #define SPI_GETFONTSMOOTHINGCONTRAST 0x200C
50 #endif
52 static void *PointerFromWindow(HWND hWnd) {
53 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
56 static void SetWindowPointer(HWND hWnd, void *ptr) {
57 ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
60 extern UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage);
62 // Declarations needed for functions dynamically loaded as not available on all Windows versions.
63 typedef BOOL (WINAPI *AlphaBlendSig)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
64 typedef HMONITOR (WINAPI *MonitorFromPointSig)(POINT, DWORD);
65 typedef HMONITOR (WINAPI *MonitorFromRectSig)(LPCRECT, DWORD);
66 typedef BOOL (WINAPI *GetMonitorInfoSig)(HMONITOR, LPMONITORINFO);
68 static CRITICAL_SECTION crPlatformLock;
69 static HINSTANCE hinstPlatformRes = 0;
71 static HMODULE hDLLImage = 0;
72 static AlphaBlendSig AlphaBlendFn = 0;
74 static HMODULE hDLLUser32 = 0;
75 static HMONITOR (WINAPI *MonitorFromPointFn)(POINT, DWORD) = 0;
76 static HMONITOR (WINAPI *MonitorFromRectFn)(LPCRECT, DWORD) = 0;
77 static BOOL (WINAPI *GetMonitorInfoFn)(HMONITOR, LPMONITORINFO) = 0;
79 static HCURSOR reverseArrowCursor = NULL;
81 #ifdef SCI_NAMESPACE
82 namespace Scintilla {
83 #endif
85 Point Point::FromLong(long lpoint) {
86 return Point(static_cast<short>(LOWORD(lpoint)), static_cast<short>(HIWORD(lpoint)));
89 static RECT RectFromPRectangle(PRectangle prc) {
90 RECT rc = {static_cast<LONG>(prc.left), static_cast<LONG>(prc.top),
91 static_cast<LONG>(prc.right), static_cast<LONG>(prc.bottom)};
92 return rc;
95 #if defined(USE_D2D)
96 IDWriteFactory *pIDWriteFactory = 0;
97 ID2D1Factory *pD2DFactory = 0;
98 IDWriteRenderingParams *defaultRenderingParams = 0;
99 IDWriteRenderingParams *customClearTypeRenderingParams = 0;
101 static HMODULE hDLLD2D = NULL;
102 static HMODULE hDLLDWrite = NULL;
104 bool LoadD2D() {
105 static bool triedLoadingD2D = false;
106 if (!triedLoadingD2D) {
107 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
108 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
109 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
110 IUnknown **factory);
112 hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
113 if (hDLLD2D) {
114 D2D1CFSig fnD2DCF = (D2D1CFSig)::GetProcAddress(hDLLD2D, "D2D1CreateFactory");
115 if (fnD2DCF) {
116 // A single threaded factory as Scintilla always draw on the GUI thread
117 fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
118 __uuidof(ID2D1Factory),
120 reinterpret_cast<IUnknown**>(&pD2DFactory));
123 hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
124 if (hDLLDWrite) {
125 DWriteCFSig fnDWCF = (DWriteCFSig)::GetProcAddress(hDLLDWrite, "DWriteCreateFactory");
126 if (fnDWCF) {
127 fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
128 __uuidof(IDWriteFactory),
129 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
133 if (pIDWriteFactory) {
134 HRESULT hr = pIDWriteFactory->CreateRenderingParams(&defaultRenderingParams);
135 if (SUCCEEDED(hr)) {
136 unsigned int clearTypeContrast;
137 if (::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0)) {
139 FLOAT gamma;
140 if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200)
141 gamma = static_cast<FLOAT>(clearTypeContrast) / 1000.0f;
142 else
143 gamma = defaultRenderingParams->GetGamma();
145 pIDWriteFactory->CreateCustomRenderingParams(gamma, defaultRenderingParams->GetEnhancedContrast(), defaultRenderingParams->GetClearTypeLevel(),
146 defaultRenderingParams->GetPixelGeometry(), defaultRenderingParams->GetRenderingMode(), &customClearTypeRenderingParams);
152 triedLoadingD2D = true;
153 return pIDWriteFactory && pD2DFactory;
155 #endif
157 struct FormatAndMetrics {
158 int technology;
159 HFONT hfont;
160 #if defined(USE_D2D)
161 IDWriteTextFormat *pTextFormat;
162 #endif
163 int extraFontFlag;
164 int characterSet;
165 FLOAT yAscent;
166 FLOAT yDescent;
167 FLOAT yInternalLeading;
168 FormatAndMetrics(HFONT hfont_, int extraFontFlag_, int characterSet_) :
169 technology(SCWIN_TECH_GDI), hfont(hfont_),
170 #if defined(USE_D2D)
171 pTextFormat(0),
172 #endif
173 extraFontFlag(extraFontFlag_), characterSet(characterSet_), yAscent(2), yDescent(1), yInternalLeading(0) {
175 #if defined(USE_D2D)
176 FormatAndMetrics(IDWriteTextFormat *pTextFormat_,
177 int extraFontFlag_,
178 int characterSet_,
179 FLOAT yAscent_,
180 FLOAT yDescent_,
181 FLOAT yInternalLeading_) :
182 technology(SCWIN_TECH_DIRECTWRITE),
183 hfont(0),
184 pTextFormat(pTextFormat_),
185 extraFontFlag(extraFontFlag_),
186 characterSet(characterSet_),
187 yAscent(yAscent_),
188 yDescent(yDescent_),
189 yInternalLeading(yInternalLeading_) {
191 #endif
192 ~FormatAndMetrics() {
193 if (hfont)
194 ::DeleteObject(hfont);
195 #if defined(USE_D2D)
196 if (pTextFormat)
197 pTextFormat->Release();
198 pTextFormat = 0;
199 #endif
200 extraFontFlag = 0;
201 characterSet = 0;
202 yAscent = 2;
203 yDescent = 1;
204 yInternalLeading = 0;
206 HFONT HFont();
209 HFONT FormatAndMetrics::HFont() {
210 LOGFONTW lf = {};
211 #if defined(USE_D2D)
212 if (technology == SCWIN_TECH_GDI) {
213 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
214 return 0;
216 } else {
217 HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
218 if (!SUCCEEDED(hr)) {
219 return 0;
221 lf.lfWeight = pTextFormat->GetFontWeight();
222 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
223 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
225 #else
226 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
227 return 0;
229 #endif
230 return ::CreateFontIndirectW(&lf);
233 #ifndef CLEARTYPE_QUALITY
234 #define CLEARTYPE_QUALITY 5
235 #endif
237 static BYTE Win32MapFontQuality(int extraFontFlag) {
238 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
240 case SC_EFF_QUALITY_NON_ANTIALIASED:
241 return NONANTIALIASED_QUALITY;
243 case SC_EFF_QUALITY_ANTIALIASED:
244 return ANTIALIASED_QUALITY;
246 case SC_EFF_QUALITY_LCD_OPTIMIZED:
247 return CLEARTYPE_QUALITY;
249 default:
250 return SC_EFF_QUALITY_DEFAULT;
254 #if defined(USE_D2D)
255 static D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) {
256 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
258 case SC_EFF_QUALITY_NON_ANTIALIASED:
259 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
261 case SC_EFF_QUALITY_ANTIALIASED:
262 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
264 case SC_EFF_QUALITY_LCD_OPTIMIZED:
265 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
267 default:
268 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
271 #endif
273 static void SetLogFont(LOGFONTW &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) {
274 lf = LOGFONTW();
275 // The negative is to allow for leading
276 lf.lfHeight = -(abs(static_cast<int>(size + 0.5)));
277 lf.lfWeight = weight;
278 lf.lfItalic = static_cast<BYTE>(italic ? 1 : 0);
279 lf.lfCharSet = static_cast<BYTE>(characterSet);
280 lf.lfQuality = Win32MapFontQuality(extraFontFlag);
281 UTF16FromUTF8(faceName, strlen(faceName)+1, lf.lfFaceName, LF_FACESIZE);
285 * Create a hash from the parameters for a font to allow easy checking for identity.
286 * If one font is the same as another, its hash will be the same, but if the hash is the
287 * same then they may still be different.
289 static int HashFont(const FontParameters &fp) {
290 return
291 static_cast<int>(fp.size) ^
292 (fp.characterSet << 10) ^
293 ((fp.extraFontFlag & SC_EFF_QUALITY_MASK) << 9) ^
294 ((fp.weight/100) << 12) ^
295 (fp.italic ? 0x20000000 : 0) ^
296 (fp.technology << 15) ^
297 fp.faceName[0];
300 class FontCached : Font {
301 FontCached *next;
302 int usage;
303 float size;
304 LOGFONTW lf;
305 int technology;
306 int hash;
307 explicit FontCached(const FontParameters &fp);
308 ~FontCached() {}
309 bool SameAs(const FontParameters &fp);
310 virtual void Release();
312 static FontCached *first;
313 public:
314 static FontID FindOrCreate(const FontParameters &fp);
315 static void ReleaseId(FontID fid_);
318 FontCached *FontCached::first = 0;
320 FontCached::FontCached(const FontParameters &fp) :
321 next(0), usage(0), size(1.0), hash(0) {
322 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
323 technology = fp.technology;
324 hash = HashFont(fp);
325 fid = 0;
326 if (technology == SCWIN_TECH_GDI) {
327 HFONT hfont = ::CreateFontIndirectW(&lf);
328 fid = reinterpret_cast<void *>(new FormatAndMetrics(hfont, fp.extraFontFlag, fp.characterSet));
329 } else {
330 #if defined(USE_D2D)
331 IDWriteTextFormat *pTextFormat;
332 const int faceSize = 200;
333 WCHAR wszFace[faceSize];
334 UTF16FromUTF8(fp.faceName, strlen(fp.faceName)+1, wszFace, faceSize);
335 FLOAT fHeight = fp.size;
336 DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
337 HRESULT hr = pIDWriteFactory->CreateTextFormat(wszFace, NULL,
338 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
339 style,
340 DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
341 if (SUCCEEDED(hr)) {
342 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
344 const int maxLines = 2;
345 DWRITE_LINE_METRICS lineMetrics[maxLines];
346 UINT32 lineCount = 0;
347 FLOAT yAscent = 1.0f;
348 FLOAT yDescent = 1.0f;
349 FLOAT yInternalLeading = 0.0f;
350 IDWriteTextLayout *pTextLayout = 0;
351 hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
352 100.0f, 100.0f, &pTextLayout);
353 if (SUCCEEDED(hr)) {
354 hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
355 if (SUCCEEDED(hr)) {
356 yAscent = lineMetrics[0].baseline;
357 yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
359 FLOAT emHeight;
360 hr = pTextLayout->GetFontSize(0, &emHeight);
361 if (SUCCEEDED(hr)) {
362 yInternalLeading = lineMetrics[0].height - emHeight;
365 pTextLayout->Release();
366 pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline);
368 fid = reinterpret_cast<void *>(new FormatAndMetrics(pTextFormat, fp.extraFontFlag, fp.characterSet, yAscent, yDescent, yInternalLeading));
370 #endif
372 usage = 1;
375 bool FontCached::SameAs(const FontParameters &fp) {
376 if (
377 (size == fp.size) &&
378 (lf.lfWeight == fp.weight) &&
379 (lf.lfItalic == static_cast<BYTE>(fp.italic ? 1 : 0)) &&
380 (lf.lfCharSet == fp.characterSet) &&
381 (lf.lfQuality == Win32MapFontQuality(fp.extraFontFlag)) &&
382 (technology == fp.technology)) {
383 wchar_t wszFace[LF_FACESIZE];
384 UTF16FromUTF8(fp.faceName, strlen(fp.faceName)+1, wszFace, LF_FACESIZE);
385 return 0 == wcscmp(lf.lfFaceName,wszFace);
387 return false;
390 void FontCached::Release() {
391 delete reinterpret_cast<FormatAndMetrics *>(fid);
392 fid = 0;
395 FontID FontCached::FindOrCreate(const FontParameters &fp) {
396 FontID ret = 0;
397 ::EnterCriticalSection(&crPlatformLock);
398 int hashFind = HashFont(fp);
399 for (FontCached *cur=first; cur; cur=cur->next) {
400 if ((cur->hash == hashFind) &&
401 cur->SameAs(fp)) {
402 cur->usage++;
403 ret = cur->fid;
406 if (ret == 0) {
407 FontCached *fc = new FontCached(fp);
408 fc->next = first;
409 first = fc;
410 ret = fc->fid;
412 ::LeaveCriticalSection(&crPlatformLock);
413 return ret;
416 void FontCached::ReleaseId(FontID fid_) {
417 ::EnterCriticalSection(&crPlatformLock);
418 FontCached **pcur=&first;
419 for (FontCached *cur=first; cur; cur=cur->next) {
420 if (cur->fid == fid_) {
421 cur->usage--;
422 if (cur->usage == 0) {
423 *pcur = cur->next;
424 cur->Release();
425 cur->next = 0;
426 delete cur;
428 break;
430 pcur=&cur->next;
432 ::LeaveCriticalSection(&crPlatformLock);
435 Font::Font() {
436 fid = 0;
439 Font::~Font() {
442 #define FONTS_CACHED
444 void Font::Create(const FontParameters &fp) {
445 Release();
446 if (fp.faceName)
447 fid = FontCached::FindOrCreate(fp);
450 void Font::Release() {
451 if (fid)
452 FontCached::ReleaseId(fid);
453 fid = 0;
456 // Buffer to hold strings and string position arrays without always allocating on heap.
457 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
458 // when less than safe size otherwise allocate on heap and free automatically.
459 template<typename T, int lengthStandard>
460 class VarBuffer {
461 T bufferStandard[lengthStandard];
462 // Private so VarBuffer objects can not be copied
463 VarBuffer(const VarBuffer &);
464 VarBuffer &operator=(const VarBuffer &);
465 public:
466 T *buffer;
467 explicit VarBuffer(size_t length) : buffer(0) {
468 if (length > lengthStandard) {
469 buffer = new T[length];
470 } else {
471 buffer = bufferStandard;
474 ~VarBuffer() {
475 if (buffer != bufferStandard) {
476 delete []buffer;
477 buffer = 0;
482 const int stackBufferLength = 1000;
483 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
484 public:
485 int tlen;
486 TextWide(const char *s, int len, bool unicodeMode, int codePage=0) :
487 VarBuffer<wchar_t, stackBufferLength>(len) {
488 if (unicodeMode) {
489 tlen = static_cast<int>(UTF16FromUTF8(s, len, buffer, len));
490 } else {
491 // Support Asian string display in 9x English
492 tlen = ::MultiByteToWideChar(codePage, 0, s, len, buffer, len);
496 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
498 class SurfaceGDI : public Surface {
499 bool unicodeMode;
500 HDC hdc;
501 bool hdcOwned;
502 HPEN pen;
503 HPEN penOld;
504 HBRUSH brush;
505 HBRUSH brushOld;
506 HFONT font;
507 HFONT fontOld;
508 HBITMAP bitmap;
509 HBITMAP bitmapOld;
510 int maxWidthMeasure;
511 int maxLenText;
513 int codePage;
515 void BrushColor(ColourDesired back);
516 void SetFont(Font &font_);
518 // Private so SurfaceGDI objects can not be copied
519 SurfaceGDI(const SurfaceGDI &);
520 SurfaceGDI &operator=(const SurfaceGDI &);
521 public:
522 SurfaceGDI();
523 virtual ~SurfaceGDI();
525 void Init(WindowID wid);
526 void Init(SurfaceID sid, WindowID wid);
527 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
529 void Release();
530 bool Initialised();
531 void PenColour(ColourDesired fore);
532 int LogPixelsY();
533 int DeviceHeightFont(int points);
534 void MoveTo(int x_, int y_);
535 void LineTo(int x_, int y_);
536 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
537 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
538 void FillRectangle(PRectangle rc, ColourDesired back);
539 void FillRectangle(PRectangle rc, Surface &surfacePattern);
540 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
541 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
542 ColourDesired outline, int alphaOutline, int flags);
543 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
544 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
545 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
547 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
548 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
549 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
550 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
551 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
552 XYPOSITION WidthText(Font &font_, const char *s, int len);
553 XYPOSITION WidthChar(Font &font_, char ch);
554 XYPOSITION Ascent(Font &font_);
555 XYPOSITION Descent(Font &font_);
556 XYPOSITION InternalLeading(Font &font_);
557 XYPOSITION ExternalLeading(Font &font_);
558 XYPOSITION Height(Font &font_);
559 XYPOSITION AverageCharWidth(Font &font_);
561 void SetClip(PRectangle rc);
562 void FlushCachedState();
564 void SetUnicodeMode(bool unicodeMode_);
565 void SetDBCSMode(int codePage_);
568 SurfaceGDI::SurfaceGDI() :
569 unicodeMode(false),
570 hdc(0), hdcOwned(false),
571 pen(0), penOld(0),
572 brush(0), brushOld(0),
573 font(0), fontOld(0),
574 bitmap(0), bitmapOld(0) {
575 maxWidthMeasure = INT_MAX;
576 // There appears to be a 16 bit string length limit in GDI on NT.
577 maxLenText = 65535;
579 codePage = 0;
582 SurfaceGDI::~SurfaceGDI() {
583 Release();
586 void SurfaceGDI::Release() {
587 if (penOld) {
588 ::SelectObject(reinterpret_cast<HDC>(hdc), penOld);
589 ::DeleteObject(pen);
590 penOld = 0;
592 pen = 0;
593 if (brushOld) {
594 ::SelectObject(reinterpret_cast<HDC>(hdc), brushOld);
595 ::DeleteObject(brush);
596 brushOld = 0;
598 brush = 0;
599 if (fontOld) {
600 // Fonts are not deleted as they are owned by a Font object
601 ::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);
602 fontOld = 0;
604 font = 0;
605 if (bitmapOld) {
606 ::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);
607 ::DeleteObject(bitmap);
608 bitmapOld = 0;
610 bitmap = 0;
611 if (hdcOwned) {
612 ::DeleteDC(reinterpret_cast<HDC>(hdc));
613 hdc = 0;
614 hdcOwned = false;
618 bool SurfaceGDI::Initialised() {
619 return hdc != 0;
622 void SurfaceGDI::Init(WindowID) {
623 Release();
624 hdc = ::CreateCompatibleDC(NULL);
625 hdcOwned = true;
626 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
629 void SurfaceGDI::Init(SurfaceID sid, WindowID) {
630 Release();
631 hdc = reinterpret_cast<HDC>(sid);
632 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
635 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID) {
636 Release();
637 SurfaceGDI *psurfOther = static_cast<SurfaceGDI *>(surface_);
638 hdc = ::CreateCompatibleDC(psurfOther->hdc);
639 hdcOwned = true;
640 bitmap = ::CreateCompatibleBitmap(psurfOther->hdc, width, height);
641 bitmapOld = static_cast<HBITMAP>(::SelectObject(hdc, bitmap));
642 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
643 SetUnicodeMode(psurfOther->unicodeMode);
644 SetDBCSMode(psurfOther->codePage);
647 void SurfaceGDI::PenColour(ColourDesired fore) {
648 if (pen) {
649 ::SelectObject(hdc, penOld);
650 ::DeleteObject(pen);
651 pen = 0;
652 penOld = 0;
654 pen = ::CreatePen(0,1,fore.AsLong());
655 penOld = static_cast<HPEN>(::SelectObject(reinterpret_cast<HDC>(hdc), pen));
658 void SurfaceGDI::BrushColor(ColourDesired back) {
659 if (brush) {
660 ::SelectObject(hdc, brushOld);
661 ::DeleteObject(brush);
662 brush = 0;
663 brushOld = 0;
665 // Only ever want pure, non-dithered brushes
666 ColourDesired colourNearest = ::GetNearestColor(hdc, back.AsLong());
667 brush = ::CreateSolidBrush(colourNearest.AsLong());
668 brushOld = static_cast<HBRUSH>(::SelectObject(hdc, brush));
671 void SurfaceGDI::SetFont(Font &font_) {
672 if (font_.GetID() != font) {
673 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
674 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI);
675 if (fontOld) {
676 ::SelectObject(hdc, pfm->hfont);
677 } else {
678 fontOld = static_cast<HFONT>(::SelectObject(hdc, pfm->hfont));
680 font = reinterpret_cast<HFONT>(pfm->hfont);
684 int SurfaceGDI::LogPixelsY() {
685 return ::GetDeviceCaps(hdc, LOGPIXELSY);
688 int SurfaceGDI::DeviceHeightFont(int points) {
689 return ::MulDiv(points, LogPixelsY(), 72);
692 void SurfaceGDI::MoveTo(int x_, int y_) {
693 ::MoveToEx(hdc, x_, y_, 0);
696 void SurfaceGDI::LineTo(int x_, int y_) {
697 ::LineTo(hdc, x_, y_);
700 void SurfaceGDI::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
701 PenColour(fore);
702 BrushColor(back);
703 std::vector<POINT> outline;
704 for (int i=0; i<npts; i++) {
705 POINT pt = {static_cast<LONG>(pts[i].x), static_cast<LONG>(pts[i].y)};
706 outline.push_back(pt);
708 ::Polygon(hdc, &outline[0], npts);
711 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
712 PenColour(fore);
713 BrushColor(back);
714 const RECT rcw = RectFromPRectangle(rc);
715 ::Rectangle(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
718 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) {
719 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
720 // There is no need to allocate a brush either.
721 RECT rcw = RectFromPRectangle(rc);
722 ::SetBkColor(hdc, back.AsLong());
723 ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, NULL);
726 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
727 HBRUSH br;
728 if (static_cast<SurfaceGDI &>(surfacePattern).bitmap)
729 br = ::CreatePatternBrush(static_cast<SurfaceGDI &>(surfacePattern).bitmap);
730 else // Something is wrong so display in red
731 br = ::CreateSolidBrush(RGB(0xff, 0, 0));
732 RECT rcw = RectFromPRectangle(rc);
733 ::FillRect(hdc, &rcw, br);
734 ::DeleteObject(br);
737 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
738 PenColour(fore);
739 BrushColor(back);
740 const RECT rcw = RectFromPRectangle(rc);
741 ::RoundRect(hdc,
742 rcw.left + 1, rcw.top,
743 rcw.right - 1, rcw.bottom,
744 8, 8);
747 // Plot a point into a DWORD buffer symmetrically to all 4 quadrants
748 static void AllFour(DWORD *pixels, int width, int height, int x, int y, DWORD val) {
749 pixels[y*width+x] = val;
750 pixels[y*width+width-1-x] = val;
751 pixels[(height-1-y)*width+x] = val;
752 pixels[(height-1-y)*width+width-1-x] = val;
755 #ifndef AC_SRC_OVER
756 #define AC_SRC_OVER 0x00
757 #endif
758 #ifndef AC_SRC_ALPHA
759 #define AC_SRC_ALPHA 0x01
760 #endif
762 static DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) {
763 union {
764 byte pixVal[4];
765 DWORD val;
766 } converter;
767 converter.pixVal[0] = b;
768 converter.pixVal[1] = g;
769 converter.pixVal[2] = r;
770 converter.pixVal[3] = a;
771 return converter.val;
774 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
775 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
776 const RECT rcw = RectFromPRectangle(rc);
777 if (AlphaBlendFn && rc.Width() > 0) {
778 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
779 int width = static_cast<int>(rc.Width());
780 int height = static_cast<int>(rc.Height());
781 // Ensure not distorted too much by corners when small
782 cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
783 BITMAPINFO bpih = {{sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}};
784 void *image = 0;
785 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
786 DIB_RGB_COLORS, &image, NULL, 0);
788 if (hbmMem) {
789 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
791 DWORD valEmpty = dwordFromBGRA(0,0,0,0);
792 DWORD valFill = dwordFromBGRA(
793 static_cast<byte>(GetBValue(fill.AsLong()) * alphaFill / 255),
794 static_cast<byte>(GetGValue(fill.AsLong()) * alphaFill / 255),
795 static_cast<byte>(GetRValue(fill.AsLong()) * alphaFill / 255),
796 static_cast<byte>(alphaFill));
797 DWORD valOutline = dwordFromBGRA(
798 static_cast<byte>(GetBValue(outline.AsLong()) * alphaOutline / 255),
799 static_cast<byte>(GetGValue(outline.AsLong()) * alphaOutline / 255),
800 static_cast<byte>(GetRValue(outline.AsLong()) * alphaOutline / 255),
801 static_cast<byte>(alphaOutline));
802 DWORD *pixels = reinterpret_cast<DWORD *>(image);
803 for (int y=0; y<height; y++) {
804 for (int x=0; x<width; x++) {
805 if ((x==0) || (x==width-1) || (y == 0) || (y == height-1)) {
806 pixels[y*width+x] = valOutline;
807 } else {
808 pixels[y*width+x] = valFill;
812 for (int c=0; c<cornerSize; c++) {
813 for (int x=0; x<c+1; x++) {
814 AllFour(pixels, width, height, x, c-x, valEmpty);
817 for (int x=1; x<cornerSize; x++) {
818 AllFour(pixels, width, height, x, cornerSize-x, valOutline);
821 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
823 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rcw.left, rcw.top, width, height, hMemDC, 0, 0, width, height, merge);
825 SelectBitmap(hMemDC, hbmOld);
826 ::DeleteObject(hbmMem);
828 ::DeleteDC(hMemDC);
829 } else {
830 BrushColor(outline);
831 FrameRect(hdc, &rcw, brush);
835 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
836 if (AlphaBlendFn && rc.Width() > 0) {
837 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
838 if (rc.Width() > width)
839 rc.left += static_cast<int>((rc.Width() - width) / 2);
840 rc.right = rc.left + width;
841 if (rc.Height() > height)
842 rc.top += static_cast<int>((rc.Height() - height) / 2);
843 rc.bottom = rc.top + height;
845 BITMAPINFO bpih = {{sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}};
846 unsigned char *image = 0;
847 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
848 DIB_RGB_COLORS, reinterpret_cast<void **>(&image), NULL, 0);
849 if (hbmMem) {
850 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
852 for (int y=height-1; y>=0; y--) {
853 for (int x=0; x<width; x++) {
854 unsigned char *pixel = image + (y*width+x) * 4;
855 unsigned char alpha = pixelsImage[3];
856 // Input is RGBA, output is BGRA with premultiplied alpha
857 pixel[2] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
858 pixel[1] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
859 pixel[0] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
860 pixel[3] = static_cast<unsigned char>(*pixelsImage++);
864 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
866 AlphaBlendFn(reinterpret_cast<HDC>(hdc), static_cast<int>(rc.left), static_cast<int>(rc.top),
867 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), hMemDC, 0, 0, width, height, merge);
869 SelectBitmap(hMemDC, hbmOld);
870 ::DeleteObject(hbmMem);
872 ::DeleteDC(hMemDC);
877 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
878 PenColour(fore);
879 BrushColor(back);
880 const RECT rcw = RectFromPRectangle(rc);
881 ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
884 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
885 ::BitBlt(hdc,
886 static_cast<int>(rc.left), static_cast<int>(rc.top),
887 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()),
888 static_cast<SurfaceGDI &>(surfaceSource).hdc,
889 static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY);
892 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
894 void SurfaceGDI::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
895 SetFont(font_);
896 RECT rcw = RectFromPRectangle(rc);
897 SIZE sz={0,0};
898 int pos = 0;
899 int x = static_cast<int>(rc.left);
900 const int yBaseInt = static_cast<int>(ybase);
902 // Text drawing may fail if the text is too big.
903 // If it does fail, slice up into segments and draw each segment.
904 const int maxSegmentLength = 0x200;
906 if (!unicodeMode) {
907 // Use ANSI calls
908 int lenDraw = Platform::Minimum(len, maxLenText);
909 if (!::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, s, lenDraw, NULL)) {
910 while (lenDraw > pos) {
911 int seglen = Platform::Minimum(maxSegmentLength, lenDraw - pos);
912 if (!::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, s + pos, seglen, NULL)) {
913 PLATFORM_ASSERT(false);
914 return;
916 ::GetTextExtentPoint32A(hdc, s+pos, seglen, &sz);
917 x += sz.cx;
918 pos += seglen;
921 } else {
922 // Use Unicode calls
923 const TextWide tbuf(s, len, unicodeMode, codePage);
924 if (!::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, NULL)) {
925 while (tbuf.tlen > pos) {
926 int seglen = Platform::Minimum(maxSegmentLength, tbuf.tlen - pos);
927 if (!::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer + pos, seglen, NULL)) {
928 PLATFORM_ASSERT(false);
929 return;
931 ::GetTextExtentPoint32W(hdc, tbuf.buffer+pos, seglen, &sz);
932 x += sz.cx;
933 pos += seglen;
939 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
940 ColourDesired fore, ColourDesired back) {
941 ::SetTextColor(hdc, fore.AsLong());
942 ::SetBkColor(hdc, back.AsLong());
943 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
946 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
947 ColourDesired fore, ColourDesired back) {
948 ::SetTextColor(hdc, fore.AsLong());
949 ::SetBkColor(hdc, back.AsLong());
950 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
953 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
954 ColourDesired fore) {
955 // Avoid drawing spaces in transparent mode
956 for (int i=0; i<len; i++) {
957 if (s[i] != ' ') {
958 ::SetTextColor(hdc, fore.AsLong());
959 ::SetBkMode(hdc, TRANSPARENT);
960 DrawTextCommon(rc, font_, ybase, s, len, 0);
961 ::SetBkMode(hdc, OPAQUE);
962 return;
967 XYPOSITION SurfaceGDI::WidthText(Font &font_, const char *s, int len) {
968 SetFont(font_);
969 SIZE sz={0,0};
970 if (!unicodeMode) {
971 ::GetTextExtentPoint32A(hdc, s, Platform::Minimum(len, maxLenText), &sz);
972 } else {
973 const TextWide tbuf(s, len, unicodeMode, codePage);
974 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
976 return static_cast<XYPOSITION>(sz.cx);
979 void SurfaceGDI::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
980 SetFont(font_);
981 SIZE sz={0,0};
982 int fit = 0;
983 if (unicodeMode) {
984 const TextWide tbuf(s, len, unicodeMode, codePage);
985 TextPositionsI poses(tbuf.tlen);
986 fit = tbuf.tlen;
987 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
988 // Likely to have failed because on Windows 9x where function not available
989 // So measure the character widths by measuring each initial substring
990 // Turns a linear operation into a quadratic but seems fast enough on test files
991 for (int widthSS=0; widthSS < tbuf.tlen; widthSS++) {
992 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
993 poses.buffer[widthSS] = sz.cx;
996 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
997 int ui=0;
998 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
999 int i=0;
1000 while (ui<fit) {
1001 unsigned char uch = us[i];
1002 unsigned int lenChar = 1;
1003 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1004 lenChar = 4;
1005 ui++;
1006 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1007 lenChar = 3;
1008 } else if (uch >= (0x80)) {
1009 lenChar = 2;
1011 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1012 positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
1014 ui++;
1016 XYPOSITION lastPos = 0.0f;
1017 if (i > 0)
1018 lastPos = positions[i-1];
1019 while (i<len) {
1020 positions[i++] = lastPos;
1022 } else {
1023 // Zero positions to avoid random behaviour on failure.
1024 std::fill(positions, positions + len, 0.0f);
1025 // len may be larger than platform supports so loop over segments small enough for platform
1026 int startOffset = 0;
1027 while (len > 0) {
1028 int lenBlock = Platform::Minimum(len, maxLenText);
1029 TextPositionsI poses(len);
1030 if (!::GetTextExtentExPointA(hdc, s, lenBlock, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1031 // Eeek - a NULL DC or other foolishness could cause this.
1032 return;
1033 } else if (fit < lenBlock) {
1034 // For some reason, such as an incomplete DBCS character
1035 // Not all the positions are filled in so make them equal to end.
1036 if (fit == 0)
1037 poses.buffer[fit++] = 0;
1038 for (int i = fit; i<lenBlock; i++)
1039 poses.buffer[i] = poses.buffer[fit-1];
1041 for (int i=0; i<lenBlock; i++)
1042 positions[i] = static_cast<XYPOSITION>(poses.buffer[i] + startOffset);
1043 startOffset = poses.buffer[lenBlock-1];
1044 len -= lenBlock;
1045 positions += lenBlock;
1046 s += lenBlock;
1051 XYPOSITION SurfaceGDI::WidthChar(Font &font_, char ch) {
1052 SetFont(font_);
1053 SIZE sz;
1054 ::GetTextExtentPoint32A(hdc, &ch, 1, &sz);
1055 return static_cast<XYPOSITION>(sz.cx);
1058 XYPOSITION SurfaceGDI::Ascent(Font &font_) {
1059 SetFont(font_);
1060 TEXTMETRIC tm;
1061 ::GetTextMetrics(hdc, &tm);
1062 return static_cast<XYPOSITION>(tm.tmAscent);
1065 XYPOSITION SurfaceGDI::Descent(Font &font_) {
1066 SetFont(font_);
1067 TEXTMETRIC tm;
1068 ::GetTextMetrics(hdc, &tm);
1069 return static_cast<XYPOSITION>(tm.tmDescent);
1072 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) {
1073 SetFont(font_);
1074 TEXTMETRIC tm;
1075 ::GetTextMetrics(hdc, &tm);
1076 return static_cast<XYPOSITION>(tm.tmInternalLeading);
1079 XYPOSITION SurfaceGDI::ExternalLeading(Font &font_) {
1080 SetFont(font_);
1081 TEXTMETRIC tm;
1082 ::GetTextMetrics(hdc, &tm);
1083 return static_cast<XYPOSITION>(tm.tmExternalLeading);
1086 XYPOSITION SurfaceGDI::Height(Font &font_) {
1087 SetFont(font_);
1088 TEXTMETRIC tm;
1089 ::GetTextMetrics(hdc, &tm);
1090 return static_cast<XYPOSITION>(tm.tmHeight);
1093 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) {
1094 SetFont(font_);
1095 TEXTMETRIC tm;
1096 ::GetTextMetrics(hdc, &tm);
1097 return static_cast<XYPOSITION>(tm.tmAveCharWidth);
1100 void SurfaceGDI::SetClip(PRectangle rc) {
1101 ::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
1102 static_cast<int>(rc.right), static_cast<int>(rc.bottom));
1105 void SurfaceGDI::FlushCachedState() {
1106 pen = 0;
1107 brush = 0;
1108 font = 0;
1111 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1112 unicodeMode=unicodeMode_;
1115 void SurfaceGDI::SetDBCSMode(int codePage_) {
1116 // No action on window as automatically handled by system.
1117 codePage = codePage_;
1120 #if defined(USE_D2D)
1122 class SurfaceD2D : public Surface {
1123 bool unicodeMode;
1124 int x, y;
1126 int codePage;
1127 int codePageText;
1129 ID2D1RenderTarget *pRenderTarget;
1130 bool ownRenderTarget;
1131 int clipsActive;
1133 IDWriteTextFormat *pTextFormat;
1134 FLOAT yAscent;
1135 FLOAT yDescent;
1136 FLOAT yInternalLeading;
1138 ID2D1SolidColorBrush *pBrush;
1140 int logPixelsY;
1141 float dpiScaleX;
1142 float dpiScaleY;
1144 void SetFont(Font &font_);
1146 // Private so SurfaceD2D objects can not be copied
1147 SurfaceD2D(const SurfaceD2D &);
1148 SurfaceD2D &operator=(const SurfaceD2D &);
1149 public:
1150 SurfaceD2D();
1151 virtual ~SurfaceD2D();
1153 void SetScale();
1154 void Init(WindowID wid);
1155 void Init(SurfaceID sid, WindowID wid);
1156 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
1158 void Release();
1159 bool Initialised();
1161 HRESULT FlushDrawing();
1163 void PenColour(ColourDesired fore);
1164 void D2DPenColour(ColourDesired fore, int alpha=255);
1165 int LogPixelsY();
1166 int DeviceHeightFont(int points);
1167 void MoveTo(int x_, int y_);
1168 void LineTo(int x_, int y_);
1169 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
1170 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
1171 void FillRectangle(PRectangle rc, ColourDesired back);
1172 void FillRectangle(PRectangle rc, Surface &surfacePattern);
1173 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
1174 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1175 ColourDesired outline, int alphaOutline, int flags);
1176 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
1177 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
1178 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
1180 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
1181 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1182 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1183 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
1184 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
1185 XYPOSITION WidthText(Font &font_, const char *s, int len);
1186 XYPOSITION WidthChar(Font &font_, char ch);
1187 XYPOSITION Ascent(Font &font_);
1188 XYPOSITION Descent(Font &font_);
1189 XYPOSITION InternalLeading(Font &font_);
1190 XYPOSITION ExternalLeading(Font &font_);
1191 XYPOSITION Height(Font &font_);
1192 XYPOSITION AverageCharWidth(Font &font_);
1194 void SetClip(PRectangle rc);
1195 void FlushCachedState();
1197 void SetUnicodeMode(bool unicodeMode_);
1198 void SetDBCSMode(int codePage_);
1201 SurfaceD2D::SurfaceD2D() :
1202 unicodeMode(false),
1203 x(0), y(0) {
1205 codePage = 0;
1206 codePageText = 0;
1208 pRenderTarget = NULL;
1209 ownRenderTarget = false;
1210 clipsActive = 0;
1212 // From selected font
1213 pTextFormat = NULL;
1214 yAscent = 2;
1215 yDescent = 1;
1216 yInternalLeading = 0;
1218 pBrush = NULL;
1220 logPixelsY = 72;
1221 dpiScaleX = 1.0;
1222 dpiScaleY = 1.0;
1225 SurfaceD2D::~SurfaceD2D() {
1226 Release();
1229 void SurfaceD2D::Release() {
1230 if (pBrush) {
1231 pBrush->Release();
1232 pBrush = 0;
1234 if (pRenderTarget) {
1235 while (clipsActive) {
1236 pRenderTarget->PopAxisAlignedClip();
1237 clipsActive--;
1239 if (ownRenderTarget) {
1240 pRenderTarget->Release();
1242 pRenderTarget = 0;
1246 void SurfaceD2D::SetScale() {
1247 HDC hdcMeasure = ::CreateCompatibleDC(NULL);
1248 logPixelsY = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
1249 dpiScaleX = ::GetDeviceCaps(hdcMeasure, LOGPIXELSX) / 96.0f;
1250 dpiScaleY = logPixelsY / 96.0f;
1251 ::DeleteDC(hdcMeasure);
1254 bool SurfaceD2D::Initialised() {
1255 return pRenderTarget != 0;
1258 HRESULT SurfaceD2D::FlushDrawing() {
1259 return pRenderTarget->Flush();
1262 void SurfaceD2D::Init(WindowID /* wid */) {
1263 Release();
1264 SetScale();
1267 void SurfaceD2D::Init(SurfaceID sid, WindowID) {
1268 Release();
1269 SetScale();
1270 pRenderTarget = reinterpret_cast<ID2D1RenderTarget *>(sid);
1273 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID) {
1274 Release();
1275 SetScale();
1276 SurfaceD2D *psurfOther = static_cast<SurfaceD2D *>(surface_);
1277 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = NULL;
1278 D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height));
1279 D2D1_PIXEL_FORMAT desiredFormat;
1280 #ifdef __MINGW32__
1281 desiredFormat.format = DXGI_FORMAT_UNKNOWN;
1282 #else
1283 desiredFormat = psurfOther->pRenderTarget->GetPixelFormat();
1284 #endif
1285 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1286 HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget(
1287 &desiredSize, NULL, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pCompatibleRenderTarget);
1288 if (SUCCEEDED(hr)) {
1289 pRenderTarget = pCompatibleRenderTarget;
1290 pRenderTarget->BeginDraw();
1291 ownRenderTarget = true;
1293 SetUnicodeMode(psurfOther->unicodeMode);
1294 SetDBCSMode(psurfOther->codePage);
1297 void SurfaceD2D::PenColour(ColourDesired fore) {
1298 D2DPenColour(fore);
1301 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) {
1302 if (pRenderTarget) {
1303 D2D_COLOR_F col;
1304 col.r = (fore.AsLong() & 0xff) / 255.0f;
1305 col.g = ((fore.AsLong() & 0xff00) >> 8) / 255.0f;
1306 col.b = (fore.AsLong() >> 16) / 255.0f;
1307 col.a = alpha / 255.0f;
1308 if (pBrush) {
1309 pBrush->SetColor(col);
1310 } else {
1311 HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1312 if (!SUCCEEDED(hr) && pBrush) {
1313 pBrush->Release();
1314 pBrush = 0;
1320 void SurfaceD2D::SetFont(Font &font_) {
1321 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
1322 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE);
1323 pTextFormat = pfm->pTextFormat;
1324 yAscent = pfm->yAscent;
1325 yDescent = pfm->yDescent;
1326 yInternalLeading = pfm->yInternalLeading;
1327 codePageText = codePage;
1328 if (pfm->characterSet) {
1329 codePageText = CodePageFromCharSet(pfm->characterSet, codePage);
1331 if (pRenderTarget) {
1332 D2D1_TEXT_ANTIALIAS_MODE aaMode;
1333 aaMode = DWriteMapFontQuality(pfm->extraFontFlag);
1335 if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && customClearTypeRenderingParams)
1336 pRenderTarget->SetTextRenderingParams(customClearTypeRenderingParams);
1337 else if (defaultRenderingParams)
1338 pRenderTarget->SetTextRenderingParams(defaultRenderingParams);
1340 pRenderTarget->SetTextAntialiasMode(aaMode);
1344 int SurfaceD2D::LogPixelsY() {
1345 return logPixelsY;
1348 int SurfaceD2D::DeviceHeightFont(int points) {
1349 return ::MulDiv(points, LogPixelsY(), 72);
1352 void SurfaceD2D::MoveTo(int x_, int y_) {
1353 x = x_;
1354 y = y_;
1357 static int Delta(int difference) {
1358 if (difference < 0)
1359 return -1;
1360 else if (difference > 0)
1361 return 1;
1362 else
1363 return 0;
1366 static float RoundFloat(float f) {
1367 return float(int(f+0.5f));
1370 void SurfaceD2D::LineTo(int x_, int y_) {
1371 if (pRenderTarget) {
1372 int xDiff = x_ - x;
1373 int xDelta = Delta(xDiff);
1374 int yDiff = y_ - y;
1375 int yDelta = Delta(yDiff);
1376 if ((xDiff == 0) || (yDiff == 0)) {
1377 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
1378 int xEnd = x_ - xDelta;
1379 int left = Platform::Minimum(x, xEnd);
1380 int width = abs(x - xEnd) + 1;
1381 int yEnd = y_ - yDelta;
1382 int top = Platform::Minimum(y, yEnd);
1383 int height = abs(y - yEnd) + 1;
1384 D2D1_RECT_F rectangle1 = D2D1::RectF(static_cast<float>(left), static_cast<float>(top),
1385 static_cast<float>(left+width), static_cast<float>(top+height));
1386 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1387 } else if ((abs(xDiff) == abs(yDiff))) {
1388 // 45 degree slope
1389 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f),
1390 D2D1::Point2F(x_ + 0.5f - xDelta, y_ + 0.5f - yDelta), pBrush);
1391 } else {
1392 // Line has a different slope so difficult to avoid last pixel
1393 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f),
1394 D2D1::Point2F(x_ + 0.5f, y_ + 0.5f), pBrush);
1396 x = x_;
1397 y = y_;
1401 void SurfaceD2D::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
1402 if (pRenderTarget) {
1403 ID2D1Factory *pFactory = 0;
1404 pRenderTarget->GetFactory(&pFactory);
1405 ID2D1PathGeometry *geometry=0;
1406 HRESULT hr = pFactory->CreatePathGeometry(&geometry);
1407 if (SUCCEEDED(hr)) {
1408 ID2D1GeometrySink *sink = 0;
1409 hr = geometry->Open(&sink);
1410 if (SUCCEEDED(hr)) {
1411 sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED);
1412 for (size_t i=1; i<static_cast<size_t>(npts); i++) {
1413 sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f));
1415 sink->EndFigure(D2D1_FIGURE_END_CLOSED);
1416 sink->Close();
1417 sink->Release();
1419 D2DPenColour(back);
1420 pRenderTarget->FillGeometry(geometry,pBrush);
1421 D2DPenColour(fore);
1422 pRenderTarget->DrawGeometry(geometry,pBrush);
1425 geometry->Release();
1430 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
1431 if (pRenderTarget) {
1432 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left) + 0.5f, rc.top+0.5f, RoundFloat(rc.right) - 0.5f, rc.bottom-0.5f);
1433 D2DPenColour(back);
1434 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1435 D2DPenColour(fore);
1436 pRenderTarget->DrawRectangle(&rectangle1, pBrush);
1440 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) {
1441 if (pRenderTarget) {
1442 D2DPenColour(back);
1443 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left), rc.top, RoundFloat(rc.right), rc.bottom);
1444 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1448 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1449 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfacePattern);
1450 surfOther.FlushDrawing();
1451 ID2D1Bitmap *pBitmap = NULL;
1452 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1453 surfOther.pRenderTarget);
1454 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1455 if (SUCCEEDED(hr)) {
1456 ID2D1BitmapBrush *pBitmapBrush = NULL;
1457 D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1458 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1459 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1460 // Create the bitmap brush.
1461 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1462 pBitmap->Release();
1463 if (SUCCEEDED(hr)) {
1464 pRenderTarget->FillRectangle(
1465 D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom),
1466 pBitmapBrush);
1467 pBitmapBrush->Release();
1472 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
1473 if (pRenderTarget) {
1474 D2D1_ROUNDED_RECT roundedRectFill = {
1475 D2D1::RectF(rc.left+1.0f, rc.top+1.0f, rc.right-1.0f, rc.bottom-1.0f),
1476 4, 4};
1477 D2DPenColour(back);
1478 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1480 D2D1_ROUNDED_RECT roundedRect = {
1481 D2D1::RectF(rc.left + 0.5f, rc.top+0.5f, rc.right - 0.5f, rc.bottom-0.5f),
1482 4, 4};
1483 D2DPenColour(fore);
1484 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1488 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1489 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
1490 if (pRenderTarget) {
1491 if (cornerSize == 0) {
1492 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1493 D2D1_RECT_F rectFill = D2D1::RectF(RoundFloat(rc.left) + 1.0f, rc.top + 1.0f, RoundFloat(rc.right) - 1.0f, rc.bottom - 1.0f);
1494 D2DPenColour(fill, alphaFill);
1495 pRenderTarget->FillRectangle(rectFill, pBrush);
1497 D2D1_RECT_F rectOutline = D2D1::RectF(RoundFloat(rc.left) + 0.5f, rc.top + 0.5f, RoundFloat(rc.right) - 0.5f, rc.bottom - 0.5f);
1498 D2DPenColour(outline, alphaOutline);
1499 pRenderTarget->DrawRectangle(rectOutline, pBrush);
1500 } else {
1501 const float cornerSizeF = static_cast<float>(cornerSize);
1502 D2D1_ROUNDED_RECT roundedRectFill = {
1503 D2D1::RectF(RoundFloat(rc.left) + 1.0f, rc.top + 1.0f, RoundFloat(rc.right) - 1.0f, rc.bottom - 1.0f),
1504 cornerSizeF, cornerSizeF};
1505 D2DPenColour(fill, alphaFill);
1506 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1508 D2D1_ROUNDED_RECT roundedRect = {
1509 D2D1::RectF(RoundFloat(rc.left) + 0.5f, rc.top + 0.5f, RoundFloat(rc.right) - 0.5f, rc.bottom - 0.5f),
1510 cornerSizeF, cornerSizeF};
1511 D2DPenColour(outline, alphaOutline);
1512 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1517 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1518 if (pRenderTarget) {
1519 if (rc.Width() > width)
1520 rc.left += static_cast<int>((rc.Width() - width) / 2);
1521 rc.right = rc.left + width;
1522 if (rc.Height() > height)
1523 rc.top += static_cast<int>((rc.Height() - height) / 2);
1524 rc.bottom = rc.top + height;
1526 std::vector<unsigned char> image(height * width * 4);
1527 for (int yPixel=0; yPixel<height; yPixel++) {
1528 for (int xPixel = 0; xPixel<width; xPixel++) {
1529 unsigned char *pixel = &image[0] + (yPixel*width + xPixel) * 4;
1530 unsigned char alpha = pixelsImage[3];
1531 // Input is RGBA, output is BGRA with premultiplied alpha
1532 pixel[2] = (*pixelsImage++) * alpha / 255;
1533 pixel[1] = (*pixelsImage++) * alpha / 255;
1534 pixel[0] = (*pixelsImage++) * alpha / 255;
1535 pixel[3] = *pixelsImage++;
1539 ID2D1Bitmap *bitmap = 0;
1540 D2D1_SIZE_U size = D2D1::SizeU(width, height);
1541 D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1542 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1543 HRESULT hr = pRenderTarget->CreateBitmap(size, &image[0],
1544 width * 4, &props, &bitmap);
1545 if (SUCCEEDED(hr)) {
1546 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1547 pRenderTarget->DrawBitmap(bitmap, rcDestination);
1548 bitmap->Release();
1553 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
1554 if (pRenderTarget) {
1555 FLOAT radius = rc.Width() / 2.0f;
1556 D2D1_ELLIPSE ellipse = {
1557 D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f),
1558 radius,radius};
1560 PenColour(back);
1561 pRenderTarget->FillEllipse(ellipse, pBrush);
1562 PenColour(fore);
1563 pRenderTarget->DrawEllipse(ellipse, pBrush);
1567 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1568 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfaceSource);
1569 surfOther.FlushDrawing();
1570 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1571 surfOther.pRenderTarget);
1572 ID2D1Bitmap *pBitmap = NULL;
1573 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1574 if (SUCCEEDED(hr)) {
1575 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1576 D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()};
1577 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1578 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1579 hr = pRenderTarget->Flush();
1580 if (FAILED(hr)) {
1581 Platform::DebugPrintf("Failed Flush 0x%x\n", hr);
1583 pBitmap->Release();
1587 void SurfaceD2D::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
1588 SetFont(font_);
1590 // Use Unicode calls
1591 const TextWide tbuf(s, len, unicodeMode, codePageText);
1592 if (pRenderTarget && pTextFormat && pBrush) {
1593 if (fuOptions & ETO_CLIPPED) {
1594 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1595 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1598 // Explicitly creating a text layout appears a little faster
1599 IDWriteTextLayout *pTextLayout;
1600 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat,
1601 rc.Width(), rc.Height(), &pTextLayout);
1602 if (SUCCEEDED(hr)) {
1603 D2D1_POINT_2F origin = {rc.left, ybase-yAscent};
1604 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, D2D1_DRAW_TEXT_OPTIONS_NONE);
1605 pTextLayout->Release();
1608 if (fuOptions & ETO_CLIPPED) {
1609 pRenderTarget->PopAxisAlignedClip();
1614 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1615 ColourDesired fore, ColourDesired back) {
1616 if (pRenderTarget) {
1617 FillRectangle(rc, back);
1618 D2DPenColour(fore);
1619 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
1623 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1624 ColourDesired fore, ColourDesired back) {
1625 if (pRenderTarget) {
1626 FillRectangle(rc, back);
1627 D2DPenColour(fore);
1628 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
1632 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1633 ColourDesired fore) {
1634 // Avoid drawing spaces in transparent mode
1635 for (int i=0; i<len; i++) {
1636 if (s[i] != ' ') {
1637 if (pRenderTarget) {
1638 D2DPenColour(fore);
1639 DrawTextCommon(rc, font_, ybase, s, len, 0);
1641 return;
1646 XYPOSITION SurfaceD2D::WidthText(Font &font_, const char *s, int len) {
1647 FLOAT width = 1.0;
1648 SetFont(font_);
1649 const TextWide tbuf(s, len, unicodeMode, codePageText);
1650 if (pIDWriteFactory && pTextFormat) {
1651 // Create a layout
1652 IDWriteTextLayout *pTextLayout = 0;
1653 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1654 if (SUCCEEDED(hr)) {
1655 DWRITE_TEXT_METRICS textMetrics;
1656 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1657 width = textMetrics.widthIncludingTrailingWhitespace;
1658 pTextLayout->Release();
1661 return width;
1664 void SurfaceD2D::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
1665 SetFont(font_);
1666 int fit = 0;
1667 const TextWide tbuf(s, len, unicodeMode, codePageText);
1668 TextPositions poses(tbuf.tlen);
1669 fit = tbuf.tlen;
1670 const int clusters = 1000;
1671 DWRITE_CLUSTER_METRICS clusterMetrics[clusters];
1672 UINT32 count = 0;
1673 if (pIDWriteFactory && pTextFormat) {
1674 SetFont(font_);
1675 // Create a layout
1676 IDWriteTextLayout *pTextLayout = 0;
1677 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
1678 if (!SUCCEEDED(hr))
1679 return;
1680 // For now, assuming WCHAR == cluster
1681 if (!SUCCEEDED(pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count)))
1682 return;
1683 FLOAT position = 0.0f;
1684 size_t ti=0;
1685 for (size_t ci=0; ci<count; ci++) {
1686 position += clusterMetrics[ci].width;
1687 for (size_t inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
1688 //poses.buffer[ti++] = int(position + 0.5);
1689 poses.buffer[ti++] = position;
1692 PLATFORM_ASSERT(ti == static_cast<size_t>(tbuf.tlen));
1693 pTextLayout->Release();
1695 if (unicodeMode) {
1696 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1697 int ui=0;
1698 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1699 int i=0;
1700 while (ui<fit) {
1701 unsigned char uch = us[i];
1702 unsigned int lenChar = 1;
1703 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1704 lenChar = 4;
1705 ui++;
1706 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1707 lenChar = 3;
1708 } else if (uch >= (0x80)) {
1709 lenChar = 2;
1711 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1712 positions[i++] = poses.buffer[ui];
1714 ui++;
1716 XYPOSITION lastPos = 0.0f;
1717 if (i > 0)
1718 lastPos = positions[i-1];
1719 while (i<len) {
1720 positions[i++] = lastPos;
1722 } else if (codePageText == 0) {
1724 // One character per position
1725 PLATFORM_ASSERT(len == tbuf.tlen);
1726 for (size_t kk=0; kk<static_cast<size_t>(len); kk++) {
1727 positions[kk] = poses.buffer[kk];
1730 } else {
1732 // May be more than one byte per position
1733 unsigned int ui = 0;
1734 FLOAT position = 0.0f;
1735 for (int i=0; i<len;) {
1736 if (ui < count)
1737 position = poses.buffer[ui];
1738 if (Platform::IsDBCSLeadByte(codePageText, s[i])) {
1739 positions[i] = position;
1740 positions[i+1] = position;
1741 i += 2;
1742 } else {
1743 positions[i] = position;
1744 i++;
1747 ui++;
1752 XYPOSITION SurfaceD2D::WidthChar(Font &font_, char ch) {
1753 FLOAT width = 1.0;
1754 SetFont(font_);
1755 if (pIDWriteFactory && pTextFormat) {
1756 // Create a layout
1757 IDWriteTextLayout *pTextLayout = 0;
1758 const WCHAR wch = ch;
1759 HRESULT hr = pIDWriteFactory->CreateTextLayout(&wch, 1, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1760 if (SUCCEEDED(hr)) {
1761 DWRITE_TEXT_METRICS textMetrics;
1762 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1763 width = textMetrics.widthIncludingTrailingWhitespace;
1764 pTextLayout->Release();
1767 return width;
1770 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
1771 SetFont(font_);
1772 return ceil(yAscent);
1775 XYPOSITION SurfaceD2D::Descent(Font &font_) {
1776 SetFont(font_);
1777 return ceil(yDescent);
1780 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
1781 SetFont(font_);
1782 return floor(yInternalLeading);
1785 XYPOSITION SurfaceD2D::ExternalLeading(Font &) {
1786 // Not implemented, always return one
1787 return 1;
1790 XYPOSITION SurfaceD2D::Height(Font &font_) {
1791 return Ascent(font_) + Descent(font_);
1794 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) {
1795 FLOAT width = 1.0;
1796 SetFont(font_);
1797 if (pIDWriteFactory && pTextFormat) {
1798 // Create a layout
1799 IDWriteTextLayout *pTextLayout = 0;
1800 const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1801 const size_t lenAllAlpha = wcslen(wszAllAlpha);
1802 HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(lenAllAlpha),
1803 pTextFormat, 1000.0, 1000.0, &pTextLayout);
1804 if (SUCCEEDED(hr)) {
1805 DWRITE_TEXT_METRICS textMetrics;
1806 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1807 width = textMetrics.width / lenAllAlpha;
1808 pTextLayout->Release();
1811 return width;
1814 void SurfaceD2D::SetClip(PRectangle rc) {
1815 if (pRenderTarget) {
1816 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1817 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1818 clipsActive++;
1822 void SurfaceD2D::FlushCachedState() {
1825 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
1826 unicodeMode=unicodeMode_;
1829 void SurfaceD2D::SetDBCSMode(int codePage_) {
1830 // No action on window as automatically handled by system.
1831 codePage = codePage_;
1833 #endif
1835 Surface *Surface::Allocate(int technology) {
1836 #if defined(USE_D2D)
1837 if (technology == SCWIN_TECH_GDI)
1838 return new SurfaceGDI;
1839 else
1840 return new SurfaceD2D;
1841 #else
1842 return new SurfaceGDI;
1843 #endif
1846 Window::~Window() {
1849 void Window::Destroy() {
1850 if (wid)
1851 ::DestroyWindow(reinterpret_cast<HWND>(wid));
1852 wid = 0;
1855 bool Window::HasFocus() {
1856 return ::GetFocus() == wid;
1859 PRectangle Window::GetPosition() {
1860 RECT rc;
1861 ::GetWindowRect(reinterpret_cast<HWND>(wid), &rc);
1862 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
1865 void Window::SetPosition(PRectangle rc) {
1866 ::SetWindowPos(reinterpret_cast<HWND>(wid),
1867 0, static_cast<int>(rc.left), static_cast<int>(rc.top),
1868 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE);
1871 static RECT RectFromMonitor(HMONITOR hMonitor) {
1872 if (GetMonitorInfoFn) {
1873 MONITORINFO mi = {0};
1874 mi.cbSize = sizeof(mi);
1875 if (GetMonitorInfoFn(hMonitor, &mi)) {
1876 return mi.rcWork;
1879 RECT rc = {0, 0, 0, 0};
1880 if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) {
1881 rc.left = 0;
1882 rc.top = 0;
1883 rc.right = 0;
1884 rc.bottom = 0;
1886 return rc;
1889 void Window::SetPositionRelative(PRectangle rc, Window w) {
1890 LONG style = ::GetWindowLong(reinterpret_cast<HWND>(wid), GWL_STYLE);
1891 if (style & WS_POPUP) {
1892 POINT ptOther = {0, 0};
1893 ::ClientToScreen(reinterpret_cast<HWND>(w.GetID()), &ptOther);
1894 rc.Move(static_cast<XYPOSITION>(ptOther.x), static_cast<XYPOSITION>(ptOther.y));
1896 RECT rcMonitor = RectFromPRectangle(rc);
1898 HMONITOR hMonitor = NULL;
1899 if (MonitorFromRectFn)
1900 hMonitor = MonitorFromRectFn(&rcMonitor, MONITOR_DEFAULTTONEAREST);
1901 // If hMonitor is NULL, that's just the main screen anyways.
1902 //::GetMonitorInfo(hMonitor, &mi);
1903 RECT rcWork = RectFromMonitor(hMonitor);
1905 if (rcWork.left < rcWork.right) {
1906 // Now clamp our desired rectangle to fit inside the work area
1907 // This way, the menu will fit wholly on one screen. An improvement even
1908 // if you don't have a second monitor on the left... Menu's appears half on
1909 // one screen and half on the other are just U.G.L.Y.!
1910 if (rc.right > rcWork.right)
1911 rc.Move(rcWork.right - rc.right, 0);
1912 if (rc.bottom > rcWork.bottom)
1913 rc.Move(0, rcWork.bottom - rc.bottom);
1914 if (rc.left < rcWork.left)
1915 rc.Move(rcWork.left - rc.left, 0);
1916 if (rc.top < rcWork.top)
1917 rc.Move(0, rcWork.top - rc.top);
1920 SetPosition(rc);
1923 PRectangle Window::GetClientPosition() {
1924 RECT rc={0,0,0,0};
1925 if (wid)
1926 ::GetClientRect(reinterpret_cast<HWND>(wid), &rc);
1927 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
1930 void Window::Show(bool show) {
1931 if (show)
1932 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_SHOWNOACTIVATE);
1933 else
1934 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_HIDE);
1937 void Window::InvalidateAll() {
1938 ::InvalidateRect(reinterpret_cast<HWND>(wid), NULL, FALSE);
1941 void Window::InvalidateRectangle(PRectangle rc) {
1942 RECT rcw = RectFromPRectangle(rc);
1943 ::InvalidateRect(reinterpret_cast<HWND>(wid), &rcw, FALSE);
1946 static LRESULT Window_SendMessage(Window *w, UINT msg, WPARAM wParam=0, LPARAM lParam=0) {
1947 return ::SendMessage(reinterpret_cast<HWND>(w->GetID()), msg, wParam, lParam);
1950 void Window::SetFont(Font &font) {
1951 Window_SendMessage(this, WM_SETFONT,
1952 reinterpret_cast<WPARAM>(font.GetID()), 0);
1955 static void FlipBitmap(HBITMAP bitmap, int width, int height) {
1956 HDC hdc = ::CreateCompatibleDC(NULL);
1957 if (hdc != NULL) {
1958 HGDIOBJ prevBmp = ::SelectObject(hdc, bitmap);
1959 ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
1960 ::SelectObject(hdc, prevBmp);
1961 ::DeleteDC(hdc);
1965 static HCURSOR GetReverseArrowCursor() {
1966 if (reverseArrowCursor != NULL)
1967 return reverseArrowCursor;
1969 ::EnterCriticalSection(&crPlatformLock);
1970 HCURSOR cursor = reverseArrowCursor;
1971 if (cursor == NULL) {
1972 cursor = ::LoadCursor(NULL, IDC_ARROW);
1973 ICONINFO info;
1974 if (::GetIconInfo(cursor, &info)) {
1975 BITMAP bmp;
1976 if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) {
1977 FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
1978 if (info.hbmColor != NULL)
1979 FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
1980 info.xHotspot = (DWORD)bmp.bmWidth - 1 - info.xHotspot;
1982 reverseArrowCursor = ::CreateIconIndirect(&info);
1983 if (reverseArrowCursor != NULL)
1984 cursor = reverseArrowCursor;
1987 ::DeleteObject(info.hbmMask);
1988 if (info.hbmColor != NULL)
1989 ::DeleteObject(info.hbmColor);
1992 ::LeaveCriticalSection(&crPlatformLock);
1993 return cursor;
1996 void Window::SetCursor(Cursor curs) {
1997 switch (curs) {
1998 case cursorText:
1999 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
2000 break;
2001 case cursorUp:
2002 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
2003 break;
2004 case cursorWait:
2005 ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
2006 break;
2007 case cursorHoriz:
2008 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
2009 break;
2010 case cursorVert:
2011 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
2012 break;
2013 case cursorHand:
2014 ::SetCursor(::LoadCursor(NULL,IDC_HAND));
2015 break;
2016 case cursorReverseArrow:
2017 ::SetCursor(GetReverseArrowCursor());
2018 break;
2019 case cursorArrow:
2020 case cursorInvalid: // Should not occur, but just in case.
2021 ::SetCursor(::LoadCursor(NULL,IDC_ARROW));
2022 break;
2026 void Window::SetTitle(const char *s) {
2027 ::SetWindowTextA(reinterpret_cast<HWND>(wid), s);
2030 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
2031 coordinates */
2032 PRectangle Window::GetMonitorRect(Point pt) {
2033 // MonitorFromPoint and GetMonitorInfo are not available on Windows 95 and NT 4.
2034 PRectangle rcPosition = GetPosition();
2035 POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
2036 static_cast<LONG>(pt.y + rcPosition.top)};
2037 HMONITOR hMonitor = NULL;
2038 if (MonitorFromPointFn)
2039 hMonitor = MonitorFromPointFn(ptDesktop, MONITOR_DEFAULTTONEAREST);
2041 RECT rcWork = RectFromMonitor(hMonitor);
2042 if (rcWork.left < rcWork.right) {
2043 PRectangle rcMonitor(
2044 rcWork.left - rcPosition.left,
2045 rcWork.top - rcPosition.top,
2046 rcWork.right - rcPosition.left,
2047 rcWork.bottom - rcPosition.top);
2048 return rcMonitor;
2049 } else {
2050 return PRectangle();
2054 struct ListItemData {
2055 const char *text;
2056 int pixId;
2059 class LineToItem {
2060 std::vector<char> words;
2062 std::vector<ListItemData> data;
2064 public:
2065 LineToItem() {
2067 ~LineToItem() {
2068 Clear();
2070 void Clear() {
2071 words.clear();
2072 data.clear();
2075 ListItemData Get(int index) const {
2076 if (index >= 0 && index < static_cast<int>(data.size())) {
2077 return data[index];
2078 } else {
2079 ListItemData missing = {"", -1};
2080 return missing;
2083 int Count() const {
2084 return static_cast<int>(data.size());
2087 void AllocItem(const char *text, int pixId) {
2088 ListItemData lid = { text, pixId };
2089 data.push_back(lid);
2092 char *SetWords(const char *s) {
2093 words = std::vector<char>(s, s+strlen(s)+1);
2094 return &words[0];
2098 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
2100 ListBox::ListBox() {
2103 ListBox::~ListBox() {
2106 class ListBoxX : public ListBox {
2107 int lineHeight;
2108 FontID fontCopy;
2109 int technology;
2110 RGBAImageSet images;
2111 LineToItem lti;
2112 HWND lb;
2113 bool unicodeMode;
2114 int desiredVisibleRows;
2115 unsigned int maxItemCharacters;
2116 unsigned int aveCharWidth;
2117 Window *parent;
2118 int ctrlID;
2119 CallBackAction doubleClickAction;
2120 void *doubleClickActionData;
2121 const char *widestItem;
2122 unsigned int maxCharWidth;
2123 int resizeHit;
2124 PRectangle rcPreSize;
2125 Point dragOffset;
2126 Point location; // Caret location at which the list is opened
2127 int wheelDelta; // mouse wheel residue
2129 HWND GetHWND() const;
2130 void AppendListItem(const char *text, const char *numword);
2131 static void AdjustWindowRect(PRectangle *rc);
2132 int ItemHeight() const;
2133 int MinClientWidth() const;
2134 int TextOffset() const;
2135 POINT GetClientExtent() const;
2136 POINT MinTrackSize() const;
2137 POINT MaxTrackSize() const;
2138 void SetRedraw(bool on);
2139 void OnDoubleClick();
2140 void ResizeToCursor();
2141 void StartResize(WPARAM);
2142 LRESULT NcHitTest(WPARAM, LPARAM) const;
2143 void CentreItem(int n);
2144 void Paint(HDC);
2145 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2147 static const Point ItemInset; // Padding around whole item
2148 static const Point TextInset; // Padding around text
2149 static const Point ImageInset; // Padding around image
2151 public:
2152 ListBoxX() : lineHeight(10), fontCopy(0), technology(0), lb(0), unicodeMode(false),
2153 desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8),
2154 parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL),
2155 widestItem(NULL), maxCharWidth(1), resizeHit(0), wheelDelta(0) {
2157 virtual ~ListBoxX() {
2158 if (fontCopy) {
2159 ::DeleteObject(fontCopy);
2160 fontCopy = 0;
2163 virtual void SetFont(Font &font);
2164 virtual void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
2165 virtual void SetAverageCharWidth(int width);
2166 virtual void SetVisibleRows(int rows);
2167 virtual int GetVisibleRows() const;
2168 virtual PRectangle GetDesiredRect();
2169 virtual int CaretFromEdge();
2170 virtual void Clear();
2171 virtual void Append(char *s, int type = -1);
2172 virtual int Length();
2173 virtual void Select(int n);
2174 virtual int GetSelection();
2175 virtual int Find(const char *prefix);
2176 virtual void GetValue(int n, char *value, int len);
2177 virtual void RegisterImage(int type, const char *xpm_data);
2178 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
2179 virtual void ClearRegisteredImages();
2180 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
2181 doubleClickAction = action;
2182 doubleClickActionData = data;
2184 virtual void SetList(const char *list, char separator, char typesep);
2185 void Draw(DRAWITEMSTRUCT *pDrawItem);
2186 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2187 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2190 const Point ListBoxX::ItemInset(0, 0);
2191 const Point ListBoxX::TextInset(2, 0);
2192 const Point ListBoxX::ImageInset(1, 0);
2194 ListBox *ListBox::Allocate() {
2195 ListBoxX *lb = new ListBoxX();
2196 return lb;
2199 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) {
2200 parent = &parent_;
2201 ctrlID = ctrlID_;
2202 location = location_;
2203 lineHeight = lineHeight_;
2204 unicodeMode = unicodeMode_;
2205 technology = technology_;
2206 HWND hwndParent = reinterpret_cast<HWND>(parent->GetID());
2207 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
2208 // Window created as popup so not clipped within parent client area
2209 wid = ::CreateWindowEx(
2210 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
2211 WS_POPUP | WS_THICKFRAME,
2212 100,100, 150,80, hwndParent,
2213 NULL,
2214 hinstanceParent,
2215 this);
2217 POINT locationw = {static_cast<LONG>(location.x), static_cast<LONG>(location.y)};
2218 ::MapWindowPoints(hwndParent, NULL, &locationw, 1);
2219 location = Point::FromInts(locationw.x, locationw.y);
2222 void ListBoxX::SetFont(Font &font) {
2223 if (font.GetID()) {
2224 if (fontCopy) {
2225 ::DeleteObject(fontCopy);
2226 fontCopy = 0;
2228 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font.GetID());
2229 fontCopy = pfm->HFont();
2230 ::SendMessage(lb, WM_SETFONT, reinterpret_cast<WPARAM>(fontCopy), 0);
2234 void ListBoxX::SetAverageCharWidth(int width) {
2235 aveCharWidth = width;
2238 void ListBoxX::SetVisibleRows(int rows) {
2239 desiredVisibleRows = rows;
2242 int ListBoxX::GetVisibleRows() const {
2243 return desiredVisibleRows;
2246 HWND ListBoxX::GetHWND() const {
2247 return reinterpret_cast<HWND>(GetID());
2250 PRectangle ListBoxX::GetDesiredRect() {
2251 PRectangle rcDesired = GetPosition();
2253 int rows = Length();
2254 if ((rows == 0) || (rows > desiredVisibleRows))
2255 rows = desiredVisibleRows;
2256 rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
2258 int width = MinClientWidth();
2259 HDC hdc = ::GetDC(lb);
2260 HFONT oldFont = SelectFont(hdc, fontCopy);
2261 SIZE textSize = {0, 0};
2262 int len = 0;
2263 if (widestItem) {
2264 len = static_cast<int>(strlen(widestItem));
2265 if (unicodeMode) {
2266 const TextWide tbuf(widestItem, len, unicodeMode);
2267 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
2268 } else {
2269 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
2272 TEXTMETRIC tm;
2273 ::GetTextMetrics(hdc, &tm);
2274 maxCharWidth = tm.tmMaxCharWidth;
2275 SelectFont(hdc, oldFont);
2276 ::ReleaseDC(lb, hdc);
2278 int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
2279 if (width < widthDesired)
2280 width = widthDesired;
2282 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
2283 if (Length() > rows)
2284 rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
2286 AdjustWindowRect(&rcDesired);
2287 return rcDesired;
2290 int ListBoxX::TextOffset() const {
2291 int pixWidth = images.GetWidth();
2292 return static_cast<int>(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2));
2295 int ListBoxX::CaretFromEdge() {
2296 PRectangle rc;
2297 AdjustWindowRect(&rc);
2298 return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1);
2301 void ListBoxX::Clear() {
2302 ::SendMessage(lb, LB_RESETCONTENT, 0, 0);
2303 maxItemCharacters = 0;
2304 widestItem = NULL;
2305 lti.Clear();
2308 void ListBoxX::Append(char *, int) {
2309 // This method is no longer called in Scintilla
2310 PLATFORM_ASSERT(false);
2313 int ListBoxX::Length() {
2314 return lti.Count();
2317 void ListBoxX::Select(int n) {
2318 // We are going to scroll to centre on the new selection and then select it, so disable
2319 // redraw to avoid flicker caused by a painting new selection twice in unselected and then
2320 // selected states
2321 SetRedraw(false);
2322 CentreItem(n);
2323 ::SendMessage(lb, LB_SETCURSEL, n, 0);
2324 SetRedraw(true);
2327 int ListBoxX::GetSelection() {
2328 return static_cast<int>(::SendMessage(lb, LB_GETCURSEL, 0, 0));
2331 // This is not actually called at present
2332 int ListBoxX::Find(const char *) {
2333 return LB_ERR;
2336 void ListBoxX::GetValue(int n, char *value, int len) {
2337 ListItemData item = lti.Get(n);
2338 strncpy(value, item.text, len);
2339 value[len-1] = '\0';
2342 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2343 XPM xpmImage(xpm_data);
2344 images.Add(type, new RGBAImage(xpmImage));
2347 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2348 images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage));
2351 void ListBoxX::ClearRegisteredImages() {
2352 images.Clear();
2355 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
2356 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
2357 RECT rcBox = pDrawItem->rcItem;
2358 rcBox.left += TextOffset();
2359 if (pDrawItem->itemState & ODS_SELECTED) {
2360 RECT rcImage = pDrawItem->rcItem;
2361 rcImage.right = rcBox.left;
2362 // The image is not highlighted
2363 ::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2364 ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
2365 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
2366 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
2367 } else {
2368 ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2369 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
2370 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
2373 ListItemData item = lti.Get(pDrawItem->itemID);
2374 int pixId = item.pixId;
2375 const char *text = item.text;
2376 int len = static_cast<int>(strlen(text));
2378 RECT rcText = rcBox;
2379 ::InsetRect(&rcText, static_cast<int>(TextInset.x), static_cast<int>(TextInset.y));
2381 if (unicodeMode) {
2382 const TextWide tbuf(text, len, unicodeMode);
2383 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2384 } else {
2385 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2387 if (pDrawItem->itemState & ODS_SELECTED) {
2388 ::DrawFocusRect(pDrawItem->hDC, &rcBox);
2391 // Draw the image, if any
2392 RGBAImage *pimage = images.Get(pixId);
2393 if (pimage) {
2394 Surface *surfaceItem = Surface::Allocate(technology);
2395 if (surfaceItem) {
2396 if (technology == SCWIN_TECH_GDI) {
2397 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
2398 long left = pDrawItem->rcItem.left + static_cast<int>(ItemInset.x + ImageInset.x);
2399 PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
2400 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2401 surfaceItem->DrawRGBAImage(rcImage,
2402 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2403 delete surfaceItem;
2404 ::SetTextAlign(pDrawItem->hDC, TA_TOP);
2405 } else {
2406 #if defined(USE_D2D)
2407 D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
2408 D2D1_RENDER_TARGET_TYPE_DEFAULT,
2409 D2D1::PixelFormat(
2410 DXGI_FORMAT_B8G8R8A8_UNORM,
2411 D2D1_ALPHA_MODE_IGNORE),
2414 D2D1_RENDER_TARGET_USAGE_NONE,
2415 D2D1_FEATURE_LEVEL_DEFAULT
2417 ID2D1DCRenderTarget *pDCRT = 0;
2418 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
2419 if (SUCCEEDED(hr)) {
2420 RECT rcWindow;
2421 GetClientRect(pDrawItem->hwndItem, &rcWindow);
2422 hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow);
2423 if (SUCCEEDED(hr)) {
2424 surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
2425 pDCRT->BeginDraw();
2426 long left = pDrawItem->rcItem.left + static_cast<long>(ItemInset.x + ImageInset.x);
2427 PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
2428 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2429 surfaceItem->DrawRGBAImage(rcImage,
2430 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2431 delete surfaceItem;
2432 pDCRT->EndDraw();
2433 pDCRT->Release();
2434 } else {
2435 delete surfaceItem;
2437 } else {
2438 delete surfaceItem;
2440 #endif
2447 void ListBoxX::AppendListItem(const char *text, const char *numword) {
2448 int pixId = -1;
2449 if (numword) {
2450 pixId = 0;
2451 char ch;
2452 while ((ch = *++numword) != '\0') {
2453 pixId = 10 * pixId + (ch - '0');
2457 lti.AllocItem(text, pixId);
2458 unsigned int len = static_cast<unsigned int>(strlen(text));
2459 if (maxItemCharacters < len) {
2460 maxItemCharacters = len;
2461 widestItem = text;
2465 void ListBoxX::SetList(const char *list, char separator, char typesep) {
2466 // Turn off redraw while populating the list - this has a significant effect, even if
2467 // the listbox is not visible.
2468 SetRedraw(false);
2469 Clear();
2470 size_t size = strlen(list);
2471 char *words = lti.SetWords(list);
2472 char *startword = words;
2473 char *numword = NULL;
2474 for (size_t i=0; i < size; i++) {
2475 if (words[i] == separator) {
2476 words[i] = '\0';
2477 if (numword)
2478 *numword = '\0';
2479 AppendListItem(startword, numword);
2480 startword = words + i + 1;
2481 numword = NULL;
2482 } else if (words[i] == typesep) {
2483 numword = words + i;
2486 if (startword) {
2487 if (numword)
2488 *numword = '\0';
2489 AppendListItem(startword, numword);
2492 // Finally populate the listbox itself with the correct number of items
2493 int count = lti.Count();
2494 ::SendMessage(lb, LB_INITSTORAGE, count, 0);
2495 for (int j=0; j<count; j++) {
2496 ::SendMessage(lb, LB_ADDSTRING, 0, j+1);
2498 SetRedraw(true);
2501 void ListBoxX::AdjustWindowRect(PRectangle *rc) {
2502 RECT rcw = RectFromPRectangle(*rc);
2503 ::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
2504 *rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom);
2507 int ListBoxX::ItemHeight() const {
2508 int itemHeight = lineHeight + (static_cast<int>(TextInset.y) * 2);
2509 int pixHeight = images.GetHeight() + (static_cast<int>(ImageInset.y) * 2);
2510 if (itemHeight < pixHeight) {
2511 itemHeight = pixHeight;
2513 return itemHeight;
2516 int ListBoxX::MinClientWidth() const {
2517 return 12 * (aveCharWidth+aveCharWidth/3);
2520 POINT ListBoxX::MinTrackSize() const {
2521 PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight());
2522 AdjustWindowRect(&rc);
2523 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2524 return ret;
2527 POINT ListBoxX::MaxTrackSize() const {
2528 PRectangle rc = PRectangle::FromInts(0, 0,
2529 Platform::Maximum(MinClientWidth(),
2530 maxCharWidth * maxItemCharacters + static_cast<int>(TextInset.x) * 2 +
2531 TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL)),
2532 ItemHeight() * lti.Count());
2533 AdjustWindowRect(&rc);
2534 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2535 return ret;
2538 void ListBoxX::SetRedraw(bool on) {
2539 ::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
2540 if (on)
2541 ::InvalidateRect(lb, NULL, TRUE);
2544 static XYPOSITION XYMinimum(XYPOSITION a, XYPOSITION b) {
2545 if (a < b)
2546 return a;
2547 else
2548 return b;
2551 static XYPOSITION XYMaximum(XYPOSITION a, XYPOSITION b) {
2552 if (a > b)
2553 return a;
2554 else
2555 return b;
2558 void ListBoxX::ResizeToCursor() {
2559 PRectangle rc = GetPosition();
2560 POINT ptw;
2561 ::GetCursorPos(&ptw);
2562 Point pt = Point::FromInts(ptw.x, ptw.y);
2563 pt.x += dragOffset.x;
2564 pt.y += dragOffset.y;
2566 switch (resizeHit) {
2567 case HTLEFT:
2568 rc.left = pt.x;
2569 break;
2570 case HTRIGHT:
2571 rc.right = pt.x;
2572 break;
2573 case HTTOP:
2574 rc.top = pt.y;
2575 break;
2576 case HTTOPLEFT:
2577 rc.top = pt.y;
2578 rc.left = pt.x;
2579 break;
2580 case HTTOPRIGHT:
2581 rc.top = pt.y;
2582 rc.right = pt.x;
2583 break;
2584 case HTBOTTOM:
2585 rc.bottom = pt.y;
2586 break;
2587 case HTBOTTOMLEFT:
2588 rc.bottom = pt.y;
2589 rc.left = pt.x;
2590 break;
2591 case HTBOTTOMRIGHT:
2592 rc.bottom = pt.y;
2593 rc.right = pt.x;
2594 break;
2597 POINT ptMin = MinTrackSize();
2598 POINT ptMax = MaxTrackSize();
2599 // We don't allow the left edge to move at present, but just in case
2600 rc.left = XYMaximum(XYMinimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
2601 rc.top = XYMaximum(XYMinimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
2602 rc.right = XYMaximum(XYMinimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
2603 rc.bottom = XYMaximum(XYMinimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
2605 SetPosition(rc);
2608 void ListBoxX::StartResize(WPARAM hitCode) {
2609 rcPreSize = GetPosition();
2610 POINT cursorPos;
2611 ::GetCursorPos(&cursorPos);
2613 switch (hitCode) {
2614 case HTRIGHT:
2615 case HTBOTTOM:
2616 case HTBOTTOMRIGHT:
2617 dragOffset.x = rcPreSize.right - cursorPos.x;
2618 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2619 break;
2621 case HTTOPRIGHT:
2622 dragOffset.x = rcPreSize.right - cursorPos.x;
2623 dragOffset.y = rcPreSize.top - cursorPos.y;
2624 break;
2626 // Note that the current hit test code prevents the left edge cases ever firing
2627 // as we don't want the left edge to be moveable
2628 case HTLEFT:
2629 case HTTOP:
2630 case HTTOPLEFT:
2631 dragOffset.x = rcPreSize.left - cursorPos.x;
2632 dragOffset.y = rcPreSize.top - cursorPos.y;
2633 break;
2634 case HTBOTTOMLEFT:
2635 dragOffset.x = rcPreSize.left - cursorPos.x;
2636 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2637 break;
2639 default:
2640 return;
2643 ::SetCapture(GetHWND());
2644 resizeHit = static_cast<int>(hitCode);
2647 LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
2648 LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
2649 // There is an apparent bug in the DefWindowProc hit test code whereby it will
2650 // return HTTOPXXX if the window in question is shorter than the default
2651 // window caption height + frame, even if one is hovering over the bottom edge of
2652 // the frame, so workaround that here
2653 if (hit >= HTTOP && hit <= HTTOPRIGHT) {
2654 int minHeight = GetSystemMetrics(SM_CYMINTRACK);
2655 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2656 int yPos = GET_Y_LPARAM(lParam);
2657 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
2658 hit += HTBOTTOM - HTTOP;
2662 // Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
2663 // depending on whether the list is above or below the caret
2664 switch (hit) {
2665 case HTLEFT:
2666 case HTTOPLEFT:
2667 case HTBOTTOMLEFT:
2668 hit = HTERROR;
2669 break;
2671 case HTTOP:
2672 case HTTOPRIGHT: {
2673 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2674 // Valid only if caret below list
2675 if (location.y < rc.top)
2676 hit = HTERROR;
2678 break;
2680 case HTBOTTOM:
2681 case HTBOTTOMRIGHT: {
2682 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2683 // Valid only if caret above list
2684 if (rc.bottom < location.y)
2685 hit = HTERROR;
2687 break;
2690 return hit;
2693 void ListBoxX::OnDoubleClick() {
2695 if (doubleClickAction != NULL) {
2696 doubleClickAction(doubleClickActionData);
2700 POINT ListBoxX::GetClientExtent() const {
2701 PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
2702 POINT ret;
2703 ret.x = static_cast<LONG>(rc.Width());
2704 ret.y = static_cast<LONG>(rc.Height());
2705 return ret;
2708 void ListBoxX::CentreItem(int n) {
2709 // If below mid point, scroll up to centre, but with more items below if uneven
2710 if (n >= 0) {
2711 POINT extent = GetClientExtent();
2712 int visible = extent.y/ItemHeight();
2713 if (visible < Length()) {
2714 LRESULT top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
2715 int half = (visible - 1) / 2;
2716 if (n > (top + half))
2717 ::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
2722 // Performs a double-buffered paint operation to avoid flicker
2723 void ListBoxX::Paint(HDC hDC) {
2724 POINT extent = GetClientExtent();
2725 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
2726 HDC bitmapDC = ::CreateCompatibleDC(hDC);
2727 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
2728 // The list background is mainly erased during painting, but can be a small
2729 // unpainted area when at the end of a non-integrally sized list with a
2730 // vertical scroll bar
2731 RECT rc = { 0, 0, extent.x, extent.y };
2732 ::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2733 // Paint the entire client area and vertical scrollbar
2734 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
2735 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
2736 // Select a stock brush to prevent warnings from BoundsChecker
2737 ::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
2738 SelectBitmap(bitmapDC, hBitmapOld);
2739 ::DeleteDC(bitmapDC);
2740 ::DeleteObject(hBitmap);
2743 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
2744 try {
2745 switch (uMsg) {
2746 case WM_ERASEBKGND:
2747 return TRUE;
2749 case WM_PAINT: {
2750 PAINTSTRUCT ps;
2751 HDC hDC = ::BeginPaint(hWnd, &ps);
2752 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2753 if (lbx)
2754 lbx->Paint(hDC);
2755 ::EndPaint(hWnd, &ps);
2757 return 0;
2759 case WM_MOUSEACTIVATE:
2760 // This prevents the view activating when the scrollbar is clicked
2761 return MA_NOACTIVATE;
2763 case WM_LBUTTONDOWN: {
2764 // We must take control of selection to prevent the ListBox activating
2765 // the popup
2766 LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
2767 int item = LOWORD(lResult);
2768 if (HIWORD(lResult) == 0 && item >= 0) {
2769 ::SendMessage(hWnd, LB_SETCURSEL, item, 0);
2772 return 0;
2774 case WM_LBUTTONUP:
2775 return 0;
2777 case WM_LBUTTONDBLCLK: {
2778 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2779 if (lbx) {
2780 lbx->OnDoubleClick();
2783 return 0;
2785 case WM_MBUTTONDOWN:
2786 // disable the scroll wheel button click action
2787 return 0;
2790 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
2791 if (prevWndProc) {
2792 return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
2793 } else {
2794 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2796 } catch (...) {
2798 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2801 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2802 switch (iMessage) {
2803 case WM_CREATE: {
2804 HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID()));
2805 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
2806 // but has useful side effect of speeding up list population significantly
2807 lb = ::CreateWindowEx(
2808 0, TEXT("listbox"), TEXT(""),
2809 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
2810 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
2811 0, 0, 150,80, hWnd,
2812 reinterpret_cast<HMENU>(ctrlID),
2813 hinstanceParent,
2815 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(lb, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlWndProc)));
2816 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
2818 break;
2820 case WM_SIZE:
2821 if (lb) {
2822 SetRedraw(false);
2823 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
2824 // Ensure the selection remains visible
2825 CentreItem(GetSelection());
2826 SetRedraw(true);
2828 break;
2830 case WM_PAINT: {
2831 PAINTSTRUCT ps;
2832 ::BeginPaint(hWnd, &ps);
2833 ::EndPaint(hWnd, &ps);
2835 break;
2837 case WM_COMMAND:
2838 // This is not actually needed now - the registered double click action is used
2839 // directly to action a choice from the list.
2840 ::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);
2841 break;
2843 case WM_MEASUREITEM: {
2844 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
2845 pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());
2847 break;
2849 case WM_DRAWITEM:
2850 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
2851 break;
2853 case WM_DESTROY:
2854 lb = 0;
2855 ::SetWindowLong(hWnd, 0, 0);
2856 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2858 case WM_ERASEBKGND:
2859 // To reduce flicker we can elide background erasure since this window is
2860 // completely covered by its child.
2861 return TRUE;
2863 case WM_GETMINMAXINFO: {
2864 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
2865 minMax->ptMaxTrackSize = MaxTrackSize();
2866 minMax->ptMinTrackSize = MinTrackSize();
2868 break;
2870 case WM_MOUSEACTIVATE:
2871 return MA_NOACTIVATE;
2873 case WM_NCHITTEST:
2874 return NcHitTest(wParam, lParam);
2876 case WM_NCLBUTTONDOWN:
2877 // We have to implement our own window resizing because the DefWindowProc
2878 // implementation insists on activating the resized window
2879 StartResize(wParam);
2880 return 0;
2882 case WM_MOUSEMOVE: {
2883 if (resizeHit == 0) {
2884 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2885 } else {
2886 ResizeToCursor();
2889 break;
2891 case WM_LBUTTONUP:
2892 case WM_CANCELMODE:
2893 if (resizeHit != 0) {
2894 resizeHit = 0;
2895 ::ReleaseCapture();
2897 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2899 case WM_MOUSEWHEEL:
2900 wheelDelta -= static_cast<short>(HIWORD(wParam));
2901 if (abs(wheelDelta) >= WHEEL_DELTA) {
2902 int nRows = GetVisibleRows();
2903 int linesToScroll = 1;
2904 if (nRows > 1) {
2905 linesToScroll = nRows - 1;
2907 if (linesToScroll > 3) {
2908 linesToScroll = 3;
2910 linesToScroll *= (wheelDelta / WHEEL_DELTA);
2911 LRESULT top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0) + linesToScroll;
2912 if (top < 0) {
2913 top = 0;
2915 ::SendMessage(lb, LB_SETTOPINDEX, top, 0);
2916 // update wheel delta residue
2917 if (wheelDelta >= 0)
2918 wheelDelta = wheelDelta % WHEEL_DELTA;
2919 else
2920 wheelDelta = - (-wheelDelta % WHEEL_DELTA);
2922 break;
2924 default:
2925 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2928 return 0;
2931 LRESULT PASCAL ListBoxX::StaticWndProc(
2932 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2933 if (iMessage == WM_CREATE) {
2934 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
2935 SetWindowPointer(hWnd, pCreate->lpCreateParams);
2937 // Find C++ object associated with window.
2938 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(hWnd));
2939 if (lbx) {
2940 return lbx->WndProc(hWnd, iMessage, wParam, lParam);
2941 } else {
2942 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2946 static bool ListBoxX_Register() {
2947 WNDCLASSEX wndclassc;
2948 wndclassc.cbSize = sizeof(wndclassc);
2949 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
2950 // truncated items in the list and the appearance/disappearance of the vertical scroll bar.
2951 // The list repaint is double-buffered to avoid the flicker this would otherwise cause.
2952 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
2953 wndclassc.cbClsExtra = 0;
2954 wndclassc.cbWndExtra = sizeof(ListBoxX *);
2955 wndclassc.hInstance = hinstPlatformRes;
2956 wndclassc.hIcon = NULL;
2957 wndclassc.hbrBackground = NULL;
2958 wndclassc.lpszMenuName = NULL;
2959 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
2960 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
2961 wndclassc.lpszClassName = ListBoxX_ClassName;
2962 wndclassc.hIconSm = 0;
2964 return ::RegisterClassEx(&wndclassc) != 0;
2967 bool ListBoxX_Unregister() {
2968 return ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes) != 0;
2971 Menu::Menu() : mid(0) {
2974 void Menu::CreatePopUp() {
2975 Destroy();
2976 mid = ::CreatePopupMenu();
2979 void Menu::Destroy() {
2980 if (mid)
2981 ::DestroyMenu(reinterpret_cast<HMENU>(mid));
2982 mid = 0;
2985 void Menu::Show(Point pt, Window &w) {
2986 ::TrackPopupMenu(reinterpret_cast<HMENU>(mid),
2987 TPM_RIGHTBUTTON, static_cast<int>(pt.x - 4), static_cast<int>(pt.y), 0,
2988 reinterpret_cast<HWND>(w.GetID()), NULL);
2989 Destroy();
2992 static bool initialisedET = false;
2993 static bool usePerformanceCounter = false;
2994 static LARGE_INTEGER frequency;
2996 ElapsedTime::ElapsedTime() {
2997 if (!initialisedET) {
2998 usePerformanceCounter = ::QueryPerformanceFrequency(&frequency) != 0;
2999 initialisedET = true;
3001 if (usePerformanceCounter) {
3002 LARGE_INTEGER timeVal;
3003 ::QueryPerformanceCounter(&timeVal);
3004 bigBit = timeVal.HighPart;
3005 littleBit = timeVal.LowPart;
3006 } else {
3007 bigBit = clock();
3008 littleBit = 0;
3012 double ElapsedTime::Duration(bool reset) {
3013 double result;
3014 long endBigBit;
3015 long endLittleBit;
3017 if (usePerformanceCounter) {
3018 LARGE_INTEGER lEnd;
3019 ::QueryPerformanceCounter(&lEnd);
3020 endBigBit = lEnd.HighPart;
3021 endLittleBit = lEnd.LowPart;
3022 LARGE_INTEGER lBegin;
3023 lBegin.HighPart = bigBit;
3024 lBegin.LowPart = littleBit;
3025 double elapsed = static_cast<double>(lEnd.QuadPart - lBegin.QuadPart);
3026 result = elapsed / static_cast<double>(frequency.QuadPart);
3027 } else {
3028 endBigBit = clock();
3029 endLittleBit = 0;
3030 double elapsed = endBigBit - bigBit;
3031 result = elapsed / CLOCKS_PER_SEC;
3033 if (reset) {
3034 bigBit = endBigBit;
3035 littleBit = endLittleBit;
3037 return result;
3040 class DynamicLibraryImpl : public DynamicLibrary {
3041 protected:
3042 HMODULE h;
3043 public:
3044 explicit DynamicLibraryImpl(const char *modulePath) {
3045 h = ::LoadLibraryA(modulePath);
3048 virtual ~DynamicLibraryImpl() {
3049 if (h != NULL)
3050 ::FreeLibrary(h);
3053 // Use GetProcAddress to get a pointer to the relevant function.
3054 virtual Function FindFunction(const char *name) {
3055 if (h != NULL) {
3056 // C++ standard doesn't like casts between function pointers and void pointers so use a union
3057 union {
3058 FARPROC fp;
3059 Function f;
3060 } fnConv;
3061 fnConv.fp = ::GetProcAddress(h, name);
3062 return fnConv.f;
3063 } else {
3064 return NULL;
3068 virtual bool IsValid() {
3069 return h != NULL;
3073 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3074 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3077 ColourDesired Platform::Chrome() {
3078 return ::GetSysColor(COLOR_3DFACE);
3081 ColourDesired Platform::ChromeHighlight() {
3082 return ::GetSysColor(COLOR_3DHIGHLIGHT);
3085 const char *Platform::DefaultFont() {
3086 return "Verdana";
3089 int Platform::DefaultFontSize() {
3090 return 8;
3093 unsigned int Platform::DoubleClickTime() {
3094 return ::GetDoubleClickTime();
3097 bool Platform::MouseButtonBounce() {
3098 return false;
3101 void Platform::DebugDisplay(const char *s) {
3102 ::OutputDebugStringA(s);
3105 bool Platform::IsKeyDown(int key) {
3106 return (::GetKeyState(key) & 0x80000000) != 0;
3109 long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
3110 // This should never be called - its here to satisfy an old interface
3111 return static_cast<long>(::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam));
3114 long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
3115 // This should never be called - its here to satisfy an old interface
3116 return static_cast<long>(::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
3117 reinterpret_cast<LPARAM>(lParam)));
3120 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
3121 // Byte ranges found in Wikipedia articles with relevant search strings in each case
3122 unsigned char uch = static_cast<unsigned char>(ch);
3123 switch (codePage) {
3124 case 932:
3125 // Shift_jis
3126 return ((uch >= 0x81) && (uch <= 0x9F)) ||
3127 ((uch >= 0xE0) && (uch <= 0xEF));
3128 case 936:
3129 // GBK
3130 return (uch >= 0x81) && (uch <= 0xFE);
3131 case 949:
3132 // Korean Wansung KS C-5601-1987
3133 return (uch >= 0x81) && (uch <= 0xFE);
3134 case 950:
3135 // Big5
3136 return (uch >= 0x81) && (uch <= 0xFE);
3137 case 1361:
3138 // Korean Johab KS C-5601-1992
3139 return
3140 ((uch >= 0x84) && (uch <= 0xD3)) ||
3141 ((uch >= 0xD8) && (uch <= 0xDE)) ||
3142 ((uch >= 0xE0) && (uch <= 0xF9));
3144 return false;
3147 int Platform::DBCSCharLength(int codePage, const char *s) {
3148 if (codePage == 932 || codePage == 936 || codePage == 949 ||
3149 codePage == 950 || codePage == 1361) {
3150 return Platform::IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
3151 } else {
3152 return 1;
3156 int Platform::DBCSCharMaxLength() {
3157 return 2;
3160 // These are utility functions not really tied to a platform
3162 int Platform::Minimum(int a, int b) {
3163 if (a < b)
3164 return a;
3165 else
3166 return b;
3169 int Platform::Maximum(int a, int b) {
3170 if (a > b)
3171 return a;
3172 else
3173 return b;
3176 //#define TRACE
3178 #ifdef TRACE
3179 void Platform::DebugPrintf(const char *format, ...) {
3180 char buffer[2000];
3181 va_list pArguments;
3182 va_start(pArguments, format);
3183 vsprintf(buffer,format,pArguments);
3184 va_end(pArguments);
3185 Platform::DebugDisplay(buffer);
3187 #else
3188 void Platform::DebugPrintf(const char *, ...) {
3190 #endif
3192 static bool assertionPopUps = true;
3194 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3195 bool ret = assertionPopUps;
3196 assertionPopUps = assertionPopUps_;
3197 return ret;
3200 void Platform::Assert(const char *c, const char *file, int line) {
3201 char buffer[2000];
3202 sprintf(buffer, "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n");
3203 if (assertionPopUps) {
3204 int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
3205 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
3206 if (idButton == IDRETRY) {
3207 ::DebugBreak();
3208 } else if (idButton == IDIGNORE) {
3209 // all OK
3210 } else {
3211 abort();
3213 } else {
3214 Platform::DebugDisplay(buffer);
3215 ::DebugBreak();
3216 abort();
3220 int Platform::Clamp(int val, int minVal, int maxVal) {
3221 if (val > maxVal)
3222 val = maxVal;
3223 if (val < minVal)
3224 val = minVal;
3225 return val;
3228 #ifdef _MSC_VER
3229 // GetVersionEx has been deprecated fro Windows 8.1 but called here to determine if Windows 9x.
3230 // Too dangerous to find alternate check.
3231 #pragma warning(disable: 4996)
3232 #endif
3234 void Platform_Initialise(void *hInstance) {
3235 ::InitializeCriticalSection(&crPlatformLock);
3236 hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
3237 // This may be called from DllMain, in which case the call to LoadLibrary
3238 // is bad because it can upset the DLL load order.
3239 if (!hDLLImage) {
3240 hDLLImage = ::LoadLibrary(TEXT("Msimg32"));
3242 if (hDLLImage) {
3243 AlphaBlendFn = (AlphaBlendSig)::GetProcAddress(hDLLImage, "AlphaBlend");
3245 if (!hDLLUser32) {
3246 hDLLUser32 = ::LoadLibrary(TEXT("User32"));
3248 if (hDLLUser32) {
3249 MonitorFromPointFn = (MonitorFromPointSig)::GetProcAddress(hDLLUser32, "MonitorFromPoint");
3250 MonitorFromRectFn = (MonitorFromRectSig)::GetProcAddress(hDLLUser32, "MonitorFromRect");
3251 GetMonitorInfoFn = (GetMonitorInfoSig)::GetProcAddress(hDLLUser32, "GetMonitorInfoA");
3254 ListBoxX_Register();
3257 #ifdef _MSC_VER
3258 #pragma warning(default: 4996)
3259 #endif
3261 void Platform_Finalise(bool fromDllMain) {
3262 #if defined(USE_D2D)
3263 if (!fromDllMain) {
3264 if (defaultRenderingParams) {
3265 defaultRenderingParams->Release();
3266 defaultRenderingParams = 0;
3268 if (customClearTypeRenderingParams) {
3269 customClearTypeRenderingParams->Release();
3270 customClearTypeRenderingParams = 0;
3272 if (pIDWriteFactory) {
3273 pIDWriteFactory->Release();
3274 pIDWriteFactory = 0;
3276 if (pD2DFactory) {
3277 pD2DFactory->Release();
3278 pD2DFactory = 0;
3280 if (hDLLDWrite) {
3281 FreeLibrary(hDLLDWrite);
3282 hDLLDWrite = NULL;
3284 if (hDLLD2D) {
3285 FreeLibrary(hDLLD2D);
3286 hDLLD2D = NULL;
3289 #endif
3290 if (reverseArrowCursor != NULL)
3291 ::DestroyCursor(reverseArrowCursor);
3292 ListBoxX_Unregister();
3293 ::DeleteCriticalSection(&crPlatformLock);
3294 if (hDLLUser32) {
3295 FreeLibrary(hDLLUser32);
3296 hDLLUser32 = NULL;
3298 if (hDLLImage) {
3299 FreeLibrary(hDLLImage);
3300 hDLLImage = NULL;
3304 #ifdef SCI_NAMESPACE
3306 #endif