Synced with Transifex
[TortoiseGit.git] / ext / scintilla / win32 / PlatWin.cxx
blob6d60b4a2e051905e318d25e8918cfb1f9d922bc2
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 <ctype.h>
12 #include <stdarg.h>
13 #include <time.h>
14 #include <math.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 "UniConversion.h"
40 #include "XPM.h"
41 #include "FontQuality.h"
43 #ifndef IDC_HAND
44 #define IDC_HAND MAKEINTRESOURCE(32649)
45 #endif
47 #ifndef SPI_GETFONTSMOOTHINGCONTRAST
48 #define SPI_GETFONTSMOOTHINGCONTRAST 0x200C
49 #endif
51 // Take care of 32/64 bit pointers
52 #ifdef GetWindowLongPtr
53 static void *PointerFromWindow(HWND hWnd) {
54 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
56 static void SetWindowPointer(HWND hWnd, void *ptr) {
57 ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
59 #else
60 static void *PointerFromWindow(HWND hWnd) {
61 return reinterpret_cast<void *>(::GetWindowLong(hWnd, 0));
63 static void SetWindowPointer(HWND hWnd, void *ptr) {
64 ::SetWindowLong(hWnd, 0, reinterpret_cast<LONG>(ptr));
67 #ifndef GWLP_USERDATA
68 #define GWLP_USERDATA GWL_USERDATA
69 #endif
71 #ifndef GWLP_WNDPROC
72 #define GWLP_WNDPROC GWL_WNDPROC
73 #endif
75 #ifndef LONG_PTR
76 #define LONG_PTR LONG
77 #endif
79 static LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong) {
80 return ::SetWindowLong(hWnd, nIndex, dwNewLong);
83 static LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex) {
84 return ::GetWindowLong(hWnd, nIndex);
86 #endif
88 extern UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage);
90 // Declarations needed for functions dynamically loaded as not available on all Windows versions.
91 typedef BOOL (WINAPI *AlphaBlendSig)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
92 typedef HMONITOR (WINAPI *MonitorFromPointSig)(POINT, DWORD);
93 typedef HMONITOR (WINAPI *MonitorFromRectSig)(LPCRECT, DWORD);
94 typedef BOOL (WINAPI *GetMonitorInfoSig)(HMONITOR, LPMONITORINFO);
96 static CRITICAL_SECTION crPlatformLock;
97 static HINSTANCE hinstPlatformRes = 0;
98 static bool onNT = false;
100 static HMODULE hDLLImage = 0;
101 static AlphaBlendSig AlphaBlendFn = 0;
103 static HMODULE hDLLUser32 = 0;
104 static HMONITOR (WINAPI *MonitorFromPointFn)(POINT, DWORD) = 0;
105 static HMONITOR (WINAPI *MonitorFromRectFn)(LPCRECT, DWORD) = 0;
106 static BOOL (WINAPI *GetMonitorInfoFn)(HMONITOR, LPMONITORINFO) = 0;
108 static HCURSOR reverseArrowCursor = NULL;
110 #ifdef SCI_NAMESPACE
111 namespace Scintilla {
112 #endif
114 bool IsNT() {
115 return onNT;
118 Point Point::FromLong(long lpoint) {
119 return Point(static_cast<short>(LOWORD(lpoint)), static_cast<short>(HIWORD(lpoint)));
122 static RECT RectFromPRectangle(PRectangle prc) {
123 RECT rc = {static_cast<LONG>(prc.left), static_cast<LONG>(prc.top),
124 static_cast<LONG>(prc.right), static_cast<LONG>(prc.bottom)};
125 return rc;
128 #if defined(USE_D2D)
129 IDWriteFactory *pIDWriteFactory = 0;
130 ID2D1Factory *pD2DFactory = 0;
131 IDWriteRenderingParams *defaultRenderingParams = 0;
132 IDWriteRenderingParams *customClearTypeRenderingParams = 0;
134 bool LoadD2D() {
135 static bool triedLoadingD2D = false;
136 if (!triedLoadingD2D) {
137 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
138 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
139 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
140 IUnknown **factory);
142 HMODULE hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
143 if (hDLLD2D) {
144 D2D1CFSig fnD2DCF = (D2D1CFSig)::GetProcAddress(hDLLD2D, "D2D1CreateFactory");
145 if (fnD2DCF) {
146 // A single threaded factory as Scintilla always draw on the GUI thread
147 fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
148 __uuidof(ID2D1Factory),
150 reinterpret_cast<IUnknown**>(&pD2DFactory));
153 HMODULE hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
154 if (hDLLDWrite) {
155 DWriteCFSig fnDWCF = (DWriteCFSig)::GetProcAddress(hDLLDWrite, "DWriteCreateFactory");
156 if (fnDWCF) {
157 fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
158 __uuidof(IDWriteFactory),
159 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
163 if (pIDWriteFactory) {
164 HRESULT hr = pIDWriteFactory->CreateRenderingParams(&defaultRenderingParams);
165 if (SUCCEEDED(hr)) {
166 unsigned int clearTypeContrast;
167 ::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0);
169 FLOAT gamma;
170 if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200)
171 gamma = static_cast<FLOAT>(clearTypeContrast) / 1000.0f;
172 else
173 gamma = defaultRenderingParams->GetGamma();
175 pIDWriteFactory->CreateCustomRenderingParams(gamma, defaultRenderingParams->GetEnhancedContrast(), defaultRenderingParams->GetClearTypeLevel(),
176 defaultRenderingParams->GetPixelGeometry(), defaultRenderingParams->GetRenderingMode(), &customClearTypeRenderingParams);
181 triedLoadingD2D = true;
182 return pIDWriteFactory && pD2DFactory;
184 #endif
186 struct FormatAndMetrics {
187 int technology;
188 HFONT hfont;
189 #if defined(USE_D2D)
190 IDWriteTextFormat *pTextFormat;
191 #endif
192 int extraFontFlag;
193 int characterSet;
194 FLOAT yAscent;
195 FLOAT yDescent;
196 FLOAT yInternalLeading;
197 FormatAndMetrics(HFONT hfont_, int extraFontFlag_, int characterSet_) :
198 technology(SCWIN_TECH_GDI), hfont(hfont_),
199 #if defined(USE_D2D)
200 pTextFormat(0),
201 #endif
202 extraFontFlag(extraFontFlag_), characterSet(characterSet_), yAscent(2), yDescent(1), yInternalLeading(0) {
204 #if defined(USE_D2D)
205 FormatAndMetrics(IDWriteTextFormat *pTextFormat_,
206 int extraFontFlag_,
207 int characterSet_,
208 FLOAT yAscent_,
209 FLOAT yDescent_,
210 FLOAT yInternalLeading_) :
211 technology(SCWIN_TECH_DIRECTWRITE),
212 hfont(0),
213 pTextFormat(pTextFormat_),
214 extraFontFlag(extraFontFlag_),
215 characterSet(characterSet_),
216 yAscent(yAscent_),
217 yDescent(yDescent_),
218 yInternalLeading(yInternalLeading_) {
220 #endif
221 ~FormatAndMetrics() {
222 if (hfont)
223 ::DeleteObject(hfont);
224 #if defined(USE_D2D)
225 if (pTextFormat)
226 pTextFormat->Release();
227 pTextFormat = 0;
228 #endif
229 extraFontFlag = 0;
230 characterSet = 0;
231 yAscent = 2;
232 yDescent = 1;
233 yInternalLeading = 0;
235 HFONT HFont();
238 HFONT FormatAndMetrics::HFont() {
239 LOGFONTW lf = {};
240 #if defined(USE_D2D)
241 if (technology == SCWIN_TECH_GDI) {
242 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
243 return 0;
245 } else {
246 HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
247 if (!SUCCEEDED(hr)) {
248 return 0;
250 lf.lfWeight = pTextFormat->GetFontWeight();
251 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
252 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
254 #else
255 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
256 return 0;
258 #endif
259 return ::CreateFontIndirectW(&lf);
262 #ifndef CLEARTYPE_QUALITY
263 #define CLEARTYPE_QUALITY 5
264 #endif
266 static BYTE Win32MapFontQuality(int extraFontFlag) {
267 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
269 case SC_EFF_QUALITY_NON_ANTIALIASED:
270 return NONANTIALIASED_QUALITY;
272 case SC_EFF_QUALITY_ANTIALIASED:
273 return ANTIALIASED_QUALITY;
275 case SC_EFF_QUALITY_LCD_OPTIMIZED:
276 return CLEARTYPE_QUALITY;
278 default:
279 return SC_EFF_QUALITY_DEFAULT;
283 #if defined(USE_D2D)
284 static D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) {
285 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
287 case SC_EFF_QUALITY_NON_ANTIALIASED:
288 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
290 case SC_EFF_QUALITY_ANTIALIASED:
291 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
293 case SC_EFF_QUALITY_LCD_OPTIMIZED:
294 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
296 default:
297 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
300 #endif
302 static void SetLogFont(LOGFONTA &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) {
303 memset(&lf, 0, sizeof(lf));
304 // The negative is to allow for leading
305 lf.lfHeight = -(abs(static_cast<int>(size + 0.5)));
306 lf.lfWeight = weight;
307 lf.lfItalic = static_cast<BYTE>(italic ? 1 : 0);
308 lf.lfCharSet = static_cast<BYTE>(characterSet);
309 lf.lfQuality = Win32MapFontQuality(extraFontFlag);
310 strncpy(lf.lfFaceName, faceName, sizeof(lf.lfFaceName));
311 lf.lfFaceName[sizeof(lf.lfFaceName)-1] = '\0';
315 * Create a hash from the parameters for a font to allow easy checking for identity.
316 * If one font is the same as another, its hash will be the same, but if the hash is the
317 * same then they may still be different.
319 static int HashFont(const FontParameters &fp) {
320 return
321 static_cast<int>(fp.size) ^
322 (fp.characterSet << 10) ^
323 ((fp.extraFontFlag & SC_EFF_QUALITY_MASK) << 9) ^
324 ((fp.weight/100) << 12) ^
325 (fp.italic ? 0x20000000 : 0) ^
326 (fp.technology << 15) ^
327 fp.faceName[0];
330 class FontCached : Font {
331 FontCached *next;
332 int usage;
333 float size;
334 LOGFONTA lf;
335 int technology;
336 int hash;
337 FontCached(const FontParameters &fp);
338 ~FontCached() {}
339 bool SameAs(const FontParameters &fp);
340 virtual void Release();
342 static FontCached *first;
343 public:
344 static FontID FindOrCreate(const FontParameters &fp);
345 static void ReleaseId(FontID fid_);
348 FontCached *FontCached::first = 0;
350 FontCached::FontCached(const FontParameters &fp) :
351 next(0), usage(0), size(1.0), hash(0) {
352 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
353 technology = fp.technology;
354 hash = HashFont(fp);
355 fid = 0;
356 if (technology == SCWIN_TECH_GDI) {
357 HFONT hfont = ::CreateFontIndirectA(&lf);
358 fid = reinterpret_cast<void *>(new FormatAndMetrics(hfont, fp.extraFontFlag, fp.characterSet));
359 } else {
360 #if defined(USE_D2D)
361 IDWriteTextFormat *pTextFormat;
362 const int faceSize = 200;
363 WCHAR wszFace[faceSize];
364 UTF16FromUTF8(fp.faceName, static_cast<unsigned int>(strlen(fp.faceName))+1, wszFace, faceSize);
365 FLOAT fHeight = fp.size;
366 DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
367 HRESULT hr = pIDWriteFactory->CreateTextFormat(wszFace, NULL,
368 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
369 style,
370 DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
371 if (SUCCEEDED(hr)) {
372 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
374 const int maxLines = 2;
375 DWRITE_LINE_METRICS lineMetrics[maxLines];
376 UINT32 lineCount = 0;
377 FLOAT yAscent = 1.0f;
378 FLOAT yDescent = 1.0f;
379 FLOAT yInternalLeading = 0.0f;
380 IDWriteTextLayout *pTextLayout = 0;
381 hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
382 100.0f, 100.0f, &pTextLayout);
383 if (SUCCEEDED(hr)) {
384 hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
385 if (SUCCEEDED(hr)) {
386 yAscent = lineMetrics[0].baseline;
387 yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
389 FLOAT emHeight;
390 hr = pTextLayout->GetFontSize(0, &emHeight);
391 if (SUCCEEDED(hr)) {
392 yInternalLeading = lineMetrics[0].height - emHeight;
395 pTextLayout->Release();
396 pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline);
398 fid = reinterpret_cast<void *>(new FormatAndMetrics(pTextFormat, fp.extraFontFlag, fp.characterSet, yAscent, yDescent, yInternalLeading));
400 #endif
402 usage = 1;
405 bool FontCached::SameAs(const FontParameters &fp) {
406 return
407 (size == fp.size) &&
408 (lf.lfWeight == fp.weight) &&
409 (lf.lfItalic == static_cast<BYTE>(fp.italic ? 1 : 0)) &&
410 (lf.lfCharSet == fp.characterSet) &&
411 (lf.lfQuality == Win32MapFontQuality(fp.extraFontFlag)) &&
412 (technology == fp.technology) &&
413 0 == strcmp(lf.lfFaceName,fp.faceName);
416 void FontCached::Release() {
417 delete reinterpret_cast<FormatAndMetrics *>(fid);
418 fid = 0;
421 FontID FontCached::FindOrCreate(const FontParameters &fp) {
422 FontID ret = 0;
423 ::EnterCriticalSection(&crPlatformLock);
424 int hashFind = HashFont(fp);
425 for (FontCached *cur=first; cur; cur=cur->next) {
426 if ((cur->hash == hashFind) &&
427 cur->SameAs(fp)) {
428 cur->usage++;
429 ret = cur->fid;
432 if (ret == 0) {
433 FontCached *fc = new FontCached(fp);
434 fc->next = first;
435 first = fc;
436 ret = fc->fid;
438 ::LeaveCriticalSection(&crPlatformLock);
439 return ret;
442 void FontCached::ReleaseId(FontID fid_) {
443 ::EnterCriticalSection(&crPlatformLock);
444 FontCached **pcur=&first;
445 for (FontCached *cur=first; cur; cur=cur->next) {
446 if (cur->fid == fid_) {
447 cur->usage--;
448 if (cur->usage == 0) {
449 *pcur = cur->next;
450 cur->Release();
451 cur->next = 0;
452 delete cur;
454 break;
456 pcur=&cur->next;
458 ::LeaveCriticalSection(&crPlatformLock);
461 Font::Font() {
462 fid = 0;
465 Font::~Font() {
468 #define FONTS_CACHED
470 void Font::Create(const FontParameters &fp) {
471 Release();
472 if (fp.faceName)
473 fid = FontCached::FindOrCreate(fp);
476 void Font::Release() {
477 if (fid)
478 FontCached::ReleaseId(fid);
479 fid = 0;
482 // Buffer to hold strings and string position arrays without always allocating on heap.
483 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
484 // when less than safe size otherwise allocate on heap and free automatically.
485 template<typename T, int lengthStandard>
486 class VarBuffer {
487 T bufferStandard[lengthStandard];
488 // Private so VarBuffer objects can not be copied
489 VarBuffer(const VarBuffer &);
490 VarBuffer &operator=(const VarBuffer &);
491 public:
492 T *buffer;
493 VarBuffer(size_t length) : buffer(0) {
494 if (length > lengthStandard) {
495 buffer = new T[length];
496 } else {
497 buffer = bufferStandard;
500 ~VarBuffer() {
501 if (buffer != bufferStandard) {
502 delete []buffer;
503 buffer = 0;
508 const int stackBufferLength = 10000;
509 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
510 public:
511 int tlen;
512 TextWide(const char *s, int len, bool unicodeMode, int codePage=0) :
513 VarBuffer<wchar_t, stackBufferLength>(len) {
514 if (unicodeMode) {
515 tlen = UTF16FromUTF8(s, len, buffer, len);
516 } else {
517 // Support Asian string display in 9x English
518 tlen = ::MultiByteToWideChar(codePage, 0, s, len, buffer, len);
522 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
524 class SurfaceGDI : public Surface {
525 bool unicodeMode;
526 HDC hdc;
527 bool hdcOwned;
528 HPEN pen;
529 HPEN penOld;
530 HBRUSH brush;
531 HBRUSH brushOld;
532 HFONT font;
533 HFONT fontOld;
534 HBITMAP bitmap;
535 HBITMAP bitmapOld;
536 int maxWidthMeasure;
537 int maxLenText;
539 int codePage;
540 // If 9x OS and current code page is same as ANSI code page.
541 bool win9xACPSame;
543 void BrushColor(ColourDesired back);
544 void SetFont(Font &font_);
546 // Private so SurfaceGDI objects can not be copied
547 SurfaceGDI(const SurfaceGDI &);
548 SurfaceGDI &operator=(const SurfaceGDI &);
549 public:
550 SurfaceGDI();
551 virtual ~SurfaceGDI();
553 void Init(WindowID wid);
554 void Init(SurfaceID sid, WindowID wid);
555 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
557 void Release();
558 bool Initialised();
559 void PenColour(ColourDesired fore);
560 int LogPixelsY();
561 int DeviceHeightFont(int points);
562 void MoveTo(int x_, int y_);
563 void LineTo(int x_, int y_);
564 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
565 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
566 void FillRectangle(PRectangle rc, ColourDesired back);
567 void FillRectangle(PRectangle rc, Surface &surfacePattern);
568 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
569 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
570 ColourDesired outline, int alphaOutline, int flags);
571 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
572 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
573 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
575 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
576 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
577 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
578 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
579 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
580 XYPOSITION WidthText(Font &font_, const char *s, int len);
581 XYPOSITION WidthChar(Font &font_, char ch);
582 XYPOSITION Ascent(Font &font_);
583 XYPOSITION Descent(Font &font_);
584 XYPOSITION InternalLeading(Font &font_);
585 XYPOSITION ExternalLeading(Font &font_);
586 XYPOSITION Height(Font &font_);
587 XYPOSITION AverageCharWidth(Font &font_);
589 void SetClip(PRectangle rc);
590 void FlushCachedState();
592 void SetUnicodeMode(bool unicodeMode_);
593 void SetDBCSMode(int codePage_);
596 SurfaceGDI::SurfaceGDI() :
597 unicodeMode(false),
598 hdc(0), hdcOwned(false),
599 pen(0), penOld(0),
600 brush(0), brushOld(0),
601 font(0), fontOld(0),
602 bitmap(0), bitmapOld(0) {
603 // Windows 9x has only a 16 bit coordinate system so break after 30000 pixels
604 maxWidthMeasure = IsNT() ? INT_MAX : 30000;
605 // There appears to be a 16 bit string length limit in GDI on NT and a limit of
606 // 8192 characters on Windows 95.
607 maxLenText = IsNT() ? 65535 : 8192;
609 codePage = 0;
610 win9xACPSame = false;
613 SurfaceGDI::~SurfaceGDI() {
614 Release();
617 void SurfaceGDI::Release() {
618 if (penOld) {
619 ::SelectObject(reinterpret_cast<HDC>(hdc), penOld);
620 ::DeleteObject(pen);
621 penOld = 0;
623 pen = 0;
624 if (brushOld) {
625 ::SelectObject(reinterpret_cast<HDC>(hdc), brushOld);
626 ::DeleteObject(brush);
627 brushOld = 0;
629 brush = 0;
630 if (fontOld) {
631 // Fonts are not deleted as they are owned by a Font object
632 ::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);
633 fontOld = 0;
635 font = 0;
636 if (bitmapOld) {
637 ::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);
638 ::DeleteObject(bitmap);
639 bitmapOld = 0;
641 bitmap = 0;
642 if (hdcOwned) {
643 ::DeleteDC(reinterpret_cast<HDC>(hdc));
644 hdc = 0;
645 hdcOwned = false;
649 bool SurfaceGDI::Initialised() {
650 return hdc != 0;
653 void SurfaceGDI::Init(WindowID) {
654 Release();
655 hdc = ::CreateCompatibleDC(NULL);
656 hdcOwned = true;
657 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
660 void SurfaceGDI::Init(SurfaceID sid, WindowID) {
661 Release();
662 hdc = reinterpret_cast<HDC>(sid);
663 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
666 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID) {
667 Release();
668 hdc = ::CreateCompatibleDC(static_cast<SurfaceGDI *>(surface_)->hdc);
669 hdcOwned = true;
670 bitmap = ::CreateCompatibleBitmap(static_cast<SurfaceGDI *>(surface_)->hdc, width, height);
671 bitmapOld = static_cast<HBITMAP>(::SelectObject(hdc, bitmap));
672 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
675 void SurfaceGDI::PenColour(ColourDesired fore) {
676 if (pen) {
677 ::SelectObject(hdc, penOld);
678 ::DeleteObject(pen);
679 pen = 0;
680 penOld = 0;
682 pen = ::CreatePen(0,1,fore.AsLong());
683 penOld = static_cast<HPEN>(::SelectObject(reinterpret_cast<HDC>(hdc), pen));
686 void SurfaceGDI::BrushColor(ColourDesired back) {
687 if (brush) {
688 ::SelectObject(hdc, brushOld);
689 ::DeleteObject(brush);
690 brush = 0;
691 brushOld = 0;
693 // Only ever want pure, non-dithered brushes
694 ColourDesired colourNearest = ::GetNearestColor(hdc, back.AsLong());
695 brush = ::CreateSolidBrush(colourNearest.AsLong());
696 brushOld = static_cast<HBRUSH>(::SelectObject(hdc, brush));
699 void SurfaceGDI::SetFont(Font &font_) {
700 if (font_.GetID() != font) {
701 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
702 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI);
703 if (fontOld) {
704 ::SelectObject(hdc, pfm->hfont);
705 } else {
706 fontOld = static_cast<HFONT>(::SelectObject(hdc, pfm->hfont));
708 font = reinterpret_cast<HFONT>(pfm->hfont);
712 int SurfaceGDI::LogPixelsY() {
713 return ::GetDeviceCaps(hdc, LOGPIXELSY);
716 int SurfaceGDI::DeviceHeightFont(int points) {
717 return ::MulDiv(points, LogPixelsY(), 72);
720 void SurfaceGDI::MoveTo(int x_, int y_) {
721 ::MoveToEx(hdc, x_, y_, 0);
724 void SurfaceGDI::LineTo(int x_, int y_) {
725 ::LineTo(hdc, x_, y_);
728 void SurfaceGDI::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
729 PenColour(fore);
730 BrushColor(back);
731 std::vector<POINT> outline;
732 for (int i=0;i<npts;i++) {
733 POINT pt = {static_cast<LONG>(pts[i].x), static_cast<LONG>(pts[i].y)};
734 outline.push_back(pt);
736 ::Polygon(hdc, &outline[0], npts);
739 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
740 PenColour(fore);
741 BrushColor(back);
742 ::Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
745 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) {
746 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
747 // There is no need to allocate a brush either.
748 RECT rcw = RectFromPRectangle(rc);
749 ::SetBkColor(hdc, back.AsLong());
750 ::ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rcw, TEXT(""), 0, NULL);
753 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
754 HBRUSH br;
755 if (static_cast<SurfaceGDI &>(surfacePattern).bitmap)
756 br = ::CreatePatternBrush(static_cast<SurfaceGDI &>(surfacePattern).bitmap);
757 else // Something is wrong so display in red
758 br = ::CreateSolidBrush(RGB(0xff, 0, 0));
759 RECT rcw = RectFromPRectangle(rc);
760 ::FillRect(hdc, &rcw, br);
761 ::DeleteObject(br);
764 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
765 PenColour(fore);
766 BrushColor(back);
767 ::RoundRect(hdc,
768 rc.left + 1, rc.top,
769 rc.right - 1, rc.bottom,
770 8, 8);
773 // Plot a point into a DWORD buffer symetrically to all 4 qudrants
774 static void AllFour(DWORD *pixels, int width, int height, int x, int y, DWORD val) {
775 pixels[y*width+x] = val;
776 pixels[y*width+width-1-x] = val;
777 pixels[(height-1-y)*width+x] = val;
778 pixels[(height-1-y)*width+width-1-x] = val;
781 #ifndef AC_SRC_OVER
782 #define AC_SRC_OVER 0x00
783 #endif
784 #ifndef AC_SRC_ALPHA
785 #define AC_SRC_ALPHA 0x01
786 #endif
788 static DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) {
789 union {
790 byte pixVal[4];
791 DWORD val;
792 } converter;
793 converter.pixVal[0] = b;
794 converter.pixVal[1] = g;
795 converter.pixVal[2] = r;
796 converter.pixVal[3] = a;
797 return converter.val;
800 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
801 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
802 if (AlphaBlendFn && rc.Width() > 0) {
803 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
804 int width = rc.Width();
805 int height = rc.Height();
806 // Ensure not distorted too much by corners when small
807 cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
808 BITMAPINFO bpih = {{sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}};
809 void *image = 0;
810 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
811 DIB_RGB_COLORS, &image, NULL, 0);
813 if (hbmMem) {
814 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
816 DWORD valEmpty = dwordFromBGRA(0,0,0,0);
817 DWORD valFill = dwordFromBGRA(
818 static_cast<byte>(GetBValue(fill.AsLong()) * alphaFill / 255),
819 static_cast<byte>(GetGValue(fill.AsLong()) * alphaFill / 255),
820 static_cast<byte>(GetRValue(fill.AsLong()) * alphaFill / 255),
821 static_cast<byte>(alphaFill));
822 DWORD valOutline = dwordFromBGRA(
823 static_cast<byte>(GetBValue(outline.AsLong()) * alphaOutline / 255),
824 static_cast<byte>(GetGValue(outline.AsLong()) * alphaOutline / 255),
825 static_cast<byte>(GetRValue(outline.AsLong()) * alphaOutline / 255),
826 static_cast<byte>(alphaOutline));
827 DWORD *pixels = reinterpret_cast<DWORD *>(image);
828 for (int y=0; y<height; y++) {
829 for (int x=0; x<width; x++) {
830 if ((x==0) || (x==width-1) || (y == 0) || (y == height-1)) {
831 pixels[y*width+x] = valOutline;
832 } else {
833 pixels[y*width+x] = valFill;
837 for (int c=0;c<cornerSize; c++) {
838 for (int x=0;x<c+1; x++) {
839 AllFour(pixels, width, height, x, c-x, valEmpty);
842 for (int x=1;x<cornerSize; x++) {
843 AllFour(pixels, width, height, x, cornerSize-x, valOutline);
846 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
848 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, width, height, hMemDC, 0, 0, width, height, merge);
850 SelectBitmap(hMemDC, hbmOld);
851 ::DeleteObject(hbmMem);
853 ::DeleteDC(hMemDC);
854 } else {
855 BrushColor(outline);
856 RECT rcw = RectFromPRectangle(rc);
857 FrameRect(hdc, &rcw, brush);
861 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
862 if (AlphaBlendFn && rc.Width() > 0) {
863 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
864 if (rc.Width() > width)
865 rc.left += static_cast<int>((rc.Width() - width) / 2);
866 rc.right = rc.left + width;
867 if (rc.Height() > height)
868 rc.top += static_cast<int>((rc.Height() - height) / 2);
869 rc.bottom = rc.top + height;
871 BITMAPINFO bpih = {{sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0}};
872 unsigned char *image = 0;
873 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
874 DIB_RGB_COLORS, reinterpret_cast<void **>(&image), NULL, 0);
875 if (hbmMem) {
876 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
878 for (int y=height-1; y>=0; y--) {
879 for (int x=0; x<width; x++) {
880 unsigned char *pixel = image + (y*width+x) * 4;
881 unsigned char alpha = pixelsImage[3];
882 // Input is RGBA, output is BGRA with premultiplied alpha
883 pixel[2] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
884 pixel[1] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
885 pixel[0] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
886 pixel[3] = static_cast<unsigned char>(*pixelsImage++);
890 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
892 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, rc.Width(), rc.Height(), hMemDC, 0, 0, width, height, merge);
894 SelectBitmap(hMemDC, hbmOld);
895 ::DeleteObject(hbmMem);
897 ::DeleteDC(hMemDC);
902 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
903 PenColour(fore);
904 BrushColor(back);
905 ::Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);
908 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
909 ::BitBlt(hdc,
910 rc.left, rc.top, rc.Width(), rc.Height(),
911 static_cast<SurfaceGDI &>(surfaceSource).hdc, from.x, from.y, SRCCOPY);
914 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
916 void SurfaceGDI::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
917 SetFont(font_);
918 RECT rcw = RectFromPRectangle(rc);
919 SIZE sz={0,0};
920 int pos = 0;
921 int x = rc.left;
923 // Text drawing may fail if the text is too big.
924 // If it does fail, slice up into segments and draw each segment.
925 const int maxSegmentLength = 0x200;
927 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
928 // Use ANSI calls
929 int lenDraw = Platform::Minimum(len, maxLenText);
930 if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s, lenDraw, NULL)) {
931 while (lenDraw > pos) {
932 int seglen = Platform::Minimum(maxSegmentLength, lenDraw - pos);
933 if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s+pos, seglen, NULL)) {
934 PLATFORM_ASSERT(false);
935 return;
937 ::GetTextExtentPoint32A(hdc, s+pos, seglen, &sz);
938 x += sz.cx;
939 pos += seglen;
942 } else {
943 // Use Unicode calls
944 const TextWide tbuf(s, len, unicodeMode, codePage);
945 if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, NULL)) {
946 while (tbuf.tlen > pos) {
947 int seglen = Platform::Minimum(maxSegmentLength, tbuf.tlen - pos);
948 if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf.buffer+pos, seglen, NULL)) {
949 PLATFORM_ASSERT(false);
950 return;
952 ::GetTextExtentPoint32W(hdc, tbuf.buffer+pos, seglen, &sz);
953 x += sz.cx;
954 pos += seglen;
960 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
961 ColourDesired fore, ColourDesired back) {
962 ::SetTextColor(hdc, fore.AsLong());
963 ::SetBkColor(hdc, back.AsLong());
964 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
967 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
968 ColourDesired fore, ColourDesired back) {
969 ::SetTextColor(hdc, fore.AsLong());
970 ::SetBkColor(hdc, back.AsLong());
971 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
974 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
975 ColourDesired fore) {
976 // Avoid drawing spaces in transparent mode
977 for (int i=0;i<len;i++) {
978 if (s[i] != ' ') {
979 ::SetTextColor(hdc, fore.AsLong());
980 ::SetBkMode(hdc, TRANSPARENT);
981 DrawTextCommon(rc, font_, ybase, s, len, 0);
982 ::SetBkMode(hdc, OPAQUE);
983 return;
988 XYPOSITION SurfaceGDI::WidthText(Font &font_, const char *s, int len) {
989 SetFont(font_);
990 SIZE sz={0,0};
991 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
992 ::GetTextExtentPoint32A(hdc, s, Platform::Minimum(len, maxLenText), &sz);
993 } else {
994 const TextWide tbuf(s, len, unicodeMode, codePage);
995 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
997 return sz.cx;
1000 void SurfaceGDI::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
1001 SetFont(font_);
1002 SIZE sz={0,0};
1003 int fit = 0;
1004 if (unicodeMode) {
1005 const TextWide tbuf(s, len, unicodeMode, codePage);
1006 TextPositionsI poses(tbuf.tlen);
1007 fit = tbuf.tlen;
1008 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1009 // Likely to have failed because on Windows 9x where function not available
1010 // So measure the character widths by measuring each initial substring
1011 // Turns a linear operation into a qudratic but seems fast enough on test files
1012 for (int widthSS=0; widthSS < tbuf.tlen; widthSS++) {
1013 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
1014 poses.buffer[widthSS] = sz.cx;
1017 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1018 int ui=0;
1019 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1020 int i=0;
1021 while (ui<fit) {
1022 unsigned char uch = us[i];
1023 unsigned int lenChar = 1;
1024 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1025 lenChar = 4;
1026 ui++;
1027 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1028 lenChar = 3;
1029 } else if (uch >= (0x80)) {
1030 lenChar = 2;
1032 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1033 positions[i++] = poses.buffer[ui];
1035 ui++;
1037 int lastPos = 0;
1038 if (i > 0)
1039 lastPos = positions[i-1];
1040 while (i<len) {
1041 positions[i++] = lastPos;
1043 } else if (IsNT() || (codePage==0) || win9xACPSame) {
1044 // Zero positions to avoid random behaviour on failure.
1045 memset(positions, 0, len * sizeof(*positions));
1046 // len may be larger than platform supports so loop over segments small enough for platform
1047 int startOffset = 0;
1048 while (len > 0) {
1049 int lenBlock = Platform::Minimum(len, maxLenText);
1050 TextPositionsI poses(len);
1051 if (!::GetTextExtentExPointA(hdc, s, lenBlock, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1052 // Eeek - a NULL DC or other foolishness could cause this.
1053 return;
1054 } else if (fit < lenBlock) {
1055 // For some reason, such as an incomplete DBCS character
1056 // Not all the positions are filled in so make them equal to end.
1057 if (fit == 0)
1058 poses.buffer[fit++] = 0;
1059 for (int i = fit;i<lenBlock;i++)
1060 poses.buffer[i] = poses.buffer[fit-1];
1062 for (int i=0;i<lenBlock;i++)
1063 positions[i] = poses.buffer[i] + startOffset;
1064 startOffset = poses.buffer[lenBlock-1];
1065 len -= lenBlock;
1066 positions += lenBlock;
1067 s += lenBlock;
1069 } else {
1070 // Support Asian string display in 9x English
1071 const TextWide tbuf(s, len, unicodeMode, codePage);
1072 TextPositionsI poses(tbuf.tlen);
1073 for (int widthSS=0; widthSS<tbuf.tlen; widthSS++) {
1074 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
1075 poses.buffer[widthSS] = sz.cx;
1078 int ui = 0;
1079 for (int i=0;i<len;) {
1080 if (Platform::IsDBCSLeadByte(codePage, s[i])) {
1081 positions[i] = poses.buffer[ui];
1082 positions[i+1] = poses.buffer[ui];
1083 i += 2;
1084 } else {
1085 positions[i] = poses.buffer[ui];
1086 i++;
1089 ui++;
1094 XYPOSITION SurfaceGDI::WidthChar(Font &font_, char ch) {
1095 SetFont(font_);
1096 SIZE sz;
1097 ::GetTextExtentPoint32A(hdc, &ch, 1, &sz);
1098 return sz.cx;
1101 XYPOSITION SurfaceGDI::Ascent(Font &font_) {
1102 SetFont(font_);
1103 TEXTMETRIC tm;
1104 ::GetTextMetrics(hdc, &tm);
1105 return tm.tmAscent;
1108 XYPOSITION SurfaceGDI::Descent(Font &font_) {
1109 SetFont(font_);
1110 TEXTMETRIC tm;
1111 ::GetTextMetrics(hdc, &tm);
1112 return tm.tmDescent;
1115 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) {
1116 SetFont(font_);
1117 TEXTMETRIC tm;
1118 ::GetTextMetrics(hdc, &tm);
1119 return tm.tmInternalLeading;
1122 XYPOSITION SurfaceGDI::ExternalLeading(Font &font_) {
1123 SetFont(font_);
1124 TEXTMETRIC tm;
1125 ::GetTextMetrics(hdc, &tm);
1126 return tm.tmExternalLeading;
1129 XYPOSITION SurfaceGDI::Height(Font &font_) {
1130 SetFont(font_);
1131 TEXTMETRIC tm;
1132 ::GetTextMetrics(hdc, &tm);
1133 return tm.tmHeight;
1136 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) {
1137 SetFont(font_);
1138 TEXTMETRIC tm;
1139 ::GetTextMetrics(hdc, &tm);
1140 return tm.tmAveCharWidth;
1143 void SurfaceGDI::SetClip(PRectangle rc) {
1144 ::IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
1147 void SurfaceGDI::FlushCachedState() {
1148 pen = 0;
1149 brush = 0;
1150 font = 0;
1153 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1154 unicodeMode=unicodeMode_;
1157 void SurfaceGDI::SetDBCSMode(int codePage_) {
1158 // No action on window as automatically handled by system.
1159 codePage = codePage_;
1160 win9xACPSame = !IsNT() && ((unsigned int)codePage == ::GetACP());
1163 #if defined(USE_D2D)
1165 class SurfaceD2D : public Surface {
1166 bool unicodeMode;
1167 int x, y;
1169 int codePage;
1170 int codePageText;
1172 ID2D1RenderTarget *pRenderTarget;
1173 bool ownRenderTarget;
1174 int clipsActive;
1176 IDWriteTextFormat *pTextFormat;
1177 FLOAT yAscent;
1178 FLOAT yDescent;
1179 FLOAT yInternalLeading;
1181 ID2D1SolidColorBrush *pBrush;
1183 int logPixelsY;
1184 float dpiScaleX;
1185 float dpiScaleY;
1187 void SetFont(Font &font_);
1189 // Private so SurfaceD2D objects can not be copied
1190 SurfaceD2D(const SurfaceD2D &);
1191 SurfaceD2D &operator=(const SurfaceD2D &);
1192 public:
1193 SurfaceD2D();
1194 virtual ~SurfaceD2D();
1196 void SetScale();
1197 void Init(WindowID wid);
1198 void Init(SurfaceID sid, WindowID wid);
1199 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
1201 void Release();
1202 bool Initialised();
1204 HRESULT FlushDrawing();
1206 void PenColour(ColourDesired fore);
1207 void D2DPenColour(ColourDesired fore, int alpha=255);
1208 int LogPixelsY();
1209 int DeviceHeightFont(int points);
1210 void MoveTo(int x_, int y_);
1211 void LineTo(int x_, int y_);
1212 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
1213 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
1214 void FillRectangle(PRectangle rc, ColourDesired back);
1215 void FillRectangle(PRectangle rc, Surface &surfacePattern);
1216 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
1217 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1218 ColourDesired outline, int alphaOutline, int flags);
1219 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
1220 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
1221 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
1223 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
1224 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1225 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1226 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
1227 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
1228 XYPOSITION WidthText(Font &font_, const char *s, int len);
1229 XYPOSITION WidthChar(Font &font_, char ch);
1230 XYPOSITION Ascent(Font &font_);
1231 XYPOSITION Descent(Font &font_);
1232 XYPOSITION InternalLeading(Font &font_);
1233 XYPOSITION ExternalLeading(Font &font_);
1234 XYPOSITION Height(Font &font_);
1235 XYPOSITION AverageCharWidth(Font &font_);
1237 void SetClip(PRectangle rc);
1238 void FlushCachedState();
1240 void SetUnicodeMode(bool unicodeMode_);
1241 void SetDBCSMode(int codePage_);
1244 SurfaceD2D::SurfaceD2D() :
1245 unicodeMode(false),
1246 x(0), y(0) {
1248 codePage = 0;
1249 codePageText = 0;
1251 pRenderTarget = NULL;
1252 ownRenderTarget = false;
1253 clipsActive = 0;
1255 // From selected font
1256 pTextFormat = NULL;
1257 yAscent = 2;
1258 yDescent = 1;
1259 yInternalLeading = 0;
1261 pBrush = NULL;
1263 logPixelsY = 72;
1264 dpiScaleX = 1.0;
1265 dpiScaleY = 1.0;
1268 SurfaceD2D::~SurfaceD2D() {
1269 Release();
1272 void SurfaceD2D::Release() {
1273 if (pBrush) {
1274 pBrush->Release();
1275 pBrush = 0;
1277 if (pRenderTarget) {
1278 while (clipsActive) {
1279 pRenderTarget->PopAxisAlignedClip();
1280 clipsActive--;
1282 if (ownRenderTarget) {
1283 pRenderTarget->Release();
1285 pRenderTarget = 0;
1289 void SurfaceD2D::SetScale() {
1290 HDC hdcMeasure = ::CreateCompatibleDC(NULL);
1291 logPixelsY = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
1292 dpiScaleX = ::GetDeviceCaps(hdcMeasure, LOGPIXELSX) / 96.0f;
1293 dpiScaleY = logPixelsY / 96.0f;
1294 ::DeleteDC(hdcMeasure);
1297 bool SurfaceD2D::Initialised() {
1298 return pRenderTarget != 0;
1301 HRESULT SurfaceD2D::FlushDrawing() {
1302 return pRenderTarget->Flush();
1305 void SurfaceD2D::Init(WindowID /* wid */) {
1306 Release();
1307 SetScale();
1310 void SurfaceD2D::Init(SurfaceID sid, WindowID) {
1311 Release();
1312 SetScale();
1313 pRenderTarget = reinterpret_cast<ID2D1RenderTarget *>(sid);
1316 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID) {
1317 Release();
1318 SetScale();
1319 SurfaceD2D *psurfOther = static_cast<SurfaceD2D *>(surface_);
1320 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = NULL;
1321 D2D1_SIZE_F desiredSize = D2D1::SizeF(width, height);
1322 D2D1_PIXEL_FORMAT desiredFormat;
1323 #ifdef __MINGW32__
1324 desiredFormat.format = DXGI_FORMAT_UNKNOWN;
1325 #else
1326 desiredFormat = psurfOther->pRenderTarget->GetPixelFormat();
1327 #endif
1328 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1329 HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget(
1330 &desiredSize, NULL, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pCompatibleRenderTarget);
1331 if (SUCCEEDED(hr)) {
1332 pRenderTarget = pCompatibleRenderTarget;
1333 pRenderTarget->BeginDraw();
1334 ownRenderTarget = true;
1338 void SurfaceD2D::PenColour(ColourDesired fore) {
1339 D2DPenColour(fore);
1342 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) {
1343 if (pRenderTarget) {
1344 D2D_COLOR_F col;
1345 col.r = (fore.AsLong() & 0xff) / 255.0;
1346 col.g = ((fore.AsLong() & 0xff00) >> 8) / 255.0;
1347 col.b = (fore.AsLong() >> 16) / 255.0;
1348 col.a = alpha / 255.0;
1349 if (pBrush) {
1350 pBrush->SetColor(col);
1351 } else {
1352 HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1353 if (!SUCCEEDED(hr) && pBrush) {
1354 pBrush->Release();
1355 pBrush = 0;
1361 void SurfaceD2D::SetFont(Font &font_) {
1362 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
1363 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE);
1364 pTextFormat = pfm->pTextFormat;
1365 yAscent = pfm->yAscent;
1366 yDescent = pfm->yDescent;
1367 yInternalLeading = pfm->yInternalLeading;
1368 codePageText = codePage;
1369 if (pfm->characterSet) {
1370 codePageText = CodePageFromCharSet(pfm->characterSet, codePage);
1372 if (pRenderTarget) {
1373 D2D1_TEXT_ANTIALIAS_MODE aaMode;
1374 aaMode = DWriteMapFontQuality(pfm->extraFontFlag);
1376 if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && customClearTypeRenderingParams)
1377 pRenderTarget->SetTextRenderingParams(customClearTypeRenderingParams);
1378 else if (defaultRenderingParams)
1379 pRenderTarget->SetTextRenderingParams(defaultRenderingParams);
1381 pRenderTarget->SetTextAntialiasMode(aaMode);
1385 int SurfaceD2D::LogPixelsY() {
1386 return logPixelsY;
1389 int SurfaceD2D::DeviceHeightFont(int points) {
1390 return ::MulDiv(points, LogPixelsY(), 72);
1393 void SurfaceD2D::MoveTo(int x_, int y_) {
1394 x = x_;
1395 y = y_;
1398 static int Delta(int difference) {
1399 if (difference < 0)
1400 return -1;
1401 else if (difference > 0)
1402 return 1;
1403 else
1404 return 0;
1407 static int RoundFloat(float f) {
1408 return int(f+0.5);
1411 void SurfaceD2D::LineTo(int x_, int y_) {
1412 if (pRenderTarget) {
1413 int xDiff = x_ - x;
1414 int xDelta = Delta(xDiff);
1415 int yDiff = y_ - y;
1416 int yDelta = Delta(yDiff);
1417 if ((xDiff == 0) || (yDiff == 0)) {
1418 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
1419 int xEnd = x_ - xDelta;
1420 int left = Platform::Minimum(x, xEnd);
1421 int width = abs(x - xEnd) + 1;
1422 int yEnd = y_ - yDelta;
1423 int top = Platform::Minimum(y, yEnd);
1424 int height = abs(y - yEnd) + 1;
1425 D2D1_RECT_F rectangle1 = D2D1::RectF(left, top, left+width, top+height);
1426 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1427 } else if ((abs(xDiff) == abs(yDiff))) {
1428 // 45 degree slope
1429 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5, y + 0.5),
1430 D2D1::Point2F(x_ + 0.5 - xDelta, y_ + 0.5 - yDelta), pBrush);
1431 } else {
1432 // Line has a different slope so difficult to avoid last pixel
1433 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5, y + 0.5),
1434 D2D1::Point2F(x_ + 0.5, y_ + 0.5), pBrush);
1436 x = x_;
1437 y = y_;
1441 void SurfaceD2D::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
1442 if (pRenderTarget) {
1443 ID2D1Factory *pFactory = 0;
1444 pRenderTarget->GetFactory(&pFactory);
1445 ID2D1PathGeometry *geometry=0;
1446 HRESULT hr = pFactory->CreatePathGeometry(&geometry);
1447 if (SUCCEEDED(hr)) {
1448 ID2D1GeometrySink *sink = 0;
1449 hr = geometry->Open(&sink);
1450 if (SUCCEEDED(hr)) {
1451 sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED);
1452 for (size_t i=1; i<static_cast<size_t>(npts); i++) {
1453 sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f));
1455 sink->EndFigure(D2D1_FIGURE_END_CLOSED);
1456 sink->Close();
1457 sink->Release();
1459 D2DPenColour(back);
1460 pRenderTarget->FillGeometry(geometry,pBrush);
1461 D2DPenColour(fore);
1462 pRenderTarget->DrawGeometry(geometry,pBrush);
1465 geometry->Release();
1470 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
1471 if (pRenderTarget) {
1472 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top+0.5, RoundFloat(rc.right) - 0.5, rc.bottom-0.5);
1473 D2DPenColour(back);
1474 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1475 D2DPenColour(fore);
1476 pRenderTarget->DrawRectangle(&rectangle1, pBrush);
1480 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) {
1481 if (pRenderTarget) {
1482 D2DPenColour(back);
1483 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left), rc.top, RoundFloat(rc.right), rc.bottom);
1484 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1488 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1489 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfacePattern);
1490 surfOther.FlushDrawing();
1491 ID2D1Bitmap *pBitmap = NULL;
1492 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1493 surfOther.pRenderTarget);
1494 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1495 if (SUCCEEDED(hr)) {
1496 ID2D1BitmapBrush *pBitmapBrush = NULL;
1497 D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1498 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1499 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1500 // Create the bitmap brush.
1501 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1502 pBitmap->Release();
1503 if (SUCCEEDED(hr)) {
1504 pRenderTarget->FillRectangle(
1505 D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom),
1506 pBitmapBrush);
1507 pBitmapBrush->Release();
1512 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
1513 if (pRenderTarget) {
1514 D2D1_ROUNDED_RECT roundedRectFill = {
1515 D2D1::RectF(rc.left+1.0, rc.top+1.0, rc.right-1.0, rc.bottom-1.0),
1516 8, 8};
1517 D2DPenColour(back);
1518 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1520 D2D1_ROUNDED_RECT roundedRect = {
1521 D2D1::RectF(rc.left + 0.5, rc.top+0.5, rc.right - 0.5, rc.bottom-0.5),
1522 8, 8};
1523 D2DPenColour(fore);
1524 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1528 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1529 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
1530 if (pRenderTarget) {
1531 if (cornerSize == 0) {
1532 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1533 D2D1_RECT_F rectFill = D2D1::RectF(RoundFloat(rc.left) + 1.0, rc.top + 1.0, RoundFloat(rc.right) - 1.0, rc.bottom - 1.0);
1534 D2DPenColour(fill, alphaFill);
1535 pRenderTarget->FillRectangle(rectFill, pBrush);
1537 D2D1_RECT_F rectOutline = D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top + 0.5, RoundFloat(rc.right) - 0.5, rc.bottom - 0.5);
1538 D2DPenColour(outline, alphaOutline);
1539 pRenderTarget->DrawRectangle(rectOutline, pBrush);
1540 } else {
1541 const float cornerSizeF = static_cast<float>(cornerSize);
1542 D2D1_ROUNDED_RECT roundedRectFill = {
1543 D2D1::RectF(RoundFloat(rc.left) + 1.0, rc.top + 1.0, RoundFloat(rc.right) - 1.0, rc.bottom - 1.0),
1544 cornerSizeF, cornerSizeF};
1545 D2DPenColour(fill, alphaFill);
1546 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1548 D2D1_ROUNDED_RECT roundedRect = {
1549 D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top + 0.5, RoundFloat(rc.right) - 0.5, rc.bottom - 0.5),
1550 cornerSizeF, cornerSizeF};
1551 D2DPenColour(outline, alphaOutline);
1552 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1557 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1558 if (pRenderTarget) {
1559 if (rc.Width() > width)
1560 rc.left += static_cast<int>((rc.Width() - width) / 2);
1561 rc.right = rc.left + width;
1562 if (rc.Height() > height)
1563 rc.top += static_cast<int>((rc.Height() - height) / 2);
1564 rc.bottom = rc.top + height;
1566 std::vector<unsigned char> image(height * width * 4);
1567 for (int y=0; y<height; y++) {
1568 for (int x=0; x<width; x++) {
1569 unsigned char *pixel = &image[0] + (y*width+x) * 4;
1570 unsigned char alpha = pixelsImage[3];
1571 // Input is RGBA, output is BGRA with premultiplied alpha
1572 pixel[2] = (*pixelsImage++) * alpha / 255;
1573 pixel[1] = (*pixelsImage++) * alpha / 255;
1574 pixel[0] = (*pixelsImage++) * alpha / 255;
1575 pixel[3] = *pixelsImage++;
1579 ID2D1Bitmap *bitmap = 0;
1580 D2D1_SIZE_U size = D2D1::SizeU(width, height);
1581 D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1582 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1583 HRESULT hr = pRenderTarget->CreateBitmap(size, &image[0],
1584 width * 4, &props, &bitmap);
1585 if (SUCCEEDED(hr)) {
1586 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1587 pRenderTarget->DrawBitmap(bitmap, rcDestination);
1588 bitmap->Release();
1593 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
1594 if (pRenderTarget) {
1595 FLOAT radius = rc.Width() / 2.0f - 1.0f;
1596 D2D1_ELLIPSE ellipse = {
1597 D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f),
1598 radius,radius};
1600 PenColour(back);
1601 pRenderTarget->FillEllipse(ellipse, pBrush);
1602 PenColour(fore);
1603 pRenderTarget->DrawEllipse(ellipse, pBrush);
1607 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1608 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfaceSource);
1609 surfOther.FlushDrawing();
1610 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1611 surfOther.pRenderTarget);
1612 ID2D1Bitmap *pBitmap = NULL;
1613 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1614 if (SUCCEEDED(hr)) {
1615 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1616 D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()};
1617 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1618 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1619 pRenderTarget->Flush();
1620 pBitmap->Release();
1624 void SurfaceD2D::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
1625 SetFont(font_);
1627 // Use Unicode calls
1628 const TextWide tbuf(s, len, unicodeMode, codePageText);
1629 if (pRenderTarget && pTextFormat && pBrush) {
1630 if (fuOptions & ETO_CLIPPED) {
1631 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1632 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1635 // Explicitly creating a text layout appears a little faster
1636 IDWriteTextLayout *pTextLayout;
1637 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat,
1638 rc.Width(), rc.Height(), &pTextLayout);
1639 if (SUCCEEDED(hr)) {
1640 D2D1_POINT_2F origin = {rc.left, ybase-yAscent};
1641 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, D2D1_DRAW_TEXT_OPTIONS_NONE);
1642 pTextLayout->Release();
1645 if (fuOptions & ETO_CLIPPED) {
1646 pRenderTarget->PopAxisAlignedClip();
1651 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1652 ColourDesired fore, ColourDesired back) {
1653 if (pRenderTarget) {
1654 FillRectangle(rc, back);
1655 D2DPenColour(fore);
1656 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
1660 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1661 ColourDesired fore, ColourDesired back) {
1662 if (pRenderTarget) {
1663 FillRectangle(rc, back);
1664 D2DPenColour(fore);
1665 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
1669 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1670 ColourDesired fore) {
1671 // Avoid drawing spaces in transparent mode
1672 for (int i=0;i<len;i++) {
1673 if (s[i] != ' ') {
1674 if (pRenderTarget) {
1675 D2DPenColour(fore);
1676 DrawTextCommon(rc, font_, ybase, s, len, 0);
1678 return;
1683 XYPOSITION SurfaceD2D::WidthText(Font &font_, const char *s, int len) {
1684 FLOAT width = 1.0;
1685 SetFont(font_);
1686 const TextWide tbuf(s, len, unicodeMode, codePageText);
1687 if (pIDWriteFactory && pTextFormat) {
1688 // Create a layout
1689 IDWriteTextLayout *pTextLayout = 0;
1690 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1691 if (SUCCEEDED(hr)) {
1692 DWRITE_TEXT_METRICS textMetrics;
1693 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1694 width = textMetrics.widthIncludingTrailingWhitespace;
1695 pTextLayout->Release();
1698 return width;
1701 void SurfaceD2D::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
1702 SetFont(font_);
1703 int fit = 0;
1704 const TextWide tbuf(s, len, unicodeMode, codePageText);
1705 TextPositions poses(tbuf.tlen);
1706 fit = tbuf.tlen;
1707 const int clusters = 1000;
1708 DWRITE_CLUSTER_METRICS clusterMetrics[clusters];
1709 UINT32 count = 0;
1710 if (pIDWriteFactory && pTextFormat) {
1711 SetFont(font_);
1712 // Create a layout
1713 IDWriteTextLayout *pTextLayout = 0;
1714 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
1715 if (!SUCCEEDED(hr))
1716 return;
1717 // For now, assuming WCHAR == cluster
1718 if (!SUCCEEDED(pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count)))
1719 return;
1720 FLOAT position = 0.0f;
1721 size_t ti=0;
1722 for (size_t ci=0;ci<count;ci++) {
1723 position += clusterMetrics[ci].width;
1724 for (size_t inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
1725 //poses.buffer[ti++] = int(position + 0.5);
1726 poses.buffer[ti++] = position;
1729 PLATFORM_ASSERT(ti == static_cast<size_t>(tbuf.tlen));
1730 pTextLayout->Release();
1732 if (unicodeMode) {
1733 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1734 int ui=0;
1735 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1736 int i=0;
1737 while (ui<fit) {
1738 unsigned char uch = us[i];
1739 unsigned int lenChar = 1;
1740 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1741 lenChar = 4;
1742 ui++;
1743 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1744 lenChar = 3;
1745 } else if (uch >= (0x80)) {
1746 lenChar = 2;
1748 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1749 positions[i++] = poses.buffer[ui];
1751 ui++;
1753 int lastPos = 0;
1754 if (i > 0)
1755 lastPos = positions[i-1];
1756 while (i<len) {
1757 positions[i++] = lastPos;
1759 } else if (codePageText == 0) {
1761 // One character per position
1762 PLATFORM_ASSERT(len == tbuf.tlen);
1763 for (size_t kk=0;kk<static_cast<size_t>(len);kk++) {
1764 positions[kk] = poses.buffer[kk];
1767 } else {
1769 // May be more than one byte per position
1770 unsigned int ui = 0;
1771 FLOAT position = 0.0f;
1772 for (int i=0;i<len;) {
1773 if (ui < count)
1774 position = poses.buffer[ui];
1775 if (Platform::IsDBCSLeadByte(codePageText, s[i])) {
1776 positions[i] = position;
1777 positions[i+1] = position;
1778 i += 2;
1779 } else {
1780 positions[i] = position;
1781 i++;
1784 ui++;
1789 XYPOSITION SurfaceD2D::WidthChar(Font &font_, char ch) {
1790 FLOAT width = 1.0;
1791 SetFont(font_);
1792 if (pIDWriteFactory && pTextFormat) {
1793 // Create a layout
1794 IDWriteTextLayout *pTextLayout = 0;
1795 const WCHAR wch = ch;
1796 HRESULT hr = pIDWriteFactory->CreateTextLayout(&wch, 1, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1797 if (SUCCEEDED(hr)) {
1798 DWRITE_TEXT_METRICS textMetrics;
1799 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1800 width = textMetrics.widthIncludingTrailingWhitespace;
1801 pTextLayout->Release();
1804 return width;
1807 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
1808 SetFont(font_);
1809 return ceil(yAscent);
1812 XYPOSITION SurfaceD2D::Descent(Font &font_) {
1813 SetFont(font_);
1814 return ceil(yDescent);
1817 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
1818 SetFont(font_);
1819 return floor(yInternalLeading);
1822 XYPOSITION SurfaceD2D::ExternalLeading(Font &) {
1823 // Not implemented, always return one
1824 return 1;
1827 XYPOSITION SurfaceD2D::Height(Font &font_) {
1828 return Ascent(font_) + Descent(font_);
1831 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) {
1832 FLOAT width = 1.0;
1833 SetFont(font_);
1834 if (pIDWriteFactory && pTextFormat) {
1835 // Create a layout
1836 IDWriteTextLayout *pTextLayout = 0;
1837 const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1838 HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(wcslen(wszAllAlpha)),
1839 pTextFormat, 1000.0, 1000.0, &pTextLayout);
1840 if (SUCCEEDED(hr)) {
1841 DWRITE_TEXT_METRICS textMetrics;
1842 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
1843 width = textMetrics.width / wcslen(wszAllAlpha);
1844 pTextLayout->Release();
1847 return width;
1850 void SurfaceD2D::SetClip(PRectangle rc) {
1851 if (pRenderTarget) {
1852 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1853 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1854 clipsActive++;
1858 void SurfaceD2D::FlushCachedState() {
1861 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
1862 unicodeMode=unicodeMode_;
1865 void SurfaceD2D::SetDBCSMode(int codePage_) {
1866 // No action on window as automatically handled by system.
1867 codePage = codePage_;
1869 #endif
1871 Surface *Surface::Allocate(int technology) {
1872 #if defined(USE_D2D)
1873 if (technology == SCWIN_TECH_GDI)
1874 return new SurfaceGDI;
1875 else
1876 return new SurfaceD2D;
1877 #else
1878 return new SurfaceGDI;
1879 #endif
1882 Window::~Window() {
1885 void Window::Destroy() {
1886 if (wid)
1887 ::DestroyWindow(reinterpret_cast<HWND>(wid));
1888 wid = 0;
1891 bool Window::HasFocus() {
1892 return ::GetFocus() == wid;
1895 PRectangle Window::GetPosition() {
1896 RECT rc;
1897 ::GetWindowRect(reinterpret_cast<HWND>(wid), &rc);
1898 return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
1901 void Window::SetPosition(PRectangle rc) {
1902 ::SetWindowPos(reinterpret_cast<HWND>(wid),
1903 0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER|SWP_NOACTIVATE);
1906 static RECT RectFromMonitor(HMONITOR hMonitor) {
1907 if (GetMonitorInfoFn) {
1908 MONITORINFO mi = {0};
1909 mi.cbSize = sizeof(mi);
1910 if (GetMonitorInfoFn(hMonitor, &mi)) {
1911 return mi.rcWork;
1914 RECT rc = {0, 0, 0, 0};
1915 if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) {
1916 rc.left = 0;
1917 rc.top = 0;
1918 rc.right = 0;
1919 rc.bottom = 0;
1921 return rc;
1924 void Window::SetPositionRelative(PRectangle rc, Window w) {
1925 LONG style = ::GetWindowLong(reinterpret_cast<HWND>(wid), GWL_STYLE);
1926 if (style & WS_POPUP) {
1927 POINT ptOther = {0, 0};
1928 ::ClientToScreen(reinterpret_cast<HWND>(w.GetID()), &ptOther);
1929 rc.Move(ptOther.x, ptOther.y);
1931 RECT rcMonitor = RectFromPRectangle(rc);
1933 HMONITOR hMonitor = NULL;
1934 if (MonitorFromRectFn)
1935 hMonitor = MonitorFromRectFn(&rcMonitor, MONITOR_DEFAULTTONEAREST);
1936 // If hMonitor is NULL, that's just the main screen anyways.
1937 //::GetMonitorInfo(hMonitor, &mi);
1938 RECT rcWork = RectFromMonitor(hMonitor);
1940 if (rcWork.left < rcWork.right) {
1941 // Now clamp our desired rectangle to fit inside the work area
1942 // This way, the menu will fit wholly on one screen. An improvement even
1943 // if you don't have a second monitor on the left... Menu's appears half on
1944 // one screen and half on the other are just U.G.L.Y.!
1945 if (rc.right > rcWork.right)
1946 rc.Move(rcWork.right - rc.right, 0);
1947 if (rc.bottom > rcWork.bottom)
1948 rc.Move(0, rcWork.bottom - rc.bottom);
1949 if (rc.left < rcWork.left)
1950 rc.Move(rcWork.left - rc.left, 0);
1951 if (rc.top < rcWork.top)
1952 rc.Move(0, rcWork.top - rc.top);
1955 SetPosition(rc);
1958 PRectangle Window::GetClientPosition() {
1959 RECT rc={0,0,0,0};
1960 if (wid)
1961 ::GetClientRect(reinterpret_cast<HWND>(wid), &rc);
1962 return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
1965 void Window::Show(bool show) {
1966 if (show)
1967 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_SHOWNOACTIVATE);
1968 else
1969 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_HIDE);
1972 void Window::InvalidateAll() {
1973 ::InvalidateRect(reinterpret_cast<HWND>(wid), NULL, FALSE);
1976 void Window::InvalidateRectangle(PRectangle rc) {
1977 RECT rcw = RectFromPRectangle(rc);
1978 ::InvalidateRect(reinterpret_cast<HWND>(wid), &rcw, FALSE);
1981 static LRESULT Window_SendMessage(Window *w, UINT msg, WPARAM wParam=0, LPARAM lParam=0) {
1982 return ::SendMessage(reinterpret_cast<HWND>(w->GetID()), msg, wParam, lParam);
1985 void Window::SetFont(Font &font) {
1986 Window_SendMessage(this, WM_SETFONT,
1987 reinterpret_cast<WPARAM>(font.GetID()), 0);
1990 static void FlipBitmap(HBITMAP bitmap, int width, int height) {
1991 HDC hdc = ::CreateCompatibleDC(NULL);
1992 if (hdc != NULL) {
1993 HGDIOBJ prevBmp = ::SelectObject(hdc, bitmap);
1994 ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
1995 ::SelectObject(hdc, prevBmp);
1996 ::DeleteDC(hdc);
2000 static HCURSOR GetReverseArrowCursor() {
2001 if (reverseArrowCursor != NULL)
2002 return reverseArrowCursor;
2004 ::EnterCriticalSection(&crPlatformLock);
2005 HCURSOR cursor = reverseArrowCursor;
2006 if (cursor == NULL) {
2007 cursor = ::LoadCursor(NULL, IDC_ARROW);
2008 ICONINFO info;
2009 if (::GetIconInfo(cursor, &info)) {
2010 BITMAP bmp;
2011 if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) {
2012 FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
2013 if (info.hbmColor != NULL)
2014 FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
2015 info.xHotspot = (DWORD)bmp.bmWidth - 1 - info.xHotspot;
2017 reverseArrowCursor = ::CreateIconIndirect(&info);
2018 if (reverseArrowCursor != NULL)
2019 cursor = reverseArrowCursor;
2022 ::DeleteObject(info.hbmMask);
2023 if (info.hbmColor != NULL)
2024 ::DeleteObject(info.hbmColor);
2027 ::LeaveCriticalSection(&crPlatformLock);
2028 return cursor;
2031 void Window::SetCursor(Cursor curs) {
2032 switch (curs) {
2033 case cursorText:
2034 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
2035 break;
2036 case cursorUp:
2037 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
2038 break;
2039 case cursorWait:
2040 ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
2041 break;
2042 case cursorHoriz:
2043 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
2044 break;
2045 case cursorVert:
2046 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
2047 break;
2048 case cursorHand:
2049 ::SetCursor(::LoadCursor(NULL,IDC_HAND));
2050 break;
2051 case cursorReverseArrow:
2052 ::SetCursor(GetReverseArrowCursor());
2053 break;
2054 case cursorArrow:
2055 case cursorInvalid: // Should not occur, but just in case.
2056 ::SetCursor(::LoadCursor(NULL,IDC_ARROW));
2057 break;
2061 void Window::SetTitle(const char *s) {
2062 ::SetWindowTextA(reinterpret_cast<HWND>(wid), s);
2065 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
2066 coordinates */
2067 PRectangle Window::GetMonitorRect(Point pt) {
2068 // MonitorFromPoint and GetMonitorInfo are not available on Windows 95 and NT 4.
2069 PRectangle rcPosition = GetPosition();
2070 POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
2071 static_cast<LONG>(pt.y + rcPosition.top)};
2072 HMONITOR hMonitor = NULL;
2073 if (MonitorFromPointFn)
2074 hMonitor = MonitorFromPointFn(ptDesktop, MONITOR_DEFAULTTONEAREST);
2076 RECT rcWork = RectFromMonitor(hMonitor);
2077 if (rcWork.left < rcWork.right) {
2078 PRectangle rcMonitor(
2079 rcWork.left - rcPosition.left,
2080 rcWork.top - rcPosition.top,
2081 rcWork.right - rcPosition.left,
2082 rcWork.bottom - rcPosition.top);
2083 return rcMonitor;
2084 } else {
2085 return PRectangle();
2089 struct ListItemData {
2090 const char *text;
2091 int pixId;
2094 class LineToItem {
2095 std::vector<char> words;
2097 std::vector<ListItemData> data;
2099 public:
2100 LineToItem() {
2102 ~LineToItem() {
2103 Clear();
2105 void Clear() {
2106 words.clear();
2107 data.clear();
2110 ListItemData Get(int index) const {
2111 if (index >= 0 && index < static_cast<int>(data.size())) {
2112 return data[index];
2113 } else {
2114 ListItemData missing = {"", -1};
2115 return missing;
2118 int Count() const {
2119 return static_cast<int>(data.size());
2122 void AllocItem(const char *text, int pixId) {
2123 ListItemData lid = { text, pixId };
2124 data.push_back(lid);
2127 char *SetWords(const char *s) {
2128 words = std::vector<char>(s, s+strlen(s)+1);
2129 return &words[0];
2133 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
2135 ListBox::ListBox() {
2138 ListBox::~ListBox() {
2141 class ListBoxX : public ListBox {
2142 int lineHeight;
2143 FontID fontCopy;
2144 int technology;
2145 RGBAImageSet images;
2146 LineToItem lti;
2147 HWND lb;
2148 bool unicodeMode;
2149 int desiredVisibleRows;
2150 unsigned int maxItemCharacters;
2151 unsigned int aveCharWidth;
2152 Window *parent;
2153 int ctrlID;
2154 CallBackAction doubleClickAction;
2155 void *doubleClickActionData;
2156 const char *widestItem;
2157 unsigned int maxCharWidth;
2158 int resizeHit;
2159 PRectangle rcPreSize;
2160 Point dragOffset;
2161 Point location; // Caret location at which the list is opened
2162 int wheelDelta; // mouse wheel residue
2164 HWND GetHWND() const;
2165 void AppendListItem(const char *text, const char *numword);
2166 static void AdjustWindowRect(PRectangle *rc);
2167 int ItemHeight() const;
2168 int MinClientWidth() const;
2169 int TextOffset() const;
2170 Point GetClientExtent() const;
2171 POINT MinTrackSize() const;
2172 POINT MaxTrackSize() const;
2173 void SetRedraw(bool on);
2174 void OnDoubleClick();
2175 void ResizeToCursor();
2176 void StartResize(WPARAM);
2177 int NcHitTest(WPARAM, LPARAM) const;
2178 void CentreItem(int);
2179 void Paint(HDC);
2180 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2182 static const Point ItemInset; // Padding around whole item
2183 static const Point TextInset; // Padding around text
2184 static const Point ImageInset; // Padding around image
2186 public:
2187 ListBoxX() : lineHeight(10), fontCopy(0), technology(0), lb(0), unicodeMode(false),
2188 desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8),
2189 parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL),
2190 widestItem(NULL), maxCharWidth(1), resizeHit(0), wheelDelta(0) {
2192 virtual ~ListBoxX() {
2193 if (fontCopy) {
2194 ::DeleteObject(fontCopy);
2195 fontCopy = 0;
2198 virtual void SetFont(Font &font);
2199 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
2200 virtual void SetAverageCharWidth(int width);
2201 virtual void SetVisibleRows(int rows);
2202 virtual int GetVisibleRows() const;
2203 virtual PRectangle GetDesiredRect();
2204 virtual int CaretFromEdge();
2205 virtual void Clear();
2206 virtual void Append(char *s, int type = -1);
2207 virtual int Length();
2208 virtual void Select(int n);
2209 virtual int GetSelection();
2210 virtual int Find(const char *prefix);
2211 virtual void GetValue(int n, char *value, int len);
2212 virtual void RegisterImage(int type, const char *xpm_data);
2213 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
2214 virtual void ClearRegisteredImages();
2215 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
2216 doubleClickAction = action;
2217 doubleClickActionData = data;
2219 virtual void SetList(const char *list, char separator, char typesep);
2220 void Draw(DRAWITEMSTRUCT *pDrawItem);
2221 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2222 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2225 const Point ListBoxX::ItemInset(0, 0);
2226 const Point ListBoxX::TextInset(2, 0);
2227 const Point ListBoxX::ImageInset(1, 0);
2229 ListBox *ListBox::Allocate() {
2230 ListBoxX *lb = new ListBoxX();
2231 return lb;
2234 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) {
2235 parent = &parent_;
2236 ctrlID = ctrlID_;
2237 location = location_;
2238 lineHeight = lineHeight_;
2239 unicodeMode = unicodeMode_;
2240 technology = technology_;
2241 HWND hwndParent = reinterpret_cast<HWND>(parent->GetID());
2242 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
2243 // Window created as popup so not clipped within parent client area
2244 wid = ::CreateWindowEx(
2245 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
2246 WS_POPUP | WS_THICKFRAME,
2247 100,100, 150,80, hwndParent,
2248 NULL,
2249 hinstanceParent,
2250 this);
2252 POINT locationw = {static_cast<LONG>(location.x), static_cast<LONG>(location.y)};
2253 ::MapWindowPoints(hwndParent, NULL, &locationw, 1);
2254 location = Point(locationw.x, locationw.y);
2257 void ListBoxX::SetFont(Font &font) {
2258 if (font.GetID()) {
2259 if (fontCopy) {
2260 ::DeleteObject(fontCopy);
2261 fontCopy = 0;
2263 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font.GetID());
2264 fontCopy = pfm->HFont();
2265 ::SendMessage(lb, WM_SETFONT, reinterpret_cast<WPARAM>(fontCopy), 0);
2269 void ListBoxX::SetAverageCharWidth(int width) {
2270 aveCharWidth = width;
2273 void ListBoxX::SetVisibleRows(int rows) {
2274 desiredVisibleRows = rows;
2277 int ListBoxX::GetVisibleRows() const {
2278 return desiredVisibleRows;
2281 HWND ListBoxX::GetHWND() const {
2282 return reinterpret_cast<HWND>(GetID());
2285 PRectangle ListBoxX::GetDesiredRect() {
2286 PRectangle rcDesired = GetPosition();
2288 int rows = Length();
2289 if ((rows == 0) || (rows > desiredVisibleRows))
2290 rows = desiredVisibleRows;
2291 rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
2293 int width = MinClientWidth();
2294 HDC hdc = ::GetDC(lb);
2295 HFONT oldFont = SelectFont(hdc, fontCopy);
2296 SIZE textSize = {0, 0};
2297 int len = 0;
2298 if (widestItem) {
2299 len = static_cast<int>(strlen(widestItem));
2300 if (unicodeMode) {
2301 const TextWide tbuf(widestItem, len, unicodeMode);
2302 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
2303 } else {
2304 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
2307 TEXTMETRIC tm;
2308 ::GetTextMetrics(hdc, &tm);
2309 maxCharWidth = tm.tmMaxCharWidth;
2310 SelectFont(hdc, oldFont);
2311 ::ReleaseDC(lb, hdc);
2313 int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
2314 if (width < widthDesired)
2315 width = widthDesired;
2317 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
2318 if (Length() > rows)
2319 rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
2321 AdjustWindowRect(&rcDesired);
2322 return rcDesired;
2325 int ListBoxX::TextOffset() const {
2326 int pixWidth = images.GetWidth();
2327 return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2);
2330 int ListBoxX::CaretFromEdge() {
2331 PRectangle rc;
2332 AdjustWindowRect(&rc);
2333 return TextOffset() + TextInset.x + (0 - rc.left) - 1;
2336 void ListBoxX::Clear() {
2337 ::SendMessage(lb, LB_RESETCONTENT, 0, 0);
2338 maxItemCharacters = 0;
2339 widestItem = NULL;
2340 lti.Clear();
2343 void ListBoxX::Append(char *, int) {
2344 // This method is no longer called in Scintilla
2345 PLATFORM_ASSERT(false);
2348 int ListBoxX::Length() {
2349 return lti.Count();
2352 void ListBoxX::Select(int n) {
2353 // We are going to scroll to centre on the new selection and then select it, so disable
2354 // redraw to avoid flicker caused by a painting new selection twice in unselected and then
2355 // selected states
2356 SetRedraw(false);
2357 CentreItem(n);
2358 ::SendMessage(lb, LB_SETCURSEL, n, 0);
2359 SetRedraw(true);
2362 int ListBoxX::GetSelection() {
2363 return ::SendMessage(lb, LB_GETCURSEL, 0, 0);
2366 // This is not actually called at present
2367 int ListBoxX::Find(const char *) {
2368 return LB_ERR;
2371 void ListBoxX::GetValue(int n, char *value, int len) {
2372 ListItemData item = lti.Get(n);
2373 strncpy(value, item.text, len);
2374 value[len-1] = '\0';
2377 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2378 XPM xpmImage(xpm_data);
2379 images.Add(type, new RGBAImage(xpmImage));
2382 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2383 images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage));
2386 void ListBoxX::ClearRegisteredImages() {
2387 images.Clear();
2390 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
2391 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
2392 RECT rcBox = pDrawItem->rcItem;
2393 rcBox.left += TextOffset();
2394 if (pDrawItem->itemState & ODS_SELECTED) {
2395 RECT rcImage = pDrawItem->rcItem;
2396 rcImage.right = rcBox.left;
2397 // The image is not highlighted
2398 ::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2399 ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
2400 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
2401 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
2402 } else {
2403 ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2404 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
2405 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
2408 ListItemData item = lti.Get(pDrawItem->itemID);
2409 int pixId = item.pixId;
2410 const char *text = item.text;
2411 int len = static_cast<int>(strlen(text));
2413 RECT rcText = rcBox;
2414 ::InsetRect(&rcText, TextInset.x, TextInset.y);
2416 if (unicodeMode) {
2417 const TextWide tbuf(text, len, unicodeMode);
2418 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2419 } else {
2420 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2422 if (pDrawItem->itemState & ODS_SELECTED) {
2423 ::DrawFocusRect(pDrawItem->hDC, &rcBox);
2426 // Draw the image, if any
2427 RGBAImage *pimage = images.Get(pixId);
2428 if (pimage) {
2429 Surface *surfaceItem = Surface::Allocate(technology);
2430 if (surfaceItem) {
2431 if (technology == SCWIN_TECH_GDI) {
2432 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
2433 int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
2434 PRectangle rcImage(left, pDrawItem->rcItem.top,
2435 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2436 surfaceItem->DrawRGBAImage(rcImage,
2437 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2438 delete surfaceItem;
2439 ::SetTextAlign(pDrawItem->hDC, TA_TOP);
2440 } else {
2441 #if defined(USE_D2D)
2442 D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
2443 D2D1_RENDER_TARGET_TYPE_DEFAULT,
2444 D2D1::PixelFormat(
2445 DXGI_FORMAT_B8G8R8A8_UNORM,
2446 D2D1_ALPHA_MODE_IGNORE),
2449 D2D1_RENDER_TARGET_USAGE_NONE,
2450 D2D1_FEATURE_LEVEL_DEFAULT
2452 ID2D1DCRenderTarget *pDCRT = 0;
2453 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
2454 if (SUCCEEDED(hr)) {
2455 RECT rcWindow;
2456 GetClientRect(pDrawItem->hwndItem, &rcWindow);
2457 hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow);
2458 if (SUCCEEDED(hr)) {
2459 surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
2460 pDCRT->BeginDraw();
2461 int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
2462 PRectangle rcImage(left, pDrawItem->rcItem.top,
2463 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2464 surfaceItem->DrawRGBAImage(rcImage,
2465 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2466 delete surfaceItem;
2467 pDCRT->EndDraw();
2468 pDCRT->Release();
2469 } else {
2470 delete surfaceItem;
2472 } else {
2473 delete surfaceItem;
2475 #endif
2482 void ListBoxX::AppendListItem(const char *text, const char *numword) {
2483 int pixId = -1;
2484 if (numword) {
2485 pixId = 0;
2486 char ch;
2487 while ((ch = *++numword) != '\0') {
2488 pixId = 10 * pixId + (ch - '0');
2492 lti.AllocItem(text, pixId);
2493 unsigned int len = static_cast<unsigned int>(strlen(text));
2494 if (maxItemCharacters < len) {
2495 maxItemCharacters = len;
2496 widestItem = text;
2500 void ListBoxX::SetList(const char *list, char separator, char typesep) {
2501 // Turn off redraw while populating the list - this has a significant effect, even if
2502 // the listbox is not visible.
2503 SetRedraw(false);
2504 Clear();
2505 size_t size = strlen(list);
2506 char *words = lti.SetWords(list);
2507 char *startword = words;
2508 char *numword = NULL;
2509 for (size_t i=0; i < size; i++) {
2510 if (words[i] == separator) {
2511 words[i] = '\0';
2512 if (numword)
2513 *numword = '\0';
2514 AppendListItem(startword, numword);
2515 startword = words + i + 1;
2516 numword = NULL;
2517 } else if (words[i] == typesep) {
2518 numword = words + i;
2521 if (startword) {
2522 if (numword)
2523 *numword = '\0';
2524 AppendListItem(startword, numword);
2527 // Finally populate the listbox itself with the correct number of items
2528 int count = lti.Count();
2529 ::SendMessage(lb, LB_INITSTORAGE, count, 0);
2530 for (int j=0; j<count; j++) {
2531 ::SendMessage(lb, LB_ADDSTRING, 0, j+1);
2533 SetRedraw(true);
2536 void ListBoxX::AdjustWindowRect(PRectangle *rc) {
2537 RECT rcw = RectFromPRectangle(*rc);
2538 ::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
2539 *rc = PRectangle(rcw.left, rcw.top, rcw.right, rcw.bottom);
2542 int ListBoxX::ItemHeight() const {
2543 int itemHeight = lineHeight + (TextInset.y * 2);
2544 int pixHeight = images.GetHeight() + (ImageInset.y * 2);
2545 if (itemHeight < pixHeight) {
2546 itemHeight = pixHeight;
2548 return itemHeight;
2551 int ListBoxX::MinClientWidth() const {
2552 return 12 * (aveCharWidth+aveCharWidth/3);
2555 POINT ListBoxX::MinTrackSize() const {
2556 PRectangle rc(0, 0, MinClientWidth(), ItemHeight());
2557 AdjustWindowRect(&rc);
2558 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2559 return ret;
2562 POINT ListBoxX::MaxTrackSize() const {
2563 PRectangle rc(0, 0,
2564 maxCharWidth * maxItemCharacters + TextInset.x * 2 +
2565 TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL),
2566 ItemHeight() * lti.Count());
2567 AdjustWindowRect(&rc);
2568 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2569 return ret;
2572 void ListBoxX::SetRedraw(bool on) {
2573 ::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
2574 if (on)
2575 ::InvalidateRect(lb, NULL, TRUE);
2578 void ListBoxX::ResizeToCursor() {
2579 PRectangle rc = GetPosition();
2580 POINT ptw;
2581 ::GetCursorPos(&ptw);
2582 Point pt(ptw.x, ptw.y);
2583 pt.x += dragOffset.x;
2584 pt.y += dragOffset.y;
2586 switch (resizeHit) {
2587 case HTLEFT:
2588 rc.left = pt.x;
2589 break;
2590 case HTRIGHT:
2591 rc.right = pt.x;
2592 break;
2593 case HTTOP:
2594 rc.top = pt.y;
2595 break;
2596 case HTTOPLEFT:
2597 rc.top = pt.y;
2598 rc.left = pt.x;
2599 break;
2600 case HTTOPRIGHT:
2601 rc.top = pt.y;
2602 rc.right = pt.x;
2603 break;
2604 case HTBOTTOM:
2605 rc.bottom = pt.y;
2606 break;
2607 case HTBOTTOMLEFT:
2608 rc.bottom = pt.y;
2609 rc.left = pt.x;
2610 break;
2611 case HTBOTTOMRIGHT:
2612 rc.bottom = pt.y;
2613 rc.right = pt.x;
2614 break;
2617 POINT ptMin = MinTrackSize();
2618 POINT ptMax = MaxTrackSize();
2619 // We don't allow the left edge to move at present, but just in case
2620 rc.left = Platform::Maximum(Platform::Minimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
2621 rc.top = Platform::Maximum(Platform::Minimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
2622 rc.right = Platform::Maximum(Platform::Minimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
2623 rc.bottom = Platform::Maximum(Platform::Minimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
2625 SetPosition(rc);
2628 void ListBoxX::StartResize(WPARAM hitCode) {
2629 rcPreSize = GetPosition();
2630 POINT cursorPos;
2631 ::GetCursorPos(&cursorPos);
2633 switch (hitCode) {
2634 case HTRIGHT:
2635 case HTBOTTOM:
2636 case HTBOTTOMRIGHT:
2637 dragOffset.x = rcPreSize.right - cursorPos.x;
2638 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2639 break;
2641 case HTTOPRIGHT:
2642 dragOffset.x = rcPreSize.right - cursorPos.x;
2643 dragOffset.y = rcPreSize.top - cursorPos.y;
2644 break;
2646 // Note that the current hit test code prevents the left edge cases ever firing
2647 // as we don't want the left edge to be moveable
2648 case HTLEFT:
2649 case HTTOP:
2650 case HTTOPLEFT:
2651 dragOffset.x = rcPreSize.left - cursorPos.x;
2652 dragOffset.y = rcPreSize.top - cursorPos.y;
2653 break;
2654 case HTBOTTOMLEFT:
2655 dragOffset.x = rcPreSize.left - cursorPos.x;
2656 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2657 break;
2659 default:
2660 return;
2663 ::SetCapture(GetHWND());
2664 resizeHit = hitCode;
2667 int ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
2668 int hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
2669 // There is an apparent bug in the DefWindowProc hit test code whereby it will
2670 // return HTTOPXXX if the window in question is shorter than the default
2671 // window caption height + frame, even if one is hovering over the bottom edge of
2672 // the frame, so workaround that here
2673 if (hit >= HTTOP && hit <= HTTOPRIGHT) {
2674 int minHeight = GetSystemMetrics(SM_CYMINTRACK);
2675 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2676 int yPos = GET_Y_LPARAM(lParam);
2677 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
2678 hit += HTBOTTOM - HTTOP;
2682 // Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
2683 // depending on whether the list is above or below the caret
2684 switch (hit) {
2685 case HTLEFT:
2686 case HTTOPLEFT:
2687 case HTBOTTOMLEFT:
2688 hit = HTERROR;
2689 break;
2691 case HTTOP:
2692 case HTTOPRIGHT: {
2693 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2694 // Valid only if caret below list
2695 if (location.y < rc.top)
2696 hit = HTERROR;
2698 break;
2700 case HTBOTTOM:
2701 case HTBOTTOMRIGHT: {
2702 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2703 // Valid only if caret above list
2704 if (rc.bottom < location.y)
2705 hit = HTERROR;
2707 break;
2710 return hit;
2713 void ListBoxX::OnDoubleClick() {
2715 if (doubleClickAction != NULL) {
2716 doubleClickAction(doubleClickActionData);
2720 Point ListBoxX::GetClientExtent() const {
2721 PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
2722 return Point(rc.Width(), rc.Height());
2725 void ListBoxX::CentreItem(int n) {
2726 // If below mid point, scroll up to centre, but with more items below if uneven
2727 if (n >= 0) {
2728 Point extent = GetClientExtent();
2729 int visible = extent.y/ItemHeight();
2730 if (visible < Length()) {
2731 int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
2732 int half = (visible - 1) / 2;
2733 if (n > (top + half))
2734 ::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
2739 // Performs a double-buffered paint operation to avoid flicker
2740 void ListBoxX::Paint(HDC hDC) {
2741 Point extent = GetClientExtent();
2742 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
2743 HDC bitmapDC = ::CreateCompatibleDC(hDC);
2744 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
2745 // The list background is mainly erased during painting, but can be a small
2746 // unpainted area when at the end of a non-integrally sized list with a
2747 // vertical scroll bar
2748 RECT rc = { 0, 0, static_cast<LONG>(extent.x), static_cast<LONG>(extent.y) };
2749 ::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2750 // Paint the entire client area and vertical scrollbar
2751 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
2752 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
2753 // Select a stock brush to prevent warnings from BoundsChecker
2754 ::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
2755 SelectBitmap(bitmapDC, hBitmapOld);
2756 ::DeleteDC(bitmapDC);
2757 ::DeleteObject(hBitmap);
2760 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
2761 try {
2762 switch (uMsg) {
2763 case WM_ERASEBKGND:
2764 return TRUE;
2766 case WM_PAINT: {
2767 PAINTSTRUCT ps;
2768 HDC hDC = ::BeginPaint(hWnd, &ps);
2769 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2770 if (lbx)
2771 lbx->Paint(hDC);
2772 ::EndPaint(hWnd, &ps);
2774 return 0;
2776 case WM_MOUSEACTIVATE:
2777 // This prevents the view activating when the scrollbar is clicked
2778 return MA_NOACTIVATE;
2780 case WM_LBUTTONDOWN: {
2781 // We must take control of selection to prevent the ListBox activating
2782 // the popup
2783 LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
2784 int item = LOWORD(lResult);
2785 if (HIWORD(lResult) == 0 && item >= 0) {
2786 ::SendMessage(hWnd, LB_SETCURSEL, item, 0);
2789 return 0;
2791 case WM_LBUTTONUP:
2792 return 0;
2794 case WM_LBUTTONDBLCLK: {
2795 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2796 if (lbx) {
2797 lbx->OnDoubleClick();
2800 return 0;
2802 case WM_MBUTTONDOWN:
2803 // disable the scroll wheel button click action
2804 return 0;
2807 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
2808 if (prevWndProc) {
2809 return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
2810 } else {
2811 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2813 } catch (...) {
2815 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2818 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2819 switch (iMessage) {
2820 case WM_CREATE: {
2821 HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID()));
2822 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
2823 // but has useful side effect of speeding up list population significantly
2824 lb = ::CreateWindowEx(
2825 0, TEXT("listbox"), TEXT(""),
2826 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
2827 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
2828 0, 0, 150,80, hWnd,
2829 reinterpret_cast<HMENU>(ctrlID),
2830 hinstanceParent,
2832 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(lb, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlWndProc)));
2833 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
2835 break;
2837 case WM_SIZE:
2838 if (lb) {
2839 SetRedraw(false);
2840 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
2841 // Ensure the selection remains visible
2842 CentreItem(GetSelection());
2843 SetRedraw(true);
2845 break;
2847 case WM_PAINT: {
2848 PAINTSTRUCT ps;
2849 ::BeginPaint(hWnd, &ps);
2850 ::EndPaint(hWnd, &ps);
2852 break;
2854 case WM_COMMAND:
2855 // This is not actually needed now - the registered double click action is used
2856 // directly to action a choice from the list.
2857 ::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);
2858 break;
2860 case WM_MEASUREITEM: {
2861 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
2862 pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());
2864 break;
2866 case WM_DRAWITEM:
2867 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
2868 break;
2870 case WM_DESTROY:
2871 lb = 0;
2872 ::SetWindowLong(hWnd, 0, 0);
2873 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2875 case WM_ERASEBKGND:
2876 // To reduce flicker we can elide background erasure since this window is
2877 // completely covered by its child.
2878 return TRUE;
2880 case WM_GETMINMAXINFO: {
2881 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
2882 minMax->ptMaxTrackSize = MaxTrackSize();
2883 minMax->ptMinTrackSize = MinTrackSize();
2885 break;
2887 case WM_MOUSEACTIVATE:
2888 return MA_NOACTIVATE;
2890 case WM_NCHITTEST:
2891 return NcHitTest(wParam, lParam);
2893 case WM_NCLBUTTONDOWN:
2894 // We have to implement our own window resizing because the DefWindowProc
2895 // implementation insists on activating the resized window
2896 StartResize(wParam);
2897 return 0;
2899 case WM_MOUSEMOVE: {
2900 if (resizeHit == 0) {
2901 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2902 } else {
2903 ResizeToCursor();
2906 break;
2908 case WM_LBUTTONUP:
2909 case WM_CANCELMODE:
2910 if (resizeHit != 0) {
2911 resizeHit = 0;
2912 ::ReleaseCapture();
2914 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2916 case WM_MOUSEWHEEL:
2917 wheelDelta -= static_cast<short>(HIWORD(wParam));
2918 if (abs(wheelDelta) >= WHEEL_DELTA) {
2919 int nRows = GetVisibleRows();
2920 int linesToScroll = 1;
2921 if (nRows > 1) {
2922 linesToScroll = nRows - 1;
2924 if (linesToScroll > 3) {
2925 linesToScroll = 3;
2927 linesToScroll *= (wheelDelta / WHEEL_DELTA);
2928 int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0) + linesToScroll;
2929 if (top < 0) {
2930 top = 0;
2932 ::SendMessage(lb, LB_SETTOPINDEX, top, 0);
2933 // update wheel delta residue
2934 if (wheelDelta >= 0)
2935 wheelDelta = wheelDelta % WHEEL_DELTA;
2936 else
2937 wheelDelta = - (-wheelDelta % WHEEL_DELTA);
2939 break;
2941 default:
2942 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2945 return 0;
2948 LRESULT PASCAL ListBoxX::StaticWndProc(
2949 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2950 if (iMessage == WM_CREATE) {
2951 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
2952 SetWindowPointer(hWnd, pCreate->lpCreateParams);
2954 // Find C++ object associated with window.
2955 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(hWnd));
2956 if (lbx) {
2957 return lbx->WndProc(hWnd, iMessage, wParam, lParam);
2958 } else {
2959 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2963 static bool ListBoxX_Register() {
2964 WNDCLASSEX wndclassc;
2965 wndclassc.cbSize = sizeof(wndclassc);
2966 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
2967 // truncated items in the list and the appearance/disappearance of the vertical scroll bar.
2968 // The list repaint is double-buffered to avoid the flicker this would otherwise cause.
2969 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
2970 wndclassc.cbClsExtra = 0;
2971 wndclassc.cbWndExtra = sizeof(ListBoxX *);
2972 wndclassc.hInstance = hinstPlatformRes;
2973 wndclassc.hIcon = NULL;
2974 wndclassc.hbrBackground = NULL;
2975 wndclassc.lpszMenuName = NULL;
2976 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
2977 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
2978 wndclassc.lpszClassName = ListBoxX_ClassName;
2979 wndclassc.hIconSm = 0;
2981 return ::RegisterClassEx(&wndclassc) != 0;
2984 bool ListBoxX_Unregister() {
2985 return ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes) != 0;
2988 Menu::Menu() : mid(0) {
2991 void Menu::CreatePopUp() {
2992 Destroy();
2993 mid = ::CreatePopupMenu();
2996 void Menu::Destroy() {
2997 if (mid)
2998 ::DestroyMenu(reinterpret_cast<HMENU>(mid));
2999 mid = 0;
3002 void Menu::Show(Point pt, Window &w) {
3003 ::TrackPopupMenu(reinterpret_cast<HMENU>(mid),
3004 0, pt.x - 4, pt.y, 0,
3005 reinterpret_cast<HWND>(w.GetID()), NULL);
3006 Destroy();
3009 static bool initialisedET = false;
3010 static bool usePerformanceCounter = false;
3011 static LARGE_INTEGER frequency;
3013 ElapsedTime::ElapsedTime() {
3014 if (!initialisedET) {
3015 usePerformanceCounter = ::QueryPerformanceFrequency(&frequency) != 0;
3016 initialisedET = true;
3018 if (usePerformanceCounter) {
3019 LARGE_INTEGER timeVal;
3020 ::QueryPerformanceCounter(&timeVal);
3021 bigBit = timeVal.HighPart;
3022 littleBit = timeVal.LowPart;
3023 } else {
3024 bigBit = clock();
3025 littleBit = 0;
3029 double ElapsedTime::Duration(bool reset) {
3030 double result;
3031 long endBigBit;
3032 long endLittleBit;
3034 if (usePerformanceCounter) {
3035 LARGE_INTEGER lEnd;
3036 ::QueryPerformanceCounter(&lEnd);
3037 endBigBit = lEnd.HighPart;
3038 endLittleBit = lEnd.LowPart;
3039 LARGE_INTEGER lBegin;
3040 lBegin.HighPart = bigBit;
3041 lBegin.LowPart = littleBit;
3042 double elapsed = lEnd.QuadPart - lBegin.QuadPart;
3043 result = elapsed / static_cast<double>(frequency.QuadPart);
3044 } else {
3045 endBigBit = clock();
3046 endLittleBit = 0;
3047 double elapsed = endBigBit - bigBit;
3048 result = elapsed / CLOCKS_PER_SEC;
3050 if (reset) {
3051 bigBit = endBigBit;
3052 littleBit = endLittleBit;
3054 return result;
3057 class DynamicLibraryImpl : public DynamicLibrary {
3058 protected:
3059 HMODULE h;
3060 public:
3061 DynamicLibraryImpl(const char *modulePath) {
3062 h = ::LoadLibraryA(modulePath);
3065 virtual ~DynamicLibraryImpl() {
3066 if (h != NULL)
3067 ::FreeLibrary(h);
3070 // Use GetProcAddress to get a pointer to the relevant function.
3071 virtual Function FindFunction(const char *name) {
3072 if (h != NULL) {
3073 // C++ standard doesn't like casts betwen function pointers and void pointers so use a union
3074 union {
3075 FARPROC fp;
3076 Function f;
3077 } fnConv;
3078 fnConv.fp = ::GetProcAddress(h, name);
3079 return fnConv.f;
3080 } else
3081 return NULL;
3084 virtual bool IsValid() {
3085 return h != NULL;
3089 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3090 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3093 ColourDesired Platform::Chrome() {
3094 return ::GetSysColor(COLOR_3DFACE);
3097 ColourDesired Platform::ChromeHighlight() {
3098 return ::GetSysColor(COLOR_3DHIGHLIGHT);
3101 const char *Platform::DefaultFont() {
3102 return "Verdana";
3105 int Platform::DefaultFontSize() {
3106 return 8;
3109 unsigned int Platform::DoubleClickTime() {
3110 return ::GetDoubleClickTime();
3113 bool Platform::MouseButtonBounce() {
3114 return false;
3117 void Platform::DebugDisplay(const char *s) {
3118 ::OutputDebugStringA(s);
3121 bool Platform::IsKeyDown(int key) {
3122 return (::GetKeyState(key) & 0x80000000) != 0;
3125 long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
3126 return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam);
3129 long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
3130 return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
3131 reinterpret_cast<LPARAM>(lParam));
3134 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
3135 // Byte ranges found in Wikipedia articles with relevant search strings in each case
3136 unsigned char uch = static_cast<unsigned char>(ch);
3137 switch (codePage) {
3138 case 932:
3139 // Shift_jis
3140 return ((uch >= 0x81) && (uch <= 0x9F)) ||
3141 ((uch >= 0xE0) && (uch <= 0xEF));
3142 case 936:
3143 // GBK
3144 return (uch >= 0x81) && (uch <= 0xFE);
3145 case 949:
3146 // Korean Wansung KS C-5601-1987
3147 return (uch >= 0x81) && (uch <= 0xFE);
3148 case 950:
3149 // Big5
3150 return (uch >= 0x81) && (uch <= 0xFE);
3151 case 1361:
3152 // Korean Johab KS C-5601-1992
3153 return
3154 ((uch >= 0x84) && (uch <= 0xD3)) ||
3155 ((uch >= 0xD8) && (uch <= 0xDE)) ||
3156 ((uch >= 0xE0) && (uch <= 0xF9));
3158 return false;
3161 int Platform::DBCSCharLength(int codePage, const char *s) {
3162 if (codePage == 932 || codePage == 936 || codePage == 949 ||
3163 codePage == 950 || codePage == 1361) {
3164 return Platform::IsDBCSLeadByte(codePage, s[0]) ? 2 : 1;
3165 } else {
3166 return 1;
3170 int Platform::DBCSCharMaxLength() {
3171 return 2;
3174 // These are utility functions not really tied to a platform
3176 int Platform::Minimum(int a, int b) {
3177 if (a < b)
3178 return a;
3179 else
3180 return b;
3183 int Platform::Maximum(int a, int b) {
3184 if (a > b)
3185 return a;
3186 else
3187 return b;
3190 //#define TRACE
3192 #ifdef TRACE
3193 void Platform::DebugPrintf(const char *format, ...) {
3194 char buffer[2000];
3195 va_list pArguments;
3196 va_start(pArguments, format);
3197 vsprintf(buffer,format,pArguments);
3198 va_end(pArguments);
3199 Platform::DebugDisplay(buffer);
3201 #else
3202 void Platform::DebugPrintf(const char *, ...) {
3204 #endif
3206 static bool assertionPopUps = true;
3208 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3209 bool ret = assertionPopUps;
3210 assertionPopUps = assertionPopUps_;
3211 return ret;
3214 void Platform::Assert(const char *c, const char *file, int line) {
3215 char buffer[2000];
3216 sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
3217 if (assertionPopUps) {
3218 int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
3219 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
3220 if (idButton == IDRETRY) {
3221 ::DebugBreak();
3222 } else if (idButton == IDIGNORE) {
3223 // all OK
3224 } else {
3225 abort();
3227 } else {
3228 strcat(buffer, "\r\n");
3229 Platform::DebugDisplay(buffer);
3230 ::DebugBreak();
3231 abort();
3235 int Platform::Clamp(int val, int minVal, int maxVal) {
3236 if (val > maxVal)
3237 val = maxVal;
3238 if (val < minVal)
3239 val = minVal;
3240 return val;
3243 #ifdef _MSC_VER
3244 // GetVersionEx has been deprecated fro Windows 8.1 but called here to determine if Windows 9x.
3245 // Too dangerous to find alternate check.
3246 #pragma warning(disable: 4996)
3247 #endif
3249 void Platform_Initialise(void *hInstance) {
3250 OSVERSIONINFO osv = {sizeof(OSVERSIONINFO),0,0,0,0,TEXT("")};
3251 ::GetVersionEx(&osv);
3252 onNT = osv.dwPlatformId == VER_PLATFORM_WIN32_NT;
3253 ::InitializeCriticalSection(&crPlatformLock);
3254 hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
3255 // This may be called from DllMain, in which case the call to LoadLibrary
3256 // is bad because it can upset the DLL load order.
3257 if (!hDLLImage) {
3258 hDLLImage = ::LoadLibrary(TEXT("Msimg32"));
3260 if (hDLLImage) {
3261 AlphaBlendFn = (AlphaBlendSig)::GetProcAddress(hDLLImage, "AlphaBlend");
3263 if (!hDLLUser32) {
3264 hDLLUser32 = ::LoadLibrary(TEXT("User32"));
3266 if (hDLLUser32) {
3267 MonitorFromPointFn = (MonitorFromPointSig)::GetProcAddress(hDLLUser32, "MonitorFromPoint");
3268 MonitorFromRectFn = (MonitorFromRectSig)::GetProcAddress(hDLLUser32, "MonitorFromRect");
3269 GetMonitorInfoFn = (GetMonitorInfoSig)::GetProcAddress(hDLLUser32, "GetMonitorInfoA");
3272 ListBoxX_Register();
3275 #ifdef _MSC_VER
3276 #pragma warning(default: 4996)
3277 #endif
3279 void Platform_Finalise() {
3280 #if defined(USE_D2D)
3281 if (defaultRenderingParams) {
3282 defaultRenderingParams->Release();
3283 defaultRenderingParams = 0;
3285 if (customClearTypeRenderingParams) {
3286 customClearTypeRenderingParams->Release();
3287 customClearTypeRenderingParams = 0;
3289 #endif
3290 if (reverseArrowCursor != NULL)
3291 ::DestroyCursor(reverseArrowCursor);
3292 ListBoxX_Unregister();
3293 ::DeleteCriticalSection(&crPlatformLock);
3296 #ifdef SCI_NAMESPACE
3298 #endif