upgraded to scintilla 3.2.0
[TortoiseGit.git] / ext / scintilla / win32 / PlatWin.cxx
blob03efe7994ee7e4562aa4d7c3ae7aa114869fe687
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 <ctype.h>
11 #include <stdarg.h>
12 #include <stdio.h>
13 #include <time.h>
14 #include <limits.h>
15 #include <math.h>
17 #include <vector>
18 #include <map>
20 #undef _WIN32_WINNT
21 #define _WIN32_WINNT 0x0500
22 #include <windows.h>
23 #include <commctrl.h>
24 #include <richedit.h>
25 #include <windowsx.h>
27 #if defined(_MSC_VER) && (_MSC_VER > 1200)
28 #define USE_D2D 1
29 #endif
31 #if defined(USE_D2D)
32 #include <d2d1.h>
33 #include <dwrite.h>
34 #endif
36 #include "Platform.h"
37 #include "UniConversion.h"
38 #include "XPM.h"
39 #include "FontQuality.h"
41 // We want to use multi monitor functions, but via LoadLibrary etc
42 // Luckily microsoft has done the heavy lifting for us, so we'll just use their stub functions!
43 #if defined(_MSC_VER) && (_MSC_VER > 1200)
44 #define COMPILE_MULTIMON_STUBS
45 #include <MultiMon.h>
46 #endif
48 #ifndef IDC_HAND
49 #define IDC_HAND MAKEINTRESOURCE(32649)
50 #endif
52 // Take care of 32/64 bit pointers
53 #ifdef GetWindowLongPtr
54 static void *PointerFromWindow(HWND hWnd) {
55 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
57 static void SetWindowPointer(HWND hWnd, void *ptr) {
58 ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
60 #else
61 static void *PointerFromWindow(HWND hWnd) {
62 return reinterpret_cast<void *>(::GetWindowLong(hWnd, 0));
64 static void SetWindowPointer(HWND hWnd, void *ptr) {
65 ::SetWindowLong(hWnd, 0, reinterpret_cast<LONG>(ptr));
68 #ifndef GWLP_USERDATA
69 #define GWLP_USERDATA GWL_USERDATA
70 #endif
72 #ifndef GWLP_WNDPROC
73 #define GWLP_WNDPROC GWL_WNDPROC
74 #endif
76 #ifndef LONG_PTR
77 #define LONG_PTR LONG
78 #endif
80 static LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong) {
81 return ::SetWindowLong(hWnd, nIndex, dwNewLong);
84 static LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex) {
85 return ::GetWindowLong(hWnd, nIndex);
87 #endif
89 typedef BOOL (WINAPI *AlphaBlendSig)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
91 static CRITICAL_SECTION crPlatformLock;
92 static HINSTANCE hinstPlatformRes = 0;
93 static bool onNT = false;
94 static HMODULE hDLLImage = 0;
95 static AlphaBlendSig AlphaBlendFn = 0;
96 static HCURSOR reverseArrowCursor = NULL;
98 bool IsNT() {
99 return onNT;
102 #ifdef SCI_NAMESPACE
103 using namespace Scintilla;
104 #endif
106 Point Point::FromLong(long lpoint) {
107 return Point(static_cast<short>(LOWORD(lpoint)), static_cast<short>(HIWORD(lpoint)));
110 static RECT RectFromPRectangle(PRectangle prc) {
111 RECT rc = {static_cast<LONG>(prc.left), static_cast<LONG>(prc.top),
112 static_cast<LONG>(prc.right), static_cast<LONG>(prc.bottom)};
113 return rc;
116 #if defined(USE_D2D)
117 IDWriteFactory *pIDWriteFactory = 0;
118 ID2D1Factory *pD2DFactory = 0;
120 bool LoadD2D() {
121 static bool triedLoadingD2D = false;
122 static HMODULE hDLLD2D = 0;
123 static HMODULE hDLLDWrite = 0;
124 if (!triedLoadingD2D) {
125 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
126 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
127 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
128 IUnknown **factory);
130 hDLLD2D = ::LoadLibrary(TEXT("D2D1.DLL"));
131 if (hDLLD2D) {
132 D2D1CFSig fnD2DCF = (D2D1CFSig)::GetProcAddress(hDLLD2D, "D2D1CreateFactory");
133 if (fnD2DCF) {
134 // A single threaded factory as Scintilla always draw on the GUI thread
135 fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
136 __uuidof(ID2D1Factory),
138 reinterpret_cast<IUnknown**>(&pD2DFactory));
141 hDLLDWrite = ::LoadLibrary(TEXT("DWRITE.DLL"));
142 if (hDLLDWrite) {
143 DWriteCFSig fnDWCF = (DWriteCFSig)::GetProcAddress(hDLLDWrite, "DWriteCreateFactory");
144 if (fnDWCF) {
145 fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
146 __uuidof(IDWriteFactory),
147 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
151 triedLoadingD2D = true;
152 return pIDWriteFactory && pD2DFactory;
154 #endif
156 struct FormatAndMetrics {
157 int technology;
158 HFONT hfont;
159 #if defined(USE_D2D)
160 IDWriteTextFormat *pTextFormat;
161 #endif
162 int extraFontFlag;
163 FLOAT yAscent;
164 FLOAT yDescent;
165 FLOAT yInternalLeading;
166 FormatAndMetrics(HFONT hfont_, int extraFontFlag_) :
167 technology(SCWIN_TECH_GDI), hfont(hfont_),
168 #if defined(USE_D2D)
169 pTextFormat(0),
170 #endif
171 extraFontFlag(extraFontFlag_), yAscent(2), yDescent(1), yInternalLeading(0) {
173 #if defined(USE_D2D)
174 FormatAndMetrics(IDWriteTextFormat *pTextFormat_, int extraFontFlag_, FLOAT yAscent_, FLOAT yDescent_, FLOAT yInternalLeading_) :
175 technology(SCWIN_TECH_DIRECTWRITE), hfont(0), pTextFormat(pTextFormat_), extraFontFlag(extraFontFlag_), yAscent(yAscent_), yDescent(yDescent_), yInternalLeading(yInternalLeading_) {
177 #endif
178 ~FormatAndMetrics() {
179 if (hfont)
180 ::DeleteObject(hfont);
181 #if defined(USE_D2D)
182 if (pTextFormat)
183 pTextFormat->Release();
184 pTextFormat = 0;
185 #endif
186 extraFontFlag = 0;
187 yAscent = 2;
188 yDescent = 1;
189 yInternalLeading = 0;
191 HFONT HFont();
194 HFONT FormatAndMetrics::HFont() {
195 LOGFONTW lf;
196 memset(&lf, 0, sizeof(lf));
197 #if defined(USE_D2D)
198 if (technology == SCWIN_TECH_GDI) {
199 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
200 return 0;
202 } else {
203 HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
204 if (!SUCCEEDED(hr)) {
205 return 0;
207 lf.lfWeight = pTextFormat->GetFontWeight();
208 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
209 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
211 #else
212 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
213 return 0;
215 #endif
216 return ::CreateFontIndirectW(&lf);
219 #ifndef CLEARTYPE_QUALITY
220 #define CLEARTYPE_QUALITY 5
221 #endif
223 static BYTE Win32MapFontQuality(int extraFontFlag) {
224 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
226 case SC_EFF_QUALITY_NON_ANTIALIASED:
227 return NONANTIALIASED_QUALITY;
229 case SC_EFF_QUALITY_ANTIALIASED:
230 return ANTIALIASED_QUALITY;
232 case SC_EFF_QUALITY_LCD_OPTIMIZED:
233 return CLEARTYPE_QUALITY;
235 default:
236 return SC_EFF_QUALITY_DEFAULT;
240 #if defined(USE_D2D)
241 static D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) {
242 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
244 case SC_EFF_QUALITY_NON_ANTIALIASED:
245 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
247 case SC_EFF_QUALITY_ANTIALIASED:
248 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
250 case SC_EFF_QUALITY_LCD_OPTIMIZED:
251 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
253 default:
254 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
257 #endif
259 static void SetLogFont(LOGFONTA &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) {
260 memset(&lf, 0, sizeof(lf));
261 // The negative is to allow for leading
262 lf.lfHeight = -(abs(static_cast<int>(size + 0.5)));
263 lf.lfWeight = weight;
264 lf.lfItalic = static_cast<BYTE>(italic ? 1 : 0);
265 lf.lfCharSet = static_cast<BYTE>(characterSet);
266 lf.lfQuality = Win32MapFontQuality(extraFontFlag);
267 strncpy(lf.lfFaceName, faceName, sizeof(lf.lfFaceName));
271 * Create a hash from the parameters for a font to allow easy checking for identity.
272 * If one font is the same as another, its hash will be the same, but if the hash is the
273 * same then they may still be different.
275 static int HashFont(const FontParameters &fp) {
276 return
277 static_cast<int>(fp.size) ^
278 (fp.characterSet << 10) ^
279 ((fp.extraFontFlag & SC_EFF_QUALITY_MASK) << 9) ^
280 ((fp.weight/100) << 12) ^
281 (fp.italic ? 0x20000000 : 0) ^
282 (fp.technology << 15) ^
283 fp.faceName[0];
286 class FontCached : Font {
287 FontCached *next;
288 int usage;
289 float size;
290 LOGFONTA lf;
291 int technology;
292 int hash;
293 FontCached(const FontParameters &fp);
294 ~FontCached() {}
295 bool SameAs(const FontParameters &fp);
296 virtual void Release();
298 static FontCached *first;
299 public:
300 static FontID FindOrCreate(const FontParameters &fp);
301 static void ReleaseId(FontID fid_);
304 FontCached *FontCached::first = 0;
306 FontCached::FontCached(const FontParameters &fp) :
307 next(0), usage(0), size(1.0), hash(0) {
308 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
309 technology = fp.technology;
310 hash = HashFont(fp);
311 fid = 0;
312 if (technology == SCWIN_TECH_GDI) {
313 HFONT hfont = ::CreateFontIndirectA(&lf);
314 fid = reinterpret_cast<void *>(new FormatAndMetrics(hfont, fp.extraFontFlag));
315 } else {
316 #if defined(USE_D2D)
317 IDWriteTextFormat *pTextFormat;
318 const int faceSize = 200;
319 WCHAR wszFace[faceSize];
320 UTF16FromUTF8(fp.faceName, static_cast<unsigned int>(strlen(fp.faceName))+1, wszFace, faceSize);
321 FLOAT fHeight = fp.size;
322 DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
323 HRESULT hr = pIDWriteFactory->CreateTextFormat(wszFace, NULL,
324 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
325 style,
326 DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
327 if (SUCCEEDED(hr)) {
328 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
330 const int maxLines = 2;
331 DWRITE_LINE_METRICS lineMetrics[maxLines];
332 UINT32 lineCount = 0;
333 FLOAT yAscent = 1.0f;
334 FLOAT yDescent = 1.0f;
335 FLOAT yInternalLeading = 0.0f;
336 IDWriteTextLayout *pTextLayout = 0;
337 hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
338 100.0f, 100.0f, &pTextLayout);
339 if (SUCCEEDED(hr)) {
340 hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
341 if (SUCCEEDED(hr)) {
342 yAscent = lineMetrics[0].baseline;
343 yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
345 FLOAT emHeight;
346 hr = pTextLayout->GetFontSize(0, &emHeight);
347 if (SUCCEEDED(hr)) {
348 yInternalLeading = lineMetrics[0].height - emHeight;
351 pTextLayout->Release();
353 fid = reinterpret_cast<void *>(new FormatAndMetrics(pTextFormat, fp.extraFontFlag, yAscent, yDescent, yInternalLeading));
355 #endif
357 usage = 1;
360 bool FontCached::SameAs(const FontParameters &fp) {
361 return
362 (size == fp.size) &&
363 (lf.lfWeight == fp.weight) &&
364 (lf.lfItalic == static_cast<BYTE>(fp.italic ? 1 : 0)) &&
365 (lf.lfCharSet == fp.characterSet) &&
366 (lf.lfQuality == Win32MapFontQuality(fp.extraFontFlag)) &&
367 (technology == fp.technology) &&
368 0 == strcmp(lf.lfFaceName,fp.faceName);
371 void FontCached::Release() {
372 delete reinterpret_cast<FormatAndMetrics *>(fid);
373 fid = 0;
376 FontID FontCached::FindOrCreate(const FontParameters &fp) {
377 FontID ret = 0;
378 ::EnterCriticalSection(&crPlatformLock);
379 int hashFind = HashFont(fp);
380 for (FontCached *cur=first; cur; cur=cur->next) {
381 if ((cur->hash == hashFind) &&
382 cur->SameAs(fp)) {
383 cur->usage++;
384 ret = cur->fid;
387 if (ret == 0) {
388 FontCached *fc = new FontCached(fp);
389 if (fc) {
390 fc->next = first;
391 first = fc;
392 ret = fc->fid;
395 ::LeaveCriticalSection(&crPlatformLock);
396 return ret;
399 void FontCached::ReleaseId(FontID fid_) {
400 ::EnterCriticalSection(&crPlatformLock);
401 FontCached **pcur=&first;
402 for (FontCached *cur=first; cur; cur=cur->next) {
403 if (cur->fid == fid_) {
404 cur->usage--;
405 if (cur->usage == 0) {
406 *pcur = cur->next;
407 cur->Release();
408 cur->next = 0;
409 delete cur;
411 break;
413 pcur=&cur->next;
415 ::LeaveCriticalSection(&crPlatformLock);
418 Font::Font() {
419 fid = 0;
422 Font::~Font() {
425 #define FONTS_CACHED
427 void Font::Create(const FontParameters &fp) {
428 Release();
429 if (fp.faceName)
430 fid = FontCached::FindOrCreate(fp);
433 void Font::Release() {
434 if (fid)
435 FontCached::ReleaseId(fid);
436 fid = 0;
439 // Buffer to hold strings and string position arrays without always allocating on heap.
440 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
441 // when less than safe size otherwise allocate on heap and free automatically.
442 template<typename T, int lengthStandard>
443 class VarBuffer {
444 T bufferStandard[lengthStandard];
445 public:
446 T *buffer;
447 VarBuffer(size_t length) : buffer(0) {
448 if (length > lengthStandard) {
449 buffer = new T[length];
450 } else {
451 buffer = bufferStandard;
454 ~VarBuffer() {
455 if (buffer != bufferStandard) {
456 delete []buffer;
457 buffer = 0;
462 const int stackBufferLength = 10000;
463 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
464 public:
465 int tlen;
466 TextWide(const char *s, int len, bool unicodeMode, int codePage=0) :
467 VarBuffer<wchar_t, stackBufferLength>(len) {
468 if (unicodeMode) {
469 tlen = UTF16FromUTF8(s, len, buffer, len);
470 } else {
471 // Support Asian string display in 9x English
472 tlen = ::MultiByteToWideChar(codePage, 0, s, len, buffer, len);
476 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
478 #ifdef SCI_NAMESPACE
479 namespace Scintilla {
480 #endif
482 class SurfaceGDI : public Surface {
483 bool unicodeMode;
484 HDC hdc;
485 bool hdcOwned;
486 HPEN pen;
487 HPEN penOld;
488 HBRUSH brush;
489 HBRUSH brushOld;
490 HFONT font;
491 HFONT fontOld;
492 HBITMAP bitmap;
493 HBITMAP bitmapOld;
494 int maxWidthMeasure;
495 int maxLenText;
497 int codePage;
498 // If 9x OS and current code page is same as ANSI code page.
499 bool win9xACPSame;
501 void BrushColor(ColourDesired back);
502 void SetFont(Font &font_);
504 // Private so SurfaceGDI objects can not be copied
505 SurfaceGDI(const SurfaceGDI &);
506 SurfaceGDI &operator=(const SurfaceGDI &);
507 public:
508 SurfaceGDI();
509 virtual ~SurfaceGDI();
511 void Init(WindowID wid);
512 void Init(SurfaceID sid, WindowID wid);
513 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
515 void Release();
516 bool Initialised();
517 void PenColour(ColourDesired fore);
518 int LogPixelsY();
519 int DeviceHeightFont(int points);
520 void MoveTo(int x_, int y_);
521 void LineTo(int x_, int y_);
522 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
523 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
524 void FillRectangle(PRectangle rc, ColourDesired back);
525 void FillRectangle(PRectangle rc, Surface &surfacePattern);
526 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
527 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
528 ColourDesired outline, int alphaOutline, int flags);
529 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
530 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
531 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
533 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
534 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
535 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
536 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
537 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
538 XYPOSITION WidthText(Font &font_, const char *s, int len);
539 XYPOSITION WidthChar(Font &font_, char ch);
540 XYPOSITION Ascent(Font &font_);
541 XYPOSITION Descent(Font &font_);
542 XYPOSITION InternalLeading(Font &font_);
543 XYPOSITION ExternalLeading(Font &font_);
544 XYPOSITION Height(Font &font_);
545 XYPOSITION AverageCharWidth(Font &font_);
547 void SetClip(PRectangle rc);
548 void FlushCachedState();
550 void SetUnicodeMode(bool unicodeMode_);
551 void SetDBCSMode(int codePage_);
554 #ifdef SCI_NAMESPACE
555 } //namespace Scintilla
556 #endif
558 SurfaceGDI::SurfaceGDI() :
559 unicodeMode(false),
560 hdc(0), hdcOwned(false),
561 pen(0), penOld(0),
562 brush(0), brushOld(0),
563 font(0), fontOld(0),
564 bitmap(0), bitmapOld(0) {
565 // Windows 9x has only a 16 bit coordinate system so break after 30000 pixels
566 maxWidthMeasure = IsNT() ? INT_MAX : 30000;
567 // There appears to be a 16 bit string length limit in GDI on NT and a limit of
568 // 8192 characters on Windows 95.
569 maxLenText = IsNT() ? 65535 : 8192;
571 codePage = 0;
572 win9xACPSame = false;
575 SurfaceGDI::~SurfaceGDI() {
576 Release();
579 void SurfaceGDI::Release() {
580 if (penOld) {
581 ::SelectObject(reinterpret_cast<HDC>(hdc), penOld);
582 ::DeleteObject(pen);
583 penOld = 0;
585 pen = 0;
586 if (brushOld) {
587 ::SelectObject(reinterpret_cast<HDC>(hdc), brushOld);
588 ::DeleteObject(brush);
589 brushOld = 0;
591 brush = 0;
592 if (fontOld) {
593 // Fonts are not deleted as they are owned by a Font object
594 ::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);
595 fontOld = 0;
597 font = 0;
598 if (bitmapOld) {
599 ::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);
600 ::DeleteObject(bitmap);
601 bitmapOld = 0;
603 bitmap = 0;
604 if (hdcOwned) {
605 ::DeleteDC(reinterpret_cast<HDC>(hdc));
606 hdc = 0;
607 hdcOwned = false;
611 bool SurfaceGDI::Initialised() {
612 return hdc != 0;
615 void SurfaceGDI::Init(WindowID) {
616 Release();
617 hdc = ::CreateCompatibleDC(NULL);
618 hdcOwned = true;
619 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
622 void SurfaceGDI::Init(SurfaceID sid, WindowID) {
623 Release();
624 hdc = reinterpret_cast<HDC>(sid);
625 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
628 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID) {
629 Release();
630 hdc = ::CreateCompatibleDC(static_cast<SurfaceGDI *>(surface_)->hdc);
631 hdcOwned = true;
632 bitmap = ::CreateCompatibleBitmap(static_cast<SurfaceGDI *>(surface_)->hdc, width, height);
633 bitmapOld = static_cast<HBITMAP>(::SelectObject(hdc, bitmap));
634 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
637 void SurfaceGDI::PenColour(ColourDesired fore) {
638 if (pen) {
639 ::SelectObject(hdc, penOld);
640 ::DeleteObject(pen);
641 pen = 0;
642 penOld = 0;
644 pen = ::CreatePen(0,1,fore.AsLong());
645 penOld = static_cast<HPEN>(::SelectObject(reinterpret_cast<HDC>(hdc), pen));
648 void SurfaceGDI::BrushColor(ColourDesired back) {
649 if (brush) {
650 ::SelectObject(hdc, brushOld);
651 ::DeleteObject(brush);
652 brush = 0;
653 brushOld = 0;
655 // Only ever want pure, non-dithered brushes
656 ColourDesired colourNearest = ::GetNearestColor(hdc, back.AsLong());
657 brush = ::CreateSolidBrush(colourNearest.AsLong());
658 brushOld = static_cast<HBRUSH>(::SelectObject(hdc, brush));
661 void SurfaceGDI::SetFont(Font &font_) {
662 if (font_.GetID() != font) {
663 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
664 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI);
665 if (fontOld) {
666 ::SelectObject(hdc, pfm->hfont);
667 } else {
668 fontOld = static_cast<HFONT>(::SelectObject(hdc, pfm->hfont));
670 font = reinterpret_cast<HFONT>(pfm->hfont);
674 int SurfaceGDI::LogPixelsY() {
675 return ::GetDeviceCaps(hdc, LOGPIXELSY);
678 int SurfaceGDI::DeviceHeightFont(int points) {
679 return ::MulDiv(points, LogPixelsY(), 72);
682 void SurfaceGDI::MoveTo(int x_, int y_) {
683 ::MoveToEx(hdc, x_, y_, 0);
686 void SurfaceGDI::LineTo(int x_, int y_) {
687 ::LineTo(hdc, x_, y_);
690 void SurfaceGDI::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
691 PenColour(fore);
692 BrushColor(back);
693 std::vector<POINT> outline;
694 for (int i=0;i<npts;i++) {
695 POINT pt = {static_cast<LONG>(pts[i].x), static_cast<LONG>(pts[i].y)};
696 outline.push_back(pt);
698 ::Polygon(hdc, &outline[0], npts);
701 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
702 PenColour(fore);
703 BrushColor(back);
704 ::Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
707 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) {
708 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
709 // There is no need to allocate a brush either.
710 RECT rcw = RectFromPRectangle(rc);
711 ::SetBkColor(hdc, back.AsLong());
712 ::ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rcw, TEXT(""), 0, NULL);
715 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
716 HBRUSH br;
717 if (static_cast<SurfaceGDI &>(surfacePattern).bitmap)
718 br = ::CreatePatternBrush(static_cast<SurfaceGDI &>(surfacePattern).bitmap);
719 else // Something is wrong so display in red
720 br = ::CreateSolidBrush(RGB(0xff, 0, 0));
721 RECT rcw = RectFromPRectangle(rc);
722 ::FillRect(hdc, &rcw, br);
723 ::DeleteObject(br);
726 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
727 PenColour(fore);
728 BrushColor(back);
729 ::RoundRect(hdc,
730 rc.left + 1, rc.top,
731 rc.right - 1, rc.bottom,
732 8, 8);
735 // Plot a point into a DWORD buffer symetrically to all 4 qudrants
736 static void AllFour(DWORD *pixels, int width, int height, int x, int y, DWORD val) {
737 pixels[y*width+x] = val;
738 pixels[y*width+width-1-x] = val;
739 pixels[(height-1-y)*width+x] = val;
740 pixels[(height-1-y)*width+width-1-x] = val;
743 #ifndef AC_SRC_OVER
744 #define AC_SRC_OVER 0x00
745 #endif
746 #ifndef AC_SRC_ALPHA
747 #define AC_SRC_ALPHA 0x01
748 #endif
750 static DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) {
751 union {
752 byte pixVal[4];
753 DWORD val;
754 } converter;
755 converter.pixVal[0] = b;
756 converter.pixVal[1] = g;
757 converter.pixVal[2] = r;
758 converter.pixVal[3] = a;
759 return converter.val;
762 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
763 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
764 if (AlphaBlendFn && rc.Width() > 0) {
765 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
766 int width = rc.Width();
767 int height = rc.Height();
768 // Ensure not distorted too much by corners when small
769 cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
770 BITMAPINFO bpih = {sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0};
771 void *image = 0;
772 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
773 DIB_RGB_COLORS, &image, NULL, 0);
775 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
777 DWORD valEmpty = dwordFromBGRA(0,0,0,0);
778 DWORD valFill = dwordFromBGRA(
779 static_cast<byte>(GetBValue(fill.AsLong()) * alphaFill / 255),
780 static_cast<byte>(GetGValue(fill.AsLong()) * alphaFill / 255),
781 static_cast<byte>(GetRValue(fill.AsLong()) * alphaFill / 255),
782 static_cast<byte>(alphaFill));
783 DWORD valOutline = dwordFromBGRA(
784 static_cast<byte>(GetBValue(outline.AsLong()) * alphaOutline / 255),
785 static_cast<byte>(GetGValue(outline.AsLong()) * alphaOutline / 255),
786 static_cast<byte>(GetRValue(outline.AsLong()) * alphaOutline / 255),
787 static_cast<byte>(alphaOutline));
788 DWORD *pixels = reinterpret_cast<DWORD *>(image);
789 for (int y=0; y<height; y++) {
790 for (int x=0; x<width; x++) {
791 if ((x==0) || (x==width-1) || (y == 0) || (y == height-1)) {
792 pixels[y*width+x] = valOutline;
793 } else {
794 pixels[y*width+x] = valFill;
798 for (int c=0;c<cornerSize; c++) {
799 for (int x=0;x<c+1; x++) {
800 AllFour(pixels, width, height, x, c-x, valEmpty);
803 for (int x=1;x<cornerSize; x++) {
804 AllFour(pixels, width, height, x, cornerSize-x, valOutline);
807 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
809 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, width, height, hMemDC, 0, 0, width, height, merge);
811 SelectBitmap(hMemDC, hbmOld);
812 ::DeleteObject(hbmMem);
813 ::DeleteDC(hMemDC);
814 } else {
815 BrushColor(outline);
816 RECT rcw = RectFromPRectangle(rc);
817 FrameRect(hdc, &rcw, brush);
821 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
822 if (AlphaBlendFn && rc.Width() > 0) {
823 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
824 if (rc.Width() > width)
825 rc.left += static_cast<int>((rc.Width() - width) / 2);
826 rc.right = rc.left + width;
827 if (rc.Height() > height)
828 rc.top += static_cast<int>((rc.Height() - height) / 2);
829 rc.bottom = rc.top + height;
831 BITMAPINFO bpih = {sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0};
832 unsigned char *image = 0;
833 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
834 DIB_RGB_COLORS, reinterpret_cast<void **>(&image), NULL, 0);
835 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
837 for (int y=height-1; y>=0; y--) {
838 for (int x=0; x<width; x++) {
839 unsigned char *pixel = image + (y*width+x) * 4;
840 unsigned char alpha = pixelsImage[3];
841 // Input is RGBA, output is BGRA with premultiplied alpha
842 pixel[2] = (*pixelsImage++) * alpha / 255;
843 pixel[1] = (*pixelsImage++) * alpha / 255;
844 pixel[0] = (*pixelsImage++) * alpha / 255;
845 pixel[3] = *pixelsImage++;
849 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
851 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, rc.Width(), rc.Height(), hMemDC, 0, 0, width, height, merge);
853 SelectBitmap(hMemDC, hbmOld);
854 ::DeleteObject(hbmMem);
855 ::DeleteDC(hMemDC);
860 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
861 PenColour(fore);
862 BrushColor(back);
863 ::Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);
866 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
867 ::BitBlt(hdc,
868 rc.left, rc.top, rc.Width(), rc.Height(),
869 static_cast<SurfaceGDI &>(surfaceSource).hdc, from.x, from.y, SRCCOPY);
872 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
874 void SurfaceGDI::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
875 SetFont(font_);
876 RECT rcw = RectFromPRectangle(rc);
877 SIZE sz={0,0};
878 int pos = 0;
879 int x = rc.left;
881 // Text drawing may fail if the text is too big.
882 // If it does fail, slice up into segments and draw each segment.
883 const int maxSegmentLength = 0x200;
885 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
886 // Use ANSI calls
887 int lenDraw = Platform::Minimum(len, maxLenText);
888 if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s, lenDraw, NULL)) {
889 while (lenDraw > pos) {
890 int seglen = Platform::Minimum(maxSegmentLength, lenDraw - pos);
891 if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s+pos, seglen, NULL)) {
892 PLATFORM_ASSERT(false);
893 return;
895 ::GetTextExtentPoint32A(hdc, s+pos, seglen, &sz);
896 x += sz.cx;
897 pos += seglen;
900 } else {
901 // Use Unicode calls
902 const TextWide tbuf(s, len, unicodeMode, codePage);
903 if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, NULL)) {
904 while (tbuf.tlen > pos) {
905 int seglen = Platform::Minimum(maxSegmentLength, tbuf.tlen - pos);
906 if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf.buffer+pos, seglen, NULL)) {
907 PLATFORM_ASSERT(false);
908 return;
910 ::GetTextExtentPoint32W(hdc, tbuf.buffer+pos, seglen, &sz);
911 x += sz.cx;
912 pos += seglen;
918 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
919 ColourDesired fore, ColourDesired back) {
920 ::SetTextColor(hdc, fore.AsLong());
921 ::SetBkColor(hdc, back.AsLong());
922 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
925 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
926 ColourDesired fore, ColourDesired back) {
927 ::SetTextColor(hdc, fore.AsLong());
928 ::SetBkColor(hdc, back.AsLong());
929 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
932 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
933 ColourDesired fore) {
934 // Avoid drawing spaces in transparent mode
935 for (int i=0;i<len;i++) {
936 if (s[i] != ' ') {
937 ::SetTextColor(hdc, fore.AsLong());
938 ::SetBkMode(hdc, TRANSPARENT);
939 DrawTextCommon(rc, font_, ybase, s, len, 0);
940 ::SetBkMode(hdc, OPAQUE);
941 return;
946 XYPOSITION SurfaceGDI::WidthText(Font &font_, const char *s, int len) {
947 SetFont(font_);
948 SIZE sz={0,0};
949 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
950 ::GetTextExtentPoint32A(hdc, s, Platform::Minimum(len, maxLenText), &sz);
951 } else {
952 const TextWide tbuf(s, len, unicodeMode, codePage);
953 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
955 return sz.cx;
958 void SurfaceGDI::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
959 SetFont(font_);
960 SIZE sz={0,0};
961 int fit = 0;
962 if (unicodeMode) {
963 const TextWide tbuf(s, len, unicodeMode, codePage);
964 TextPositionsI poses(tbuf.tlen);
965 fit = tbuf.tlen;
966 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
967 // Likely to have failed because on Windows 9x where function not available
968 // So measure the character widths by measuring each initial substring
969 // Turns a linear operation into a qudratic but seems fast enough on test files
970 for (int widthSS=0; widthSS < tbuf.tlen; widthSS++) {
971 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
972 poses.buffer[widthSS] = sz.cx;
975 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
976 int ui=0;
977 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
978 int i=0;
979 while (ui<fit) {
980 unsigned char uch = us[i];
981 unsigned int lenChar = 1;
982 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
983 lenChar = 4;
984 ui++;
985 } else if (uch >= (0x80 + 0x40 + 0x20)) {
986 lenChar = 3;
987 } else if (uch >= (0x80)) {
988 lenChar = 2;
990 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
991 positions[i++] = poses.buffer[ui];
993 ui++;
995 int lastPos = 0;
996 if (i > 0)
997 lastPos = positions[i-1];
998 while (i<len) {
999 positions[i++] = lastPos;
1001 } else if (IsNT() || (codePage==0) || win9xACPSame) {
1002 // Zero positions to avoid random behaviour on failure.
1003 memset(positions, 0, len * sizeof(*positions));
1004 // len may be larger than platform supports so loop over segments small enough for platform
1005 int startOffset = 0;
1006 while (len > 0) {
1007 int lenBlock = Platform::Minimum(len, maxLenText);
1008 TextPositionsI poses(len);
1009 if (!::GetTextExtentExPointA(hdc, s, lenBlock, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1010 // Eeek - a NULL DC or other foolishness could cause this.
1011 return;
1012 } else if (fit < lenBlock) {
1013 // For some reason, such as an incomplete DBCS character
1014 // Not all the positions are filled in so make them equal to end.
1015 if (fit == 0)
1016 poses.buffer[fit++] = 0;
1017 for (int i = fit;i<lenBlock;i++)
1018 poses.buffer[i] = poses.buffer[fit-1];
1020 for (int i=0;i<lenBlock;i++)
1021 positions[i] = poses.buffer[i] + startOffset;
1022 startOffset = poses.buffer[lenBlock-1];
1023 len -= lenBlock;
1024 positions += lenBlock;
1025 s += lenBlock;
1027 } else {
1028 // Support Asian string display in 9x English
1029 const TextWide tbuf(s, len, unicodeMode, codePage);
1030 TextPositionsI poses(tbuf.tlen);
1031 for (int widthSS=0; widthSS<tbuf.tlen; widthSS++) {
1032 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
1033 poses.buffer[widthSS] = sz.cx;
1036 int ui = 0;
1037 for (int i=0;i<len;) {
1038 if (::IsDBCSLeadByteEx(codePage, s[i])) {
1039 positions[i] = poses.buffer[ui];
1040 positions[i+1] = poses.buffer[ui];
1041 i += 2;
1042 } else {
1043 positions[i] = poses.buffer[ui];
1044 i++;
1047 ui++;
1052 XYPOSITION SurfaceGDI::WidthChar(Font &font_, char ch) {
1053 SetFont(font_);
1054 SIZE sz;
1055 ::GetTextExtentPoint32A(hdc, &ch, 1, &sz);
1056 return sz.cx;
1059 XYPOSITION SurfaceGDI::Ascent(Font &font_) {
1060 SetFont(font_);
1061 TEXTMETRIC tm;
1062 ::GetTextMetrics(hdc, &tm);
1063 return tm.tmAscent;
1066 XYPOSITION SurfaceGDI::Descent(Font &font_) {
1067 SetFont(font_);
1068 TEXTMETRIC tm;
1069 ::GetTextMetrics(hdc, &tm);
1070 return tm.tmDescent;
1073 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) {
1074 SetFont(font_);
1075 TEXTMETRIC tm;
1076 ::GetTextMetrics(hdc, &tm);
1077 return tm.tmInternalLeading;
1080 XYPOSITION SurfaceGDI::ExternalLeading(Font &font_) {
1081 SetFont(font_);
1082 TEXTMETRIC tm;
1083 ::GetTextMetrics(hdc, &tm);
1084 return tm.tmExternalLeading;
1087 XYPOSITION SurfaceGDI::Height(Font &font_) {
1088 SetFont(font_);
1089 TEXTMETRIC tm;
1090 ::GetTextMetrics(hdc, &tm);
1091 return tm.tmHeight;
1094 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) {
1095 SetFont(font_);
1096 TEXTMETRIC tm;
1097 ::GetTextMetrics(hdc, &tm);
1098 return tm.tmAveCharWidth;
1101 void SurfaceGDI::SetClip(PRectangle rc) {
1102 ::IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
1105 void SurfaceGDI::FlushCachedState() {
1106 pen = 0;
1107 brush = 0;
1108 font = 0;
1111 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1112 unicodeMode=unicodeMode_;
1115 void SurfaceGDI::SetDBCSMode(int codePage_) {
1116 // No action on window as automatically handled by system.
1117 codePage = codePage_;
1118 win9xACPSame = !IsNT() && ((unsigned int)codePage == ::GetACP());
1121 #if defined(USE_D2D)
1123 #ifdef SCI_NAMESPACE
1124 namespace Scintilla {
1125 #endif
1127 class SurfaceD2D : public Surface {
1128 bool unicodeMode;
1129 int x, y;
1131 int codePage;
1133 ID2D1RenderTarget *pRenderTarget;
1134 bool ownRenderTarget;
1135 int clipsActive;
1137 IDWriteTextFormat *pTextFormat;
1138 FLOAT yAscent;
1139 FLOAT yDescent;
1140 FLOAT yInternalLeading;
1142 ID2D1SolidColorBrush *pBrush;
1144 int logPixelsY;
1145 float dpiScaleX;
1146 float dpiScaleY;
1148 void SetFont(Font &font_);
1150 // Private so SurfaceD2D objects can not be copied
1151 SurfaceD2D(const SurfaceD2D &);
1152 SurfaceD2D &operator=(const SurfaceD2D &);
1153 public:
1154 SurfaceD2D();
1155 virtual ~SurfaceD2D();
1157 void SetScale();
1158 void Init(WindowID wid);
1159 void Init(SurfaceID sid, WindowID wid);
1160 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
1162 void Release();
1163 bool Initialised();
1165 HRESULT FlushDrawing();
1167 void PenColour(ColourDesired fore);
1168 void D2DPenColour(ColourDesired fore, int alpha=255);
1169 int LogPixelsY();
1170 int DeviceHeightFont(int points);
1171 void MoveTo(int x_, int y_);
1172 void LineTo(int x_, int y_);
1173 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
1174 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
1175 void FillRectangle(PRectangle rc, ColourDesired back);
1176 void FillRectangle(PRectangle rc, Surface &surfacePattern);
1177 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
1178 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1179 ColourDesired outline, int alphaOutline, int flags);
1180 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
1181 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
1182 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
1184 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
1185 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1186 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1187 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
1188 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
1189 XYPOSITION WidthText(Font &font_, const char *s, int len);
1190 XYPOSITION WidthChar(Font &font_, char ch);
1191 XYPOSITION Ascent(Font &font_);
1192 XYPOSITION Descent(Font &font_);
1193 XYPOSITION InternalLeading(Font &font_);
1194 XYPOSITION ExternalLeading(Font &font_);
1195 XYPOSITION Height(Font &font_);
1196 XYPOSITION AverageCharWidth(Font &font_);
1198 void SetClip(PRectangle rc);
1199 void FlushCachedState();
1201 void SetUnicodeMode(bool unicodeMode_);
1202 void SetDBCSMode(int codePage_);
1205 #ifdef SCI_NAMESPACE
1206 } //namespace Scintilla
1207 #endif
1209 SurfaceD2D::SurfaceD2D() :
1210 unicodeMode(false),
1211 x(0), y(0) {
1213 codePage = 0;
1215 pRenderTarget = NULL;
1216 ownRenderTarget = false;
1217 clipsActive = 0;
1219 // From selected font
1220 pTextFormat = NULL;
1221 yAscent = 2;
1222 yDescent = 1;
1223 yInternalLeading = 0;
1225 pBrush = NULL;
1227 logPixelsY = 72;
1228 dpiScaleX = 1.0;
1229 dpiScaleY = 1.0;
1232 SurfaceD2D::~SurfaceD2D() {
1233 Release();
1236 void SurfaceD2D::Release() {
1237 if (pBrush) {
1238 pBrush->Release();
1239 pBrush = 0;
1241 if (pRenderTarget) {
1242 while (clipsActive) {
1243 pRenderTarget->PopAxisAlignedClip();
1244 clipsActive--;
1246 if (ownRenderTarget) {
1247 pRenderTarget->Release();
1249 pRenderTarget = 0;
1253 void SurfaceD2D::SetScale() {
1254 HDC hdcMeasure = ::CreateCompatibleDC(NULL);
1255 logPixelsY = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
1256 dpiScaleX = ::GetDeviceCaps(hdcMeasure, LOGPIXELSX) / 96.0f;
1257 dpiScaleY = logPixelsY / 96.0f;
1258 ::DeleteDC(hdcMeasure);
1261 bool SurfaceD2D::Initialised() {
1262 return pRenderTarget != 0;
1265 HRESULT SurfaceD2D::FlushDrawing() {
1266 return pRenderTarget->Flush();
1269 void SurfaceD2D::Init(WindowID /* wid */) {
1270 Release();
1271 SetScale();
1274 void SurfaceD2D::Init(SurfaceID sid, WindowID) {
1275 Release();
1276 SetScale();
1277 pRenderTarget = reinterpret_cast<ID2D1RenderTarget *>(sid);
1280 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID) {
1281 Release();
1282 SetScale();
1283 SurfaceD2D *psurfOther = static_cast<SurfaceD2D *>(surface_);
1284 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = NULL;
1285 D2D1_SIZE_F desiredSize = D2D1::SizeF(width, height);
1286 D2D1_PIXEL_FORMAT desiredFormat = psurfOther->pRenderTarget->GetPixelFormat();
1287 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1288 HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget(
1289 &desiredSize, NULL, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pCompatibleRenderTarget);
1290 if (SUCCEEDED(hr)) {
1291 pRenderTarget = pCompatibleRenderTarget;
1292 pRenderTarget->BeginDraw();
1293 ownRenderTarget = true;
1297 void SurfaceD2D::PenColour(ColourDesired fore) {
1298 D2DPenColour(fore);
1301 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) {
1302 if (pRenderTarget) {
1303 D2D_COLOR_F col;
1304 col.r = (fore.AsLong() & 0xff) / 255.0;
1305 col.g = ((fore.AsLong() & 0xff00) >> 8) / 255.0;
1306 col.b = (fore.AsLong() >> 16) / 255.0;
1307 col.a = alpha / 255.0;
1308 if (pBrush) {
1309 pBrush->SetColor(col);
1310 } else {
1311 HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1312 if (!SUCCEEDED(hr) && pBrush) {
1313 pBrush->Release();
1314 pBrush = 0;
1320 void SurfaceD2D::SetFont(Font &font_) {
1321 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
1322 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE);
1323 pTextFormat = pfm->pTextFormat;
1324 yAscent = pfm->yAscent;
1325 yDescent = pfm->yDescent;
1326 yInternalLeading = pfm->yInternalLeading;
1327 if (pRenderTarget) {
1328 pRenderTarget->SetTextAntialiasMode(DWriteMapFontQuality(pfm->extraFontFlag));
1332 int SurfaceD2D::LogPixelsY() {
1333 return logPixelsY;
1336 int SurfaceD2D::DeviceHeightFont(int points) {
1337 return ::MulDiv(points, LogPixelsY(), 72);
1340 void SurfaceD2D::MoveTo(int x_, int y_) {
1341 x = x_;
1342 y = y_;
1345 static int Delta(int difference) {
1346 if (difference < 0)
1347 return -1;
1348 else if (difference > 0)
1349 return 1;
1350 else
1351 return 0;
1354 static int RoundFloat(float f) {
1355 return int(f+0.5);
1358 void SurfaceD2D::LineTo(int x_, int y_) {
1359 if (pRenderTarget) {
1360 int xDiff = x_ - x;
1361 int xDelta = Delta(xDiff);
1362 int yDiff = y_ - y;
1363 int yDelta = Delta(yDiff);
1364 if ((xDiff == 0) || (yDiff == 0)) {
1365 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
1366 int xEnd = x_ - xDelta;
1367 int left = Platform::Minimum(x, xEnd);
1368 int width = abs(x - xEnd) + 1;
1369 int yEnd = y_ - yDelta;
1370 int top = Platform::Minimum(y, yEnd);
1371 int height = abs(y - yEnd) + 1;
1372 D2D1_RECT_F rectangle1 = D2D1::RectF(left, top, left+width, top+height);
1373 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1374 } else if ((abs(xDiff) == abs(yDiff))) {
1375 // 45 degree slope
1376 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5, y + 0.5),
1377 D2D1::Point2F(x_ + 0.5 - xDelta, y_ + 0.5 - yDelta), pBrush);
1378 } else {
1379 // Line has a different slope so difficult to avoid last pixel
1380 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5, y + 0.5),
1381 D2D1::Point2F(x_ + 0.5, y_ + 0.5), pBrush);
1383 x = x_;
1384 y = y_;
1388 void SurfaceD2D::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
1389 if (pRenderTarget) {
1390 ID2D1Factory *pFactory = 0;
1391 pRenderTarget->GetFactory(&pFactory);
1392 ID2D1PathGeometry *geometry=0;
1393 HRESULT hr = pFactory->CreatePathGeometry(&geometry);
1394 if (SUCCEEDED(hr)) {
1395 ID2D1GeometrySink *sink = 0;
1396 hr = geometry->Open(&sink);
1397 if (SUCCEEDED(hr)) {
1398 sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED);
1399 for (size_t i=1; i<static_cast<size_t>(npts); i++) {
1400 sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f));
1402 sink->EndFigure(D2D1_FIGURE_END_CLOSED);
1403 sink->Close();
1404 sink->Release();
1406 D2DPenColour(back);
1407 pRenderTarget->FillGeometry(geometry,pBrush);
1408 D2DPenColour(fore);
1409 pRenderTarget->DrawGeometry(geometry,pBrush);
1412 geometry->Release();
1417 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
1418 if (pRenderTarget) {
1419 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top+0.5, RoundFloat(rc.right) - 0.5, rc.bottom-0.5);
1420 D2DPenColour(back);
1421 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1422 D2DPenColour(fore);
1423 pRenderTarget->DrawRectangle(&rectangle1, pBrush);
1427 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) {
1428 if (pRenderTarget) {
1429 D2DPenColour(back);
1430 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left), rc.top, RoundFloat(rc.right), rc.bottom);
1431 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1435 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1436 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfacePattern);
1437 surfOther.FlushDrawing();
1438 ID2D1Bitmap *pBitmap = NULL;
1439 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1440 surfOther.pRenderTarget);
1441 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1442 if (SUCCEEDED(hr)) {
1443 ID2D1BitmapBrush *pBitmapBrush = NULL;
1444 D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1445 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1446 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1447 // Create the bitmap brush.
1448 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1449 pBitmap->Release();
1450 if (SUCCEEDED(hr)) {
1451 pRenderTarget->FillRectangle(
1452 D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom),
1453 pBitmapBrush);
1454 pBitmapBrush->Release();
1459 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
1460 if (pRenderTarget) {
1461 D2D1_ROUNDED_RECT roundedRectFill = D2D1::RoundedRect(
1462 D2D1::RectF(rc.left+1.0, rc.top+1.0, rc.right-1.0, rc.bottom-1.0),
1463 8, 8);
1464 D2DPenColour(back);
1465 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1467 D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
1468 D2D1::RectF(rc.left + 0.5, rc.top+0.5, rc.right - 0.5, rc.bottom-0.5),
1469 8, 8);
1470 D2DPenColour(fore);
1471 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1475 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1476 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
1477 if (pRenderTarget) {
1478 if (cornerSize == 0) {
1479 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1480 D2D1_RECT_F rectFill = D2D1::RectF(RoundFloat(rc.left) + 1.0, rc.top + 1.0, RoundFloat(rc.right) - 1.0, rc.bottom - 1.0);
1481 D2DPenColour(fill, alphaFill);
1482 pRenderTarget->FillRectangle(rectFill, pBrush);
1484 D2D1_RECT_F rectOutline = D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top + 0.5, RoundFloat(rc.right) - 0.5, rc.bottom - 0.5);
1485 D2DPenColour(outline, alphaOutline);
1486 pRenderTarget->DrawRectangle(rectOutline, pBrush);
1487 } else {
1488 D2D1_ROUNDED_RECT roundedRectFill = D2D1::RoundedRect(
1489 D2D1::RectF(RoundFloat(rc.left) + 1.0, rc.top + 1.0, RoundFloat(rc.right) - 1.0, rc.bottom - 1.0),
1490 cornerSize, cornerSize);
1491 D2DPenColour(fill, alphaFill);
1492 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1494 D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
1495 D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top + 0.5, RoundFloat(rc.right) - 0.5, rc.bottom - 0.5),
1496 cornerSize, cornerSize);
1497 D2DPenColour(outline, alphaOutline);
1498 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1503 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1504 if (pRenderTarget) {
1505 if (rc.Width() > width)
1506 rc.left += static_cast<int>((rc.Width() - width) / 2);
1507 rc.right = rc.left + width;
1508 if (rc.Height() > height)
1509 rc.top += static_cast<int>((rc.Height() - height) / 2);
1510 rc.bottom = rc.top + height;
1512 std::vector<unsigned char> image(height * width * 4);
1513 for (int y=0; y<height; y++) {
1514 for (int x=0; x<width; x++) {
1515 unsigned char *pixel = &image[0] + (y*width+x) * 4;
1516 unsigned char alpha = pixelsImage[3];
1517 // Input is RGBA, output is BGRA with premultiplied alpha
1518 pixel[2] = (*pixelsImage++) * alpha / 255;
1519 pixel[1] = (*pixelsImage++) * alpha / 255;
1520 pixel[0] = (*pixelsImage++) * alpha / 255;
1521 pixel[3] = *pixelsImage++;
1525 ID2D1Bitmap *bitmap = 0;
1526 D2D1_SIZE_U size = D2D1::SizeU(width, height);
1527 D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1528 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1529 HRESULT hr = pRenderTarget->CreateBitmap(size, &image[0],
1530 width * 4, &props, &bitmap);
1531 if (SUCCEEDED(hr)) {
1532 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1533 pRenderTarget->DrawBitmap(bitmap, rcDestination);
1535 bitmap->Release();
1539 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
1540 if (pRenderTarget) {
1541 FLOAT radius = rc.Width() / 2.0f - 1.0f;
1542 D2D1_ELLIPSE ellipse = D2D1::Ellipse(
1543 D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f),
1544 radius,radius);
1546 PenColour(back);
1547 pRenderTarget->FillEllipse(ellipse, pBrush);
1548 PenColour(fore);
1549 pRenderTarget->DrawEllipse(ellipse, pBrush);
1553 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1554 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfaceSource);
1555 surfOther.FlushDrawing();
1556 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1557 surfOther.pRenderTarget);
1558 ID2D1Bitmap *pBitmap = NULL;
1559 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1560 if (SUCCEEDED(hr)) {
1561 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1562 D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()};
1563 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1564 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1565 pRenderTarget->Flush();
1566 pBitmap->Release();
1570 void SurfaceD2D::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT) {
1571 SetFont(font_);
1573 // Use Unicode calls
1574 const TextWide tbuf(s, len, unicodeMode, codePage);
1575 if (pRenderTarget && pTextFormat && pBrush) {
1577 // Explicitly creating a text layout appears a little faster
1578 IDWriteTextLayout *pTextLayout;
1579 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat,
1580 rc.Width(), rc.Height(), &pTextLayout);
1581 if (SUCCEEDED(hr)) {
1582 D2D1_POINT_2F origin = {rc.left, ybase-yAscent};
1583 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, D2D1_DRAW_TEXT_OPTIONS_NONE);
1584 pTextLayout->Release();
1589 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1590 ColourDesired fore, ColourDesired back) {
1591 if (pRenderTarget) {
1592 FillRectangle(rc, back);
1593 D2DPenColour(fore);
1594 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
1598 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1599 ColourDesired fore, ColourDesired back) {
1600 if (pRenderTarget) {
1601 FillRectangle(rc, back);
1602 D2DPenColour(fore);
1603 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
1607 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1608 ColourDesired fore) {
1609 // Avoid drawing spaces in transparent mode
1610 for (int i=0;i<len;i++) {
1611 if (s[i] != ' ') {
1612 if (pRenderTarget) {
1613 D2DPenColour(fore);
1614 DrawTextCommon(rc, font_, ybase, s, len, 0);
1616 return;
1621 XYPOSITION SurfaceD2D::WidthText(Font &font_, const char *s, int len) {
1622 FLOAT width = 1.0;
1623 SetFont(font_);
1624 const TextWide tbuf(s, len, unicodeMode, codePage);
1625 if (pIDWriteFactory && pTextFormat) {
1626 // Create a layout
1627 IDWriteTextLayout *pTextLayout = 0;
1628 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1629 if (SUCCEEDED(hr)) {
1630 DWRITE_TEXT_METRICS textMetrics;
1631 pTextLayout->GetMetrics(&textMetrics);
1632 width = textMetrics.widthIncludingTrailingWhitespace;
1633 pTextLayout->Release();
1636 return width;
1639 void SurfaceD2D::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
1640 SetFont(font_);
1641 int fit = 0;
1642 const TextWide tbuf(s, len, unicodeMode, codePage);
1643 TextPositions poses(tbuf.tlen);
1644 fit = tbuf.tlen;
1645 const int clusters = 1000;
1646 DWRITE_CLUSTER_METRICS clusterMetrics[clusters];
1647 UINT32 count = 0;
1648 if (pIDWriteFactory && pTextFormat) {
1649 SetFont(font_);
1650 // Create a layout
1651 IDWriteTextLayout *pTextLayout = 0;
1652 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
1653 if (!SUCCEEDED(hr))
1654 return;
1655 // For now, assuming WCHAR == cluster
1656 pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count);
1657 FLOAT position = 0.0f;
1658 size_t ti=0;
1659 for (size_t ci=0;ci<count;ci++) {
1660 position += clusterMetrics[ci].width;
1661 for (size_t inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
1662 //poses.buffer[ti++] = int(position + 0.5);
1663 poses.buffer[ti++] = position;
1666 PLATFORM_ASSERT(ti == static_cast<size_t>(tbuf.tlen));
1667 pTextLayout->Release();
1669 if (unicodeMode) {
1670 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1671 int ui=0;
1672 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1673 int i=0;
1674 while (ui<fit) {
1675 unsigned char uch = us[i];
1676 unsigned int lenChar = 1;
1677 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1678 lenChar = 4;
1679 ui++;
1680 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1681 lenChar = 3;
1682 } else if (uch >= (0x80)) {
1683 lenChar = 2;
1685 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1686 positions[i++] = poses.buffer[ui];
1688 ui++;
1690 int lastPos = 0;
1691 if (i > 0)
1692 lastPos = positions[i-1];
1693 while (i<len) {
1694 positions[i++] = lastPos;
1696 } else if (codePage == 0) {
1698 // One character per position
1699 PLATFORM_ASSERT(len == tbuf.tlen);
1700 for (size_t kk=0;kk<static_cast<size_t>(len);kk++) {
1701 positions[kk] = poses.buffer[kk];
1704 } else {
1706 // May be more than one byte per position
1707 int ui = 0;
1708 for (int i=0;i<len;) {
1709 if (::IsDBCSLeadByteEx(codePage, s[i])) {
1710 positions[i] = poses.buffer[ui];
1711 positions[i+1] = poses.buffer[ui];
1712 i += 2;
1713 } else {
1714 positions[i] = poses.buffer[ui];
1715 i++;
1718 ui++;
1723 XYPOSITION SurfaceD2D::WidthChar(Font &font_, char ch) {
1724 FLOAT width = 1.0;
1725 SetFont(font_);
1726 if (pIDWriteFactory && pTextFormat) {
1727 // Create a layout
1728 IDWriteTextLayout *pTextLayout = 0;
1729 const WCHAR wch = ch;
1730 HRESULT hr = pIDWriteFactory->CreateTextLayout(&wch, 1, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1731 if (SUCCEEDED(hr)) {
1732 DWRITE_TEXT_METRICS textMetrics;
1733 pTextLayout->GetMetrics(&textMetrics);
1734 width = textMetrics.widthIncludingTrailingWhitespace;
1735 pTextLayout->Release();
1738 return width;
1741 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
1742 SetFont(font_);
1743 return ceil(yAscent);
1746 XYPOSITION SurfaceD2D::Descent(Font &font_) {
1747 SetFont(font_);
1748 return ceil(yDescent);
1751 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
1752 SetFont(font_);
1753 return floor(yInternalLeading);
1756 XYPOSITION SurfaceD2D::ExternalLeading(Font &) {
1757 // Not implemented, always return one
1758 return 1;
1761 XYPOSITION SurfaceD2D::Height(Font &font_) {
1762 return Ascent(font_) + Descent(font_);
1765 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) {
1766 FLOAT width = 1.0;
1767 SetFont(font_);
1768 if (pIDWriteFactory && pTextFormat) {
1769 // Create a layout
1770 IDWriteTextLayout *pTextLayout = 0;
1771 const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1772 HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(wcslen(wszAllAlpha)),
1773 pTextFormat, 1000.0, 1000.0, &pTextLayout);
1774 if (SUCCEEDED(hr)) {
1775 DWRITE_TEXT_METRICS textMetrics;
1776 pTextLayout->GetMetrics(&textMetrics);
1777 width = textMetrics.width / wcslen(wszAllAlpha);
1778 pTextLayout->Release();
1781 return width;
1784 void SurfaceD2D::SetClip(PRectangle rc) {
1785 if (pRenderTarget) {
1786 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1787 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1788 clipsActive++;
1792 void SurfaceD2D::FlushCachedState() {
1795 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
1796 unicodeMode=unicodeMode_;
1799 void SurfaceD2D::SetDBCSMode(int codePage_) {
1800 // No action on window as automatically handled by system.
1801 codePage = codePage_;
1803 #endif
1805 Surface *Surface::Allocate(int technology) {
1806 #if defined(USE_D2D)
1807 if (technology == SCWIN_TECH_GDI)
1808 return new SurfaceGDI;
1809 else
1810 return new SurfaceD2D;
1811 #else
1812 return new SurfaceGDI;
1813 #endif
1816 Window::~Window() {
1819 void Window::Destroy() {
1820 if (wid)
1821 ::DestroyWindow(reinterpret_cast<HWND>(wid));
1822 wid = 0;
1825 bool Window::HasFocus() {
1826 return ::GetFocus() == wid;
1829 PRectangle Window::GetPosition() {
1830 RECT rc;
1831 ::GetWindowRect(reinterpret_cast<HWND>(wid), &rc);
1832 return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
1835 void Window::SetPosition(PRectangle rc) {
1836 ::SetWindowPos(reinterpret_cast<HWND>(wid),
1837 0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER|SWP_NOACTIVATE);
1840 void Window::SetPositionRelative(PRectangle rc, Window w) {
1841 LONG style = ::GetWindowLong(reinterpret_cast<HWND>(wid), GWL_STYLE);
1842 if (style & WS_POPUP) {
1843 POINT ptOther = {0, 0};
1844 ::ClientToScreen(reinterpret_cast<HWND>(w.GetID()), &ptOther);
1845 rc.Move(ptOther.x, ptOther.y);
1847 // This #ifdef is for VC 98 which has problems with MultiMon.h under some conditions.
1848 #ifdef MONITOR_DEFAULTTONULL
1849 // We're using the stub functionality of MultiMon.h to decay gracefully on machines
1850 // (ie, pre Win2000, Win95) that do not support the newer functions.
1851 RECT rcMonitor = RectFromPRectangle(rc);
1852 MONITORINFO mi = {0};
1853 mi.cbSize = sizeof(mi);
1855 HMONITOR hMonitor = ::MonitorFromRect(&rcMonitor, MONITOR_DEFAULTTONEAREST);
1856 // If hMonitor is NULL, that's just the main screen anyways.
1857 ::GetMonitorInfo(hMonitor, &mi);
1859 // Now clamp our desired rectangle to fit inside the work area
1860 // This way, the menu will fit wholly on one screen. An improvement even
1861 // if you don't have a second monitor on the left... Menu's appears half on
1862 // one screen and half on the other are just U.G.L.Y.!
1863 if (rc.right > mi.rcWork.right)
1864 rc.Move(mi.rcWork.right - rc.right, 0);
1865 if (rc.bottom > mi.rcWork.bottom)
1866 rc.Move(0, mi.rcWork.bottom - rc.bottom);
1867 if (rc.left < mi.rcWork.left)
1868 rc.Move(mi.rcWork.left - rc.left, 0);
1869 if (rc.top < mi.rcWork.top)
1870 rc.Move(0, mi.rcWork.top - rc.top);
1871 #endif
1873 SetPosition(rc);
1876 PRectangle Window::GetClientPosition() {
1877 RECT rc={0,0,0,0};
1878 if (wid)
1879 ::GetClientRect(reinterpret_cast<HWND>(wid), &rc);
1880 return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
1883 void Window::Show(bool show) {
1884 if (show)
1885 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_SHOWNOACTIVATE);
1886 else
1887 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_HIDE);
1890 void Window::InvalidateAll() {
1891 ::InvalidateRect(reinterpret_cast<HWND>(wid), NULL, FALSE);
1894 void Window::InvalidateRectangle(PRectangle rc) {
1895 RECT rcw = RectFromPRectangle(rc);
1896 ::InvalidateRect(reinterpret_cast<HWND>(wid), &rcw, FALSE);
1899 static LRESULT Window_SendMessage(Window *w, UINT msg, WPARAM wParam=0, LPARAM lParam=0) {
1900 return ::SendMessage(reinterpret_cast<HWND>(w->GetID()), msg, wParam, lParam);
1903 void Window::SetFont(Font &font) {
1904 Window_SendMessage(this, WM_SETFONT,
1905 reinterpret_cast<WPARAM>(font.GetID()), 0);
1908 static void FlipBitmap(HBITMAP bitmap, int width, int height) {
1909 HDC hdc = ::CreateCompatibleDC(NULL);
1910 if (hdc != NULL) {
1911 HGDIOBJ prevBmp = ::SelectObject(hdc, bitmap);
1912 ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
1913 ::SelectObject(hdc, prevBmp);
1914 ::DeleteDC(hdc);
1918 static HCURSOR GetReverseArrowCursor() {
1919 if (reverseArrowCursor != NULL)
1920 return reverseArrowCursor;
1922 ::EnterCriticalSection(&crPlatformLock);
1923 HCURSOR cursor = reverseArrowCursor;
1924 if (cursor == NULL) {
1925 cursor = ::LoadCursor(NULL, IDC_ARROW);
1926 ICONINFO info;
1927 if (::GetIconInfo(cursor, &info)) {
1928 BITMAP bmp;
1929 if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) {
1930 FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
1931 if (info.hbmColor != NULL)
1932 FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
1933 info.xHotspot = (DWORD)bmp.bmWidth - 1 - info.xHotspot;
1935 reverseArrowCursor = ::CreateIconIndirect(&info);
1936 if (reverseArrowCursor != NULL)
1937 cursor = reverseArrowCursor;
1940 ::DeleteObject(info.hbmMask);
1941 if (info.hbmColor != NULL)
1942 ::DeleteObject(info.hbmColor);
1945 ::LeaveCriticalSection(&crPlatformLock);
1946 return cursor;
1949 void Window::SetCursor(Cursor curs) {
1950 switch (curs) {
1951 case cursorText:
1952 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
1953 break;
1954 case cursorUp:
1955 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
1956 break;
1957 case cursorWait:
1958 ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
1959 break;
1960 case cursorHoriz:
1961 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
1962 break;
1963 case cursorVert:
1964 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
1965 break;
1966 case cursorHand:
1967 ::SetCursor(::LoadCursor(NULL,IDC_HAND));
1968 break;
1969 case cursorReverseArrow:
1970 ::SetCursor(GetReverseArrowCursor());
1971 break;
1972 case cursorArrow:
1973 case cursorInvalid: // Should not occur, but just in case.
1974 ::SetCursor(::LoadCursor(NULL,IDC_ARROW));
1975 break;
1979 void Window::SetTitle(const char *s) {
1980 ::SetWindowTextA(reinterpret_cast<HWND>(wid), s);
1983 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
1984 coordinates */
1985 PRectangle Window::GetMonitorRect(Point pt) {
1986 #ifdef MONITOR_DEFAULTTONULL
1987 // MonitorFromPoint and GetMonitorInfo are not available on Windows 95 so are not used.
1988 // There could be conditional code and dynamic loading in a future version
1989 // so this would work on those platforms where they are available.
1990 PRectangle rcPosition = GetPosition();
1991 POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
1992 static_cast<LONG>(pt.y + rcPosition.top)};
1993 HMONITOR hMonitor = ::MonitorFromPoint(ptDesktop, MONITOR_DEFAULTTONEAREST);
1994 MONITORINFO mi = {0};
1995 memset(&mi, 0, sizeof(mi));
1996 mi.cbSize = sizeof(mi);
1997 if (::GetMonitorInfo(hMonitor, &mi)) {
1998 PRectangle rcMonitor(
1999 mi.rcWork.left - rcPosition.left,
2000 mi.rcWork.top - rcPosition.top,
2001 mi.rcWork.right - rcPosition.left,
2002 mi.rcWork.bottom - rcPosition.top);
2003 return rcMonitor;
2004 } else {
2005 return PRectangle();
2007 #else
2008 return PRectangle();
2009 #endif
2012 struct ListItemData {
2013 const char *text;
2014 int pixId;
2017 #define _ROUND2(n,pow2) \
2018 ( ( (n) + (pow2) - 1) & ~((pow2) - 1) )
2020 class LineToItem {
2021 char *words;
2022 int wordsCount;
2023 int wordsSize;
2025 ListItemData *data;
2026 int len;
2027 int count;
2029 private:
2030 void FreeWords() {
2031 delete []words;
2032 words = NULL;
2033 wordsCount = 0;
2034 wordsSize = 0;
2036 char *AllocWord(const char *word);
2038 public:
2039 LineToItem() : words(NULL), wordsCount(0), wordsSize(0), data(NULL), len(0), count(0) {
2041 ~LineToItem() {
2042 Clear();
2044 void Clear() {
2045 FreeWords();
2046 delete []data;
2047 data = NULL;
2048 len = 0;
2049 count = 0;
2052 ListItemData *Append(const char *text, int value);
2054 ListItemData Get(int index) const {
2055 if (index >= 0 && index < count) {
2056 return data[index];
2057 } else {
2058 ListItemData missing = {"", -1};
2059 return missing;
2062 int Count() const {
2063 return count;
2066 ListItemData *AllocItem();
2068 void SetWords(char *s) {
2069 words = s; // N.B. will be deleted on destruction
2073 char *LineToItem::AllocWord(const char *text) {
2074 int chars = static_cast<int>(strlen(text) + 1);
2075 int newCount = wordsCount + chars;
2076 if (newCount > wordsSize) {
2077 wordsSize = _ROUND2(newCount * 2, 8192);
2078 char *wordsNew = new char[wordsSize];
2079 memcpy(wordsNew, words, wordsCount);
2080 int offset = wordsNew - words;
2081 for (int i=0; i<count; i++)
2082 data[i].text += offset;
2083 delete []words;
2084 words = wordsNew;
2086 char *s = &words[wordsCount];
2087 wordsCount = newCount;
2088 strncpy(s, text, chars);
2089 return s;
2092 ListItemData *LineToItem::AllocItem() {
2093 if (count >= len) {
2094 int lenNew = _ROUND2((count+1) * 2, 1024);
2095 ListItemData *dataNew = new ListItemData[lenNew];
2096 memcpy(dataNew, data, count * sizeof(ListItemData));
2097 delete []data;
2098 data = dataNew;
2099 len = lenNew;
2101 ListItemData *item = &data[count];
2102 count++;
2103 return item;
2106 ListItemData *LineToItem::Append(const char *text, int imageIndex) {
2107 ListItemData *item = AllocItem();
2108 item->text = AllocWord(text);
2109 item->pixId = imageIndex;
2110 return item;
2113 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
2115 ListBox::ListBox() {
2118 ListBox::~ListBox() {
2121 class ListBoxX : public ListBox {
2122 int lineHeight;
2123 FontID fontCopy;
2124 int technology;
2125 RGBAImageSet images;
2126 LineToItem lti;
2127 HWND lb;
2128 bool unicodeMode;
2129 int desiredVisibleRows;
2130 unsigned int maxItemCharacters;
2131 unsigned int aveCharWidth;
2132 Window *parent;
2133 int ctrlID;
2134 CallBackAction doubleClickAction;
2135 void *doubleClickActionData;
2136 const char *widestItem;
2137 unsigned int maxCharWidth;
2138 int resizeHit;
2139 PRectangle rcPreSize;
2140 Point dragOffset;
2141 Point location; // Caret location at which the list is opened
2142 int wheelDelta; // mouse wheel residue
2144 HWND GetHWND() const;
2145 void AppendListItem(const char *startword, const char *numword);
2146 void AdjustWindowRect(PRectangle *rc) const;
2147 int ItemHeight() const;
2148 int MinClientWidth() const;
2149 int TextOffset() const;
2150 Point GetClientExtent() const;
2151 POINT MinTrackSize() const;
2152 POINT MaxTrackSize() const;
2153 void SetRedraw(bool on);
2154 void OnDoubleClick();
2155 void ResizeToCursor();
2156 void StartResize(WPARAM);
2157 int NcHitTest(WPARAM, LPARAM) const;
2158 void CentreItem(int);
2159 void Paint(HDC);
2160 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2162 static const Point ItemInset; // Padding around whole item
2163 static const Point TextInset; // Padding around text
2164 static const Point ImageInset; // Padding around image
2166 public:
2167 ListBoxX() : lineHeight(10), fontCopy(0), technology(0), lb(0), unicodeMode(false),
2168 desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8),
2169 parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL),
2170 widestItem(NULL), maxCharWidth(1), resizeHit(0), wheelDelta(0) {
2172 virtual ~ListBoxX() {
2173 if (fontCopy) {
2174 ::DeleteObject(fontCopy);
2175 fontCopy = 0;
2178 virtual void SetFont(Font &font);
2179 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
2180 virtual void SetAverageCharWidth(int width);
2181 virtual void SetVisibleRows(int rows);
2182 virtual int GetVisibleRows() const;
2183 virtual PRectangle GetDesiredRect();
2184 virtual int CaretFromEdge();
2185 virtual void Clear();
2186 virtual void Append(char *s, int type = -1);
2187 virtual int Length();
2188 virtual void Select(int n);
2189 virtual int GetSelection();
2190 virtual int Find(const char *prefix);
2191 virtual void GetValue(int n, char *value, int len);
2192 virtual void RegisterImage(int type, const char *xpm_data);
2193 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
2194 virtual void ClearRegisteredImages();
2195 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
2196 doubleClickAction = action;
2197 doubleClickActionData = data;
2199 virtual void SetList(const char *list, char separator, char typesep);
2200 void Draw(DRAWITEMSTRUCT *pDrawItem);
2201 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2202 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2205 const Point ListBoxX::ItemInset(0, 0);
2206 const Point ListBoxX::TextInset(2, 0);
2207 const Point ListBoxX::ImageInset(1, 0);
2209 ListBox *ListBox::Allocate() {
2210 ListBoxX *lb = new ListBoxX();
2211 return lb;
2214 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) {
2215 parent = &parent_;
2216 ctrlID = ctrlID_;
2217 location = location_;
2218 lineHeight = lineHeight_;
2219 unicodeMode = unicodeMode_;
2220 technology = technology_;
2221 HWND hwndParent = reinterpret_cast<HWND>(parent->GetID());
2222 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
2223 // Window created as popup so not clipped within parent client area
2224 wid = ::CreateWindowEx(
2225 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
2226 WS_POPUP | WS_THICKFRAME,
2227 100,100, 150,80, hwndParent,
2228 NULL,
2229 hinstanceParent,
2230 this);
2232 POINT locationw = {static_cast<LONG>(location.x), static_cast<LONG>(location.y)};
2233 ::MapWindowPoints(hwndParent, NULL, &locationw, 1);
2234 location = Point(locationw.x, locationw.y);
2237 void ListBoxX::SetFont(Font &font) {
2238 if (font.GetID()) {
2239 if (fontCopy) {
2240 ::DeleteObject(fontCopy);
2241 fontCopy = 0;
2243 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font.GetID());
2244 fontCopy = pfm->HFont();
2245 ::SendMessage(lb, WM_SETFONT, reinterpret_cast<WPARAM>(fontCopy), 0);
2249 void ListBoxX::SetAverageCharWidth(int width) {
2250 aveCharWidth = width;
2253 void ListBoxX::SetVisibleRows(int rows) {
2254 desiredVisibleRows = rows;
2257 int ListBoxX::GetVisibleRows() const {
2258 return desiredVisibleRows;
2261 HWND ListBoxX::GetHWND() const {
2262 return reinterpret_cast<HWND>(GetID());
2265 PRectangle ListBoxX::GetDesiredRect() {
2266 PRectangle rcDesired = GetPosition();
2268 int rows = Length();
2269 if ((rows == 0) || (rows > desiredVisibleRows))
2270 rows = desiredVisibleRows;
2271 rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
2273 int width = MinClientWidth();
2274 HDC hdc = ::GetDC(lb);
2275 HFONT oldFont = SelectFont(hdc, fontCopy);
2276 SIZE textSize = {0, 0};
2277 int len = static_cast<int>(widestItem ? strlen(widestItem) : 0);
2278 if (unicodeMode) {
2279 const TextWide tbuf(widestItem, len, unicodeMode);
2280 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
2281 } else {
2282 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
2284 TEXTMETRIC tm;
2285 ::GetTextMetrics(hdc, &tm);
2286 maxCharWidth = tm.tmMaxCharWidth;
2287 SelectFont(hdc, oldFont);
2288 ::ReleaseDC(lb, hdc);
2290 int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
2291 if (width < widthDesired)
2292 width = widthDesired;
2294 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
2295 if (Length() > rows)
2296 rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
2298 AdjustWindowRect(&rcDesired);
2299 return rcDesired;
2302 int ListBoxX::TextOffset() const {
2303 int pixWidth = images.GetWidth();
2304 return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2);
2307 int ListBoxX::CaretFromEdge() {
2308 PRectangle rc;
2309 AdjustWindowRect(&rc);
2310 return TextOffset() + TextInset.x + (0 - rc.left) - 1;
2313 void ListBoxX::Clear() {
2314 ::SendMessage(lb, LB_RESETCONTENT, 0, 0);
2315 maxItemCharacters = 0;
2316 widestItem = NULL;
2317 lti.Clear();
2320 void ListBoxX::Append(char *s, int type) {
2321 int index = ::SendMessage(lb, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s));
2322 if (index < 0)
2323 return;
2324 ListItemData *newItem = lti.Append(s, type);
2325 unsigned int len = static_cast<unsigned int>(strlen(s));
2326 if (maxItemCharacters < len) {
2327 maxItemCharacters = len;
2328 widestItem = newItem->text;
2332 int ListBoxX::Length() {
2333 return lti.Count();
2336 void ListBoxX::Select(int n) {
2337 // We are going to scroll to centre on the new selection and then select it, so disable
2338 // redraw to avoid flicker caused by a painting new selection twice in unselected and then
2339 // selected states
2340 SetRedraw(false);
2341 CentreItem(n);
2342 ::SendMessage(lb, LB_SETCURSEL, n, 0);
2343 SetRedraw(true);
2346 int ListBoxX::GetSelection() {
2347 return ::SendMessage(lb, LB_GETCURSEL, 0, 0);
2350 // This is not actually called at present
2351 int ListBoxX::Find(const char *) {
2352 return LB_ERR;
2355 void ListBoxX::GetValue(int n, char *value, int len) {
2356 ListItemData item = lti.Get(n);
2357 strncpy(value, item.text, len);
2358 value[len-1] = '\0';
2361 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2362 XPM xpmImage(xpm_data);
2363 images.Add(type, new RGBAImage(xpmImage));
2366 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2367 images.Add(type, new RGBAImage(width, height, pixelsImage));
2370 void ListBoxX::ClearRegisteredImages() {
2371 images.Clear();
2374 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
2375 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
2376 RECT rcBox = pDrawItem->rcItem;
2377 rcBox.left += TextOffset();
2378 if (pDrawItem->itemState & ODS_SELECTED) {
2379 RECT rcImage = pDrawItem->rcItem;
2380 rcImage.right = rcBox.left;
2381 // The image is not highlighted
2382 ::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2383 ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
2384 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
2385 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
2386 } else {
2387 ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2388 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
2389 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
2392 ListItemData item = lti.Get(pDrawItem->itemID);
2393 int pixId = item.pixId;
2394 const char *text = item.text;
2395 int len = static_cast<int>(strlen(text));
2397 RECT rcText = rcBox;
2398 ::InsetRect(&rcText, TextInset.x, TextInset.y);
2400 if (unicodeMode) {
2401 const TextWide tbuf(text, len, unicodeMode);
2402 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2403 } else {
2404 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2406 if (pDrawItem->itemState & ODS_SELECTED) {
2407 ::DrawFocusRect(pDrawItem->hDC, &rcBox);
2410 // Draw the image, if any
2411 RGBAImage *pimage = images.Get(pixId);
2412 if (pimage) {
2413 Surface *surfaceItem = Surface::Allocate(technology);
2414 if (surfaceItem) {
2415 if (technology == SCWIN_TECH_GDI) {
2416 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
2417 int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
2418 PRectangle rcImage(left, pDrawItem->rcItem.top,
2419 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2420 surfaceItem->DrawRGBAImage(rcImage,
2421 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2422 delete surfaceItem;
2423 ::SetTextAlign(pDrawItem->hDC, TA_TOP);
2424 } else {
2425 #if defined(USE_D2D)
2426 D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
2427 D2D1_RENDER_TARGET_TYPE_DEFAULT,
2428 D2D1::PixelFormat(
2429 DXGI_FORMAT_B8G8R8A8_UNORM,
2430 D2D1_ALPHA_MODE_IGNORE),
2433 D2D1_RENDER_TARGET_USAGE_NONE,
2434 D2D1_FEATURE_LEVEL_DEFAULT
2436 ID2D1DCRenderTarget *pDCRT = 0;
2437 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
2438 RECT rcWindow;
2439 GetClientRect(pDrawItem->hwndItem, &rcWindow);
2440 hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow);
2441 if (SUCCEEDED(hr)) {
2442 surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
2443 pDCRT->BeginDraw();
2444 int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
2445 PRectangle rcImage(left, pDrawItem->rcItem.top,
2446 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2447 surfaceItem->DrawRGBAImage(rcImage,
2448 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2449 delete surfaceItem;
2450 pDCRT->EndDraw();
2451 pDCRT->Release();
2453 #endif
2460 void ListBoxX::AppendListItem(const char *startword, const char *numword) {
2461 ListItemData *item = lti.AllocItem();
2462 item->text = startword;
2463 if (numword) {
2464 int pixId = 0;
2465 char ch;
2466 while ((ch = *++numword) != '\0') {
2467 pixId = 10 * pixId + (ch - '0');
2469 item->pixId = pixId;
2470 } else {
2471 item->pixId = -1;
2474 unsigned int len = static_cast<unsigned int>(strlen(item->text));
2475 if (maxItemCharacters < len) {
2476 maxItemCharacters = len;
2477 widestItem = item->text;
2481 void ListBoxX::SetList(const char *list, char separator, char typesep) {
2482 // Turn off redraw while populating the list - this has a significant effect, even if
2483 // the listbox is not visible.
2484 SetRedraw(false);
2485 Clear();
2486 size_t size = strlen(list) + 1;
2487 char *words = new char[size];
2488 lti.SetWords(words);
2489 memcpy(words, list, size);
2490 char *startword = words;
2491 char *numword = NULL;
2492 int i = 0;
2493 for (; words[i]; i++) {
2494 if (words[i] == separator) {
2495 words[i] = '\0';
2496 if (numword)
2497 *numword = '\0';
2498 AppendListItem(startword, numword);
2499 startword = words + i + 1;
2500 numword = NULL;
2501 } else if (words[i] == typesep) {
2502 numword = words + i;
2505 if (startword) {
2506 if (numword)
2507 *numword = '\0';
2508 AppendListItem(startword, numword);
2511 // Finally populate the listbox itself with the correct number of items
2512 int count = lti.Count();
2513 ::SendMessage(lb, LB_INITSTORAGE, count, 0);
2514 for (int j=0; j<count; j++) {
2515 ::SendMessage(lb, LB_ADDSTRING, 0, j+1);
2517 SetRedraw(true);
2520 void ListBoxX::AdjustWindowRect(PRectangle *rc) const {
2521 RECT rcw = RectFromPRectangle(*rc);
2522 ::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
2523 *rc = PRectangle(rcw.left, rcw.top, rcw.right, rcw.bottom);
2526 int ListBoxX::ItemHeight() const {
2527 int itemHeight = lineHeight + (TextInset.y * 2);
2528 int pixHeight = images.GetHeight() + (ImageInset.y * 2);
2529 if (itemHeight < pixHeight) {
2530 itemHeight = pixHeight;
2532 return itemHeight;
2535 int ListBoxX::MinClientWidth() const {
2536 return 12 * (aveCharWidth+aveCharWidth/3);
2539 POINT ListBoxX::MinTrackSize() const {
2540 PRectangle rc(0, 0, MinClientWidth(), ItemHeight());
2541 AdjustWindowRect(&rc);
2542 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2543 return ret;
2546 POINT ListBoxX::MaxTrackSize() const {
2547 PRectangle rc(0, 0,
2548 maxCharWidth * maxItemCharacters + TextInset.x * 2 +
2549 TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL),
2550 ItemHeight() * lti.Count());
2551 AdjustWindowRect(&rc);
2552 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2553 return ret;
2556 void ListBoxX::SetRedraw(bool on) {
2557 ::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
2558 if (on)
2559 ::InvalidateRect(lb, NULL, TRUE);
2562 void ListBoxX::ResizeToCursor() {
2563 PRectangle rc = GetPosition();
2564 POINT ptw;
2565 ::GetCursorPos(&ptw);
2566 Point pt(ptw.x, ptw.y);
2567 pt.x += dragOffset.x;
2568 pt.y += dragOffset.y;
2570 switch (resizeHit) {
2571 case HTLEFT:
2572 rc.left = pt.x;
2573 break;
2574 case HTRIGHT:
2575 rc.right = pt.x;
2576 break;
2577 case HTTOP:
2578 rc.top = pt.y;
2579 break;
2580 case HTTOPLEFT:
2581 rc.top = pt.y;
2582 rc.left = pt.x;
2583 break;
2584 case HTTOPRIGHT:
2585 rc.top = pt.y;
2586 rc.right = pt.x;
2587 break;
2588 case HTBOTTOM:
2589 rc.bottom = pt.y;
2590 break;
2591 case HTBOTTOMLEFT:
2592 rc.bottom = pt.y;
2593 rc.left = pt.x;
2594 break;
2595 case HTBOTTOMRIGHT:
2596 rc.bottom = pt.y;
2597 rc.right = pt.x;
2598 break;
2601 POINT ptMin = MinTrackSize();
2602 POINT ptMax = MaxTrackSize();
2603 // We don't allow the left edge to move at present, but just in case
2604 rc.left = Platform::Maximum(Platform::Minimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
2605 rc.top = Platform::Maximum(Platform::Minimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
2606 rc.right = Platform::Maximum(Platform::Minimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
2607 rc.bottom = Platform::Maximum(Platform::Minimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
2609 SetPosition(rc);
2612 void ListBoxX::StartResize(WPARAM hitCode) {
2613 rcPreSize = GetPosition();
2614 POINT cursorPos;
2615 ::GetCursorPos(&cursorPos);
2617 switch (hitCode) {
2618 case HTRIGHT:
2619 case HTBOTTOM:
2620 case HTBOTTOMRIGHT:
2621 dragOffset.x = rcPreSize.right - cursorPos.x;
2622 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2623 break;
2625 case HTTOPRIGHT:
2626 dragOffset.x = rcPreSize.right - cursorPos.x;
2627 dragOffset.y = rcPreSize.top - cursorPos.y;
2628 break;
2630 // Note that the current hit test code prevents the left edge cases ever firing
2631 // as we don't want the left edge to be moveable
2632 case HTLEFT:
2633 case HTTOP:
2634 case HTTOPLEFT:
2635 dragOffset.x = rcPreSize.left - cursorPos.x;
2636 dragOffset.y = rcPreSize.top - cursorPos.y;
2637 break;
2638 case HTBOTTOMLEFT:
2639 dragOffset.x = rcPreSize.left - cursorPos.x;
2640 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2641 break;
2643 default:
2644 return;
2647 ::SetCapture(GetHWND());
2648 resizeHit = hitCode;
2651 int ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
2652 int hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
2653 // There is an apparent bug in the DefWindowProc hit test code whereby it will
2654 // return HTTOPXXX if the window in question is shorter than the default
2655 // window caption height + frame, even if one is hovering over the bottom edge of
2656 // the frame, so workaround that here
2657 if (hit >= HTTOP && hit <= HTTOPRIGHT) {
2658 int minHeight = GetSystemMetrics(SM_CYMINTRACK);
2659 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2660 int yPos = GET_Y_LPARAM(lParam);
2661 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
2662 hit += HTBOTTOM - HTTOP;
2666 // Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
2667 // depending on whether the list is above or below the caret
2668 switch (hit) {
2669 case HTLEFT:
2670 case HTTOPLEFT:
2671 case HTBOTTOMLEFT:
2672 hit = HTERROR;
2673 break;
2675 case HTTOP:
2676 case HTTOPRIGHT: {
2677 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2678 // Valid only if caret below list
2679 if (location.y < rc.top)
2680 hit = HTERROR;
2682 break;
2684 case HTBOTTOM:
2685 case HTBOTTOMRIGHT: {
2686 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2687 // Valid only if caret above list
2688 if (rc.bottom < location.y)
2689 hit = HTERROR;
2691 break;
2694 return hit;
2697 void ListBoxX::OnDoubleClick() {
2699 if (doubleClickAction != NULL) {
2700 doubleClickAction(doubleClickActionData);
2704 Point ListBoxX::GetClientExtent() const {
2705 PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
2706 return Point(rc.Width(), rc.Height());
2709 void ListBoxX::CentreItem(int n) {
2710 // If below mid point, scroll up to centre, but with more items below if uneven
2711 if (n >= 0) {
2712 Point extent = GetClientExtent();
2713 int visible = extent.y/ItemHeight();
2714 if (visible < Length()) {
2715 int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
2716 int half = (visible - 1) / 2;
2717 if (n > (top + half))
2718 ::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
2723 // Performs a double-buffered paint operation to avoid flicker
2724 void ListBoxX::Paint(HDC hDC) {
2725 Point extent = GetClientExtent();
2726 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
2727 HDC bitmapDC = ::CreateCompatibleDC(hDC);
2728 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
2729 // The list background is mainly erased during painting, but can be a small
2730 // unpainted area when at the end of a non-integrally sized list with a
2731 // vertical scroll bar
2732 RECT rc = { 0, 0, static_cast<LONG>(extent.x), static_cast<LONG>(extent.y) };
2733 ::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2734 // Paint the entire client area and vertical scrollbar
2735 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
2736 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
2737 // Select a stock brush to prevent warnings from BoundsChecker
2738 ::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
2739 SelectBitmap(bitmapDC, hBitmapOld);
2740 ::DeleteDC(bitmapDC);
2741 ::DeleteObject(hBitmap);
2744 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
2745 try {
2746 switch (uMsg) {
2747 case WM_ERASEBKGND:
2748 return TRUE;
2750 case WM_PAINT: {
2751 PAINTSTRUCT ps;
2752 HDC hDC = ::BeginPaint(hWnd, &ps);
2753 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2754 if (lbx)
2755 lbx->Paint(hDC);
2756 ::EndPaint(hWnd, &ps);
2758 return 0;
2760 case WM_MOUSEACTIVATE:
2761 // This prevents the view activating when the scrollbar is clicked
2762 return MA_NOACTIVATE;
2764 case WM_LBUTTONDOWN: {
2765 // We must take control of selection to prevent the ListBox activating
2766 // the popup
2767 LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
2768 int item = LOWORD(lResult);
2769 if (HIWORD(lResult) == 0 && item >= 0) {
2770 ::SendMessage(hWnd, LB_SETCURSEL, item, 0);
2773 return 0;
2775 case WM_LBUTTONUP:
2776 return 0;
2778 case WM_LBUTTONDBLCLK: {
2779 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2780 if (lbx) {
2781 lbx->OnDoubleClick();
2784 return 0;
2786 case WM_MBUTTONDOWN:
2787 // disable the scroll wheel button click action
2788 return 0;
2791 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
2792 if (prevWndProc) {
2793 return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
2794 } else {
2795 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2797 } catch (...) {
2799 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2802 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2803 switch (iMessage) {
2804 case WM_CREATE: {
2805 HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID()));
2806 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
2807 // but has useful side effect of speeding up list population significantly
2808 lb = ::CreateWindowEx(
2809 0, TEXT("listbox"), TEXT(""),
2810 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
2811 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
2812 0, 0, 150,80, hWnd,
2813 reinterpret_cast<HMENU>(ctrlID),
2814 hinstanceParent,
2816 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(lb, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlWndProc)));
2817 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
2819 break;
2821 case WM_SIZE:
2822 if (lb) {
2823 SetRedraw(false);
2824 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
2825 // Ensure the selection remains visible
2826 CentreItem(GetSelection());
2827 SetRedraw(true);
2829 break;
2831 case WM_PAINT: {
2832 PAINTSTRUCT ps;
2833 ::BeginPaint(hWnd, &ps);
2834 ::EndPaint(hWnd, &ps);
2836 break;
2838 case WM_COMMAND:
2839 // This is not actually needed now - the registered double click action is used
2840 // directly to action a choice from the list.
2841 ::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);
2842 break;
2844 case WM_MEASUREITEM: {
2845 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
2846 pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());
2848 break;
2850 case WM_DRAWITEM:
2851 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
2852 break;
2854 case WM_DESTROY:
2855 lb = 0;
2856 ::SetWindowLong(hWnd, 0, 0);
2857 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2859 case WM_ERASEBKGND:
2860 // To reduce flicker we can elide background erasure since this window is
2861 // completely covered by its child.
2862 return TRUE;
2864 case WM_GETMINMAXINFO: {
2865 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
2866 minMax->ptMaxTrackSize = MaxTrackSize();
2867 minMax->ptMinTrackSize = MinTrackSize();
2869 break;
2871 case WM_MOUSEACTIVATE:
2872 return MA_NOACTIVATE;
2874 case WM_NCHITTEST:
2875 return NcHitTest(wParam, lParam);
2877 case WM_NCLBUTTONDOWN:
2878 // We have to implement our own window resizing because the DefWindowProc
2879 // implementation insists on activating the resized window
2880 StartResize(wParam);
2881 return 0;
2883 case WM_MOUSEMOVE: {
2884 if (resizeHit == 0) {
2885 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2886 } else {
2887 ResizeToCursor();
2890 break;
2892 case WM_LBUTTONUP:
2893 case WM_CANCELMODE:
2894 if (resizeHit != 0) {
2895 resizeHit = 0;
2896 ::ReleaseCapture();
2898 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2900 case WM_MOUSEWHEEL:
2901 wheelDelta -= static_cast<short>(HIWORD(wParam));
2902 if (abs(wheelDelta) >= WHEEL_DELTA) {
2903 int nRows = GetVisibleRows();
2904 int linesToScroll = 1;
2905 if (nRows > 1) {
2906 linesToScroll = nRows - 1;
2908 if (linesToScroll > 3) {
2909 linesToScroll = 3;
2911 linesToScroll *= (wheelDelta / WHEEL_DELTA);
2912 int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0) + linesToScroll;
2913 if (top < 0) {
2914 top = 0;
2916 ::SendMessage(lb, LB_SETTOPINDEX, top, 0);
2917 // update wheel delta residue
2918 if (wheelDelta >= 0)
2919 wheelDelta = wheelDelta % WHEEL_DELTA;
2920 else
2921 wheelDelta = - (-wheelDelta % WHEEL_DELTA);
2923 break;
2925 default:
2926 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2929 return 0;
2932 LRESULT PASCAL ListBoxX::StaticWndProc(
2933 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2934 if (iMessage == WM_CREATE) {
2935 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
2936 SetWindowPointer(hWnd, pCreate->lpCreateParams);
2938 // Find C++ object associated with window.
2939 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(hWnd));
2940 if (lbx) {
2941 return lbx->WndProc(hWnd, iMessage, wParam, lParam);
2942 } else {
2943 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2947 static bool ListBoxX_Register() {
2948 WNDCLASSEX wndclassc;
2949 wndclassc.cbSize = sizeof(wndclassc);
2950 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
2951 // truncated items in the list and the appearance/disappearance of the vertical scroll bar.
2952 // The list repaint is double-buffered to avoid the flicker this would otherwise cause.
2953 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
2954 wndclassc.cbClsExtra = 0;
2955 wndclassc.cbWndExtra = sizeof(ListBoxX *);
2956 wndclassc.hInstance = hinstPlatformRes;
2957 wndclassc.hIcon = NULL;
2958 wndclassc.hbrBackground = NULL;
2959 wndclassc.lpszMenuName = NULL;
2960 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
2961 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
2962 wndclassc.lpszClassName = ListBoxX_ClassName;
2963 wndclassc.hIconSm = 0;
2965 return ::RegisterClassEx(&wndclassc) != 0;
2968 bool ListBoxX_Unregister() {
2969 return ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes) != 0;
2972 Menu::Menu() : mid(0) {
2975 void Menu::CreatePopUp() {
2976 Destroy();
2977 mid = ::CreatePopupMenu();
2980 void Menu::Destroy() {
2981 if (mid)
2982 ::DestroyMenu(reinterpret_cast<HMENU>(mid));
2983 mid = 0;
2986 void Menu::Show(Point pt, Window &w) {
2987 ::TrackPopupMenu(reinterpret_cast<HMENU>(mid),
2988 0, pt.x - 4, pt.y, 0,
2989 reinterpret_cast<HWND>(w.GetID()), NULL);
2990 Destroy();
2993 static bool initialisedET = false;
2994 static bool usePerformanceCounter = false;
2995 static LARGE_INTEGER frequency;
2997 ElapsedTime::ElapsedTime() {
2998 if (!initialisedET) {
2999 usePerformanceCounter = ::QueryPerformanceFrequency(&frequency) != 0;
3000 initialisedET = true;
3002 if (usePerformanceCounter) {
3003 LARGE_INTEGER timeVal;
3004 ::QueryPerformanceCounter(&timeVal);
3005 bigBit = timeVal.HighPart;
3006 littleBit = timeVal.LowPart;
3007 } else {
3008 bigBit = clock();
3012 double ElapsedTime::Duration(bool reset) {
3013 double result;
3014 long endBigBit;
3015 long endLittleBit;
3017 if (usePerformanceCounter) {
3018 LARGE_INTEGER lEnd;
3019 ::QueryPerformanceCounter(&lEnd);
3020 endBigBit = lEnd.HighPart;
3021 endLittleBit = lEnd.LowPart;
3022 LARGE_INTEGER lBegin;
3023 lBegin.HighPart = bigBit;
3024 lBegin.LowPart = littleBit;
3025 double elapsed = lEnd.QuadPart - lBegin.QuadPart;
3026 result = elapsed / static_cast<double>(frequency.QuadPart);
3027 } else {
3028 endBigBit = clock();
3029 endLittleBit = 0;
3030 double elapsed = endBigBit - bigBit;
3031 result = elapsed / CLOCKS_PER_SEC;
3033 if (reset) {
3034 bigBit = endBigBit;
3035 littleBit = endLittleBit;
3037 return result;
3040 class DynamicLibraryImpl : public DynamicLibrary {
3041 protected:
3042 HMODULE h;
3043 public:
3044 DynamicLibraryImpl(const char *modulePath) {
3045 h = ::LoadLibraryA(modulePath);
3048 virtual ~DynamicLibraryImpl() {
3049 if (h != NULL)
3050 ::FreeLibrary(h);
3053 // Use GetProcAddress to get a pointer to the relevant function.
3054 virtual Function FindFunction(const char *name) {
3055 if (h != NULL) {
3056 // C++ standard doesn't like casts betwen function pointers and void pointers so use a union
3057 union {
3058 FARPROC fp;
3059 Function f;
3060 } fnConv;
3061 fnConv.fp = ::GetProcAddress(h, name);
3062 return fnConv.f;
3063 } else
3064 return NULL;
3067 virtual bool IsValid() {
3068 return h != NULL;
3072 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3073 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3076 ColourDesired Platform::Chrome() {
3077 return ::GetSysColor(COLOR_3DFACE);
3080 ColourDesired Platform::ChromeHighlight() {
3081 return ::GetSysColor(COLOR_3DHIGHLIGHT);
3084 const char *Platform::DefaultFont() {
3085 return "Verdana";
3088 int Platform::DefaultFontSize() {
3089 return 8;
3092 unsigned int Platform::DoubleClickTime() {
3093 return ::GetDoubleClickTime();
3096 bool Platform::MouseButtonBounce() {
3097 return false;
3100 void Platform::DebugDisplay(const char *s) {
3101 ::OutputDebugStringA(s);
3104 bool Platform::IsKeyDown(int key) {
3105 return (::GetKeyState(key) & 0x80000000) != 0;
3108 long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
3109 return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam);
3112 long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
3113 return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
3114 reinterpret_cast<LPARAM>(lParam));
3117 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
3118 return ::IsDBCSLeadByteEx(codePage, ch) != 0;
3121 int Platform::DBCSCharLength(int codePage, const char *s) {
3122 return (::IsDBCSLeadByteEx(codePage, s[0]) != 0) ? 2 : 1;
3125 int Platform::DBCSCharMaxLength() {
3126 return 2;
3129 // These are utility functions not really tied to a platform
3131 int Platform::Minimum(int a, int b) {
3132 if (a < b)
3133 return a;
3134 else
3135 return b;
3138 int Platform::Maximum(int a, int b) {
3139 if (a > b)
3140 return a;
3141 else
3142 return b;
3145 //#define TRACE
3147 #ifdef TRACE
3148 void Platform::DebugPrintf(const char *format, ...) {
3149 char buffer[2000];
3150 va_list pArguments;
3151 va_start(pArguments, format);
3152 vsprintf(buffer,format,pArguments);
3153 va_end(pArguments);
3154 Platform::DebugDisplay(buffer);
3156 #else
3157 void Platform::DebugPrintf(const char *, ...) {
3159 #endif
3161 static bool assertionPopUps = true;
3163 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3164 bool ret = assertionPopUps;
3165 assertionPopUps = assertionPopUps_;
3166 return ret;
3169 void Platform::Assert(const char *c, const char *file, int line) {
3170 char buffer[2000];
3171 sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
3172 if (assertionPopUps) {
3173 int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
3174 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
3175 if (idButton == IDRETRY) {
3176 ::DebugBreak();
3177 } else if (idButton == IDIGNORE) {
3178 // all OK
3179 } else {
3180 abort();
3182 } else {
3183 strcat(buffer, "\r\n");
3184 Platform::DebugDisplay(buffer);
3185 ::DebugBreak();
3186 abort();
3190 int Platform::Clamp(int val, int minVal, int maxVal) {
3191 if (val > maxVal)
3192 val = maxVal;
3193 if (val < minVal)
3194 val = minVal;
3195 return val;
3198 void Platform_Initialise(void *hInstance) {
3199 OSVERSIONINFO osv = {sizeof(OSVERSIONINFO),0,0,0,0,TEXT("")};
3200 ::GetVersionEx(&osv);
3201 onNT = osv.dwPlatformId == VER_PLATFORM_WIN32_NT;
3202 ::InitializeCriticalSection(&crPlatformLock);
3203 hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
3204 // This may be called from DllMain, in which case the call to LoadLibrary
3205 // is bad because it can upset the DLL load order.
3206 if (!hDLLImage) {
3207 hDLLImage = ::LoadLibrary(TEXT("Msimg32"));
3209 if (hDLLImage) {
3210 AlphaBlendFn = (AlphaBlendSig)::GetProcAddress(hDLLImage, "AlphaBlend");
3212 ListBoxX_Register();
3215 void Platform_Finalise() {
3216 if (reverseArrowCursor != NULL)
3217 ::DestroyCursor(reverseArrowCursor);
3218 ListBoxX_Unregister();
3219 ::DeleteCriticalSection(&crPlatformLock);