Applied unicodefont.patch
[TortoiseGit.git] / ext / scintilla / win32 / PlatWin.cxx
blobaf1ba6d3c963962cbb6c96b691d2aadea3e216cb
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;
70 static bool onNT = false;
72 static HMODULE hDLLImage = 0;
73 static AlphaBlendSig AlphaBlendFn = 0;
75 static HMODULE hDLLUser32 = 0;
76 static HMONITOR (WINAPI *MonitorFromPointFn)(POINT, DWORD) = 0;
77 static HMONITOR (WINAPI *MonitorFromRectFn)(LPCRECT, DWORD) = 0;
78 static BOOL (WINAPI *GetMonitorInfoFn)(HMONITOR, LPMONITORINFO) = 0;
80 static HCURSOR reverseArrowCursor = NULL;
82 #ifdef SCI_NAMESPACE
83 namespace Scintilla {
84 #endif
86 bool IsNT() {
87 return onNT;
90 Point Point::FromLong(long lpoint) {
91 return Point(static_cast<short>(LOWORD(lpoint)), static_cast<short>(HIWORD(lpoint)));
94 static RECT RectFromPRectangle(PRectangle prc) {
95 RECT rc = {static_cast<LONG>(prc.left), static_cast<LONG>(prc.top),
96 static_cast<LONG>(prc.right), static_cast<LONG>(prc.bottom)};
97 return rc;
100 #if defined(USE_D2D)
101 IDWriteFactory *pIDWriteFactory = 0;
102 ID2D1Factory *pD2DFactory = 0;
103 IDWriteRenderingParams *defaultRenderingParams = 0;
104 IDWriteRenderingParams *customClearTypeRenderingParams = 0;
106 static HMODULE hDLLD2D = NULL;
107 static HMODULE hDLLDWrite = NULL;
109 bool LoadD2D() {
110 static bool triedLoadingD2D = false;
111 if (!triedLoadingD2D) {
112 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
113 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
114 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
115 IUnknown **factory);
117 hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
118 if (hDLLD2D) {
119 D2D1CFSig fnD2DCF = (D2D1CFSig)::GetProcAddress(hDLLD2D, "D2D1CreateFactory");
120 if (fnD2DCF) {
121 // A single threaded factory as Scintilla always draw on the GUI thread
122 fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
123 __uuidof(ID2D1Factory),
125 reinterpret_cast<IUnknown**>(&pD2DFactory));
128 hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
129 if (hDLLDWrite) {
130 DWriteCFSig fnDWCF = (DWriteCFSig)::GetProcAddress(hDLLDWrite, "DWriteCreateFactory");
131 if (fnDWCF) {
132 fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
133 __uuidof(IDWriteFactory),
134 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
138 if (pIDWriteFactory) {
139 HRESULT hr = pIDWriteFactory->CreateRenderingParams(&defaultRenderingParams);
140 if (SUCCEEDED(hr)) {
141 unsigned int clearTypeContrast;
142 ::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0);
144 FLOAT gamma;
145 if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200)
146 gamma = static_cast<FLOAT>(clearTypeContrast) / 1000.0f;
147 else
148 gamma = defaultRenderingParams->GetGamma();
150 pIDWriteFactory->CreateCustomRenderingParams(gamma, defaultRenderingParams->GetEnhancedContrast(), defaultRenderingParams->GetClearTypeLevel(),
151 defaultRenderingParams->GetPixelGeometry(), defaultRenderingParams->GetRenderingMode(), &customClearTypeRenderingParams);
156 triedLoadingD2D = true;
157 return pIDWriteFactory && pD2DFactory;
159 #endif
161 struct FormatAndMetrics {
162 int technology;
163 HFONT hfont;
164 #if defined(USE_D2D)
165 IDWriteTextFormat *pTextFormat;
166 #endif
167 int extraFontFlag;
168 int characterSet;
169 FLOAT yAscent;
170 FLOAT yDescent;
171 FLOAT yInternalLeading;
172 FormatAndMetrics(HFONT hfont_, int extraFontFlag_, int characterSet_) :
173 technology(SCWIN_TECH_GDI), hfont(hfont_),
174 #if defined(USE_D2D)
175 pTextFormat(0),
176 #endif
177 extraFontFlag(extraFontFlag_), characterSet(characterSet_), yAscent(2), yDescent(1), yInternalLeading(0) {
179 #if defined(USE_D2D)
180 FormatAndMetrics(IDWriteTextFormat *pTextFormat_,
181 int extraFontFlag_,
182 int characterSet_,
183 FLOAT yAscent_,
184 FLOAT yDescent_,
185 FLOAT yInternalLeading_) :
186 technology(SCWIN_TECH_DIRECTWRITE),
187 hfont(0),
188 pTextFormat(pTextFormat_),
189 extraFontFlag(extraFontFlag_),
190 characterSet(characterSet_),
191 yAscent(yAscent_),
192 yDescent(yDescent_),
193 yInternalLeading(yInternalLeading_) {
195 #endif
196 ~FormatAndMetrics() {
197 if (hfont)
198 ::DeleteObject(hfont);
199 #if defined(USE_D2D)
200 if (pTextFormat)
201 pTextFormat->Release();
202 pTextFormat = 0;
203 #endif
204 extraFontFlag = 0;
205 characterSet = 0;
206 yAscent = 2;
207 yDescent = 1;
208 yInternalLeading = 0;
210 HFONT HFont();
213 HFONT FormatAndMetrics::HFont() {
214 LOGFONTW lf = {};
215 #if defined(USE_D2D)
216 if (technology == SCWIN_TECH_GDI) {
217 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
218 return 0;
220 } else {
221 HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
222 if (!SUCCEEDED(hr)) {
223 return 0;
225 lf.lfWeight = pTextFormat->GetFontWeight();
226 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
227 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
229 #else
230 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
231 return 0;
233 #endif
234 return ::CreateFontIndirectW(&lf);
237 #ifndef CLEARTYPE_QUALITY
238 #define CLEARTYPE_QUALITY 5
239 #endif
241 static BYTE Win32MapFontQuality(int extraFontFlag) {
242 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
244 case SC_EFF_QUALITY_NON_ANTIALIASED:
245 return NONANTIALIASED_QUALITY;
247 case SC_EFF_QUALITY_ANTIALIASED:
248 return ANTIALIASED_QUALITY;
250 case SC_EFF_QUALITY_LCD_OPTIMIZED:
251 return CLEARTYPE_QUALITY;
253 default:
254 return SC_EFF_QUALITY_DEFAULT;
258 #if defined(USE_D2D)
259 static D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) {
260 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
262 case SC_EFF_QUALITY_NON_ANTIALIASED:
263 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
265 case SC_EFF_QUALITY_ANTIALIASED:
266 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
268 case SC_EFF_QUALITY_LCD_OPTIMIZED:
269 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
271 default:
272 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
275 #endif
277 static void SetLogFont(LOGFONTW &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) {
278 lf = LOGFONTW();
279 // The negative is to allow for leading
280 lf.lfHeight = -(abs(static_cast<int>(size + 0.5)));
281 lf.lfWeight = weight;
282 lf.lfItalic = static_cast<BYTE>(italic ? 1 : 0);
283 lf.lfCharSet = static_cast<BYTE>(characterSet);
284 lf.lfQuality = Win32MapFontQuality(extraFontFlag);
285 UTF16FromUTF8(faceName, strlen(faceName)+1, lf.lfFaceName, LF_FACESIZE);
289 * Create a hash from the parameters for a font to allow easy checking for identity.
290 * If one font is the same as another, its hash will be the same, but if the hash is the
291 * same then they may still be different.
293 static int HashFont(const FontParameters &fp) {
294 return
295 static_cast<int>(fp.size) ^
296 (fp.characterSet << 10) ^
297 ((fp.extraFontFlag & SC_EFF_QUALITY_MASK) << 9) ^
298 ((fp.weight/100) << 12) ^
299 (fp.italic ? 0x20000000 : 0) ^
300 (fp.technology << 15) ^
301 fp.faceName[0];
304 class FontCached : Font {
305 FontCached *next;
306 int usage;
307 float size;
308 LOGFONTW lf;
309 int technology;
310 int hash;
311 explicit FontCached(const FontParameters &fp);
312 ~FontCached() {}
313 bool SameAs(const FontParameters &fp);
314 virtual void Release();
316 static FontCached *first;
317 public:
318 static FontID FindOrCreate(const FontParameters &fp);
319 static void ReleaseId(FontID fid_);
322 FontCached *FontCached::first = 0;
324 FontCached::FontCached(const FontParameters &fp) :
325 next(0), usage(0), size(1.0), hash(0) {
326 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
327 technology = fp.technology;
328 hash = HashFont(fp);
329 fid = 0;
330 if (technology == SCWIN_TECH_GDI) {
331 HFONT hfont = ::CreateFontIndirectW(&lf);
332 fid = reinterpret_cast<void *>(new FormatAndMetrics(hfont, fp.extraFontFlag, fp.characterSet));
333 } else {
334 #if defined(USE_D2D)
335 IDWriteTextFormat *pTextFormat;
336 const int faceSize = 200;
337 WCHAR wszFace[faceSize];
338 UTF16FromUTF8(fp.faceName, static_cast<unsigned int>(strlen(fp.faceName))+1, wszFace, faceSize);
339 FLOAT fHeight = fp.size;
340 DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
341 HRESULT hr = pIDWriteFactory->CreateTextFormat(wszFace, NULL,
342 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
343 style,
344 DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
345 if (SUCCEEDED(hr)) {
346 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
348 const int maxLines = 2;
349 DWRITE_LINE_METRICS lineMetrics[maxLines];
350 UINT32 lineCount = 0;
351 FLOAT yAscent = 1.0f;
352 FLOAT yDescent = 1.0f;
353 FLOAT yInternalLeading = 0.0f;
354 IDWriteTextLayout *pTextLayout = 0;
355 hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
356 100.0f, 100.0f, &pTextLayout);
357 if (SUCCEEDED(hr)) {
358 hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
359 if (SUCCEEDED(hr)) {
360 yAscent = lineMetrics[0].baseline;
361 yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
363 FLOAT emHeight;
364 hr = pTextLayout->GetFontSize(0, &emHeight);
365 if (SUCCEEDED(hr)) {
366 yInternalLeading = lineMetrics[0].height - emHeight;
369 pTextLayout->Release();
370 pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline);
372 fid = reinterpret_cast<void *>(new FormatAndMetrics(pTextFormat, fp.extraFontFlag, fp.characterSet, yAscent, yDescent, yInternalLeading));
374 #endif
376 usage = 1;
379 bool FontCached::SameAs(const FontParameters &fp) {
380 if (
381 (size == fp.size) &&
382 (lf.lfWeight == fp.weight) &&
383 (lf.lfItalic == static_cast<BYTE>(fp.italic ? 1 : 0)) &&
384 (lf.lfCharSet == fp.characterSet) &&
385 (lf.lfQuality == Win32MapFontQuality(fp.extraFontFlag)) &&
386 (technology == fp.technology)) {
387 wchar_t wszFace[LF_FACESIZE];
388 UTF16FromUTF8(fp.faceName, strlen(fp.faceName)+1, wszFace, LF_FACESIZE);
389 return 0 == wcscmp(lf.lfFaceName,wszFace);
391 return false;
394 void FontCached::Release() {
395 delete reinterpret_cast<FormatAndMetrics *>(fid);
396 fid = 0;
399 FontID FontCached::FindOrCreate(const FontParameters &fp) {
400 FontID ret = 0;
401 ::EnterCriticalSection(&crPlatformLock);
402 int hashFind = HashFont(fp);
403 for (FontCached *cur=first; cur; cur=cur->next) {
404 if ((cur->hash == hashFind) &&
405 cur->SameAs(fp)) {
406 cur->usage++;
407 ret = cur->fid;
410 if (ret == 0) {
411 FontCached *fc = new FontCached(fp);
412 fc->next = first;
413 first = fc;
414 ret = fc->fid;
416 ::LeaveCriticalSection(&crPlatformLock);
417 return ret;
420 void FontCached::ReleaseId(FontID fid_) {
421 ::EnterCriticalSection(&crPlatformLock);
422 FontCached **pcur=&first;
423 for (FontCached *cur=first; cur; cur=cur->next) {
424 if (cur->fid == fid_) {
425 cur->usage--;
426 if (cur->usage == 0) {
427 *pcur = cur->next;
428 cur->Release();
429 cur->next = 0;
430 delete cur;
432 break;
434 pcur=&cur->next;
436 ::LeaveCriticalSection(&crPlatformLock);
439 Font::Font() {
440 fid = 0;
443 Font::~Font() {
446 #define FONTS_CACHED
448 void Font::Create(const FontParameters &fp) {
449 Release();
450 if (fp.faceName)
451 fid = FontCached::FindOrCreate(fp);
454 void Font::Release() {
455 if (fid)
456 FontCached::ReleaseId(fid);
457 fid = 0;
460 // Buffer to hold strings and string position arrays without always allocating on heap.
461 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
462 // when less than safe size otherwise allocate on heap and free automatically.
463 template<typename T, int lengthStandard>
464 class VarBuffer {
465 T bufferStandard[lengthStandard];
466 // Private so VarBuffer objects can not be copied
467 VarBuffer(const VarBuffer &);
468 VarBuffer &operator=(const VarBuffer &);
469 public:
470 T *buffer;
471 explicit VarBuffer(size_t length) : buffer(0) {
472 if (length > lengthStandard) {
473 buffer = new T[length];
474 } else {
475 buffer = bufferStandard;
478 ~VarBuffer() {
479 if (buffer != bufferStandard) {
480 delete []buffer;
481 buffer = 0;
486 const int stackBufferLength = 10000;
487 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
488 public:
489 int tlen;
490 TextWide(const char *s, int len, bool unicodeMode, int codePage=0) :
491 VarBuffer<wchar_t, stackBufferLength>(len) {
492 if (unicodeMode) {
493 tlen = UTF16FromUTF8(s, len, buffer, len);
494 } else {
495 // Support Asian string display in 9x English
496 tlen = ::MultiByteToWideChar(codePage, 0, s, len, buffer, len);
500 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
502 class SurfaceGDI : public Surface {
503 bool unicodeMode;
504 HDC hdc;
505 bool hdcOwned;
506 HPEN pen;
507 HPEN penOld;
508 HBRUSH brush;
509 HBRUSH brushOld;
510 HFONT font;
511 HFONT fontOld;
512 HBITMAP bitmap;
513 HBITMAP bitmapOld;
514 int maxWidthMeasure;
515 int maxLenText;
517 int codePage;
518 // If 9x OS and current code page is same as ANSI code page.
519 bool win9xACPSame;
521 void BrushColor(ColourDesired back);
522 void SetFont(Font &font_);
524 // Private so SurfaceGDI objects can not be copied
525 SurfaceGDI(const SurfaceGDI &);
526 SurfaceGDI &operator=(const SurfaceGDI &);
527 public:
528 SurfaceGDI();
529 virtual ~SurfaceGDI();
531 void Init(WindowID wid);
532 void Init(SurfaceID sid, WindowID wid);
533 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
535 void Release();
536 bool Initialised();
537 void PenColour(ColourDesired fore);
538 int LogPixelsY();
539 int DeviceHeightFont(int points);
540 void MoveTo(int x_, int y_);
541 void LineTo(int x_, int y_);
542 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
543 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
544 void FillRectangle(PRectangle rc, ColourDesired back);
545 void FillRectangle(PRectangle rc, Surface &surfacePattern);
546 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
547 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
548 ColourDesired outline, int alphaOutline, int flags);
549 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
550 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
551 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
553 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
554 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
555 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
556 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
557 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
558 XYPOSITION WidthText(Font &font_, const char *s, int len);
559 XYPOSITION WidthChar(Font &font_, char ch);
560 XYPOSITION Ascent(Font &font_);
561 XYPOSITION Descent(Font &font_);
562 XYPOSITION InternalLeading(Font &font_);
563 XYPOSITION ExternalLeading(Font &font_);
564 XYPOSITION Height(Font &font_);
565 XYPOSITION AverageCharWidth(Font &font_);
567 void SetClip(PRectangle rc);
568 void FlushCachedState();
570 void SetUnicodeMode(bool unicodeMode_);
571 void SetDBCSMode(int codePage_);
574 SurfaceGDI::SurfaceGDI() :
575 unicodeMode(false),
576 hdc(0), hdcOwned(false),
577 pen(0), penOld(0),
578 brush(0), brushOld(0),
579 font(0), fontOld(0),
580 bitmap(0), bitmapOld(0) {
581 // Windows 9x has only a 16 bit coordinate system so break after 30000 pixels
582 maxWidthMeasure = IsNT() ? INT_MAX : 30000;
583 // There appears to be a 16 bit string length limit in GDI on NT and a limit of
584 // 8192 characters on Windows 95.
585 maxLenText = IsNT() ? 65535 : 8192;
587 codePage = 0;
588 win9xACPSame = false;
591 SurfaceGDI::~SurfaceGDI() {
592 Release();
595 void SurfaceGDI::Release() {
596 if (penOld) {
597 ::SelectObject(reinterpret_cast<HDC>(hdc), penOld);
598 ::DeleteObject(pen);
599 penOld = 0;
601 pen = 0;
602 if (brushOld) {
603 ::SelectObject(reinterpret_cast<HDC>(hdc), brushOld);
604 ::DeleteObject(brush);
605 brushOld = 0;
607 brush = 0;
608 if (fontOld) {
609 // Fonts are not deleted as they are owned by a Font object
610 ::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);
611 fontOld = 0;
613 font = 0;
614 if (bitmapOld) {
615 ::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);
616 ::DeleteObject(bitmap);
617 bitmapOld = 0;
619 bitmap = 0;
620 if (hdcOwned) {
621 ::DeleteDC(reinterpret_cast<HDC>(hdc));
622 hdc = 0;
623 hdcOwned = false;
627 bool SurfaceGDI::Initialised() {
628 return hdc != 0;
631 void SurfaceGDI::Init(WindowID) {
632 Release();
633 hdc = ::CreateCompatibleDC(NULL);
634 hdcOwned = true;
635 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
638 void SurfaceGDI::Init(SurfaceID sid, WindowID) {
639 Release();
640 hdc = reinterpret_cast<HDC>(sid);
641 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
644 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID) {
645 Release();
646 hdc = ::CreateCompatibleDC(static_cast<SurfaceGDI *>(surface_)->hdc);
647 hdcOwned = true;
648 bitmap = ::CreateCompatibleBitmap(static_cast<SurfaceGDI *>(surface_)->hdc, width, height);
649 bitmapOld = static_cast<HBITMAP>(::SelectObject(hdc, bitmap));
650 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
653 void SurfaceGDI::PenColour(ColourDesired fore) {
654 if (pen) {
655 ::SelectObject(hdc, penOld);
656 ::DeleteObject(pen);
657 pen = 0;
658 penOld = 0;
660 pen = ::CreatePen(0,1,fore.AsLong());
661 penOld = static_cast<HPEN>(::SelectObject(reinterpret_cast<HDC>(hdc), pen));
664 void SurfaceGDI::BrushColor(ColourDesired back) {
665 if (brush) {
666 ::SelectObject(hdc, brushOld);
667 ::DeleteObject(brush);
668 brush = 0;
669 brushOld = 0;
671 // Only ever want pure, non-dithered brushes
672 ColourDesired colourNearest = ::GetNearestColor(hdc, back.AsLong());
673 brush = ::CreateSolidBrush(colourNearest.AsLong());
674 brushOld = static_cast<HBRUSH>(::SelectObject(hdc, brush));
677 void SurfaceGDI::SetFont(Font &font_) {
678 if (font_.GetID() != font) {
679 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
680 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI);
681 if (fontOld) {
682 ::SelectObject(hdc, pfm->hfont);
683 } else {
684 fontOld = static_cast<HFONT>(::SelectObject(hdc, pfm->hfont));
686 font = reinterpret_cast<HFONT>(pfm->hfont);
690 int SurfaceGDI::LogPixelsY() {
691 return ::GetDeviceCaps(hdc, LOGPIXELSY);
694 int SurfaceGDI::DeviceHeightFont(int points) {
695 return ::MulDiv(points, LogPixelsY(), 72);
698 void SurfaceGDI::MoveTo(int x_, int y_) {
699 ::MoveToEx(hdc, x_, y_, 0);
702 void SurfaceGDI::LineTo(int x_, int y_) {
703 ::LineTo(hdc, x_, y_);
706 void SurfaceGDI::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
707 PenColour(fore);
708 BrushColor(back);
709 std::vector<POINT> outline;
710 for (int i=0; i<npts; i++) {
711 POINT pt = {static_cast<LONG>(pts[i].x), static_cast<LONG>(pts[i].y)};
712 outline.push_back(pt);
714 ::Polygon(hdc, &outline[0], npts);
717 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
718 PenColour(fore);
719 BrushColor(back);
720 const RECT rcw = RectFromPRectangle(rc);
721 ::Rectangle(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
724 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) {
725 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
726 // There is no need to allocate a brush either.
727 RECT rcw = RectFromPRectangle(rc);
728 ::SetBkColor(hdc, back.AsLong());
729 ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, NULL);
732 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
733 HBRUSH br;
734 if (static_cast<SurfaceGDI &>(surfacePattern).bitmap)
735 br = ::CreatePatternBrush(static_cast<SurfaceGDI &>(surfacePattern).bitmap);
736 else // Something is wrong so display in red
737 br = ::CreateSolidBrush(RGB(0xff, 0, 0));
738 RECT rcw = RectFromPRectangle(rc);
739 ::FillRect(hdc, &rcw, br);
740 ::DeleteObject(br);
743 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
744 PenColour(fore);
745 BrushColor(back);
746 const RECT rcw = RectFromPRectangle(rc);
747 ::RoundRect(hdc,
748 rcw.left + 1, rcw.top,
749 rcw.right - 1, rcw.bottom,
750 8, 8);
753 // Plot a point into a DWORD buffer symetrically to all 4 qudrants
754 static void AllFour(DWORD *pixels, int width, int height, int x, int y, DWORD val) {
755 pixels[y*width+x] = val;
756 pixels[y*width+width-1-x] = val;
757 pixels[(height-1-y)*width+x] = val;
758 pixels[(height-1-y)*width+width-1-x] = val;
761 #ifndef AC_SRC_OVER
762 #define AC_SRC_OVER 0x00
763 #endif
764 #ifndef AC_SRC_ALPHA
765 #define AC_SRC_ALPHA 0x01
766 #endif
768 static DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) {
769 union {
770 byte pixVal[4];
771 DWORD val;
772 } converter;
773 converter.pixVal[0] = b;
774 converter.pixVal[1] = g;
775 converter.pixVal[2] = r;
776 converter.pixVal[3] = a;
777 return converter.val;
780 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
781 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
782 const RECT rcw = RectFromPRectangle(rc);
783 if (AlphaBlendFn && rc.Width() > 0) {
784 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
785 int width = static_cast<int>(rc.Width());
786 int height = static_cast<int>(rc.Height());
787 // Ensure not distorted too much by corners when small
788 cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
789 BITMAPINFO bpih = {{sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}};
790 void *image = 0;
791 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
792 DIB_RGB_COLORS, &image, NULL, 0);
794 if (hbmMem) {
795 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
797 DWORD valEmpty = dwordFromBGRA(0,0,0,0);
798 DWORD valFill = dwordFromBGRA(
799 static_cast<byte>(GetBValue(fill.AsLong()) * alphaFill / 255),
800 static_cast<byte>(GetGValue(fill.AsLong()) * alphaFill / 255),
801 static_cast<byte>(GetRValue(fill.AsLong()) * alphaFill / 255),
802 static_cast<byte>(alphaFill));
803 DWORD valOutline = dwordFromBGRA(
804 static_cast<byte>(GetBValue(outline.AsLong()) * alphaOutline / 255),
805 static_cast<byte>(GetGValue(outline.AsLong()) * alphaOutline / 255),
806 static_cast<byte>(GetRValue(outline.AsLong()) * alphaOutline / 255),
807 static_cast<byte>(alphaOutline));
808 DWORD *pixels = reinterpret_cast<DWORD *>(image);
809 for (int y=0; y<height; y++) {
810 for (int x=0; x<width; x++) {
811 if ((x==0) || (x==width-1) || (y == 0) || (y == height-1)) {
812 pixels[y*width+x] = valOutline;
813 } else {
814 pixels[y*width+x] = valFill;
818 for (int c=0; c<cornerSize; c++) {
819 for (int x=0; x<c+1; x++) {
820 AllFour(pixels, width, height, x, c-x, valEmpty);
823 for (int x=1; x<cornerSize; x++) {
824 AllFour(pixels, width, height, x, cornerSize-x, valOutline);
827 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
829 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rcw.left, rcw.top, width, height, hMemDC, 0, 0, width, height, merge);
831 SelectBitmap(hMemDC, hbmOld);
832 ::DeleteObject(hbmMem);
834 ::DeleteDC(hMemDC);
835 } else {
836 BrushColor(outline);
837 FrameRect(hdc, &rcw, brush);
841 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
842 if (AlphaBlendFn && rc.Width() > 0) {
843 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
844 if (rc.Width() > width)
845 rc.left += static_cast<int>((rc.Width() - width) / 2);
846 rc.right = rc.left + width;
847 if (rc.Height() > height)
848 rc.top += static_cast<int>((rc.Height() - height) / 2);
849 rc.bottom = rc.top + height;
851 BITMAPINFO bpih = {{sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}};
852 unsigned char *image = 0;
853 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
854 DIB_RGB_COLORS, reinterpret_cast<void **>(&image), NULL, 0);
855 if (hbmMem) {
856 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
858 for (int y=height-1; y>=0; y--) {
859 for (int x=0; x<width; x++) {
860 unsigned char *pixel = image + (y*width+x) * 4;
861 unsigned char alpha = pixelsImage[3];
862 // Input is RGBA, output is BGRA with premultiplied alpha
863 pixel[2] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
864 pixel[1] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
865 pixel[0] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
866 pixel[3] = static_cast<unsigned char>(*pixelsImage++);
870 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
872 AlphaBlendFn(reinterpret_cast<HDC>(hdc), static_cast<int>(rc.left), static_cast<int>(rc.top),
873 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), hMemDC, 0, 0, width, height, merge);
875 SelectBitmap(hMemDC, hbmOld);
876 ::DeleteObject(hbmMem);
878 ::DeleteDC(hMemDC);
883 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
884 PenColour(fore);
885 BrushColor(back);
886 const RECT rcw = RectFromPRectangle(rc);
887 ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
890 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
891 ::BitBlt(hdc,
892 static_cast<int>(rc.left), static_cast<int>(rc.top),
893 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()),
894 static_cast<SurfaceGDI &>(surfaceSource).hdc,
895 static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY);
898 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
900 void SurfaceGDI::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
901 SetFont(font_);
902 RECT rcw = RectFromPRectangle(rc);
903 SIZE sz={0,0};
904 int pos = 0;
905 int x = static_cast<int>(rc.left);
906 const int yBaseInt = static_cast<int>(ybase);
908 // Text drawing may fail if the text is too big.
909 // If it does fail, slice up into segments and draw each segment.
910 const int maxSegmentLength = 0x200;
912 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
913 // Use ANSI calls
914 int lenDraw = Platform::Minimum(len, maxLenText);
915 if (!::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, s, lenDraw, NULL)) {
916 while (lenDraw > pos) {
917 int seglen = Platform::Minimum(maxSegmentLength, lenDraw - pos);
918 if (!::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, s + pos, seglen, NULL)) {
919 PLATFORM_ASSERT(false);
920 return;
922 ::GetTextExtentPoint32A(hdc, s+pos, seglen, &sz);
923 x += sz.cx;
924 pos += seglen;
927 } else {
928 // Use Unicode calls
929 const TextWide tbuf(s, len, unicodeMode, codePage);
930 if (!::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, NULL)) {
931 while (tbuf.tlen > pos) {
932 int seglen = Platform::Minimum(maxSegmentLength, tbuf.tlen - pos);
933 if (!::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer + pos, seglen, NULL)) {
934 PLATFORM_ASSERT(false);
935 return;
937 ::GetTextExtentPoint32W(hdc, tbuf.buffer+pos, seglen, &sz);
938 x += sz.cx;
939 pos += seglen;
945 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
946 ColourDesired fore, ColourDesired back) {
947 ::SetTextColor(hdc, fore.AsLong());
948 ::SetBkColor(hdc, back.AsLong());
949 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
952 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
953 ColourDesired fore, ColourDesired back) {
954 ::SetTextColor(hdc, fore.AsLong());
955 ::SetBkColor(hdc, back.AsLong());
956 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
959 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
960 ColourDesired fore) {
961 // Avoid drawing spaces in transparent mode
962 for (int i=0; i<len; i++) {
963 if (s[i] != ' ') {
964 ::SetTextColor(hdc, fore.AsLong());
965 ::SetBkMode(hdc, TRANSPARENT);
966 DrawTextCommon(rc, font_, ybase, s, len, 0);
967 ::SetBkMode(hdc, OPAQUE);
968 return;
973 XYPOSITION SurfaceGDI::WidthText(Font &font_, const char *s, int len) {
974 SetFont(font_);
975 SIZE sz={0,0};
976 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
977 ::GetTextExtentPoint32A(hdc, s, Platform::Minimum(len, maxLenText), &sz);
978 } else {
979 const TextWide tbuf(s, len, unicodeMode, codePage);
980 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
982 return static_cast<XYPOSITION>(sz.cx);
985 void SurfaceGDI::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
986 SetFont(font_);
987 SIZE sz={0,0};
988 int fit = 0;
989 if (unicodeMode) {
990 const TextWide tbuf(s, len, unicodeMode, codePage);
991 TextPositionsI poses(tbuf.tlen);
992 fit = tbuf.tlen;
993 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
994 // Likely to have failed because on Windows 9x where function not available
995 // So measure the character widths by measuring each initial substring
996 // Turns a linear operation into a qudratic but seems fast enough on test files
997 for (int widthSS=0; widthSS < tbuf.tlen; widthSS++) {
998 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
999 poses.buffer[widthSS] = sz.cx;
1002 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1003 int ui=0;
1004 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1005 int i=0;
1006 while (ui<fit) {
1007 unsigned char uch = us[i];
1008 unsigned int lenChar = 1;
1009 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1010 lenChar = 4;
1011 ui++;
1012 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1013 lenChar = 3;
1014 } else if (uch >= (0x80)) {
1015 lenChar = 2;
1017 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1018 positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
1020 ui++;
1022 XYPOSITION lastPos = 0.0f;
1023 if (i > 0)
1024 lastPos = positions[i-1];
1025 while (i<len) {
1026 positions[i++] = lastPos;
1028 } else if (IsNT() || (codePage==0) || win9xACPSame) {
1029 // Zero positions to avoid random behaviour on failure.
1030 std::fill(positions, positions + len, 0.0f);
1031 // len may be larger than platform supports so loop over segments small enough for platform
1032 int startOffset = 0;
1033 while (len > 0) {
1034 int lenBlock = Platform::Minimum(len, maxLenText);
1035 TextPositionsI poses(len);
1036 if (!::GetTextExtentExPointA(hdc, s, lenBlock, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1037 // Eeek - a NULL DC or other foolishness could cause this.
1038 return;
1039 } else if (fit < lenBlock) {
1040 // For some reason, such as an incomplete DBCS character
1041 // Not all the positions are filled in so make them equal to end.
1042 if (fit == 0)
1043 poses.buffer[fit++] = 0;
1044 for (int i = fit; i<lenBlock; i++)
1045 poses.buffer[i] = poses.buffer[fit-1];
1047 for (int i=0; i<lenBlock; i++)
1048 positions[i] = static_cast<XYPOSITION>(poses.buffer[i] + startOffset);
1049 startOffset = poses.buffer[lenBlock-1];
1050 len -= lenBlock;
1051 positions += lenBlock;
1052 s += lenBlock;
1054 } else {
1055 // Support Asian string display in 9x English
1056 const TextWide tbuf(s, len, unicodeMode, codePage);
1057 TextPositionsI poses(tbuf.tlen);
1058 for (int widthSS=0; widthSS<tbuf.tlen; widthSS++) {
1059 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
1060 poses.buffer[widthSS] = sz.cx;
1063 int ui = 0;
1064 for (int i=0; i<len;) {
1065 if (Platform::IsDBCSLeadByte(codePage, s[i])) {
1066 positions[i] = static_cast<XYPOSITION>(poses.buffer[ui]);
1067 positions[i + 1] = static_cast<XYPOSITION>(poses.buffer[ui]);
1068 i += 2;
1069 } else {
1070 positions[i] = static_cast<XYPOSITION>(poses.buffer[ui]);
1071 i++;
1074 ui++;
1079 XYPOSITION SurfaceGDI::WidthChar(Font &font_, char ch) {
1080 SetFont(font_);
1081 SIZE sz;
1082 ::GetTextExtentPoint32A(hdc, &ch, 1, &sz);
1083 return static_cast<XYPOSITION>(sz.cx);
1086 XYPOSITION SurfaceGDI::Ascent(Font &font_) {
1087 SetFont(font_);
1088 TEXTMETRIC tm;
1089 ::GetTextMetrics(hdc, &tm);
1090 return static_cast<XYPOSITION>(tm.tmAscent);
1093 XYPOSITION SurfaceGDI::Descent(Font &font_) {
1094 SetFont(font_);
1095 TEXTMETRIC tm;
1096 ::GetTextMetrics(hdc, &tm);
1097 return static_cast<XYPOSITION>(tm.tmDescent);
1100 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) {
1101 SetFont(font_);
1102 TEXTMETRIC tm;
1103 ::GetTextMetrics(hdc, &tm);
1104 return static_cast<XYPOSITION>(tm.tmInternalLeading);
1107 XYPOSITION SurfaceGDI::ExternalLeading(Font &font_) {
1108 SetFont(font_);
1109 TEXTMETRIC tm;
1110 ::GetTextMetrics(hdc, &tm);
1111 return static_cast<XYPOSITION>(tm.tmExternalLeading);
1114 XYPOSITION SurfaceGDI::Height(Font &font_) {
1115 SetFont(font_);
1116 TEXTMETRIC tm;
1117 ::GetTextMetrics(hdc, &tm);
1118 return static_cast<XYPOSITION>(tm.tmHeight);
1121 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) {
1122 SetFont(font_);
1123 TEXTMETRIC tm;
1124 ::GetTextMetrics(hdc, &tm);
1125 return static_cast<XYPOSITION>(tm.tmAveCharWidth);
1128 void SurfaceGDI::SetClip(PRectangle rc) {
1129 ::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
1130 static_cast<int>(rc.right), static_cast<int>(rc.bottom));
1133 void SurfaceGDI::FlushCachedState() {
1134 pen = 0;
1135 brush = 0;
1136 font = 0;
1139 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1140 unicodeMode=unicodeMode_;
1143 void SurfaceGDI::SetDBCSMode(int codePage_) {
1144 // No action on window as automatically handled by system.
1145 codePage = codePage_;
1146 win9xACPSame = !IsNT() && ((unsigned int)codePage == ::GetACP());
1149 #if defined(USE_D2D)
1151 class SurfaceD2D : public Surface {
1152 bool unicodeMode;
1153 int x, y;
1155 int codePage;
1156 int codePageText;
1158 ID2D1RenderTarget *pRenderTarget;
1159 bool ownRenderTarget;
1160 int clipsActive;
1162 IDWriteTextFormat *pTextFormat;
1163 FLOAT yAscent;
1164 FLOAT yDescent;
1165 FLOAT yInternalLeading;
1167 ID2D1SolidColorBrush *pBrush;
1169 int logPixelsY;
1170 float dpiScaleX;
1171 float dpiScaleY;
1173 void SetFont(Font &font_);
1175 // Private so SurfaceD2D objects can not be copied
1176 SurfaceD2D(const SurfaceD2D &);
1177 SurfaceD2D &operator=(const SurfaceD2D &);
1178 public:
1179 SurfaceD2D();
1180 virtual ~SurfaceD2D();
1182 void SetScale();
1183 void Init(WindowID wid);
1184 void Init(SurfaceID sid, WindowID wid);
1185 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
1187 void Release();
1188 bool Initialised();
1190 HRESULT FlushDrawing();
1192 void PenColour(ColourDesired fore);
1193 void D2DPenColour(ColourDesired fore, int alpha=255);
1194 int LogPixelsY();
1195 int DeviceHeightFont(int points);
1196 void MoveTo(int x_, int y_);
1197 void LineTo(int x_, int y_);
1198 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
1199 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
1200 void FillRectangle(PRectangle rc, ColourDesired back);
1201 void FillRectangle(PRectangle rc, Surface &surfacePattern);
1202 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
1203 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1204 ColourDesired outline, int alphaOutline, int flags);
1205 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
1206 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
1207 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
1209 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
1210 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1211 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1212 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
1213 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
1214 XYPOSITION WidthText(Font &font_, const char *s, int len);
1215 XYPOSITION WidthChar(Font &font_, char ch);
1216 XYPOSITION Ascent(Font &font_);
1217 XYPOSITION Descent(Font &font_);
1218 XYPOSITION InternalLeading(Font &font_);
1219 XYPOSITION ExternalLeading(Font &font_);
1220 XYPOSITION Height(Font &font_);
1221 XYPOSITION AverageCharWidth(Font &font_);
1223 void SetClip(PRectangle rc);
1224 void FlushCachedState();
1226 void SetUnicodeMode(bool unicodeMode_);
1227 void SetDBCSMode(int codePage_);
1230 SurfaceD2D::SurfaceD2D() :
1231 unicodeMode(false),
1232 x(0), y(0) {
1234 codePage = 0;
1235 codePageText = 0;
1237 pRenderTarget = NULL;
1238 ownRenderTarget = false;
1239 clipsActive = 0;
1241 // From selected font
1242 pTextFormat = NULL;
1243 yAscent = 2;
1244 yDescent = 1;
1245 yInternalLeading = 0;
1247 pBrush = NULL;
1249 logPixelsY = 72;
1250 dpiScaleX = 1.0;
1251 dpiScaleY = 1.0;
1254 SurfaceD2D::~SurfaceD2D() {
1255 Release();
1258 void SurfaceD2D::Release() {
1259 if (pBrush) {
1260 pBrush->Release();
1261 pBrush = 0;
1263 if (pRenderTarget) {
1264 while (clipsActive) {
1265 pRenderTarget->PopAxisAlignedClip();
1266 clipsActive--;
1268 if (ownRenderTarget) {
1269 pRenderTarget->Release();
1271 pRenderTarget = 0;
1275 void SurfaceD2D::SetScale() {
1276 HDC hdcMeasure = ::CreateCompatibleDC(NULL);
1277 logPixelsY = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
1278 dpiScaleX = ::GetDeviceCaps(hdcMeasure, LOGPIXELSX) / 96.0f;
1279 dpiScaleY = logPixelsY / 96.0f;
1280 ::DeleteDC(hdcMeasure);
1283 bool SurfaceD2D::Initialised() {
1284 return pRenderTarget != 0;
1287 HRESULT SurfaceD2D::FlushDrawing() {
1288 return pRenderTarget->Flush();
1291 void SurfaceD2D::Init(WindowID /* wid */) {
1292 Release();
1293 SetScale();
1296 void SurfaceD2D::Init(SurfaceID sid, WindowID) {
1297 Release();
1298 SetScale();
1299 pRenderTarget = reinterpret_cast<ID2D1RenderTarget *>(sid);
1302 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID) {
1303 Release();
1304 SetScale();
1305 SurfaceD2D *psurfOther = static_cast<SurfaceD2D *>(surface_);
1306 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = NULL;
1307 D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height));
1308 D2D1_PIXEL_FORMAT desiredFormat;
1309 #ifdef __MINGW32__
1310 desiredFormat.format = DXGI_FORMAT_UNKNOWN;
1311 #else
1312 desiredFormat = psurfOther->pRenderTarget->GetPixelFormat();
1313 #endif
1314 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1315 HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget(
1316 &desiredSize, NULL, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pCompatibleRenderTarget);
1317 if (SUCCEEDED(hr)) {
1318 pRenderTarget = pCompatibleRenderTarget;
1319 pRenderTarget->BeginDraw();
1320 ownRenderTarget = true;
1324 void SurfaceD2D::PenColour(ColourDesired fore) {
1325 D2DPenColour(fore);
1328 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) {
1329 if (pRenderTarget) {
1330 D2D_COLOR_F col;
1331 col.r = (fore.AsLong() & 0xff) / 255.0f;
1332 col.g = ((fore.AsLong() & 0xff00) >> 8) / 255.0f;
1333 col.b = (fore.AsLong() >> 16) / 255.0f;
1334 col.a = alpha / 255.0f;
1335 if (pBrush) {
1336 pBrush->SetColor(col);
1337 } else {
1338 HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1339 if (!SUCCEEDED(hr) && pBrush) {
1340 pBrush->Release();
1341 pBrush = 0;
1347 void SurfaceD2D::SetFont(Font &font_) {
1348 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
1349 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE);
1350 pTextFormat = pfm->pTextFormat;
1351 yAscent = pfm->yAscent;
1352 yDescent = pfm->yDescent;
1353 yInternalLeading = pfm->yInternalLeading;
1354 codePageText = codePage;
1355 if (pfm->characterSet) {
1356 codePageText = CodePageFromCharSet(pfm->characterSet, codePage);
1358 if (pRenderTarget) {
1359 D2D1_TEXT_ANTIALIAS_MODE aaMode;
1360 aaMode = DWriteMapFontQuality(pfm->extraFontFlag);
1362 if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && customClearTypeRenderingParams)
1363 pRenderTarget->SetTextRenderingParams(customClearTypeRenderingParams);
1364 else if (defaultRenderingParams)
1365 pRenderTarget->SetTextRenderingParams(defaultRenderingParams);
1367 pRenderTarget->SetTextAntialiasMode(aaMode);
1371 int SurfaceD2D::LogPixelsY() {
1372 return logPixelsY;
1375 int SurfaceD2D::DeviceHeightFont(int points) {
1376 return ::MulDiv(points, LogPixelsY(), 72);
1379 void SurfaceD2D::MoveTo(int x_, int y_) {
1380 x = x_;
1381 y = y_;
1384 static int Delta(int difference) {
1385 if (difference < 0)
1386 return -1;
1387 else if (difference > 0)
1388 return 1;
1389 else
1390 return 0;
1393 static float RoundFloat(float f) {
1394 return float(int(f+0.5f));
1397 void SurfaceD2D::LineTo(int x_, int y_) {
1398 if (pRenderTarget) {
1399 int xDiff = x_ - x;
1400 int xDelta = Delta(xDiff);
1401 int yDiff = y_ - y;
1402 int yDelta = Delta(yDiff);
1403 if ((xDiff == 0) || (yDiff == 0)) {
1404 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
1405 int xEnd = x_ - xDelta;
1406 int left = Platform::Minimum(x, xEnd);
1407 int width = abs(x - xEnd) + 1;
1408 int yEnd = y_ - yDelta;
1409 int top = Platform::Minimum(y, yEnd);
1410 int height = abs(y - yEnd) + 1;
1411 D2D1_RECT_F rectangle1 = D2D1::RectF(static_cast<float>(left), static_cast<float>(top),
1412 static_cast<float>(left+width), static_cast<float>(top+height));
1413 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1414 } else if ((abs(xDiff) == abs(yDiff))) {
1415 // 45 degree slope
1416 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f),
1417 D2D1::Point2F(x_ + 0.5f - xDelta, y_ + 0.5f - yDelta), pBrush);
1418 } else {
1419 // Line has a different slope so difficult to avoid last pixel
1420 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f),
1421 D2D1::Point2F(x_ + 0.5f, y_ + 0.5f), pBrush);
1423 x = x_;
1424 y = y_;
1428 void SurfaceD2D::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
1429 if (pRenderTarget) {
1430 ID2D1Factory *pFactory = 0;
1431 pRenderTarget->GetFactory(&pFactory);
1432 ID2D1PathGeometry *geometry=0;
1433 HRESULT hr = pFactory->CreatePathGeometry(&geometry);
1434 if (SUCCEEDED(hr)) {
1435 ID2D1GeometrySink *sink = 0;
1436 hr = geometry->Open(&sink);
1437 if (SUCCEEDED(hr)) {
1438 sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED);
1439 for (size_t i=1; i<static_cast<size_t>(npts); i++) {
1440 sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f));
1442 sink->EndFigure(D2D1_FIGURE_END_CLOSED);
1443 sink->Close();
1444 sink->Release();
1446 D2DPenColour(back);
1447 pRenderTarget->FillGeometry(geometry,pBrush);
1448 D2DPenColour(fore);
1449 pRenderTarget->DrawGeometry(geometry,pBrush);
1452 geometry->Release();
1457 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
1458 if (pRenderTarget) {
1459 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left) + 0.5f, rc.top+0.5f, RoundFloat(rc.right) - 0.5f, rc.bottom-0.5f);
1460 D2DPenColour(back);
1461 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1462 D2DPenColour(fore);
1463 pRenderTarget->DrawRectangle(&rectangle1, pBrush);
1467 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) {
1468 if (pRenderTarget) {
1469 D2DPenColour(back);
1470 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left), rc.top, RoundFloat(rc.right), rc.bottom);
1471 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1475 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1476 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfacePattern);
1477 surfOther.FlushDrawing();
1478 ID2D1Bitmap *pBitmap = NULL;
1479 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1480 surfOther.pRenderTarget);
1481 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1482 if (SUCCEEDED(hr)) {
1483 ID2D1BitmapBrush *pBitmapBrush = NULL;
1484 D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1485 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1486 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1487 // Create the bitmap brush.
1488 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1489 pBitmap->Release();
1490 if (SUCCEEDED(hr)) {
1491 pRenderTarget->FillRectangle(
1492 D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom),
1493 pBitmapBrush);
1494 pBitmapBrush->Release();
1499 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
1500 if (pRenderTarget) {
1501 D2D1_ROUNDED_RECT roundedRectFill = {
1502 D2D1::RectF(rc.left+1.0f, rc.top+1.0f, rc.right-1.0f, rc.bottom-1.0f),
1503 4, 4};
1504 D2DPenColour(back);
1505 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1507 D2D1_ROUNDED_RECT roundedRect = {
1508 D2D1::RectF(rc.left + 0.5f, rc.top+0.5f, rc.right - 0.5f, rc.bottom-0.5f),
1509 4, 4};
1510 D2DPenColour(fore);
1511 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1515 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1516 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
1517 if (pRenderTarget) {
1518 if (cornerSize == 0) {
1519 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1520 D2D1_RECT_F rectFill = D2D1::RectF(RoundFloat(rc.left) + 1.0f, rc.top + 1.0f, RoundFloat(rc.right) - 1.0f, rc.bottom - 1.0f);
1521 D2DPenColour(fill, alphaFill);
1522 pRenderTarget->FillRectangle(rectFill, pBrush);
1524 D2D1_RECT_F rectOutline = D2D1::RectF(RoundFloat(rc.left) + 0.5f, rc.top + 0.5f, RoundFloat(rc.right) - 0.5f, rc.bottom - 0.5f);
1525 D2DPenColour(outline, alphaOutline);
1526 pRenderTarget->DrawRectangle(rectOutline, pBrush);
1527 } else {
1528 const float cornerSizeF = static_cast<float>(cornerSize);
1529 D2D1_ROUNDED_RECT roundedRectFill = {
1530 D2D1::RectF(RoundFloat(rc.left) + 1.0f, rc.top + 1.0f, RoundFloat(rc.right) - 1.0f, rc.bottom - 1.0f),
1531 cornerSizeF, cornerSizeF};
1532 D2DPenColour(fill, alphaFill);
1533 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1535 D2D1_ROUNDED_RECT roundedRect = {
1536 D2D1::RectF(RoundFloat(rc.left) + 0.5f, rc.top + 0.5f, RoundFloat(rc.right) - 0.5f, rc.bottom - 0.5f),
1537 cornerSizeF, cornerSizeF};
1538 D2DPenColour(outline, alphaOutline);
1539 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1544 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1545 if (pRenderTarget) {
1546 if (rc.Width() > width)
1547 rc.left += static_cast<int>((rc.Width() - width) / 2);
1548 rc.right = rc.left + width;
1549 if (rc.Height() > height)
1550 rc.top += static_cast<int>((rc.Height() - height) / 2);
1551 rc.bottom = rc.top + height;
1553 std::vector<unsigned char> image(height * width * 4);
1554 for (int y=0; y<height; y++) {
1555 for (int x=0; x<width; x++) {
1556 unsigned char *pixel = &image[0] + (y*width+x) * 4;
1557 unsigned char alpha = pixelsImage[3];
1558 // Input is RGBA, output is BGRA with premultiplied alpha
1559 pixel[2] = (*pixelsImage++) * alpha / 255;
1560 pixel[1] = (*pixelsImage++) * alpha / 255;
1561 pixel[0] = (*pixelsImage++) * alpha / 255;
1562 pixel[3] = *pixelsImage++;
1566 ID2D1Bitmap *bitmap = 0;
1567 D2D1_SIZE_U size = D2D1::SizeU(width, height);
1568 D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1569 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1570 HRESULT hr = pRenderTarget->CreateBitmap(size, &image[0],
1571 width * 4, &props, &bitmap);
1572 if (SUCCEEDED(hr)) {
1573 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1574 pRenderTarget->DrawBitmap(bitmap, rcDestination);
1575 bitmap->Release();
1580 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
1581 if (pRenderTarget) {
1582 FLOAT radius = rc.Width() / 2.0f;
1583 D2D1_ELLIPSE ellipse = {
1584 D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f),
1585 radius,radius};
1587 PenColour(back);
1588 pRenderTarget->FillEllipse(ellipse, pBrush);
1589 PenColour(fore);
1590 pRenderTarget->DrawEllipse(ellipse, pBrush);
1594 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1595 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfaceSource);
1596 surfOther.FlushDrawing();
1597 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1598 surfOther.pRenderTarget);
1599 ID2D1Bitmap *pBitmap = NULL;
1600 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1601 if (SUCCEEDED(hr)) {
1602 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1603 D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()};
1604 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1605 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1606 pRenderTarget->Flush();
1607 pBitmap->Release();
1611 void SurfaceD2D::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
1612 SetFont(font_);
1614 // Use Unicode calls
1615 const TextWide tbuf(s, len, unicodeMode, codePageText);
1616 if (pRenderTarget && pTextFormat && pBrush) {
1617 if (fuOptions & ETO_CLIPPED) {
1618 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1619 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1622 // Explicitly creating a text layout appears a little faster
1623 IDWriteTextLayout *pTextLayout;
1624 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat,
1625 rc.Width(), rc.Height(), &pTextLayout);
1626 if (SUCCEEDED(hr)) {
1627 D2D1_POINT_2F origin = {rc.left, ybase-yAscent};
1628 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, D2D1_DRAW_TEXT_OPTIONS_NONE);
1629 pTextLayout->Release();
1632 if (fuOptions & ETO_CLIPPED) {
1633 pRenderTarget->PopAxisAlignedClip();
1638 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1639 ColourDesired fore, ColourDesired back) {
1640 if (pRenderTarget) {
1641 FillRectangle(rc, back);
1642 D2DPenColour(fore);
1643 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
1647 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1648 ColourDesired fore, ColourDesired back) {
1649 if (pRenderTarget) {
1650 FillRectangle(rc, back);
1651 D2DPenColour(fore);
1652 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
1656 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1657 ColourDesired fore) {
1658 // Avoid drawing spaces in transparent mode
1659 for (int i=0; i<len; i++) {
1660 if (s[i] != ' ') {
1661 if (pRenderTarget) {
1662 D2DPenColour(fore);
1663 DrawTextCommon(rc, font_, ybase, s, len, 0);
1665 return;
1670 XYPOSITION SurfaceD2D::WidthText(Font &font_, const char *s, int len) {
1671 FLOAT width = 1.0;
1672 SetFont(font_);
1673 const TextWide tbuf(s, len, unicodeMode, codePageText);
1674 if (pIDWriteFactory && pTextFormat) {
1675 // Create a layout
1676 IDWriteTextLayout *pTextLayout = 0;
1677 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1678 if (SUCCEEDED(hr)) {
1679 DWRITE_TEXT_METRICS textMetrics;
1680 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1681 width = textMetrics.widthIncludingTrailingWhitespace;
1682 pTextLayout->Release();
1685 return width;
1688 void SurfaceD2D::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
1689 SetFont(font_);
1690 int fit = 0;
1691 const TextWide tbuf(s, len, unicodeMode, codePageText);
1692 TextPositions poses(tbuf.tlen);
1693 fit = tbuf.tlen;
1694 const int clusters = 1000;
1695 DWRITE_CLUSTER_METRICS clusterMetrics[clusters];
1696 UINT32 count = 0;
1697 if (pIDWriteFactory && pTextFormat) {
1698 SetFont(font_);
1699 // Create a layout
1700 IDWriteTextLayout *pTextLayout = 0;
1701 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
1702 if (!SUCCEEDED(hr))
1703 return;
1704 // For now, assuming WCHAR == cluster
1705 if (!SUCCEEDED(pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count)))
1706 return;
1707 FLOAT position = 0.0f;
1708 size_t ti=0;
1709 for (size_t ci=0; ci<count; ci++) {
1710 position += clusterMetrics[ci].width;
1711 for (size_t inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
1712 //poses.buffer[ti++] = int(position + 0.5);
1713 poses.buffer[ti++] = position;
1716 PLATFORM_ASSERT(ti == static_cast<size_t>(tbuf.tlen));
1717 pTextLayout->Release();
1719 if (unicodeMode) {
1720 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1721 int ui=0;
1722 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1723 int i=0;
1724 while (ui<fit) {
1725 unsigned char uch = us[i];
1726 unsigned int lenChar = 1;
1727 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1728 lenChar = 4;
1729 ui++;
1730 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1731 lenChar = 3;
1732 } else if (uch >= (0x80)) {
1733 lenChar = 2;
1735 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1736 positions[i++] = poses.buffer[ui];
1738 ui++;
1740 XYPOSITION lastPos = 0.0f;
1741 if (i > 0)
1742 lastPos = positions[i-1];
1743 while (i<len) {
1744 positions[i++] = lastPos;
1746 } else if (codePageText == 0) {
1748 // One character per position
1749 PLATFORM_ASSERT(len == tbuf.tlen);
1750 for (size_t kk=0; kk<static_cast<size_t>(len); kk++) {
1751 positions[kk] = poses.buffer[kk];
1754 } else {
1756 // May be more than one byte per position
1757 unsigned int ui = 0;
1758 FLOAT position = 0.0f;
1759 for (int i=0; i<len;) {
1760 if (ui < count)
1761 position = poses.buffer[ui];
1762 if (Platform::IsDBCSLeadByte(codePageText, s[i])) {
1763 positions[i] = position;
1764 positions[i+1] = position;
1765 i += 2;
1766 } else {
1767 positions[i] = position;
1768 i++;
1771 ui++;
1776 XYPOSITION SurfaceD2D::WidthChar(Font &font_, char ch) {
1777 FLOAT width = 1.0;
1778 SetFont(font_);
1779 if (pIDWriteFactory && pTextFormat) {
1780 // Create a layout
1781 IDWriteTextLayout *pTextLayout = 0;
1782 const WCHAR wch = ch;
1783 HRESULT hr = pIDWriteFactory->CreateTextLayout(&wch, 1, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1784 if (SUCCEEDED(hr)) {
1785 DWRITE_TEXT_METRICS textMetrics;
1786 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1787 width = textMetrics.widthIncludingTrailingWhitespace;
1788 pTextLayout->Release();
1791 return width;
1794 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
1795 SetFont(font_);
1796 return ceil(yAscent);
1799 XYPOSITION SurfaceD2D::Descent(Font &font_) {
1800 SetFont(font_);
1801 return ceil(yDescent);
1804 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
1805 SetFont(font_);
1806 return floor(yInternalLeading);
1809 XYPOSITION SurfaceD2D::ExternalLeading(Font &) {
1810 // Not implemented, always return one
1811 return 1;
1814 XYPOSITION SurfaceD2D::Height(Font &font_) {
1815 return Ascent(font_) + Descent(font_);
1818 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) {
1819 FLOAT width = 1.0;
1820 SetFont(font_);
1821 if (pIDWriteFactory && pTextFormat) {
1822 // Create a layout
1823 IDWriteTextLayout *pTextLayout = 0;
1824 const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1825 HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(wcslen(wszAllAlpha)),
1826 pTextFormat, 1000.0, 1000.0, &pTextLayout);
1827 if (SUCCEEDED(hr)) {
1828 DWRITE_TEXT_METRICS textMetrics;
1829 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1830 width = textMetrics.width / wcslen(wszAllAlpha);
1831 pTextLayout->Release();
1834 return width;
1837 void SurfaceD2D::SetClip(PRectangle rc) {
1838 if (pRenderTarget) {
1839 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1840 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1841 clipsActive++;
1845 void SurfaceD2D::FlushCachedState() {
1848 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
1849 unicodeMode=unicodeMode_;
1852 void SurfaceD2D::SetDBCSMode(int codePage_) {
1853 // No action on window as automatically handled by system.
1854 codePage = codePage_;
1856 #endif
1858 Surface *Surface::Allocate(int technology) {
1859 #if defined(USE_D2D)
1860 if (technology == SCWIN_TECH_GDI)
1861 return new SurfaceGDI;
1862 else
1863 return new SurfaceD2D;
1864 #else
1865 return new SurfaceGDI;
1866 #endif
1869 Window::~Window() {
1872 void Window::Destroy() {
1873 if (wid)
1874 ::DestroyWindow(reinterpret_cast<HWND>(wid));
1875 wid = 0;
1878 bool Window::HasFocus() {
1879 return ::GetFocus() == wid;
1882 PRectangle Window::GetPosition() {
1883 RECT rc;
1884 ::GetWindowRect(reinterpret_cast<HWND>(wid), &rc);
1885 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
1888 void Window::SetPosition(PRectangle rc) {
1889 ::SetWindowPos(reinterpret_cast<HWND>(wid),
1890 0, static_cast<int>(rc.left), static_cast<int>(rc.top),
1891 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE);
1894 static RECT RectFromMonitor(HMONITOR hMonitor) {
1895 if (GetMonitorInfoFn) {
1896 MONITORINFO mi = {0};
1897 mi.cbSize = sizeof(mi);
1898 if (GetMonitorInfoFn(hMonitor, &mi)) {
1899 return mi.rcWork;
1902 RECT rc = {0, 0, 0, 0};
1903 if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) {
1904 rc.left = 0;
1905 rc.top = 0;
1906 rc.right = 0;
1907 rc.bottom = 0;
1909 return rc;
1912 void Window::SetPositionRelative(PRectangle rc, Window w) {
1913 LONG style = ::GetWindowLong(reinterpret_cast<HWND>(wid), GWL_STYLE);
1914 if (style & WS_POPUP) {
1915 POINT ptOther = {0, 0};
1916 ::ClientToScreen(reinterpret_cast<HWND>(w.GetID()), &ptOther);
1917 rc.Move(static_cast<XYPOSITION>(ptOther.x), static_cast<XYPOSITION>(ptOther.y));
1919 RECT rcMonitor = RectFromPRectangle(rc);
1921 HMONITOR hMonitor = NULL;
1922 if (MonitorFromRectFn)
1923 hMonitor = MonitorFromRectFn(&rcMonitor, MONITOR_DEFAULTTONEAREST);
1924 // If hMonitor is NULL, that's just the main screen anyways.
1925 //::GetMonitorInfo(hMonitor, &mi);
1926 RECT rcWork = RectFromMonitor(hMonitor);
1928 if (rcWork.left < rcWork.right) {
1929 // Now clamp our desired rectangle to fit inside the work area
1930 // This way, the menu will fit wholly on one screen. An improvement even
1931 // if you don't have a second monitor on the left... Menu's appears half on
1932 // one screen and half on the other are just U.G.L.Y.!
1933 if (rc.right > rcWork.right)
1934 rc.Move(rcWork.right - rc.right, 0);
1935 if (rc.bottom > rcWork.bottom)
1936 rc.Move(0, rcWork.bottom - rc.bottom);
1937 if (rc.left < rcWork.left)
1938 rc.Move(rcWork.left - rc.left, 0);
1939 if (rc.top < rcWork.top)
1940 rc.Move(0, rcWork.top - rc.top);
1943 SetPosition(rc);
1946 PRectangle Window::GetClientPosition() {
1947 RECT rc={0,0,0,0};
1948 if (wid)
1949 ::GetClientRect(reinterpret_cast<HWND>(wid), &rc);
1950 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
1953 void Window::Show(bool show) {
1954 if (show)
1955 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_SHOWNOACTIVATE);
1956 else
1957 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_HIDE);
1960 void Window::InvalidateAll() {
1961 ::InvalidateRect(reinterpret_cast<HWND>(wid), NULL, FALSE);
1964 void Window::InvalidateRectangle(PRectangle rc) {
1965 RECT rcw = RectFromPRectangle(rc);
1966 ::InvalidateRect(reinterpret_cast<HWND>(wid), &rcw, FALSE);
1969 static LRESULT Window_SendMessage(Window *w, UINT msg, WPARAM wParam=0, LPARAM lParam=0) {
1970 return ::SendMessage(reinterpret_cast<HWND>(w->GetID()), msg, wParam, lParam);
1973 void Window::SetFont(Font &font) {
1974 Window_SendMessage(this, WM_SETFONT,
1975 reinterpret_cast<WPARAM>(font.GetID()), 0);
1978 static void FlipBitmap(HBITMAP bitmap, int width, int height) {
1979 HDC hdc = ::CreateCompatibleDC(NULL);
1980 if (hdc != NULL) {
1981 HGDIOBJ prevBmp = ::SelectObject(hdc, bitmap);
1982 ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
1983 ::SelectObject(hdc, prevBmp);
1984 ::DeleteDC(hdc);
1988 static HCURSOR GetReverseArrowCursor() {
1989 if (reverseArrowCursor != NULL)
1990 return reverseArrowCursor;
1992 ::EnterCriticalSection(&crPlatformLock);
1993 HCURSOR cursor = reverseArrowCursor;
1994 if (cursor == NULL) {
1995 cursor = ::LoadCursor(NULL, IDC_ARROW);
1996 ICONINFO info;
1997 if (::GetIconInfo(cursor, &info)) {
1998 BITMAP bmp;
1999 if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) {
2000 FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
2001 if (info.hbmColor != NULL)
2002 FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
2003 info.xHotspot = (DWORD)bmp.bmWidth - 1 - info.xHotspot;
2005 reverseArrowCursor = ::CreateIconIndirect(&info);
2006 if (reverseArrowCursor != NULL)
2007 cursor = reverseArrowCursor;
2010 ::DeleteObject(info.hbmMask);
2011 if (info.hbmColor != NULL)
2012 ::DeleteObject(info.hbmColor);
2015 ::LeaveCriticalSection(&crPlatformLock);
2016 return cursor;
2019 void Window::SetCursor(Cursor curs) {
2020 switch (curs) {
2021 case cursorText:
2022 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
2023 break;
2024 case cursorUp:
2025 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
2026 break;
2027 case cursorWait:
2028 ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
2029 break;
2030 case cursorHoriz:
2031 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
2032 break;
2033 case cursorVert:
2034 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
2035 break;
2036 case cursorHand:
2037 ::SetCursor(::LoadCursor(NULL,IDC_HAND));
2038 break;
2039 case cursorReverseArrow:
2040 ::SetCursor(GetReverseArrowCursor());
2041 break;
2042 case cursorArrow:
2043 case cursorInvalid: // Should not occur, but just in case.
2044 ::SetCursor(::LoadCursor(NULL,IDC_ARROW));
2045 break;
2049 void Window::SetTitle(const char *s) {
2050 ::SetWindowTextA(reinterpret_cast<HWND>(wid), s);
2053 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
2054 coordinates */
2055 PRectangle Window::GetMonitorRect(Point pt) {
2056 // MonitorFromPoint and GetMonitorInfo are not available on Windows 95 and NT 4.
2057 PRectangle rcPosition = GetPosition();
2058 POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
2059 static_cast<LONG>(pt.y + rcPosition.top)};
2060 HMONITOR hMonitor = NULL;
2061 if (MonitorFromPointFn)
2062 hMonitor = MonitorFromPointFn(ptDesktop, MONITOR_DEFAULTTONEAREST);
2064 RECT rcWork = RectFromMonitor(hMonitor);
2065 if (rcWork.left < rcWork.right) {
2066 PRectangle rcMonitor(
2067 rcWork.left - rcPosition.left,
2068 rcWork.top - rcPosition.top,
2069 rcWork.right - rcPosition.left,
2070 rcWork.bottom - rcPosition.top);
2071 return rcMonitor;
2072 } else {
2073 return PRectangle();
2077 struct ListItemData {
2078 const char *text;
2079 int pixId;
2082 class LineToItem {
2083 std::vector<char> words;
2085 std::vector<ListItemData> data;
2087 public:
2088 LineToItem() {
2090 ~LineToItem() {
2091 Clear();
2093 void Clear() {
2094 words.clear();
2095 data.clear();
2098 ListItemData Get(int index) const {
2099 if (index >= 0 && index < static_cast<int>(data.size())) {
2100 return data[index];
2101 } else {
2102 ListItemData missing = {"", -1};
2103 return missing;
2106 int Count() const {
2107 return static_cast<int>(data.size());
2110 void AllocItem(const char *text, int pixId) {
2111 ListItemData lid = { text, pixId };
2112 data.push_back(lid);
2115 char *SetWords(const char *s) {
2116 words = std::vector<char>(s, s+strlen(s)+1);
2117 return &words[0];
2121 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
2123 ListBox::ListBox() {
2126 ListBox::~ListBox() {
2129 class ListBoxX : public ListBox {
2130 int lineHeight;
2131 FontID fontCopy;
2132 int technology;
2133 RGBAImageSet images;
2134 LineToItem lti;
2135 HWND lb;
2136 bool unicodeMode;
2137 int desiredVisibleRows;
2138 unsigned int maxItemCharacters;
2139 unsigned int aveCharWidth;
2140 Window *parent;
2141 int ctrlID;
2142 CallBackAction doubleClickAction;
2143 void *doubleClickActionData;
2144 const char *widestItem;
2145 unsigned int maxCharWidth;
2146 int resizeHit;
2147 PRectangle rcPreSize;
2148 Point dragOffset;
2149 Point location; // Caret location at which the list is opened
2150 int wheelDelta; // mouse wheel residue
2152 HWND GetHWND() const;
2153 void AppendListItem(const char *text, const char *numword);
2154 static void AdjustWindowRect(PRectangle *rc);
2155 int ItemHeight() const;
2156 int MinClientWidth() const;
2157 int TextOffset() const;
2158 POINT GetClientExtent() const;
2159 POINT MinTrackSize() const;
2160 POINT MaxTrackSize() const;
2161 void SetRedraw(bool on);
2162 void OnDoubleClick();
2163 void ResizeToCursor();
2164 void StartResize(WPARAM);
2165 LRESULT NcHitTest(WPARAM, LPARAM) const;
2166 void CentreItem(int n);
2167 void Paint(HDC);
2168 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2170 static const Point ItemInset; // Padding around whole item
2171 static const Point TextInset; // Padding around text
2172 static const Point ImageInset; // Padding around image
2174 public:
2175 ListBoxX() : lineHeight(10), fontCopy(0), technology(0), lb(0), unicodeMode(false),
2176 desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8),
2177 parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL),
2178 widestItem(NULL), maxCharWidth(1), resizeHit(0), wheelDelta(0) {
2180 virtual ~ListBoxX() {
2181 if (fontCopy) {
2182 ::DeleteObject(fontCopy);
2183 fontCopy = 0;
2186 virtual void SetFont(Font &font);
2187 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
2188 virtual void SetAverageCharWidth(int width);
2189 virtual void SetVisibleRows(int rows);
2190 virtual int GetVisibleRows() const;
2191 virtual PRectangle GetDesiredRect();
2192 virtual int CaretFromEdge();
2193 virtual void Clear();
2194 virtual void Append(char *s, int type = -1);
2195 virtual int Length();
2196 virtual void Select(int n);
2197 virtual int GetSelection();
2198 virtual int Find(const char *prefix);
2199 virtual void GetValue(int n, char *value, int len);
2200 virtual void RegisterImage(int type, const char *xpm_data);
2201 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
2202 virtual void ClearRegisteredImages();
2203 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
2204 doubleClickAction = action;
2205 doubleClickActionData = data;
2207 virtual void SetList(const char *list, char separator, char typesep);
2208 void Draw(DRAWITEMSTRUCT *pDrawItem);
2209 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2210 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2213 const Point ListBoxX::ItemInset(0, 0);
2214 const Point ListBoxX::TextInset(2, 0);
2215 const Point ListBoxX::ImageInset(1, 0);
2217 ListBox *ListBox::Allocate() {
2218 ListBoxX *lb = new ListBoxX();
2219 return lb;
2222 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) {
2223 parent = &parent_;
2224 ctrlID = ctrlID_;
2225 location = location_;
2226 lineHeight = lineHeight_;
2227 unicodeMode = unicodeMode_;
2228 technology = technology_;
2229 HWND hwndParent = reinterpret_cast<HWND>(parent->GetID());
2230 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
2231 // Window created as popup so not clipped within parent client area
2232 wid = ::CreateWindowEx(
2233 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
2234 WS_POPUP | WS_THICKFRAME,
2235 100,100, 150,80, hwndParent,
2236 NULL,
2237 hinstanceParent,
2238 this);
2240 POINT locationw = {static_cast<LONG>(location.x), static_cast<LONG>(location.y)};
2241 ::MapWindowPoints(hwndParent, NULL, &locationw, 1);
2242 location = Point::FromInts(locationw.x, locationw.y);
2245 void ListBoxX::SetFont(Font &font) {
2246 if (font.GetID()) {
2247 if (fontCopy) {
2248 ::DeleteObject(fontCopy);
2249 fontCopy = 0;
2251 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font.GetID());
2252 fontCopy = pfm->HFont();
2253 ::SendMessage(lb, WM_SETFONT, reinterpret_cast<WPARAM>(fontCopy), 0);
2257 void ListBoxX::SetAverageCharWidth(int width) {
2258 aveCharWidth = width;
2261 void ListBoxX::SetVisibleRows(int rows) {
2262 desiredVisibleRows = rows;
2265 int ListBoxX::GetVisibleRows() const {
2266 return desiredVisibleRows;
2269 HWND ListBoxX::GetHWND() const {
2270 return reinterpret_cast<HWND>(GetID());
2273 PRectangle ListBoxX::GetDesiredRect() {
2274 PRectangle rcDesired = GetPosition();
2276 int rows = Length();
2277 if ((rows == 0) || (rows > desiredVisibleRows))
2278 rows = desiredVisibleRows;
2279 rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
2281 int width = MinClientWidth();
2282 HDC hdc = ::GetDC(lb);
2283 HFONT oldFont = SelectFont(hdc, fontCopy);
2284 SIZE textSize = {0, 0};
2285 int len = 0;
2286 if (widestItem) {
2287 len = static_cast<int>(strlen(widestItem));
2288 if (unicodeMode) {
2289 const TextWide tbuf(widestItem, len, unicodeMode);
2290 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
2291 } else {
2292 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
2295 TEXTMETRIC tm;
2296 ::GetTextMetrics(hdc, &tm);
2297 maxCharWidth = tm.tmMaxCharWidth;
2298 SelectFont(hdc, oldFont);
2299 ::ReleaseDC(lb, hdc);
2301 int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
2302 if (width < widthDesired)
2303 width = widthDesired;
2305 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
2306 if (Length() > rows)
2307 rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
2309 AdjustWindowRect(&rcDesired);
2310 return rcDesired;
2313 int ListBoxX::TextOffset() const {
2314 int pixWidth = images.GetWidth();
2315 return static_cast<int>(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2));
2318 int ListBoxX::CaretFromEdge() {
2319 PRectangle rc;
2320 AdjustWindowRect(&rc);
2321 return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1);
2324 void ListBoxX::Clear() {
2325 ::SendMessage(lb, LB_RESETCONTENT, 0, 0);
2326 maxItemCharacters = 0;
2327 widestItem = NULL;
2328 lti.Clear();
2331 void ListBoxX::Append(char *, int) {
2332 // This method is no longer called in Scintilla
2333 PLATFORM_ASSERT(false);
2336 int ListBoxX::Length() {
2337 return lti.Count();
2340 void ListBoxX::Select(int n) {
2341 // We are going to scroll to centre on the new selection and then select it, so disable
2342 // redraw to avoid flicker caused by a painting new selection twice in unselected and then
2343 // selected states
2344 SetRedraw(false);
2345 CentreItem(n);
2346 ::SendMessage(lb, LB_SETCURSEL, n, 0);
2347 SetRedraw(true);
2350 int ListBoxX::GetSelection() {
2351 return static_cast<int>(::SendMessage(lb, LB_GETCURSEL, 0, 0));
2354 // This is not actually called at present
2355 int ListBoxX::Find(const char *) {
2356 return LB_ERR;
2359 void ListBoxX::GetValue(int n, char *value, int len) {
2360 ListItemData item = lti.Get(n);
2361 strncpy(value, item.text, len);
2362 value[len-1] = '\0';
2365 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2366 XPM xpmImage(xpm_data);
2367 images.Add(type, new RGBAImage(xpmImage));
2370 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2371 images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage));
2374 void ListBoxX::ClearRegisteredImages() {
2375 images.Clear();
2378 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
2379 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
2380 RECT rcBox = pDrawItem->rcItem;
2381 rcBox.left += TextOffset();
2382 if (pDrawItem->itemState & ODS_SELECTED) {
2383 RECT rcImage = pDrawItem->rcItem;
2384 rcImage.right = rcBox.left;
2385 // The image is not highlighted
2386 ::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2387 ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
2388 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
2389 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
2390 } else {
2391 ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2392 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
2393 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
2396 ListItemData item = lti.Get(pDrawItem->itemID);
2397 int pixId = item.pixId;
2398 const char *text = item.text;
2399 int len = static_cast<int>(strlen(text));
2401 RECT rcText = rcBox;
2402 ::InsetRect(&rcText, static_cast<int>(TextInset.x), static_cast<int>(TextInset.y));
2404 if (unicodeMode) {
2405 const TextWide tbuf(text, len, unicodeMode);
2406 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2407 } else {
2408 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2410 if (pDrawItem->itemState & ODS_SELECTED) {
2411 ::DrawFocusRect(pDrawItem->hDC, &rcBox);
2414 // Draw the image, if any
2415 RGBAImage *pimage = images.Get(pixId);
2416 if (pimage) {
2417 Surface *surfaceItem = Surface::Allocate(technology);
2418 if (surfaceItem) {
2419 if (technology == SCWIN_TECH_GDI) {
2420 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
2421 long left = pDrawItem->rcItem.left + static_cast<int>(ItemInset.x + ImageInset.x);
2422 PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
2423 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2424 surfaceItem->DrawRGBAImage(rcImage,
2425 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2426 delete surfaceItem;
2427 ::SetTextAlign(pDrawItem->hDC, TA_TOP);
2428 } else {
2429 #if defined(USE_D2D)
2430 D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
2431 D2D1_RENDER_TARGET_TYPE_DEFAULT,
2432 D2D1::PixelFormat(
2433 DXGI_FORMAT_B8G8R8A8_UNORM,
2434 D2D1_ALPHA_MODE_IGNORE),
2437 D2D1_RENDER_TARGET_USAGE_NONE,
2438 D2D1_FEATURE_LEVEL_DEFAULT
2440 ID2D1DCRenderTarget *pDCRT = 0;
2441 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
2442 if (SUCCEEDED(hr)) {
2443 RECT rcWindow;
2444 GetClientRect(pDrawItem->hwndItem, &rcWindow);
2445 hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow);
2446 if (SUCCEEDED(hr)) {
2447 surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
2448 pDCRT->BeginDraw();
2449 long left = pDrawItem->rcItem.left + static_cast<long>(ItemInset.x + ImageInset.x);
2450 PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
2451 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2452 surfaceItem->DrawRGBAImage(rcImage,
2453 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2454 delete surfaceItem;
2455 pDCRT->EndDraw();
2456 pDCRT->Release();
2457 } else {
2458 delete surfaceItem;
2460 } else {
2461 delete surfaceItem;
2463 #endif
2470 void ListBoxX::AppendListItem(const char *text, const char *numword) {
2471 int pixId = -1;
2472 if (numword) {
2473 pixId = 0;
2474 char ch;
2475 while ((ch = *++numword) != '\0') {
2476 pixId = 10 * pixId + (ch - '0');
2480 lti.AllocItem(text, pixId);
2481 unsigned int len = static_cast<unsigned int>(strlen(text));
2482 if (maxItemCharacters < len) {
2483 maxItemCharacters = len;
2484 widestItem = text;
2488 void ListBoxX::SetList(const char *list, char separator, char typesep) {
2489 // Turn off redraw while populating the list - this has a significant effect, even if
2490 // the listbox is not visible.
2491 SetRedraw(false);
2492 Clear();
2493 size_t size = strlen(list);
2494 char *words = lti.SetWords(list);
2495 char *startword = words;
2496 char *numword = NULL;
2497 for (size_t i=0; i < size; i++) {
2498 if (words[i] == separator) {
2499 words[i] = '\0';
2500 if (numword)
2501 *numword = '\0';
2502 AppendListItem(startword, numword);
2503 startword = words + i + 1;
2504 numword = NULL;
2505 } else if (words[i] == typesep) {
2506 numword = words + i;
2509 if (startword) {
2510 if (numword)
2511 *numword = '\0';
2512 AppendListItem(startword, numword);
2515 // Finally populate the listbox itself with the correct number of items
2516 int count = lti.Count();
2517 ::SendMessage(lb, LB_INITSTORAGE, count, 0);
2518 for (int j=0; j<count; j++) {
2519 ::SendMessage(lb, LB_ADDSTRING, 0, j+1);
2521 SetRedraw(true);
2524 void ListBoxX::AdjustWindowRect(PRectangle *rc) {
2525 RECT rcw = RectFromPRectangle(*rc);
2526 ::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
2527 *rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom);
2530 int ListBoxX::ItemHeight() const {
2531 int itemHeight = lineHeight + (static_cast<int>(TextInset.y) * 2);
2532 int pixHeight = images.GetHeight() + (static_cast<int>(ImageInset.y) * 2);
2533 if (itemHeight < pixHeight) {
2534 itemHeight = pixHeight;
2536 return itemHeight;
2539 int ListBoxX::MinClientWidth() const {
2540 return 12 * (aveCharWidth+aveCharWidth/3);
2543 POINT ListBoxX::MinTrackSize() const {
2544 PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight());
2545 AdjustWindowRect(&rc);
2546 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2547 return ret;
2550 POINT ListBoxX::MaxTrackSize() const {
2551 PRectangle rc = PRectangle::FromInts(0, 0,
2552 Platform::Maximum(MinClientWidth(),
2553 maxCharWidth * maxItemCharacters + static_cast<int>(TextInset.x) * 2 +
2554 TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL)),
2555 ItemHeight() * lti.Count());
2556 AdjustWindowRect(&rc);
2557 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2558 return ret;
2561 void ListBoxX::SetRedraw(bool on) {
2562 ::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
2563 if (on)
2564 ::InvalidateRect(lb, NULL, TRUE);
2567 static XYPOSITION XYMinimum(XYPOSITION a, XYPOSITION b) {
2568 if (a < b)
2569 return a;
2570 else
2571 return b;
2574 static XYPOSITION XYMaximum(XYPOSITION a, XYPOSITION b) {
2575 if (a > b)
2576 return a;
2577 else
2578 return b;
2581 void ListBoxX::ResizeToCursor() {
2582 PRectangle rc = GetPosition();
2583 POINT ptw;
2584 ::GetCursorPos(&ptw);
2585 Point pt = Point::FromInts(ptw.x, ptw.y);
2586 pt.x += dragOffset.x;
2587 pt.y += dragOffset.y;
2589 switch (resizeHit) {
2590 case HTLEFT:
2591 rc.left = pt.x;
2592 break;
2593 case HTRIGHT:
2594 rc.right = pt.x;
2595 break;
2596 case HTTOP:
2597 rc.top = pt.y;
2598 break;
2599 case HTTOPLEFT:
2600 rc.top = pt.y;
2601 rc.left = pt.x;
2602 break;
2603 case HTTOPRIGHT:
2604 rc.top = pt.y;
2605 rc.right = pt.x;
2606 break;
2607 case HTBOTTOM:
2608 rc.bottom = pt.y;
2609 break;
2610 case HTBOTTOMLEFT:
2611 rc.bottom = pt.y;
2612 rc.left = pt.x;
2613 break;
2614 case HTBOTTOMRIGHT:
2615 rc.bottom = pt.y;
2616 rc.right = pt.x;
2617 break;
2620 POINT ptMin = MinTrackSize();
2621 POINT ptMax = MaxTrackSize();
2622 // We don't allow the left edge to move at present, but just in case
2623 rc.left = XYMaximum(XYMinimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
2624 rc.top = XYMaximum(XYMinimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
2625 rc.right = XYMaximum(XYMinimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
2626 rc.bottom = XYMaximum(XYMinimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
2628 SetPosition(rc);
2631 void ListBoxX::StartResize(WPARAM hitCode) {
2632 rcPreSize = GetPosition();
2633 POINT cursorPos;
2634 ::GetCursorPos(&cursorPos);
2636 switch (hitCode) {
2637 case HTRIGHT:
2638 case HTBOTTOM:
2639 case HTBOTTOMRIGHT:
2640 dragOffset.x = rcPreSize.right - cursorPos.x;
2641 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2642 break;
2644 case HTTOPRIGHT:
2645 dragOffset.x = rcPreSize.right - cursorPos.x;
2646 dragOffset.y = rcPreSize.top - cursorPos.y;
2647 break;
2649 // Note that the current hit test code prevents the left edge cases ever firing
2650 // as we don't want the left edge to be moveable
2651 case HTLEFT:
2652 case HTTOP:
2653 case HTTOPLEFT:
2654 dragOffset.x = rcPreSize.left - cursorPos.x;
2655 dragOffset.y = rcPreSize.top - cursorPos.y;
2656 break;
2657 case HTBOTTOMLEFT:
2658 dragOffset.x = rcPreSize.left - cursorPos.x;
2659 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2660 break;
2662 default:
2663 return;
2666 ::SetCapture(GetHWND());
2667 resizeHit = static_cast<int>(hitCode);
2670 LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
2671 LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
2672 // There is an apparent bug in the DefWindowProc hit test code whereby it will
2673 // return HTTOPXXX if the window in question is shorter than the default
2674 // window caption height + frame, even if one is hovering over the bottom edge of
2675 // the frame, so workaround that here
2676 if (hit >= HTTOP && hit <= HTTOPRIGHT) {
2677 int minHeight = GetSystemMetrics(SM_CYMINTRACK);
2678 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2679 int yPos = GET_Y_LPARAM(lParam);
2680 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
2681 hit += HTBOTTOM - HTTOP;
2685 // Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
2686 // depending on whether the list is above or below the caret
2687 switch (hit) {
2688 case HTLEFT:
2689 case HTTOPLEFT:
2690 case HTBOTTOMLEFT:
2691 hit = HTERROR;
2692 break;
2694 case HTTOP:
2695 case HTTOPRIGHT: {
2696 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2697 // Valid only if caret below list
2698 if (location.y < rc.top)
2699 hit = HTERROR;
2701 break;
2703 case HTBOTTOM:
2704 case HTBOTTOMRIGHT: {
2705 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2706 // Valid only if caret above list
2707 if (rc.bottom < location.y)
2708 hit = HTERROR;
2710 break;
2713 return hit;
2716 void ListBoxX::OnDoubleClick() {
2718 if (doubleClickAction != NULL) {
2719 doubleClickAction(doubleClickActionData);
2723 POINT ListBoxX::GetClientExtent() const {
2724 PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
2725 POINT ret;
2726 ret.x = static_cast<LONG>(rc.Width());
2727 ret.y = static_cast<LONG>(rc.Height());
2728 return ret;
2731 void ListBoxX::CentreItem(int n) {
2732 // If below mid point, scroll up to centre, but with more items below if uneven
2733 if (n >= 0) {
2734 POINT extent = GetClientExtent();
2735 int visible = extent.y/ItemHeight();
2736 if (visible < Length()) {
2737 LRESULT top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
2738 int half = (visible - 1) / 2;
2739 if (n > (top + half))
2740 ::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
2745 // Performs a double-buffered paint operation to avoid flicker
2746 void ListBoxX::Paint(HDC hDC) {
2747 POINT extent = GetClientExtent();
2748 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
2749 HDC bitmapDC = ::CreateCompatibleDC(hDC);
2750 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
2751 // The list background is mainly erased during painting, but can be a small
2752 // unpainted area when at the end of a non-integrally sized list with a
2753 // vertical scroll bar
2754 RECT rc = { 0, 0, extent.x, extent.y };
2755 ::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2756 // Paint the entire client area and vertical scrollbar
2757 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
2758 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
2759 // Select a stock brush to prevent warnings from BoundsChecker
2760 ::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
2761 SelectBitmap(bitmapDC, hBitmapOld);
2762 ::DeleteDC(bitmapDC);
2763 ::DeleteObject(hBitmap);
2766 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
2767 try {
2768 switch (uMsg) {
2769 case WM_ERASEBKGND:
2770 return TRUE;
2772 case WM_PAINT: {
2773 PAINTSTRUCT ps;
2774 HDC hDC = ::BeginPaint(hWnd, &ps);
2775 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2776 if (lbx)
2777 lbx->Paint(hDC);
2778 ::EndPaint(hWnd, &ps);
2780 return 0;
2782 case WM_MOUSEACTIVATE:
2783 // This prevents the view activating when the scrollbar is clicked
2784 return MA_NOACTIVATE;
2786 case WM_LBUTTONDOWN: {
2787 // We must take control of selection to prevent the ListBox activating
2788 // the popup
2789 LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
2790 int item = LOWORD(lResult);
2791 if (HIWORD(lResult) == 0 && item >= 0) {
2792 ::SendMessage(hWnd, LB_SETCURSEL, item, 0);
2795 return 0;
2797 case WM_LBUTTONUP:
2798 return 0;
2800 case WM_LBUTTONDBLCLK: {
2801 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2802 if (lbx) {
2803 lbx->OnDoubleClick();
2806 return 0;
2808 case WM_MBUTTONDOWN:
2809 // disable the scroll wheel button click action
2810 return 0;
2813 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
2814 if (prevWndProc) {
2815 return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
2816 } else {
2817 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2819 } catch (...) {
2821 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2824 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2825 switch (iMessage) {
2826 case WM_CREATE: {
2827 HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID()));
2828 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
2829 // but has useful side effect of speeding up list population significantly
2830 lb = ::CreateWindowEx(
2831 0, TEXT("listbox"), TEXT(""),
2832 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
2833 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
2834 0, 0, 150,80, hWnd,
2835 reinterpret_cast<HMENU>(ctrlID),
2836 hinstanceParent,
2838 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(lb, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlWndProc)));
2839 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
2841 break;
2843 case WM_SIZE:
2844 if (lb) {
2845 SetRedraw(false);
2846 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
2847 // Ensure the selection remains visible
2848 CentreItem(GetSelection());
2849 SetRedraw(true);
2851 break;
2853 case WM_PAINT: {
2854 PAINTSTRUCT ps;
2855 ::BeginPaint(hWnd, &ps);
2856 ::EndPaint(hWnd, &ps);
2858 break;
2860 case WM_COMMAND:
2861 // This is not actually needed now - the registered double click action is used
2862 // directly to action a choice from the list.
2863 ::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);
2864 break;
2866 case WM_MEASUREITEM: {
2867 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
2868 pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());
2870 break;
2872 case WM_DRAWITEM:
2873 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
2874 break;
2876 case WM_DESTROY:
2877 lb = 0;
2878 ::SetWindowLong(hWnd, 0, 0);
2879 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2881 case WM_ERASEBKGND:
2882 // To reduce flicker we can elide background erasure since this window is
2883 // completely covered by its child.
2884 return TRUE;
2886 case WM_GETMINMAXINFO: {
2887 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
2888 minMax->ptMaxTrackSize = MaxTrackSize();
2889 minMax->ptMinTrackSize = MinTrackSize();
2891 break;
2893 case WM_MOUSEACTIVATE:
2894 return MA_NOACTIVATE;
2896 case WM_NCHITTEST:
2897 return NcHitTest(wParam, lParam);
2899 case WM_NCLBUTTONDOWN:
2900 // We have to implement our own window resizing because the DefWindowProc
2901 // implementation insists on activating the resized window
2902 StartResize(wParam);
2903 return 0;
2905 case WM_MOUSEMOVE: {
2906 if (resizeHit == 0) {
2907 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2908 } else {
2909 ResizeToCursor();
2912 break;
2914 case WM_LBUTTONUP:
2915 case WM_CANCELMODE:
2916 if (resizeHit != 0) {
2917 resizeHit = 0;
2918 ::ReleaseCapture();
2920 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2922 case WM_MOUSEWHEEL:
2923 wheelDelta -= static_cast<short>(HIWORD(wParam));
2924 if (abs(wheelDelta) >= WHEEL_DELTA) {
2925 int nRows = GetVisibleRows();
2926 int linesToScroll = 1;
2927 if (nRows > 1) {
2928 linesToScroll = nRows - 1;
2930 if (linesToScroll > 3) {
2931 linesToScroll = 3;
2933 linesToScroll *= (wheelDelta / WHEEL_DELTA);
2934 LRESULT top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0) + linesToScroll;
2935 if (top < 0) {
2936 top = 0;
2938 ::SendMessage(lb, LB_SETTOPINDEX, top, 0);
2939 // update wheel delta residue
2940 if (wheelDelta >= 0)
2941 wheelDelta = wheelDelta % WHEEL_DELTA;
2942 else
2943 wheelDelta = - (-wheelDelta % WHEEL_DELTA);
2945 break;
2947 default:
2948 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2951 return 0;
2954 LRESULT PASCAL ListBoxX::StaticWndProc(
2955 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2956 if (iMessage == WM_CREATE) {
2957 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
2958 SetWindowPointer(hWnd, pCreate->lpCreateParams);
2960 // Find C++ object associated with window.
2961 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(hWnd));
2962 if (lbx) {
2963 return lbx->WndProc(hWnd, iMessage, wParam, lParam);
2964 } else {
2965 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2969 static bool ListBoxX_Register() {
2970 WNDCLASSEX wndclassc;
2971 wndclassc.cbSize = sizeof(wndclassc);
2972 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
2973 // truncated items in the list and the appearance/disappearance of the vertical scroll bar.
2974 // The list repaint is double-buffered to avoid the flicker this would otherwise cause.
2975 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
2976 wndclassc.cbClsExtra = 0;
2977 wndclassc.cbWndExtra = sizeof(ListBoxX *);
2978 wndclassc.hInstance = hinstPlatformRes;
2979 wndclassc.hIcon = NULL;
2980 wndclassc.hbrBackground = NULL;
2981 wndclassc.lpszMenuName = NULL;
2982 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
2983 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
2984 wndclassc.lpszClassName = ListBoxX_ClassName;
2985 wndclassc.hIconSm = 0;
2987 return ::RegisterClassEx(&wndclassc) != 0;
2990 bool ListBoxX_Unregister() {
2991 return ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes) != 0;
2994 Menu::Menu() : mid(0) {
2997 void Menu::CreatePopUp() {
2998 Destroy();
2999 mid = ::CreatePopupMenu();
3002 void Menu::Destroy() {
3003 if (mid)
3004 ::DestroyMenu(reinterpret_cast<HMENU>(mid));
3005 mid = 0;
3008 void Menu::Show(Point pt, Window &w) {
3009 ::TrackPopupMenu(reinterpret_cast<HMENU>(mid),
3010 0, static_cast<int>(pt.x - 4), static_cast<int>(pt.y), 0,
3011 reinterpret_cast<HWND>(w.GetID()), NULL);
3012 Destroy();
3015 static bool initialisedET = false;
3016 static bool usePerformanceCounter = false;
3017 static LARGE_INTEGER frequency;
3019 ElapsedTime::ElapsedTime() {
3020 if (!initialisedET) {
3021 usePerformanceCounter = ::QueryPerformanceFrequency(&frequency) != 0;
3022 initialisedET = true;
3024 if (usePerformanceCounter) {
3025 LARGE_INTEGER timeVal;
3026 ::QueryPerformanceCounter(&timeVal);
3027 bigBit = timeVal.HighPart;
3028 littleBit = timeVal.LowPart;
3029 } else {
3030 bigBit = clock();
3031 littleBit = 0;
3035 double ElapsedTime::Duration(bool reset) {
3036 double result;
3037 long endBigBit;
3038 long endLittleBit;
3040 if (usePerformanceCounter) {
3041 LARGE_INTEGER lEnd;
3042 ::QueryPerformanceCounter(&lEnd);
3043 endBigBit = lEnd.HighPart;
3044 endLittleBit = lEnd.LowPart;
3045 LARGE_INTEGER lBegin;
3046 lBegin.HighPart = bigBit;
3047 lBegin.LowPart = littleBit;
3048 double elapsed = static_cast<double>(lEnd.QuadPart - lBegin.QuadPart);
3049 result = elapsed / static_cast<double>(frequency.QuadPart);
3050 } else {
3051 endBigBit = clock();
3052 endLittleBit = 0;
3053 double elapsed = endBigBit - bigBit;
3054 result = elapsed / CLOCKS_PER_SEC;
3056 if (reset) {
3057 bigBit = endBigBit;
3058 littleBit = endLittleBit;
3060 return result;
3063 class DynamicLibraryImpl : public DynamicLibrary {
3064 protected:
3065 HMODULE h;
3066 public:
3067 explicit DynamicLibraryImpl(const char *modulePath) {
3068 h = ::LoadLibraryA(modulePath);
3071 virtual ~DynamicLibraryImpl() {
3072 if (h != NULL)
3073 ::FreeLibrary(h);
3076 // Use GetProcAddress to get a pointer to the relevant function.
3077 virtual Function FindFunction(const char *name) {
3078 if (h != NULL) {
3079 // C++ standard doesn't like casts betwen function pointers and void pointers so use a union
3080 union {
3081 FARPROC fp;
3082 Function f;
3083 } fnConv;
3084 fnConv.fp = ::GetProcAddress(h, name);
3085 return fnConv.f;
3086 } else {
3087 return NULL;
3091 virtual bool IsValid() {
3092 return h != NULL;
3096 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3097 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3100 ColourDesired Platform::Chrome() {
3101 return ::GetSysColor(COLOR_3DFACE);
3104 ColourDesired Platform::ChromeHighlight() {
3105 return ::GetSysColor(COLOR_3DHIGHLIGHT);
3108 const char *Platform::DefaultFont() {
3109 return "Verdana";
3112 int Platform::DefaultFontSize() {
3113 return 8;
3116 unsigned int Platform::DoubleClickTime() {
3117 return ::GetDoubleClickTime();
3120 bool Platform::MouseButtonBounce() {
3121 return false;
3124 void Platform::DebugDisplay(const char *s) {
3125 ::OutputDebugStringA(s);
3128 bool Platform::IsKeyDown(int key) {
3129 return (::GetKeyState(key) & 0x80000000) != 0;
3132 long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
3133 // This should never be called - its here to satisfy an old interface
3134 return static_cast<long>(::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam));
3137 long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
3138 // This should never be called - its here to satisfy an old interface
3139 return static_cast<long>(::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
3140 reinterpret_cast<LPARAM>(lParam)));
3143 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
3144 // Byte ranges found in Wikipedia articles with relevant search strings in each case
3145 unsigned char uch = static_cast<unsigned char>(ch);
3146 switch (codePage) {
3147 case 932:
3148 // Shift_jis
3149 return ((uch >= 0x81) && (uch <= 0x9F)) ||
3150 ((uch >= 0xE0) && (uch <= 0xEF));
3151 case 936:
3152 // GBK
3153 return (uch >= 0x81) && (uch <= 0xFE);
3154 case 949:
3155 // Korean Wansung KS C-5601-1987
3156 return (uch >= 0x81) && (uch <= 0xFE);
3157 case 950:
3158 // Big5
3159 return (uch >= 0x81) && (uch <= 0xFE);
3160 case 1361:
3161 // Korean Johab KS C-5601-1992
3162 return
3163 ((uch >= 0x84) && (uch <= 0xD3)) ||
3164 ((uch >= 0xD8) && (uch <= 0xDE)) ||
3165 ((uch >= 0xE0) && (uch <= 0xF9));
3167 return false;
3170 int Platform::DBCSCharLength(int codePage, const char *s) {
3171 if (codePage == 932 || codePage == 936 || codePage == 949 ||
3172 codePage == 950 || codePage == 1361) {
3173 return Platform::IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
3174 } else {
3175 return 1;
3179 int Platform::DBCSCharMaxLength() {
3180 return 2;
3183 // These are utility functions not really tied to a platform
3185 int Platform::Minimum(int a, int b) {
3186 if (a < b)
3187 return a;
3188 else
3189 return b;
3192 int Platform::Maximum(int a, int b) {
3193 if (a > b)
3194 return a;
3195 else
3196 return b;
3199 //#define TRACE
3201 #ifdef TRACE
3202 void Platform::DebugPrintf(const char *format, ...) {
3203 char buffer[2000];
3204 va_list pArguments;
3205 va_start(pArguments, format);
3206 vsprintf(buffer,format,pArguments);
3207 va_end(pArguments);
3208 Platform::DebugDisplay(buffer);
3210 #else
3211 void Platform::DebugPrintf(const char *, ...) {
3213 #endif
3215 static bool assertionPopUps = true;
3217 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3218 bool ret = assertionPopUps;
3219 assertionPopUps = assertionPopUps_;
3220 return ret;
3223 void Platform::Assert(const char *c, const char *file, int line) {
3224 char buffer[2000];
3225 sprintf(buffer, "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n");
3226 if (assertionPopUps) {
3227 int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
3228 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
3229 if (idButton == IDRETRY) {
3230 ::DebugBreak();
3231 } else if (idButton == IDIGNORE) {
3232 // all OK
3233 } else {
3234 abort();
3236 } else {
3237 Platform::DebugDisplay(buffer);
3238 ::DebugBreak();
3239 abort();
3243 int Platform::Clamp(int val, int minVal, int maxVal) {
3244 if (val > maxVal)
3245 val = maxVal;
3246 if (val < minVal)
3247 val = minVal;
3248 return val;
3251 #ifdef _MSC_VER
3252 // GetVersionEx has been deprecated fro Windows 8.1 but called here to determine if Windows 9x.
3253 // Too dangerous to find alternate check.
3254 #pragma warning(disable: 4996)
3255 #endif
3257 void Platform_Initialise(void *hInstance) {
3258 OSVERSIONINFO osv = {sizeof(OSVERSIONINFO),0,0,0,0,TEXT("")};
3259 ::GetVersionEx(&osv);
3260 onNT = osv.dwPlatformId == VER_PLATFORM_WIN32_NT;
3261 ::InitializeCriticalSection(&crPlatformLock);
3262 hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
3263 // This may be called from DllMain, in which case the call to LoadLibrary
3264 // is bad because it can upset the DLL load order.
3265 if (!hDLLImage) {
3266 hDLLImage = ::LoadLibrary(TEXT("Msimg32"));
3268 if (hDLLImage) {
3269 AlphaBlendFn = (AlphaBlendSig)::GetProcAddress(hDLLImage, "AlphaBlend");
3271 if (!hDLLUser32) {
3272 hDLLUser32 = ::LoadLibrary(TEXT("User32"));
3274 if (hDLLUser32) {
3275 MonitorFromPointFn = (MonitorFromPointSig)::GetProcAddress(hDLLUser32, "MonitorFromPoint");
3276 MonitorFromRectFn = (MonitorFromRectSig)::GetProcAddress(hDLLUser32, "MonitorFromRect");
3277 GetMonitorInfoFn = (GetMonitorInfoSig)::GetProcAddress(hDLLUser32, "GetMonitorInfoA");
3280 ListBoxX_Register();
3283 #ifdef _MSC_VER
3284 #pragma warning(default: 4996)
3285 #endif
3287 void Platform_Finalise(bool fromDllMain) {
3288 #if defined(USE_D2D)
3289 if (!fromDllMain) {
3290 if (defaultRenderingParams) {
3291 defaultRenderingParams->Release();
3292 defaultRenderingParams = 0;
3294 if (customClearTypeRenderingParams) {
3295 customClearTypeRenderingParams->Release();
3296 customClearTypeRenderingParams = 0;
3298 if (pIDWriteFactory) {
3299 pIDWriteFactory->Release();
3300 pIDWriteFactory = 0;
3302 if (pD2DFactory) {
3303 pD2DFactory->Release();
3304 pD2DFactory = 0;
3306 if (hDLLDWrite) {
3307 FreeLibrary(hDLLDWrite);
3308 hDLLDWrite = NULL;
3310 if (hDLLD2D) {
3311 FreeLibrary(hDLLD2D);
3312 hDLLD2D = NULL;
3315 #endif
3316 if (reverseArrowCursor != NULL)
3317 ::DestroyCursor(reverseArrowCursor);
3318 ListBoxX_Unregister();
3319 ::DeleteCriticalSection(&crPlatformLock);
3320 if (hDLLUser32) {
3321 FreeLibrary(hDLLUser32);
3322 hDLLUser32 = NULL;
3324 if (hDLLImage) {
3325 FreeLibrary(hDLLImage);
3326 hDLLImage = NULL;
3330 #ifdef SCI_NAMESPACE
3332 #endif