Upgraded to scintilla 3.2.3
[TortoiseGit.git] / ext / scintilla / win32 / PlatWin.cxx
blobc9ec34eee7d0b869f7e9d72ce5d6ab254d929e27
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 #undef WINVER
23 #define WINVER 0x0500
24 #include <windows.h>
25 #include <commctrl.h>
26 #include <richedit.h>
27 #include <windowsx.h>
29 #if defined(NTDDI_WIN7) && !defined(DISABLE_D2D)
30 #define USE_D2D 1
31 #endif
33 #if defined(USE_D2D)
34 #include <d2d1.h>
35 #include <dwrite.h>
36 #endif
38 #include "Platform.h"
39 #include "UniConversion.h"
40 #include "XPM.h"
41 #include "FontQuality.h"
43 #ifndef IDC_HAND
44 #define IDC_HAND MAKEINTRESOURCE(32649)
45 #endif
47 // Take care of 32/64 bit pointers
48 #ifdef GetWindowLongPtr
49 static void *PointerFromWindow(HWND hWnd) {
50 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
52 static void SetWindowPointer(HWND hWnd, void *ptr) {
53 ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
55 #else
56 static void *PointerFromWindow(HWND hWnd) {
57 return reinterpret_cast<void *>(::GetWindowLong(hWnd, 0));
59 static void SetWindowPointer(HWND hWnd, void *ptr) {
60 ::SetWindowLong(hWnd, 0, reinterpret_cast<LONG>(ptr));
63 #ifndef GWLP_USERDATA
64 #define GWLP_USERDATA GWL_USERDATA
65 #endif
67 #ifndef GWLP_WNDPROC
68 #define GWLP_WNDPROC GWL_WNDPROC
69 #endif
71 #ifndef LONG_PTR
72 #define LONG_PTR LONG
73 #endif
75 static LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong) {
76 return ::SetWindowLong(hWnd, nIndex, dwNewLong);
79 static LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex) {
80 return ::GetWindowLong(hWnd, nIndex);
82 #endif
84 // Declarations needed for functions dynamically loaded as not available on all Windows versions.
85 typedef BOOL (WINAPI *AlphaBlendSig)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
86 typedef HMONITOR (WINAPI *MonitorFromPointSig)(POINT, DWORD);
87 typedef HMONITOR (WINAPI *MonitorFromRectSig)(LPCRECT, DWORD);
88 typedef BOOL (WINAPI *GetMonitorInfoSig)(HMONITOR, LPMONITORINFO);
90 static CRITICAL_SECTION crPlatformLock;
91 static HINSTANCE hinstPlatformRes = 0;
92 static bool onNT = false;
94 static HMODULE hDLLImage = 0;
95 static AlphaBlendSig AlphaBlendFn = 0;
97 static HMODULE hDLLUser32 = 0;
98 static HMONITOR (WINAPI *MonitorFromPointFn)(POINT, DWORD) = 0;
99 static HMONITOR (WINAPI *MonitorFromRectFn)(LPCRECT, DWORD) = 0;
100 static BOOL (WINAPI *GetMonitorInfoFn)(HMONITOR, LPMONITORINFO) = 0;
102 static HCURSOR reverseArrowCursor = NULL;
104 bool IsNT() {
105 return onNT;
108 #ifdef SCI_NAMESPACE
109 using namespace Scintilla;
110 #endif
112 Point Point::FromLong(long lpoint) {
113 return Point(static_cast<short>(LOWORD(lpoint)), static_cast<short>(HIWORD(lpoint)));
116 static RECT RectFromPRectangle(PRectangle prc) {
117 RECT rc = {static_cast<LONG>(prc.left), static_cast<LONG>(prc.top),
118 static_cast<LONG>(prc.right), static_cast<LONG>(prc.bottom)};
119 return rc;
122 #if defined(USE_D2D)
123 IDWriteFactory *pIDWriteFactory = 0;
124 ID2D1Factory *pD2DFactory = 0;
126 bool LoadD2D() {
127 static bool triedLoadingD2D = false;
128 static HMODULE hDLLD2D = 0;
129 static HMODULE hDLLDWrite = 0;
130 if (!triedLoadingD2D) {
131 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
132 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
133 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
134 IUnknown **factory);
136 hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
137 if (hDLLD2D) {
138 D2D1CFSig fnD2DCF = (D2D1CFSig)::GetProcAddress(hDLLD2D, "D2D1CreateFactory");
139 if (fnD2DCF) {
140 // A single threaded factory as Scintilla always draw on the GUI thread
141 fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
142 __uuidof(ID2D1Factory),
144 reinterpret_cast<IUnknown**>(&pD2DFactory));
147 hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
148 if (hDLLDWrite) {
149 DWriteCFSig fnDWCF = (DWriteCFSig)::GetProcAddress(hDLLDWrite, "DWriteCreateFactory");
150 if (fnDWCF) {
151 fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
152 __uuidof(IDWriteFactory),
153 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
157 triedLoadingD2D = true;
158 return pIDWriteFactory && pD2DFactory;
160 #endif
162 struct FormatAndMetrics {
163 int technology;
164 HFONT hfont;
165 #if defined(USE_D2D)
166 IDWriteTextFormat *pTextFormat;
167 #endif
168 int extraFontFlag;
169 FLOAT yAscent;
170 FLOAT yDescent;
171 FLOAT yInternalLeading;
172 FormatAndMetrics(HFONT hfont_, int extraFontFlag_) :
173 technology(SCWIN_TECH_GDI), hfont(hfont_),
174 #if defined(USE_D2D)
175 pTextFormat(0),
176 #endif
177 extraFontFlag(extraFontFlag_), yAscent(2), yDescent(1), yInternalLeading(0) {
179 #if defined(USE_D2D)
180 FormatAndMetrics(IDWriteTextFormat *pTextFormat_, int extraFontFlag_, FLOAT yAscent_, FLOAT yDescent_, FLOAT yInternalLeading_) :
181 technology(SCWIN_TECH_DIRECTWRITE), hfont(0), pTextFormat(pTextFormat_), extraFontFlag(extraFontFlag_), yAscent(yAscent_), yDescent(yDescent_), yInternalLeading(yInternalLeading_) {
183 #endif
184 ~FormatAndMetrics() {
185 if (hfont)
186 ::DeleteObject(hfont);
187 #if defined(USE_D2D)
188 if (pTextFormat)
189 pTextFormat->Release();
190 pTextFormat = 0;
191 #endif
192 extraFontFlag = 0;
193 yAscent = 2;
194 yDescent = 1;
195 yInternalLeading = 0;
197 HFONT HFont();
200 HFONT FormatAndMetrics::HFont() {
201 LOGFONTW lf;
202 memset(&lf, 0, sizeof(lf));
203 #if defined(USE_D2D)
204 if (technology == SCWIN_TECH_GDI) {
205 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
206 return 0;
208 } else {
209 HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
210 if (!SUCCEEDED(hr)) {
211 return 0;
213 lf.lfWeight = pTextFormat->GetFontWeight();
214 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
215 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
217 #else
218 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
219 return 0;
221 #endif
222 return ::CreateFontIndirectW(&lf);
225 #ifndef CLEARTYPE_QUALITY
226 #define CLEARTYPE_QUALITY 5
227 #endif
229 static BYTE Win32MapFontQuality(int extraFontFlag) {
230 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
232 case SC_EFF_QUALITY_NON_ANTIALIASED:
233 return NONANTIALIASED_QUALITY;
235 case SC_EFF_QUALITY_ANTIALIASED:
236 return ANTIALIASED_QUALITY;
238 case SC_EFF_QUALITY_LCD_OPTIMIZED:
239 return CLEARTYPE_QUALITY;
241 default:
242 return SC_EFF_QUALITY_DEFAULT;
246 #if defined(USE_D2D)
247 static D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) {
248 switch (extraFontFlag & SC_EFF_QUALITY_MASK) {
250 case SC_EFF_QUALITY_NON_ANTIALIASED:
251 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
253 case SC_EFF_QUALITY_ANTIALIASED:
254 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
256 case SC_EFF_QUALITY_LCD_OPTIMIZED:
257 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
259 default:
260 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
263 #endif
265 static void SetLogFont(LOGFONTA &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) {
266 memset(&lf, 0, sizeof(lf));
267 // The negative is to allow for leading
268 lf.lfHeight = -(abs(static_cast<int>(size + 0.5)));
269 lf.lfWeight = weight;
270 lf.lfItalic = static_cast<BYTE>(italic ? 1 : 0);
271 lf.lfCharSet = static_cast<BYTE>(characterSet);
272 lf.lfQuality = Win32MapFontQuality(extraFontFlag);
273 strncpy(lf.lfFaceName, faceName, sizeof(lf.lfFaceName));
277 * Create a hash from the parameters for a font to allow easy checking for identity.
278 * If one font is the same as another, its hash will be the same, but if the hash is the
279 * same then they may still be different.
281 static int HashFont(const FontParameters &fp) {
282 return
283 static_cast<int>(fp.size) ^
284 (fp.characterSet << 10) ^
285 ((fp.extraFontFlag & SC_EFF_QUALITY_MASK) << 9) ^
286 ((fp.weight/100) << 12) ^
287 (fp.italic ? 0x20000000 : 0) ^
288 (fp.technology << 15) ^
289 fp.faceName[0];
292 class FontCached : Font {
293 FontCached *next;
294 int usage;
295 float size;
296 LOGFONTA lf;
297 int technology;
298 int hash;
299 FontCached(const FontParameters &fp);
300 ~FontCached() {}
301 bool SameAs(const FontParameters &fp);
302 virtual void Release();
304 static FontCached *first;
305 public:
306 static FontID FindOrCreate(const FontParameters &fp);
307 static void ReleaseId(FontID fid_);
310 FontCached *FontCached::first = 0;
312 FontCached::FontCached(const FontParameters &fp) :
313 next(0), usage(0), size(1.0), hash(0) {
314 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
315 technology = fp.technology;
316 hash = HashFont(fp);
317 fid = 0;
318 if (technology == SCWIN_TECH_GDI) {
319 HFONT hfont = ::CreateFontIndirectA(&lf);
320 fid = reinterpret_cast<void *>(new FormatAndMetrics(hfont, fp.extraFontFlag));
321 } else {
322 #if defined(USE_D2D)
323 IDWriteTextFormat *pTextFormat;
324 const int faceSize = 200;
325 WCHAR wszFace[faceSize];
326 UTF16FromUTF8(fp.faceName, static_cast<unsigned int>(strlen(fp.faceName))+1, wszFace, faceSize);
327 FLOAT fHeight = fp.size;
328 DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
329 HRESULT hr = pIDWriteFactory->CreateTextFormat(wszFace, NULL,
330 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
331 style,
332 DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
333 if (SUCCEEDED(hr)) {
334 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
336 const int maxLines = 2;
337 DWRITE_LINE_METRICS lineMetrics[maxLines];
338 UINT32 lineCount = 0;
339 FLOAT yAscent = 1.0f;
340 FLOAT yDescent = 1.0f;
341 FLOAT yInternalLeading = 0.0f;
342 IDWriteTextLayout *pTextLayout = 0;
343 hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
344 100.0f, 100.0f, &pTextLayout);
345 if (SUCCEEDED(hr)) {
346 hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
347 if (SUCCEEDED(hr)) {
348 yAscent = lineMetrics[0].baseline;
349 yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
351 FLOAT emHeight;
352 hr = pTextLayout->GetFontSize(0, &emHeight);
353 if (SUCCEEDED(hr)) {
354 yInternalLeading = lineMetrics[0].height - emHeight;
357 pTextLayout->Release();
359 fid = reinterpret_cast<void *>(new FormatAndMetrics(pTextFormat, fp.extraFontFlag, yAscent, yDescent, yInternalLeading));
361 #endif
363 usage = 1;
366 bool FontCached::SameAs(const FontParameters &fp) {
367 return
368 (size == fp.size) &&
369 (lf.lfWeight == fp.weight) &&
370 (lf.lfItalic == static_cast<BYTE>(fp.italic ? 1 : 0)) &&
371 (lf.lfCharSet == fp.characterSet) &&
372 (lf.lfQuality == Win32MapFontQuality(fp.extraFontFlag)) &&
373 (technology == fp.technology) &&
374 0 == strcmp(lf.lfFaceName,fp.faceName);
377 void FontCached::Release() {
378 delete reinterpret_cast<FormatAndMetrics *>(fid);
379 fid = 0;
382 FontID FontCached::FindOrCreate(const FontParameters &fp) {
383 FontID ret = 0;
384 ::EnterCriticalSection(&crPlatformLock);
385 int hashFind = HashFont(fp);
386 for (FontCached *cur=first; cur; cur=cur->next) {
387 if ((cur->hash == hashFind) &&
388 cur->SameAs(fp)) {
389 cur->usage++;
390 ret = cur->fid;
393 if (ret == 0) {
394 FontCached *fc = new FontCached(fp);
395 if (fc) {
396 fc->next = first;
397 first = fc;
398 ret = fc->fid;
401 ::LeaveCriticalSection(&crPlatformLock);
402 return ret;
405 void FontCached::ReleaseId(FontID fid_) {
406 ::EnterCriticalSection(&crPlatformLock);
407 FontCached **pcur=&first;
408 for (FontCached *cur=first; cur; cur=cur->next) {
409 if (cur->fid == fid_) {
410 cur->usage--;
411 if (cur->usage == 0) {
412 *pcur = cur->next;
413 cur->Release();
414 cur->next = 0;
415 delete cur;
417 break;
419 pcur=&cur->next;
421 ::LeaveCriticalSection(&crPlatformLock);
424 Font::Font() {
425 fid = 0;
428 Font::~Font() {
431 #define FONTS_CACHED
433 void Font::Create(const FontParameters &fp) {
434 Release();
435 if (fp.faceName)
436 fid = FontCached::FindOrCreate(fp);
439 void Font::Release() {
440 if (fid)
441 FontCached::ReleaseId(fid);
442 fid = 0;
445 // Buffer to hold strings and string position arrays without always allocating on heap.
446 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
447 // when less than safe size otherwise allocate on heap and free automatically.
448 template<typename T, int lengthStandard>
449 class VarBuffer {
450 T bufferStandard[lengthStandard];
451 public:
452 T *buffer;
453 VarBuffer(size_t length) : buffer(0) {
454 if (length > lengthStandard) {
455 buffer = new T[length];
456 } else {
457 buffer = bufferStandard;
460 ~VarBuffer() {
461 if (buffer != bufferStandard) {
462 delete []buffer;
463 buffer = 0;
468 const int stackBufferLength = 10000;
469 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
470 public:
471 int tlen;
472 TextWide(const char *s, int len, bool unicodeMode, int codePage=0) :
473 VarBuffer<wchar_t, stackBufferLength>(len) {
474 if (unicodeMode) {
475 tlen = UTF16FromUTF8(s, len, buffer, len);
476 } else {
477 // Support Asian string display in 9x English
478 tlen = ::MultiByteToWideChar(codePage, 0, s, len, buffer, len);
482 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
484 #ifdef SCI_NAMESPACE
485 namespace Scintilla {
486 #endif
488 class SurfaceGDI : public Surface {
489 bool unicodeMode;
490 HDC hdc;
491 bool hdcOwned;
492 HPEN pen;
493 HPEN penOld;
494 HBRUSH brush;
495 HBRUSH brushOld;
496 HFONT font;
497 HFONT fontOld;
498 HBITMAP bitmap;
499 HBITMAP bitmapOld;
500 int maxWidthMeasure;
501 int maxLenText;
503 int codePage;
504 // If 9x OS and current code page is same as ANSI code page.
505 bool win9xACPSame;
507 void BrushColor(ColourDesired back);
508 void SetFont(Font &font_);
510 // Private so SurfaceGDI objects can not be copied
511 SurfaceGDI(const SurfaceGDI &);
512 SurfaceGDI &operator=(const SurfaceGDI &);
513 public:
514 SurfaceGDI();
515 virtual ~SurfaceGDI();
517 void Init(WindowID wid);
518 void Init(SurfaceID sid, WindowID wid);
519 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
521 void Release();
522 bool Initialised();
523 void PenColour(ColourDesired fore);
524 int LogPixelsY();
525 int DeviceHeightFont(int points);
526 void MoveTo(int x_, int y_);
527 void LineTo(int x_, int y_);
528 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
529 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
530 void FillRectangle(PRectangle rc, ColourDesired back);
531 void FillRectangle(PRectangle rc, Surface &surfacePattern);
532 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
533 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
534 ColourDesired outline, int alphaOutline, int flags);
535 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
536 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
537 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
539 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
540 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
541 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
542 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
543 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
544 XYPOSITION WidthText(Font &font_, const char *s, int len);
545 XYPOSITION WidthChar(Font &font_, char ch);
546 XYPOSITION Ascent(Font &font_);
547 XYPOSITION Descent(Font &font_);
548 XYPOSITION InternalLeading(Font &font_);
549 XYPOSITION ExternalLeading(Font &font_);
550 XYPOSITION Height(Font &font_);
551 XYPOSITION AverageCharWidth(Font &font_);
553 void SetClip(PRectangle rc);
554 void FlushCachedState();
556 void SetUnicodeMode(bool unicodeMode_);
557 void SetDBCSMode(int codePage_);
560 #ifdef SCI_NAMESPACE
561 } //namespace Scintilla
562 #endif
564 SurfaceGDI::SurfaceGDI() :
565 unicodeMode(false),
566 hdc(0), hdcOwned(false),
567 pen(0), penOld(0),
568 brush(0), brushOld(0),
569 font(0), fontOld(0),
570 bitmap(0), bitmapOld(0) {
571 // Windows 9x has only a 16 bit coordinate system so break after 30000 pixels
572 maxWidthMeasure = IsNT() ? INT_MAX : 30000;
573 // There appears to be a 16 bit string length limit in GDI on NT and a limit of
574 // 8192 characters on Windows 95.
575 maxLenText = IsNT() ? 65535 : 8192;
577 codePage = 0;
578 win9xACPSame = false;
581 SurfaceGDI::~SurfaceGDI() {
582 Release();
585 void SurfaceGDI::Release() {
586 if (penOld) {
587 ::SelectObject(reinterpret_cast<HDC>(hdc), penOld);
588 ::DeleteObject(pen);
589 penOld = 0;
591 pen = 0;
592 if (brushOld) {
593 ::SelectObject(reinterpret_cast<HDC>(hdc), brushOld);
594 ::DeleteObject(brush);
595 brushOld = 0;
597 brush = 0;
598 if (fontOld) {
599 // Fonts are not deleted as they are owned by a Font object
600 ::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);
601 fontOld = 0;
603 font = 0;
604 if (bitmapOld) {
605 ::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);
606 ::DeleteObject(bitmap);
607 bitmapOld = 0;
609 bitmap = 0;
610 if (hdcOwned) {
611 ::DeleteDC(reinterpret_cast<HDC>(hdc));
612 hdc = 0;
613 hdcOwned = false;
617 bool SurfaceGDI::Initialised() {
618 return hdc != 0;
621 void SurfaceGDI::Init(WindowID) {
622 Release();
623 hdc = ::CreateCompatibleDC(NULL);
624 hdcOwned = true;
625 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
628 void SurfaceGDI::Init(SurfaceID sid, WindowID) {
629 Release();
630 hdc = reinterpret_cast<HDC>(sid);
631 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
634 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID) {
635 Release();
636 hdc = ::CreateCompatibleDC(static_cast<SurfaceGDI *>(surface_)->hdc);
637 hdcOwned = true;
638 bitmap = ::CreateCompatibleBitmap(static_cast<SurfaceGDI *>(surface_)->hdc, width, height);
639 bitmapOld = static_cast<HBITMAP>(::SelectObject(hdc, bitmap));
640 ::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
643 void SurfaceGDI::PenColour(ColourDesired fore) {
644 if (pen) {
645 ::SelectObject(hdc, penOld);
646 ::DeleteObject(pen);
647 pen = 0;
648 penOld = 0;
650 pen = ::CreatePen(0,1,fore.AsLong());
651 penOld = static_cast<HPEN>(::SelectObject(reinterpret_cast<HDC>(hdc), pen));
654 void SurfaceGDI::BrushColor(ColourDesired back) {
655 if (brush) {
656 ::SelectObject(hdc, brushOld);
657 ::DeleteObject(brush);
658 brush = 0;
659 brushOld = 0;
661 // Only ever want pure, non-dithered brushes
662 ColourDesired colourNearest = ::GetNearestColor(hdc, back.AsLong());
663 brush = ::CreateSolidBrush(colourNearest.AsLong());
664 brushOld = static_cast<HBRUSH>(::SelectObject(hdc, brush));
667 void SurfaceGDI::SetFont(Font &font_) {
668 if (font_.GetID() != font) {
669 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
670 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI);
671 if (fontOld) {
672 ::SelectObject(hdc, pfm->hfont);
673 } else {
674 fontOld = static_cast<HFONT>(::SelectObject(hdc, pfm->hfont));
676 font = reinterpret_cast<HFONT>(pfm->hfont);
680 int SurfaceGDI::LogPixelsY() {
681 return ::GetDeviceCaps(hdc, LOGPIXELSY);
684 int SurfaceGDI::DeviceHeightFont(int points) {
685 return ::MulDiv(points, LogPixelsY(), 72);
688 void SurfaceGDI::MoveTo(int x_, int y_) {
689 ::MoveToEx(hdc, x_, y_, 0);
692 void SurfaceGDI::LineTo(int x_, int y_) {
693 ::LineTo(hdc, x_, y_);
696 void SurfaceGDI::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
697 PenColour(fore);
698 BrushColor(back);
699 std::vector<POINT> outline;
700 for (int i=0;i<npts;i++) {
701 POINT pt = {static_cast<LONG>(pts[i].x), static_cast<LONG>(pts[i].y)};
702 outline.push_back(pt);
704 ::Polygon(hdc, &outline[0], npts);
707 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
708 PenColour(fore);
709 BrushColor(back);
710 ::Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
713 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) {
714 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
715 // There is no need to allocate a brush either.
716 RECT rcw = RectFromPRectangle(rc);
717 ::SetBkColor(hdc, back.AsLong());
718 ::ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rcw, TEXT(""), 0, NULL);
721 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
722 HBRUSH br;
723 if (static_cast<SurfaceGDI &>(surfacePattern).bitmap)
724 br = ::CreatePatternBrush(static_cast<SurfaceGDI &>(surfacePattern).bitmap);
725 else // Something is wrong so display in red
726 br = ::CreateSolidBrush(RGB(0xff, 0, 0));
727 RECT rcw = RectFromPRectangle(rc);
728 ::FillRect(hdc, &rcw, br);
729 ::DeleteObject(br);
732 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
733 PenColour(fore);
734 BrushColor(back);
735 ::RoundRect(hdc,
736 rc.left + 1, rc.top,
737 rc.right - 1, rc.bottom,
738 8, 8);
741 // Plot a point into a DWORD buffer symetrically to all 4 qudrants
742 static void AllFour(DWORD *pixels, int width, int height, int x, int y, DWORD val) {
743 pixels[y*width+x] = val;
744 pixels[y*width+width-1-x] = val;
745 pixels[(height-1-y)*width+x] = val;
746 pixels[(height-1-y)*width+width-1-x] = val;
749 #ifndef AC_SRC_OVER
750 #define AC_SRC_OVER 0x00
751 #endif
752 #ifndef AC_SRC_ALPHA
753 #define AC_SRC_ALPHA 0x01
754 #endif
756 static DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) {
757 union {
758 byte pixVal[4];
759 DWORD val;
760 } converter;
761 converter.pixVal[0] = b;
762 converter.pixVal[1] = g;
763 converter.pixVal[2] = r;
764 converter.pixVal[3] = a;
765 return converter.val;
768 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
769 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
770 if (AlphaBlendFn && rc.Width() > 0) {
771 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
772 int width = rc.Width();
773 int height = rc.Height();
774 // Ensure not distorted too much by corners when small
775 cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
776 BITMAPINFO bpih = {sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0};
777 void *image = 0;
778 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
779 DIB_RGB_COLORS, &image, NULL, 0);
781 if (hbmMem) {
782 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
784 DWORD valEmpty = dwordFromBGRA(0,0,0,0);
785 DWORD valFill = dwordFromBGRA(
786 static_cast<byte>(GetBValue(fill.AsLong()) * alphaFill / 255),
787 static_cast<byte>(GetGValue(fill.AsLong()) * alphaFill / 255),
788 static_cast<byte>(GetRValue(fill.AsLong()) * alphaFill / 255),
789 static_cast<byte>(alphaFill));
790 DWORD valOutline = dwordFromBGRA(
791 static_cast<byte>(GetBValue(outline.AsLong()) * alphaOutline / 255),
792 static_cast<byte>(GetGValue(outline.AsLong()) * alphaOutline / 255),
793 static_cast<byte>(GetRValue(outline.AsLong()) * alphaOutline / 255),
794 static_cast<byte>(alphaOutline));
795 DWORD *pixels = reinterpret_cast<DWORD *>(image);
796 for (int y=0; y<height; y++) {
797 for (int x=0; x<width; x++) {
798 if ((x==0) || (x==width-1) || (y == 0) || (y == height-1)) {
799 pixels[y*width+x] = valOutline;
800 } else {
801 pixels[y*width+x] = valFill;
805 for (int c=0;c<cornerSize; c++) {
806 for (int x=0;x<c+1; x++) {
807 AllFour(pixels, width, height, x, c-x, valEmpty);
810 for (int x=1;x<cornerSize; x++) {
811 AllFour(pixels, width, height, x, cornerSize-x, valOutline);
814 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
816 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, width, height, hMemDC, 0, 0, width, height, merge);
818 SelectBitmap(hMemDC, hbmOld);
819 ::DeleteObject(hbmMem);
821 ::DeleteDC(hMemDC);
822 } else {
823 BrushColor(outline);
824 RECT rcw = RectFromPRectangle(rc);
825 FrameRect(hdc, &rcw, brush);
829 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
830 if (AlphaBlendFn && rc.Width() > 0) {
831 HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
832 if (rc.Width() > width)
833 rc.left += static_cast<int>((rc.Width() - width) / 2);
834 rc.right = rc.left + width;
835 if (rc.Height() > height)
836 rc.top += static_cast<int>((rc.Height() - height) / 2);
837 rc.bottom = rc.top + height;
839 BITMAPINFO bpih = {sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0};
840 unsigned char *image = 0;
841 HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
842 DIB_RGB_COLORS, reinterpret_cast<void **>(&image), NULL, 0);
843 if (hbmMem) {
844 HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
846 for (int y=height-1; y>=0; y--) {
847 for (int x=0; x<width; x++) {
848 unsigned char *pixel = image + (y*width+x) * 4;
849 unsigned char alpha = pixelsImage[3];
850 // Input is RGBA, output is BGRA with premultiplied alpha
851 pixel[2] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
852 pixel[1] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
853 pixel[0] = static_cast<unsigned char>((*pixelsImage++) * alpha / 255);
854 pixel[3] = static_cast<unsigned char>(*pixelsImage++);
858 BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
860 AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, rc.Width(), rc.Height(), hMemDC, 0, 0, width, height, merge);
862 SelectBitmap(hMemDC, hbmOld);
863 ::DeleteObject(hbmMem);
865 ::DeleteDC(hMemDC);
870 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
871 PenColour(fore);
872 BrushColor(back);
873 ::Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);
876 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
877 ::BitBlt(hdc,
878 rc.left, rc.top, rc.Width(), rc.Height(),
879 static_cast<SurfaceGDI &>(surfaceSource).hdc, from.x, from.y, SRCCOPY);
882 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
884 void SurfaceGDI::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
885 SetFont(font_);
886 RECT rcw = RectFromPRectangle(rc);
887 SIZE sz={0,0};
888 int pos = 0;
889 int x = rc.left;
891 // Text drawing may fail if the text is too big.
892 // If it does fail, slice up into segments and draw each segment.
893 const int maxSegmentLength = 0x200;
895 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
896 // Use ANSI calls
897 int lenDraw = Platform::Minimum(len, maxLenText);
898 if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s, lenDraw, NULL)) {
899 while (lenDraw > pos) {
900 int seglen = Platform::Minimum(maxSegmentLength, lenDraw - pos);
901 if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s+pos, seglen, NULL)) {
902 PLATFORM_ASSERT(false);
903 return;
905 ::GetTextExtentPoint32A(hdc, s+pos, seglen, &sz);
906 x += sz.cx;
907 pos += seglen;
910 } else {
911 // Use Unicode calls
912 const TextWide tbuf(s, len, unicodeMode, codePage);
913 if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, NULL)) {
914 while (tbuf.tlen > pos) {
915 int seglen = Platform::Minimum(maxSegmentLength, tbuf.tlen - pos);
916 if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf.buffer+pos, seglen, NULL)) {
917 PLATFORM_ASSERT(false);
918 return;
920 ::GetTextExtentPoint32W(hdc, tbuf.buffer+pos, seglen, &sz);
921 x += sz.cx;
922 pos += seglen;
928 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
929 ColourDesired fore, ColourDesired back) {
930 ::SetTextColor(hdc, fore.AsLong());
931 ::SetBkColor(hdc, back.AsLong());
932 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
935 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
936 ColourDesired fore, ColourDesired back) {
937 ::SetTextColor(hdc, fore.AsLong());
938 ::SetBkColor(hdc, back.AsLong());
939 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
942 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
943 ColourDesired fore) {
944 // Avoid drawing spaces in transparent mode
945 for (int i=0;i<len;i++) {
946 if (s[i] != ' ') {
947 ::SetTextColor(hdc, fore.AsLong());
948 ::SetBkMode(hdc, TRANSPARENT);
949 DrawTextCommon(rc, font_, ybase, s, len, 0);
950 ::SetBkMode(hdc, OPAQUE);
951 return;
956 XYPOSITION SurfaceGDI::WidthText(Font &font_, const char *s, int len) {
957 SetFont(font_);
958 SIZE sz={0,0};
959 if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
960 ::GetTextExtentPoint32A(hdc, s, Platform::Minimum(len, maxLenText), &sz);
961 } else {
962 const TextWide tbuf(s, len, unicodeMode, codePage);
963 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
965 return sz.cx;
968 void SurfaceGDI::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
969 SetFont(font_);
970 SIZE sz={0,0};
971 int fit = 0;
972 if (unicodeMode) {
973 const TextWide tbuf(s, len, unicodeMode, codePage);
974 TextPositionsI poses(tbuf.tlen);
975 fit = tbuf.tlen;
976 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
977 // Likely to have failed because on Windows 9x where function not available
978 // So measure the character widths by measuring each initial substring
979 // Turns a linear operation into a qudratic but seems fast enough on test files
980 for (int widthSS=0; widthSS < tbuf.tlen; widthSS++) {
981 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
982 poses.buffer[widthSS] = sz.cx;
985 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
986 int ui=0;
987 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
988 int i=0;
989 while (ui<fit) {
990 unsigned char uch = us[i];
991 unsigned int lenChar = 1;
992 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
993 lenChar = 4;
994 ui++;
995 } else if (uch >= (0x80 + 0x40 + 0x20)) {
996 lenChar = 3;
997 } else if (uch >= (0x80)) {
998 lenChar = 2;
1000 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1001 positions[i++] = poses.buffer[ui];
1003 ui++;
1005 int lastPos = 0;
1006 if (i > 0)
1007 lastPos = positions[i-1];
1008 while (i<len) {
1009 positions[i++] = lastPos;
1011 } else if (IsNT() || (codePage==0) || win9xACPSame) {
1012 // Zero positions to avoid random behaviour on failure.
1013 memset(positions, 0, len * sizeof(*positions));
1014 // len may be larger than platform supports so loop over segments small enough for platform
1015 int startOffset = 0;
1016 while (len > 0) {
1017 int lenBlock = Platform::Minimum(len, maxLenText);
1018 TextPositionsI poses(len);
1019 if (!::GetTextExtentExPointA(hdc, s, lenBlock, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1020 // Eeek - a NULL DC or other foolishness could cause this.
1021 return;
1022 } else if (fit < lenBlock) {
1023 // For some reason, such as an incomplete DBCS character
1024 // Not all the positions are filled in so make them equal to end.
1025 if (fit == 0)
1026 poses.buffer[fit++] = 0;
1027 for (int i = fit;i<lenBlock;i++)
1028 poses.buffer[i] = poses.buffer[fit-1];
1030 for (int i=0;i<lenBlock;i++)
1031 positions[i] = poses.buffer[i] + startOffset;
1032 startOffset = poses.buffer[lenBlock-1];
1033 len -= lenBlock;
1034 positions += lenBlock;
1035 s += lenBlock;
1037 } else {
1038 // Support Asian string display in 9x English
1039 const TextWide tbuf(s, len, unicodeMode, codePage);
1040 TextPositionsI poses(tbuf.tlen);
1041 for (int widthSS=0; widthSS<tbuf.tlen; widthSS++) {
1042 ::GetTextExtentPoint32W(hdc, tbuf.buffer, widthSS+1, &sz);
1043 poses.buffer[widthSS] = sz.cx;
1046 int ui = 0;
1047 for (int i=0;i<len;) {
1048 if (::IsDBCSLeadByteEx(codePage, s[i])) {
1049 positions[i] = poses.buffer[ui];
1050 positions[i+1] = poses.buffer[ui];
1051 i += 2;
1052 } else {
1053 positions[i] = poses.buffer[ui];
1054 i++;
1057 ui++;
1062 XYPOSITION SurfaceGDI::WidthChar(Font &font_, char ch) {
1063 SetFont(font_);
1064 SIZE sz;
1065 ::GetTextExtentPoint32A(hdc, &ch, 1, &sz);
1066 return sz.cx;
1069 XYPOSITION SurfaceGDI::Ascent(Font &font_) {
1070 SetFont(font_);
1071 TEXTMETRIC tm;
1072 ::GetTextMetrics(hdc, &tm);
1073 return tm.tmAscent;
1076 XYPOSITION SurfaceGDI::Descent(Font &font_) {
1077 SetFont(font_);
1078 TEXTMETRIC tm;
1079 ::GetTextMetrics(hdc, &tm);
1080 return tm.tmDescent;
1083 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) {
1084 SetFont(font_);
1085 TEXTMETRIC tm;
1086 ::GetTextMetrics(hdc, &tm);
1087 return tm.tmInternalLeading;
1090 XYPOSITION SurfaceGDI::ExternalLeading(Font &font_) {
1091 SetFont(font_);
1092 TEXTMETRIC tm;
1093 ::GetTextMetrics(hdc, &tm);
1094 return tm.tmExternalLeading;
1097 XYPOSITION SurfaceGDI::Height(Font &font_) {
1098 SetFont(font_);
1099 TEXTMETRIC tm;
1100 ::GetTextMetrics(hdc, &tm);
1101 return tm.tmHeight;
1104 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) {
1105 SetFont(font_);
1106 TEXTMETRIC tm;
1107 ::GetTextMetrics(hdc, &tm);
1108 return tm.tmAveCharWidth;
1111 void SurfaceGDI::SetClip(PRectangle rc) {
1112 ::IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
1115 void SurfaceGDI::FlushCachedState() {
1116 pen = 0;
1117 brush = 0;
1118 font = 0;
1121 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1122 unicodeMode=unicodeMode_;
1125 void SurfaceGDI::SetDBCSMode(int codePage_) {
1126 // No action on window as automatically handled by system.
1127 codePage = codePage_;
1128 win9xACPSame = !IsNT() && ((unsigned int)codePage == ::GetACP());
1131 #if defined(USE_D2D)
1133 #ifdef SCI_NAMESPACE
1134 namespace Scintilla {
1135 #endif
1137 class SurfaceD2D : public Surface {
1138 bool unicodeMode;
1139 int x, y;
1141 int codePage;
1143 ID2D1RenderTarget *pRenderTarget;
1144 bool ownRenderTarget;
1145 int clipsActive;
1147 IDWriteTextFormat *pTextFormat;
1148 FLOAT yAscent;
1149 FLOAT yDescent;
1150 FLOAT yInternalLeading;
1152 ID2D1SolidColorBrush *pBrush;
1154 int logPixelsY;
1155 float dpiScaleX;
1156 float dpiScaleY;
1158 void SetFont(Font &font_);
1160 // Private so SurfaceD2D objects can not be copied
1161 SurfaceD2D(const SurfaceD2D &);
1162 SurfaceD2D &operator=(const SurfaceD2D &);
1163 public:
1164 SurfaceD2D();
1165 virtual ~SurfaceD2D();
1167 void SetScale();
1168 void Init(WindowID wid);
1169 void Init(SurfaceID sid, WindowID wid);
1170 void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
1172 void Release();
1173 bool Initialised();
1175 HRESULT FlushDrawing();
1177 void PenColour(ColourDesired fore);
1178 void D2DPenColour(ColourDesired fore, int alpha=255);
1179 int LogPixelsY();
1180 int DeviceHeightFont(int points);
1181 void MoveTo(int x_, int y_);
1182 void LineTo(int x_, int y_);
1183 void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back);
1184 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back);
1185 void FillRectangle(PRectangle rc, ColourDesired back);
1186 void FillRectangle(PRectangle rc, Surface &surfacePattern);
1187 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back);
1188 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1189 ColourDesired outline, int alphaOutline, int flags);
1190 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage);
1191 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back);
1192 void Copy(PRectangle rc, Point from, Surface &surfaceSource);
1194 void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions);
1195 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1196 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back);
1197 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore);
1198 void MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions);
1199 XYPOSITION WidthText(Font &font_, const char *s, int len);
1200 XYPOSITION WidthChar(Font &font_, char ch);
1201 XYPOSITION Ascent(Font &font_);
1202 XYPOSITION Descent(Font &font_);
1203 XYPOSITION InternalLeading(Font &font_);
1204 XYPOSITION ExternalLeading(Font &font_);
1205 XYPOSITION Height(Font &font_);
1206 XYPOSITION AverageCharWidth(Font &font_);
1208 void SetClip(PRectangle rc);
1209 void FlushCachedState();
1211 void SetUnicodeMode(bool unicodeMode_);
1212 void SetDBCSMode(int codePage_);
1215 #ifdef SCI_NAMESPACE
1216 } //namespace Scintilla
1217 #endif
1219 SurfaceD2D::SurfaceD2D() :
1220 unicodeMode(false),
1221 x(0), y(0) {
1223 codePage = 0;
1225 pRenderTarget = NULL;
1226 ownRenderTarget = false;
1227 clipsActive = 0;
1229 // From selected font
1230 pTextFormat = NULL;
1231 yAscent = 2;
1232 yDescent = 1;
1233 yInternalLeading = 0;
1235 pBrush = NULL;
1237 logPixelsY = 72;
1238 dpiScaleX = 1.0;
1239 dpiScaleY = 1.0;
1242 SurfaceD2D::~SurfaceD2D() {
1243 Release();
1246 void SurfaceD2D::Release() {
1247 if (pBrush) {
1248 pBrush->Release();
1249 pBrush = 0;
1251 if (pRenderTarget) {
1252 while (clipsActive) {
1253 pRenderTarget->PopAxisAlignedClip();
1254 clipsActive--;
1256 if (ownRenderTarget) {
1257 pRenderTarget->Release();
1259 pRenderTarget = 0;
1263 void SurfaceD2D::SetScale() {
1264 HDC hdcMeasure = ::CreateCompatibleDC(NULL);
1265 logPixelsY = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
1266 dpiScaleX = ::GetDeviceCaps(hdcMeasure, LOGPIXELSX) / 96.0f;
1267 dpiScaleY = logPixelsY / 96.0f;
1268 ::DeleteDC(hdcMeasure);
1271 bool SurfaceD2D::Initialised() {
1272 return pRenderTarget != 0;
1275 HRESULT SurfaceD2D::FlushDrawing() {
1276 return pRenderTarget->Flush();
1279 void SurfaceD2D::Init(WindowID /* wid */) {
1280 Release();
1281 SetScale();
1284 void SurfaceD2D::Init(SurfaceID sid, WindowID) {
1285 Release();
1286 SetScale();
1287 pRenderTarget = reinterpret_cast<ID2D1RenderTarget *>(sid);
1290 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID) {
1291 Release();
1292 SetScale();
1293 SurfaceD2D *psurfOther = static_cast<SurfaceD2D *>(surface_);
1294 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = NULL;
1295 D2D1_SIZE_F desiredSize = D2D1::SizeF(width, height);
1296 D2D1_PIXEL_FORMAT desiredFormat = psurfOther->pRenderTarget->GetPixelFormat();
1297 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1298 HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget(
1299 &desiredSize, NULL, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pCompatibleRenderTarget);
1300 if (SUCCEEDED(hr)) {
1301 pRenderTarget = pCompatibleRenderTarget;
1302 pRenderTarget->BeginDraw();
1303 ownRenderTarget = true;
1307 void SurfaceD2D::PenColour(ColourDesired fore) {
1308 D2DPenColour(fore);
1311 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) {
1312 if (pRenderTarget) {
1313 D2D_COLOR_F col;
1314 col.r = (fore.AsLong() & 0xff) / 255.0;
1315 col.g = ((fore.AsLong() & 0xff00) >> 8) / 255.0;
1316 col.b = (fore.AsLong() >> 16) / 255.0;
1317 col.a = alpha / 255.0;
1318 if (pBrush) {
1319 pBrush->SetColor(col);
1320 } else {
1321 HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1322 if (!SUCCEEDED(hr) && pBrush) {
1323 pBrush->Release();
1324 pBrush = 0;
1330 void SurfaceD2D::SetFont(Font &font_) {
1331 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font_.GetID());
1332 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE);
1333 pTextFormat = pfm->pTextFormat;
1334 yAscent = pfm->yAscent;
1335 yDescent = pfm->yDescent;
1336 yInternalLeading = pfm->yInternalLeading;
1337 if (pRenderTarget) {
1338 pRenderTarget->SetTextAntialiasMode(DWriteMapFontQuality(pfm->extraFontFlag));
1342 int SurfaceD2D::LogPixelsY() {
1343 return logPixelsY;
1346 int SurfaceD2D::DeviceHeightFont(int points) {
1347 return ::MulDiv(points, LogPixelsY(), 72);
1350 void SurfaceD2D::MoveTo(int x_, int y_) {
1351 x = x_;
1352 y = y_;
1355 static int Delta(int difference) {
1356 if (difference < 0)
1357 return -1;
1358 else if (difference > 0)
1359 return 1;
1360 else
1361 return 0;
1364 static int RoundFloat(float f) {
1365 return int(f+0.5);
1368 void SurfaceD2D::LineTo(int x_, int y_) {
1369 if (pRenderTarget) {
1370 int xDiff = x_ - x;
1371 int xDelta = Delta(xDiff);
1372 int yDiff = y_ - y;
1373 int yDelta = Delta(yDiff);
1374 if ((xDiff == 0) || (yDiff == 0)) {
1375 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle
1376 int xEnd = x_ - xDelta;
1377 int left = Platform::Minimum(x, xEnd);
1378 int width = abs(x - xEnd) + 1;
1379 int yEnd = y_ - yDelta;
1380 int top = Platform::Minimum(y, yEnd);
1381 int height = abs(y - yEnd) + 1;
1382 D2D1_RECT_F rectangle1 = D2D1::RectF(left, top, left+width, top+height);
1383 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1384 } else if ((abs(xDiff) == abs(yDiff))) {
1385 // 45 degree slope
1386 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5, y + 0.5),
1387 D2D1::Point2F(x_ + 0.5 - xDelta, y_ + 0.5 - yDelta), pBrush);
1388 } else {
1389 // Line has a different slope so difficult to avoid last pixel
1390 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5, y + 0.5),
1391 D2D1::Point2F(x_ + 0.5, y_ + 0.5), pBrush);
1393 x = x_;
1394 y = y_;
1398 void SurfaceD2D::Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
1399 if (pRenderTarget) {
1400 ID2D1Factory *pFactory = 0;
1401 pRenderTarget->GetFactory(&pFactory);
1402 ID2D1PathGeometry *geometry=0;
1403 HRESULT hr = pFactory->CreatePathGeometry(&geometry);
1404 if (SUCCEEDED(hr)) {
1405 ID2D1GeometrySink *sink = 0;
1406 hr = geometry->Open(&sink);
1407 if (SUCCEEDED(hr)) {
1408 sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED);
1409 for (size_t i=1; i<static_cast<size_t>(npts); i++) {
1410 sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f));
1412 sink->EndFigure(D2D1_FIGURE_END_CLOSED);
1413 sink->Close();
1414 sink->Release();
1416 D2DPenColour(back);
1417 pRenderTarget->FillGeometry(geometry,pBrush);
1418 D2DPenColour(fore);
1419 pRenderTarget->DrawGeometry(geometry,pBrush);
1422 geometry->Release();
1427 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {
1428 if (pRenderTarget) {
1429 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top+0.5, RoundFloat(rc.right) - 0.5, rc.bottom-0.5);
1430 D2DPenColour(back);
1431 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1432 D2DPenColour(fore);
1433 pRenderTarget->DrawRectangle(&rectangle1, pBrush);
1437 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) {
1438 if (pRenderTarget) {
1439 D2DPenColour(back);
1440 D2D1_RECT_F rectangle1 = D2D1::RectF(RoundFloat(rc.left), rc.top, RoundFloat(rc.right), rc.bottom);
1441 pRenderTarget->FillRectangle(&rectangle1, pBrush);
1445 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1446 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfacePattern);
1447 surfOther.FlushDrawing();
1448 ID2D1Bitmap *pBitmap = NULL;
1449 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1450 surfOther.pRenderTarget);
1451 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1452 if (SUCCEEDED(hr)) {
1453 ID2D1BitmapBrush *pBitmapBrush = NULL;
1454 D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1455 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1456 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1457 // Create the bitmap brush.
1458 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1459 pBitmap->Release();
1460 if (SUCCEEDED(hr)) {
1461 pRenderTarget->FillRectangle(
1462 D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom),
1463 pBitmapBrush);
1464 pBitmapBrush->Release();
1469 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) {
1470 if (pRenderTarget) {
1471 D2D1_ROUNDED_RECT roundedRectFill = D2D1::RoundedRect(
1472 D2D1::RectF(rc.left+1.0, rc.top+1.0, rc.right-1.0, rc.bottom-1.0),
1473 8, 8);
1474 D2DPenColour(back);
1475 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1477 D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
1478 D2D1::RectF(rc.left + 0.5, rc.top+0.5, rc.right - 0.5, rc.bottom-0.5),
1479 8, 8);
1480 D2DPenColour(fore);
1481 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1485 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
1486 ColourDesired outline, int alphaOutline, int /* flags*/ ) {
1487 if (pRenderTarget) {
1488 if (cornerSize == 0) {
1489 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1490 D2D1_RECT_F rectFill = D2D1::RectF(RoundFloat(rc.left) + 1.0, rc.top + 1.0, RoundFloat(rc.right) - 1.0, rc.bottom - 1.0);
1491 D2DPenColour(fill, alphaFill);
1492 pRenderTarget->FillRectangle(rectFill, pBrush);
1494 D2D1_RECT_F rectOutline = D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top + 0.5, RoundFloat(rc.right) - 0.5, rc.bottom - 0.5);
1495 D2DPenColour(outline, alphaOutline);
1496 pRenderTarget->DrawRectangle(rectOutline, pBrush);
1497 } else {
1498 D2D1_ROUNDED_RECT roundedRectFill = D2D1::RoundedRect(
1499 D2D1::RectF(RoundFloat(rc.left) + 1.0, rc.top + 1.0, RoundFloat(rc.right) - 1.0, rc.bottom - 1.0),
1500 cornerSize, cornerSize);
1501 D2DPenColour(fill, alphaFill);
1502 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1504 D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
1505 D2D1::RectF(RoundFloat(rc.left) + 0.5, rc.top + 0.5, RoundFloat(rc.right) - 0.5, rc.bottom - 0.5),
1506 cornerSize, cornerSize);
1507 D2DPenColour(outline, alphaOutline);
1508 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush);
1513 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1514 if (pRenderTarget) {
1515 if (rc.Width() > width)
1516 rc.left += static_cast<int>((rc.Width() - width) / 2);
1517 rc.right = rc.left + width;
1518 if (rc.Height() > height)
1519 rc.top += static_cast<int>((rc.Height() - height) / 2);
1520 rc.bottom = rc.top + height;
1522 std::vector<unsigned char> image(height * width * 4);
1523 for (int y=0; y<height; y++) {
1524 for (int x=0; x<width; x++) {
1525 unsigned char *pixel = &image[0] + (y*width+x) * 4;
1526 unsigned char alpha = pixelsImage[3];
1527 // Input is RGBA, output is BGRA with premultiplied alpha
1528 pixel[2] = (*pixelsImage++) * alpha / 255;
1529 pixel[1] = (*pixelsImage++) * alpha / 255;
1530 pixel[0] = (*pixelsImage++) * alpha / 255;
1531 pixel[3] = *pixelsImage++;
1535 ID2D1Bitmap *bitmap = 0;
1536 D2D1_SIZE_U size = D2D1::SizeU(width, height);
1537 D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1538 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1539 HRESULT hr = pRenderTarget->CreateBitmap(size, &image[0],
1540 width * 4, &props, &bitmap);
1541 if (SUCCEEDED(hr)) {
1542 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1543 pRenderTarget->DrawBitmap(bitmap, rcDestination);
1545 bitmap->Release();
1549 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {
1550 if (pRenderTarget) {
1551 FLOAT radius = rc.Width() / 2.0f - 1.0f;
1552 D2D1_ELLIPSE ellipse = D2D1::Ellipse(
1553 D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f),
1554 radius,radius);
1556 PenColour(back);
1557 pRenderTarget->FillEllipse(ellipse, pBrush);
1558 PenColour(fore);
1559 pRenderTarget->DrawEllipse(ellipse, pBrush);
1563 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1564 SurfaceD2D &surfOther = static_cast<SurfaceD2D &>(surfaceSource);
1565 surfOther.FlushDrawing();
1566 ID2D1BitmapRenderTarget *pCompatibleRenderTarget = reinterpret_cast<ID2D1BitmapRenderTarget *>(
1567 surfOther.pRenderTarget);
1568 ID2D1Bitmap *pBitmap = NULL;
1569 HRESULT hr = pCompatibleRenderTarget->GetBitmap(&pBitmap);
1570 if (SUCCEEDED(hr)) {
1571 D2D1_RECT_F rcDestination = {rc.left, rc.top, rc.right, rc.bottom};
1572 D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()};
1573 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1574 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1575 pRenderTarget->Flush();
1576 pBitmap->Release();
1580 void SurfaceD2D::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, UINT fuOptions) {
1581 SetFont(font_);
1583 // Use Unicode calls
1584 const TextWide tbuf(s, len, unicodeMode, codePage);
1585 if (pRenderTarget && pTextFormat && pBrush) {
1586 if (fuOptions & ETO_CLIPPED) {
1587 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1588 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1591 // Explicitly creating a text layout appears a little faster
1592 IDWriteTextLayout *pTextLayout;
1593 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat,
1594 rc.Width(), rc.Height(), &pTextLayout);
1595 if (SUCCEEDED(hr)) {
1596 D2D1_POINT_2F origin = {rc.left, ybase-yAscent};
1597 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, D2D1_DRAW_TEXT_OPTIONS_NONE);
1598 pTextLayout->Release();
1601 if (fuOptions & ETO_CLIPPED) {
1602 pRenderTarget->PopAxisAlignedClip();
1607 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1608 ColourDesired fore, ColourDesired back) {
1609 if (pRenderTarget) {
1610 FillRectangle(rc, back);
1611 D2DPenColour(fore);
1612 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
1616 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1617 ColourDesired fore, ColourDesired back) {
1618 if (pRenderTarget) {
1619 FillRectangle(rc, back);
1620 D2DPenColour(fore);
1621 DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
1625 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len,
1626 ColourDesired fore) {
1627 // Avoid drawing spaces in transparent mode
1628 for (int i=0;i<len;i++) {
1629 if (s[i] != ' ') {
1630 if (pRenderTarget) {
1631 D2DPenColour(fore);
1632 DrawTextCommon(rc, font_, ybase, s, len, 0);
1634 return;
1639 XYPOSITION SurfaceD2D::WidthText(Font &font_, const char *s, int len) {
1640 FLOAT width = 1.0;
1641 SetFont(font_);
1642 const TextWide tbuf(s, len, unicodeMode, codePage);
1643 if (pIDWriteFactory && pTextFormat) {
1644 // Create a layout
1645 IDWriteTextLayout *pTextLayout = 0;
1646 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1647 if (SUCCEEDED(hr)) {
1648 DWRITE_TEXT_METRICS textMetrics;
1649 pTextLayout->GetMetrics(&textMetrics);
1650 width = textMetrics.widthIncludingTrailingWhitespace;
1651 pTextLayout->Release();
1654 return width;
1657 void SurfaceD2D::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) {
1658 SetFont(font_);
1659 int fit = 0;
1660 const TextWide tbuf(s, len, unicodeMode, codePage);
1661 TextPositions poses(tbuf.tlen);
1662 fit = tbuf.tlen;
1663 const int clusters = 1000;
1664 DWRITE_CLUSTER_METRICS clusterMetrics[clusters];
1665 UINT32 count = 0;
1666 if (pIDWriteFactory && pTextFormat) {
1667 SetFont(font_);
1668 // Create a layout
1669 IDWriteTextLayout *pTextLayout = 0;
1670 HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
1671 if (!SUCCEEDED(hr))
1672 return;
1673 // For now, assuming WCHAR == cluster
1674 pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count);
1675 FLOAT position = 0.0f;
1676 size_t ti=0;
1677 for (size_t ci=0;ci<count;ci++) {
1678 position += clusterMetrics[ci].width;
1679 for (size_t inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
1680 //poses.buffer[ti++] = int(position + 0.5);
1681 poses.buffer[ti++] = position;
1684 PLATFORM_ASSERT(ti == static_cast<size_t>(tbuf.tlen));
1685 pTextLayout->Release();
1687 if (unicodeMode) {
1688 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1689 int ui=0;
1690 const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
1691 int i=0;
1692 while (ui<fit) {
1693 unsigned char uch = us[i];
1694 unsigned int lenChar = 1;
1695 if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
1696 lenChar = 4;
1697 ui++;
1698 } else if (uch >= (0x80 + 0x40 + 0x20)) {
1699 lenChar = 3;
1700 } else if (uch >= (0x80)) {
1701 lenChar = 2;
1703 for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
1704 positions[i++] = poses.buffer[ui];
1706 ui++;
1708 int lastPos = 0;
1709 if (i > 0)
1710 lastPos = positions[i-1];
1711 while (i<len) {
1712 positions[i++] = lastPos;
1714 } else if (codePage == 0) {
1716 // One character per position
1717 PLATFORM_ASSERT(len == tbuf.tlen);
1718 for (size_t kk=0;kk<static_cast<size_t>(len);kk++) {
1719 positions[kk] = poses.buffer[kk];
1722 } else {
1724 // May be more than one byte per position
1725 int ui = 0;
1726 for (int i=0;i<len;) {
1727 if (::IsDBCSLeadByteEx(codePage, s[i])) {
1728 positions[i] = poses.buffer[ui];
1729 positions[i+1] = poses.buffer[ui];
1730 i += 2;
1731 } else {
1732 positions[i] = poses.buffer[ui];
1733 i++;
1736 ui++;
1741 XYPOSITION SurfaceD2D::WidthChar(Font &font_, char ch) {
1742 FLOAT width = 1.0;
1743 SetFont(font_);
1744 if (pIDWriteFactory && pTextFormat) {
1745 // Create a layout
1746 IDWriteTextLayout *pTextLayout = 0;
1747 const WCHAR wch = ch;
1748 HRESULT hr = pIDWriteFactory->CreateTextLayout(&wch, 1, pTextFormat, 1000.0, 1000.0, &pTextLayout);
1749 if (SUCCEEDED(hr)) {
1750 DWRITE_TEXT_METRICS textMetrics;
1751 pTextLayout->GetMetrics(&textMetrics);
1752 width = textMetrics.widthIncludingTrailingWhitespace;
1753 pTextLayout->Release();
1756 return width;
1759 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
1760 SetFont(font_);
1761 return ceil(yAscent);
1764 XYPOSITION SurfaceD2D::Descent(Font &font_) {
1765 SetFont(font_);
1766 return ceil(yDescent);
1769 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
1770 SetFont(font_);
1771 return floor(yInternalLeading);
1774 XYPOSITION SurfaceD2D::ExternalLeading(Font &) {
1775 // Not implemented, always return one
1776 return 1;
1779 XYPOSITION SurfaceD2D::Height(Font &font_) {
1780 return Ascent(font_) + Descent(font_);
1783 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) {
1784 FLOAT width = 1.0;
1785 SetFont(font_);
1786 if (pIDWriteFactory && pTextFormat) {
1787 // Create a layout
1788 IDWriteTextLayout *pTextLayout = 0;
1789 const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1790 HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(wcslen(wszAllAlpha)),
1791 pTextFormat, 1000.0, 1000.0, &pTextLayout);
1792 if (SUCCEEDED(hr)) {
1793 DWRITE_TEXT_METRICS textMetrics;
1794 pTextLayout->GetMetrics(&textMetrics);
1795 width = textMetrics.width / wcslen(wszAllAlpha);
1796 pTextLayout->Release();
1799 return width;
1802 void SurfaceD2D::SetClip(PRectangle rc) {
1803 if (pRenderTarget) {
1804 D2D1_RECT_F rcClip = {rc.left, rc.top, rc.right, rc.bottom};
1805 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
1806 clipsActive++;
1810 void SurfaceD2D::FlushCachedState() {
1813 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
1814 unicodeMode=unicodeMode_;
1817 void SurfaceD2D::SetDBCSMode(int codePage_) {
1818 // No action on window as automatically handled by system.
1819 codePage = codePage_;
1821 #endif
1823 Surface *Surface::Allocate(int technology) {
1824 #if defined(USE_D2D)
1825 if (technology == SCWIN_TECH_GDI)
1826 return new SurfaceGDI;
1827 else
1828 return new SurfaceD2D;
1829 #else
1830 return new SurfaceGDI;
1831 #endif
1834 Window::~Window() {
1837 void Window::Destroy() {
1838 if (wid)
1839 ::DestroyWindow(reinterpret_cast<HWND>(wid));
1840 wid = 0;
1843 bool Window::HasFocus() {
1844 return ::GetFocus() == wid;
1847 PRectangle Window::GetPosition() {
1848 RECT rc;
1849 ::GetWindowRect(reinterpret_cast<HWND>(wid), &rc);
1850 return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
1853 void Window::SetPosition(PRectangle rc) {
1854 ::SetWindowPos(reinterpret_cast<HWND>(wid),
1855 0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER|SWP_NOACTIVATE);
1858 static RECT RectFromMonitor(HMONITOR hMonitor) {
1859 if (GetMonitorInfoFn) {
1860 MONITORINFO mi = {0};
1861 mi.cbSize = sizeof(mi);
1862 if (GetMonitorInfoFn(hMonitor, &mi)) {
1863 return mi.rcWork;
1866 RECT rc = {0, 0, 0, 0};
1867 ::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0);
1868 return rc;
1871 void Window::SetPositionRelative(PRectangle rc, Window w) {
1872 LONG style = ::GetWindowLong(reinterpret_cast<HWND>(wid), GWL_STYLE);
1873 if (style & WS_POPUP) {
1874 POINT ptOther = {0, 0};
1875 ::ClientToScreen(reinterpret_cast<HWND>(w.GetID()), &ptOther);
1876 rc.Move(ptOther.x, ptOther.y);
1878 RECT rcMonitor = RectFromPRectangle(rc);
1880 HMONITOR hMonitor = NULL;
1881 if (MonitorFromRectFn)
1882 hMonitor = MonitorFromRectFn(&rcMonitor, MONITOR_DEFAULTTONEAREST);
1883 // If hMonitor is NULL, that's just the main screen anyways.
1884 //::GetMonitorInfo(hMonitor, &mi);
1885 RECT rcWork = RectFromMonitor(hMonitor);
1887 if (rcWork.left < rcWork.right) {
1888 // Now clamp our desired rectangle to fit inside the work area
1889 // This way, the menu will fit wholly on one screen. An improvement even
1890 // if you don't have a second monitor on the left... Menu's appears half on
1891 // one screen and half on the other are just U.G.L.Y.!
1892 if (rc.right > rcWork.right)
1893 rc.Move(rcWork.right - rc.right, 0);
1894 if (rc.bottom > rcWork.bottom)
1895 rc.Move(0, rcWork.bottom - rc.bottom);
1896 if (rc.left < rcWork.left)
1897 rc.Move(rcWork.left - rc.left, 0);
1898 if (rc.top < rcWork.top)
1899 rc.Move(0, rcWork.top - rc.top);
1902 SetPosition(rc);
1905 PRectangle Window::GetClientPosition() {
1906 RECT rc={0,0,0,0};
1907 if (wid)
1908 ::GetClientRect(reinterpret_cast<HWND>(wid), &rc);
1909 return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
1912 void Window::Show(bool show) {
1913 if (show)
1914 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_SHOWNOACTIVATE);
1915 else
1916 ::ShowWindow(reinterpret_cast<HWND>(wid), SW_HIDE);
1919 void Window::InvalidateAll() {
1920 ::InvalidateRect(reinterpret_cast<HWND>(wid), NULL, FALSE);
1923 void Window::InvalidateRectangle(PRectangle rc) {
1924 RECT rcw = RectFromPRectangle(rc);
1925 ::InvalidateRect(reinterpret_cast<HWND>(wid), &rcw, FALSE);
1928 static LRESULT Window_SendMessage(Window *w, UINT msg, WPARAM wParam=0, LPARAM lParam=0) {
1929 return ::SendMessage(reinterpret_cast<HWND>(w->GetID()), msg, wParam, lParam);
1932 void Window::SetFont(Font &font) {
1933 Window_SendMessage(this, WM_SETFONT,
1934 reinterpret_cast<WPARAM>(font.GetID()), 0);
1937 static void FlipBitmap(HBITMAP bitmap, int width, int height) {
1938 HDC hdc = ::CreateCompatibleDC(NULL);
1939 if (hdc != NULL) {
1940 HGDIOBJ prevBmp = ::SelectObject(hdc, bitmap);
1941 ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
1942 ::SelectObject(hdc, prevBmp);
1943 ::DeleteDC(hdc);
1947 static HCURSOR GetReverseArrowCursor() {
1948 if (reverseArrowCursor != NULL)
1949 return reverseArrowCursor;
1951 ::EnterCriticalSection(&crPlatformLock);
1952 HCURSOR cursor = reverseArrowCursor;
1953 if (cursor == NULL) {
1954 cursor = ::LoadCursor(NULL, IDC_ARROW);
1955 ICONINFO info;
1956 if (::GetIconInfo(cursor, &info)) {
1957 BITMAP bmp;
1958 if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) {
1959 FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
1960 if (info.hbmColor != NULL)
1961 FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
1962 info.xHotspot = (DWORD)bmp.bmWidth - 1 - info.xHotspot;
1964 reverseArrowCursor = ::CreateIconIndirect(&info);
1965 if (reverseArrowCursor != NULL)
1966 cursor = reverseArrowCursor;
1969 ::DeleteObject(info.hbmMask);
1970 if (info.hbmColor != NULL)
1971 ::DeleteObject(info.hbmColor);
1974 ::LeaveCriticalSection(&crPlatformLock);
1975 return cursor;
1978 void Window::SetCursor(Cursor curs) {
1979 switch (curs) {
1980 case cursorText:
1981 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
1982 break;
1983 case cursorUp:
1984 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
1985 break;
1986 case cursorWait:
1987 ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
1988 break;
1989 case cursorHoriz:
1990 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
1991 break;
1992 case cursorVert:
1993 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
1994 break;
1995 case cursorHand:
1996 ::SetCursor(::LoadCursor(NULL,IDC_HAND));
1997 break;
1998 case cursorReverseArrow:
1999 ::SetCursor(GetReverseArrowCursor());
2000 break;
2001 case cursorArrow:
2002 case cursorInvalid: // Should not occur, but just in case.
2003 ::SetCursor(::LoadCursor(NULL,IDC_ARROW));
2004 break;
2008 void Window::SetTitle(const char *s) {
2009 ::SetWindowTextA(reinterpret_cast<HWND>(wid), s);
2012 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
2013 coordinates */
2014 PRectangle Window::GetMonitorRect(Point pt) {
2015 // MonitorFromPoint and GetMonitorInfo are not available on Windows 95 and NT 4.
2016 PRectangle rcPosition = GetPosition();
2017 POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
2018 static_cast<LONG>(pt.y + rcPosition.top)};
2019 HMONITOR hMonitor = NULL;
2020 if (MonitorFromPointFn)
2021 hMonitor = MonitorFromPointFn(ptDesktop, MONITOR_DEFAULTTONEAREST);
2023 RECT rcWork = RectFromMonitor(hMonitor);
2024 if (rcWork.left < rcWork.right) {
2025 PRectangle rcMonitor(
2026 rcWork.left - rcPosition.left,
2027 rcWork.top - rcPosition.top,
2028 rcWork.right - rcPosition.left,
2029 rcWork.bottom - rcPosition.top);
2030 return rcMonitor;
2031 } else {
2032 return PRectangle();
2036 struct ListItemData {
2037 const char *text;
2038 int pixId;
2041 #define _ROUND2(n,pow2) \
2042 ( ( (n) + (pow2) - 1) & ~((pow2) - 1) )
2044 class LineToItem {
2045 char *words;
2046 int wordsCount;
2047 int wordsSize;
2049 ListItemData *data;
2050 int len;
2051 int count;
2053 private:
2054 void FreeWords() {
2055 delete []words;
2056 words = NULL;
2057 wordsCount = 0;
2058 wordsSize = 0;
2060 char *AllocWord(const char *word);
2062 public:
2063 LineToItem() : words(NULL), wordsCount(0), wordsSize(0), data(NULL), len(0), count(0) {
2065 ~LineToItem() {
2066 Clear();
2068 void Clear() {
2069 FreeWords();
2070 delete []data;
2071 data = NULL;
2072 len = 0;
2073 count = 0;
2076 ListItemData *Append(const char *text, int value);
2078 ListItemData Get(int index) const {
2079 if (index >= 0 && index < count) {
2080 return data[index];
2081 } else {
2082 ListItemData missing = {"", -1};
2083 return missing;
2086 int Count() const {
2087 return count;
2090 ListItemData *AllocItem();
2092 void SetWords(char *s) {
2093 words = s; // N.B. will be deleted on destruction
2097 char *LineToItem::AllocWord(const char *text) {
2098 int chars = static_cast<int>(strlen(text) + 1);
2099 int newCount = wordsCount + chars;
2100 if (newCount > wordsSize) {
2101 wordsSize = _ROUND2(newCount * 2, 8192);
2102 char *wordsNew = new char[wordsSize];
2103 memcpy(wordsNew, words, wordsCount);
2104 int offset = wordsNew - words;
2105 for (int i=0; i<count; i++)
2106 data[i].text += offset;
2107 delete []words;
2108 words = wordsNew;
2110 char *s = &words[wordsCount];
2111 wordsCount = newCount;
2112 strncpy(s, text, chars);
2113 return s;
2116 ListItemData *LineToItem::AllocItem() {
2117 if (count >= len) {
2118 int lenNew = _ROUND2((count+1) * 2, 1024);
2119 ListItemData *dataNew = new ListItemData[lenNew];
2120 memcpy(dataNew, data, count * sizeof(ListItemData));
2121 delete []data;
2122 data = dataNew;
2123 len = lenNew;
2125 ListItemData *item = &data[count];
2126 count++;
2127 return item;
2130 ListItemData *LineToItem::Append(const char *text, int imageIndex) {
2131 ListItemData *item = AllocItem();
2132 item->text = AllocWord(text);
2133 item->pixId = imageIndex;
2134 return item;
2137 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
2139 ListBox::ListBox() {
2142 ListBox::~ListBox() {
2145 class ListBoxX : public ListBox {
2146 int lineHeight;
2147 FontID fontCopy;
2148 int technology;
2149 RGBAImageSet images;
2150 LineToItem lti;
2151 HWND lb;
2152 bool unicodeMode;
2153 int desiredVisibleRows;
2154 unsigned int maxItemCharacters;
2155 unsigned int aveCharWidth;
2156 Window *parent;
2157 int ctrlID;
2158 CallBackAction doubleClickAction;
2159 void *doubleClickActionData;
2160 const char *widestItem;
2161 unsigned int maxCharWidth;
2162 int resizeHit;
2163 PRectangle rcPreSize;
2164 Point dragOffset;
2165 Point location; // Caret location at which the list is opened
2166 int wheelDelta; // mouse wheel residue
2168 HWND GetHWND() const;
2169 void AppendListItem(const char *startword, const char *numword);
2170 void AdjustWindowRect(PRectangle *rc) const;
2171 int ItemHeight() const;
2172 int MinClientWidth() const;
2173 int TextOffset() const;
2174 Point GetClientExtent() const;
2175 POINT MinTrackSize() const;
2176 POINT MaxTrackSize() const;
2177 void SetRedraw(bool on);
2178 void OnDoubleClick();
2179 void ResizeToCursor();
2180 void StartResize(WPARAM);
2181 int NcHitTest(WPARAM, LPARAM) const;
2182 void CentreItem(int);
2183 void Paint(HDC);
2184 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2186 static const Point ItemInset; // Padding around whole item
2187 static const Point TextInset; // Padding around text
2188 static const Point ImageInset; // Padding around image
2190 public:
2191 ListBoxX() : lineHeight(10), fontCopy(0), technology(0), lb(0), unicodeMode(false),
2192 desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8),
2193 parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL),
2194 widestItem(NULL), maxCharWidth(1), resizeHit(0), wheelDelta(0) {
2196 virtual ~ListBoxX() {
2197 if (fontCopy) {
2198 ::DeleteObject(fontCopy);
2199 fontCopy = 0;
2202 virtual void SetFont(Font &font);
2203 virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_);
2204 virtual void SetAverageCharWidth(int width);
2205 virtual void SetVisibleRows(int rows);
2206 virtual int GetVisibleRows() const;
2207 virtual PRectangle GetDesiredRect();
2208 virtual int CaretFromEdge();
2209 virtual void Clear();
2210 virtual void Append(char *s, int type = -1);
2211 virtual int Length();
2212 virtual void Select(int n);
2213 virtual int GetSelection();
2214 virtual int Find(const char *prefix);
2215 virtual void GetValue(int n, char *value, int len);
2216 virtual void RegisterImage(int type, const char *xpm_data);
2217 virtual void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage);
2218 virtual void ClearRegisteredImages();
2219 virtual void SetDoubleClickAction(CallBackAction action, void *data) {
2220 doubleClickAction = action;
2221 doubleClickActionData = data;
2223 virtual void SetList(const char *list, char separator, char typesep);
2224 void Draw(DRAWITEMSTRUCT *pDrawItem);
2225 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2226 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
2229 const Point ListBoxX::ItemInset(0, 0);
2230 const Point ListBoxX::TextInset(2, 0);
2231 const Point ListBoxX::ImageInset(1, 0);
2233 ListBox *ListBox::Allocate() {
2234 ListBoxX *lb = new ListBoxX();
2235 return lb;
2238 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) {
2239 parent = &parent_;
2240 ctrlID = ctrlID_;
2241 location = location_;
2242 lineHeight = lineHeight_;
2243 unicodeMode = unicodeMode_;
2244 technology = technology_;
2245 HWND hwndParent = reinterpret_cast<HWND>(parent->GetID());
2246 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
2247 // Window created as popup so not clipped within parent client area
2248 wid = ::CreateWindowEx(
2249 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
2250 WS_POPUP | WS_THICKFRAME,
2251 100,100, 150,80, hwndParent,
2252 NULL,
2253 hinstanceParent,
2254 this);
2256 POINT locationw = {static_cast<LONG>(location.x), static_cast<LONG>(location.y)};
2257 ::MapWindowPoints(hwndParent, NULL, &locationw, 1);
2258 location = Point(locationw.x, locationw.y);
2261 void ListBoxX::SetFont(Font &font) {
2262 if (font.GetID()) {
2263 if (fontCopy) {
2264 ::DeleteObject(fontCopy);
2265 fontCopy = 0;
2267 FormatAndMetrics *pfm = reinterpret_cast<FormatAndMetrics *>(font.GetID());
2268 fontCopy = pfm->HFont();
2269 ::SendMessage(lb, WM_SETFONT, reinterpret_cast<WPARAM>(fontCopy), 0);
2273 void ListBoxX::SetAverageCharWidth(int width) {
2274 aveCharWidth = width;
2277 void ListBoxX::SetVisibleRows(int rows) {
2278 desiredVisibleRows = rows;
2281 int ListBoxX::GetVisibleRows() const {
2282 return desiredVisibleRows;
2285 HWND ListBoxX::GetHWND() const {
2286 return reinterpret_cast<HWND>(GetID());
2289 PRectangle ListBoxX::GetDesiredRect() {
2290 PRectangle rcDesired = GetPosition();
2292 int rows = Length();
2293 if ((rows == 0) || (rows > desiredVisibleRows))
2294 rows = desiredVisibleRows;
2295 rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
2297 int width = MinClientWidth();
2298 HDC hdc = ::GetDC(lb);
2299 HFONT oldFont = SelectFont(hdc, fontCopy);
2300 SIZE textSize = {0, 0};
2301 int len = static_cast<int>(widestItem ? strlen(widestItem) : 0);
2302 if (unicodeMode) {
2303 const TextWide tbuf(widestItem, len, unicodeMode);
2304 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
2305 } else {
2306 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
2308 TEXTMETRIC tm;
2309 ::GetTextMetrics(hdc, &tm);
2310 maxCharWidth = tm.tmMaxCharWidth;
2311 SelectFont(hdc, oldFont);
2312 ::ReleaseDC(lb, hdc);
2314 int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
2315 if (width < widthDesired)
2316 width = widthDesired;
2318 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
2319 if (Length() > rows)
2320 rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
2322 AdjustWindowRect(&rcDesired);
2323 return rcDesired;
2326 int ListBoxX::TextOffset() const {
2327 int pixWidth = images.GetWidth();
2328 return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2);
2331 int ListBoxX::CaretFromEdge() {
2332 PRectangle rc;
2333 AdjustWindowRect(&rc);
2334 return TextOffset() + TextInset.x + (0 - rc.left) - 1;
2337 void ListBoxX::Clear() {
2338 ::SendMessage(lb, LB_RESETCONTENT, 0, 0);
2339 maxItemCharacters = 0;
2340 widestItem = NULL;
2341 lti.Clear();
2344 void ListBoxX::Append(char *s, int type) {
2345 int index = ::SendMessage(lb, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s));
2346 if (index < 0)
2347 return;
2348 ListItemData *newItem = lti.Append(s, type);
2349 unsigned int len = static_cast<unsigned int>(strlen(s));
2350 if (maxItemCharacters < len) {
2351 maxItemCharacters = len;
2352 widestItem = newItem->text;
2356 int ListBoxX::Length() {
2357 return lti.Count();
2360 void ListBoxX::Select(int n) {
2361 // We are going to scroll to centre on the new selection and then select it, so disable
2362 // redraw to avoid flicker caused by a painting new selection twice in unselected and then
2363 // selected states
2364 SetRedraw(false);
2365 CentreItem(n);
2366 ::SendMessage(lb, LB_SETCURSEL, n, 0);
2367 SetRedraw(true);
2370 int ListBoxX::GetSelection() {
2371 return ::SendMessage(lb, LB_GETCURSEL, 0, 0);
2374 // This is not actually called at present
2375 int ListBoxX::Find(const char *) {
2376 return LB_ERR;
2379 void ListBoxX::GetValue(int n, char *value, int len) {
2380 ListItemData item = lti.Get(n);
2381 strncpy(value, item.text, len);
2382 value[len-1] = '\0';
2385 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2386 XPM xpmImage(xpm_data);
2387 images.Add(type, new RGBAImage(xpmImage));
2390 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
2391 images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage));
2394 void ListBoxX::ClearRegisteredImages() {
2395 images.Clear();
2398 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
2399 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
2400 RECT rcBox = pDrawItem->rcItem;
2401 rcBox.left += TextOffset();
2402 if (pDrawItem->itemState & ODS_SELECTED) {
2403 RECT rcImage = pDrawItem->rcItem;
2404 rcImage.right = rcBox.left;
2405 // The image is not highlighted
2406 ::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2407 ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
2408 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
2409 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
2410 } else {
2411 ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2412 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
2413 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
2416 ListItemData item = lti.Get(pDrawItem->itemID);
2417 int pixId = item.pixId;
2418 const char *text = item.text;
2419 int len = static_cast<int>(strlen(text));
2421 RECT rcText = rcBox;
2422 ::InsetRect(&rcText, TextInset.x, TextInset.y);
2424 if (unicodeMode) {
2425 const TextWide tbuf(text, len, unicodeMode);
2426 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2427 } else {
2428 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
2430 if (pDrawItem->itemState & ODS_SELECTED) {
2431 ::DrawFocusRect(pDrawItem->hDC, &rcBox);
2434 // Draw the image, if any
2435 RGBAImage *pimage = images.Get(pixId);
2436 if (pimage) {
2437 Surface *surfaceItem = Surface::Allocate(technology);
2438 if (surfaceItem) {
2439 if (technology == SCWIN_TECH_GDI) {
2440 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
2441 int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
2442 PRectangle rcImage(left, pDrawItem->rcItem.top,
2443 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2444 surfaceItem->DrawRGBAImage(rcImage,
2445 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2446 delete surfaceItem;
2447 ::SetTextAlign(pDrawItem->hDC, TA_TOP);
2448 } else {
2449 #if defined(USE_D2D)
2450 D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
2451 D2D1_RENDER_TARGET_TYPE_DEFAULT,
2452 D2D1::PixelFormat(
2453 DXGI_FORMAT_B8G8R8A8_UNORM,
2454 D2D1_ALPHA_MODE_IGNORE),
2457 D2D1_RENDER_TARGET_USAGE_NONE,
2458 D2D1_FEATURE_LEVEL_DEFAULT
2460 ID2D1DCRenderTarget *pDCRT = 0;
2461 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
2462 RECT rcWindow;
2463 GetClientRect(pDrawItem->hwndItem, &rcWindow);
2464 hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow);
2465 if (SUCCEEDED(hr)) {
2466 surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
2467 pDCRT->BeginDraw();
2468 int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
2469 PRectangle rcImage(left, pDrawItem->rcItem.top,
2470 left + images.GetWidth(), pDrawItem->rcItem.bottom);
2471 surfaceItem->DrawRGBAImage(rcImage,
2472 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
2473 delete surfaceItem;
2474 pDCRT->EndDraw();
2475 pDCRT->Release();
2477 #endif
2484 void ListBoxX::AppendListItem(const char *startword, const char *numword) {
2485 ListItemData *item = lti.AllocItem();
2486 item->text = startword;
2487 if (numword) {
2488 int pixId = 0;
2489 char ch;
2490 while ((ch = *++numword) != '\0') {
2491 pixId = 10 * pixId + (ch - '0');
2493 item->pixId = pixId;
2494 } else {
2495 item->pixId = -1;
2498 unsigned int len = static_cast<unsigned int>(strlen(item->text));
2499 if (maxItemCharacters < len) {
2500 maxItemCharacters = len;
2501 widestItem = item->text;
2505 void ListBoxX::SetList(const char *list, char separator, char typesep) {
2506 // Turn off redraw while populating the list - this has a significant effect, even if
2507 // the listbox is not visible.
2508 SetRedraw(false);
2509 Clear();
2510 size_t size = strlen(list);
2511 char *words = new char[size+1];
2512 lti.SetWords(words);
2513 memcpy(words, list, size+1);
2514 char *startword = words;
2515 char *numword = NULL;
2516 for (size_t i=0; i < size; i++) {
2517 if (words[i] == separator) {
2518 words[i] = '\0';
2519 if (numword)
2520 *numword = '\0';
2521 AppendListItem(startword, numword);
2522 startword = words + i + 1;
2523 numword = NULL;
2524 } else if (words[i] == typesep) {
2525 numword = words + i;
2528 if (startword) {
2529 if (numword)
2530 *numword = '\0';
2531 AppendListItem(startword, numword);
2534 // Finally populate the listbox itself with the correct number of items
2535 int count = lti.Count();
2536 ::SendMessage(lb, LB_INITSTORAGE, count, 0);
2537 for (int j=0; j<count; j++) {
2538 ::SendMessage(lb, LB_ADDSTRING, 0, j+1);
2540 SetRedraw(true);
2543 void ListBoxX::AdjustWindowRect(PRectangle *rc) const {
2544 RECT rcw = RectFromPRectangle(*rc);
2545 ::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
2546 *rc = PRectangle(rcw.left, rcw.top, rcw.right, rcw.bottom);
2549 int ListBoxX::ItemHeight() const {
2550 int itemHeight = lineHeight + (TextInset.y * 2);
2551 int pixHeight = images.GetHeight() + (ImageInset.y * 2);
2552 if (itemHeight < pixHeight) {
2553 itemHeight = pixHeight;
2555 return itemHeight;
2558 int ListBoxX::MinClientWidth() const {
2559 return 12 * (aveCharWidth+aveCharWidth/3);
2562 POINT ListBoxX::MinTrackSize() const {
2563 PRectangle rc(0, 0, MinClientWidth(), ItemHeight());
2564 AdjustWindowRect(&rc);
2565 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2566 return ret;
2569 POINT ListBoxX::MaxTrackSize() const {
2570 PRectangle rc(0, 0,
2571 maxCharWidth * maxItemCharacters + TextInset.x * 2 +
2572 TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL),
2573 ItemHeight() * lti.Count());
2574 AdjustWindowRect(&rc);
2575 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
2576 return ret;
2579 void ListBoxX::SetRedraw(bool on) {
2580 ::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
2581 if (on)
2582 ::InvalidateRect(lb, NULL, TRUE);
2585 void ListBoxX::ResizeToCursor() {
2586 PRectangle rc = GetPosition();
2587 POINT ptw;
2588 ::GetCursorPos(&ptw);
2589 Point pt(ptw.x, ptw.y);
2590 pt.x += dragOffset.x;
2591 pt.y += dragOffset.y;
2593 switch (resizeHit) {
2594 case HTLEFT:
2595 rc.left = pt.x;
2596 break;
2597 case HTRIGHT:
2598 rc.right = pt.x;
2599 break;
2600 case HTTOP:
2601 rc.top = pt.y;
2602 break;
2603 case HTTOPLEFT:
2604 rc.top = pt.y;
2605 rc.left = pt.x;
2606 break;
2607 case HTTOPRIGHT:
2608 rc.top = pt.y;
2609 rc.right = pt.x;
2610 break;
2611 case HTBOTTOM:
2612 rc.bottom = pt.y;
2613 break;
2614 case HTBOTTOMLEFT:
2615 rc.bottom = pt.y;
2616 rc.left = pt.x;
2617 break;
2618 case HTBOTTOMRIGHT:
2619 rc.bottom = pt.y;
2620 rc.right = pt.x;
2621 break;
2624 POINT ptMin = MinTrackSize();
2625 POINT ptMax = MaxTrackSize();
2626 // We don't allow the left edge to move at present, but just in case
2627 rc.left = Platform::Maximum(Platform::Minimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
2628 rc.top = Platform::Maximum(Platform::Minimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
2629 rc.right = Platform::Maximum(Platform::Minimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
2630 rc.bottom = Platform::Maximum(Platform::Minimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
2632 SetPosition(rc);
2635 void ListBoxX::StartResize(WPARAM hitCode) {
2636 rcPreSize = GetPosition();
2637 POINT cursorPos;
2638 ::GetCursorPos(&cursorPos);
2640 switch (hitCode) {
2641 case HTRIGHT:
2642 case HTBOTTOM:
2643 case HTBOTTOMRIGHT:
2644 dragOffset.x = rcPreSize.right - cursorPos.x;
2645 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2646 break;
2648 case HTTOPRIGHT:
2649 dragOffset.x = rcPreSize.right - cursorPos.x;
2650 dragOffset.y = rcPreSize.top - cursorPos.y;
2651 break;
2653 // Note that the current hit test code prevents the left edge cases ever firing
2654 // as we don't want the left edge to be moveable
2655 case HTLEFT:
2656 case HTTOP:
2657 case HTTOPLEFT:
2658 dragOffset.x = rcPreSize.left - cursorPos.x;
2659 dragOffset.y = rcPreSize.top - cursorPos.y;
2660 break;
2661 case HTBOTTOMLEFT:
2662 dragOffset.x = rcPreSize.left - cursorPos.x;
2663 dragOffset.y = rcPreSize.bottom - cursorPos.y;
2664 break;
2666 default:
2667 return;
2670 ::SetCapture(GetHWND());
2671 resizeHit = hitCode;
2674 int ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
2675 int hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
2676 // There is an apparent bug in the DefWindowProc hit test code whereby it will
2677 // return HTTOPXXX if the window in question is shorter than the default
2678 // window caption height + frame, even if one is hovering over the bottom edge of
2679 // the frame, so workaround that here
2680 if (hit >= HTTOP && hit <= HTTOPRIGHT) {
2681 int minHeight = GetSystemMetrics(SM_CYMINTRACK);
2682 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2683 int yPos = GET_Y_LPARAM(lParam);
2684 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
2685 hit += HTBOTTOM - HTTOP;
2689 // Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
2690 // depending on whether the list is above or below the caret
2691 switch (hit) {
2692 case HTLEFT:
2693 case HTTOPLEFT:
2694 case HTBOTTOMLEFT:
2695 hit = HTERROR;
2696 break;
2698 case HTTOP:
2699 case HTTOPRIGHT: {
2700 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2701 // Valid only if caret below list
2702 if (location.y < rc.top)
2703 hit = HTERROR;
2705 break;
2707 case HTBOTTOM:
2708 case HTBOTTOMRIGHT: {
2709 PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
2710 // Valid only if caret above list
2711 if (rc.bottom < location.y)
2712 hit = HTERROR;
2714 break;
2717 return hit;
2720 void ListBoxX::OnDoubleClick() {
2722 if (doubleClickAction != NULL) {
2723 doubleClickAction(doubleClickActionData);
2727 Point ListBoxX::GetClientExtent() const {
2728 PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
2729 return Point(rc.Width(), rc.Height());
2732 void ListBoxX::CentreItem(int n) {
2733 // If below mid point, scroll up to centre, but with more items below if uneven
2734 if (n >= 0) {
2735 Point extent = GetClientExtent();
2736 int visible = extent.y/ItemHeight();
2737 if (visible < Length()) {
2738 int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
2739 int half = (visible - 1) / 2;
2740 if (n > (top + half))
2741 ::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
2746 // Performs a double-buffered paint operation to avoid flicker
2747 void ListBoxX::Paint(HDC hDC) {
2748 Point extent = GetClientExtent();
2749 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
2750 HDC bitmapDC = ::CreateCompatibleDC(hDC);
2751 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
2752 // The list background is mainly erased during painting, but can be a small
2753 // unpainted area when at the end of a non-integrally sized list with a
2754 // vertical scroll bar
2755 RECT rc = { 0, 0, static_cast<LONG>(extent.x), static_cast<LONG>(extent.y) };
2756 ::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
2757 // Paint the entire client area and vertical scrollbar
2758 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
2759 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
2760 // Select a stock brush to prevent warnings from BoundsChecker
2761 ::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
2762 SelectBitmap(bitmapDC, hBitmapOld);
2763 ::DeleteDC(bitmapDC);
2764 ::DeleteObject(hBitmap);
2767 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
2768 try {
2769 switch (uMsg) {
2770 case WM_ERASEBKGND:
2771 return TRUE;
2773 case WM_PAINT: {
2774 PAINTSTRUCT ps;
2775 HDC hDC = ::BeginPaint(hWnd, &ps);
2776 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2777 if (lbx)
2778 lbx->Paint(hDC);
2779 ::EndPaint(hWnd, &ps);
2781 return 0;
2783 case WM_MOUSEACTIVATE:
2784 // This prevents the view activating when the scrollbar is clicked
2785 return MA_NOACTIVATE;
2787 case WM_LBUTTONDOWN: {
2788 // We must take control of selection to prevent the ListBox activating
2789 // the popup
2790 LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
2791 int item = LOWORD(lResult);
2792 if (HIWORD(lResult) == 0 && item >= 0) {
2793 ::SendMessage(hWnd, LB_SETCURSEL, item, 0);
2796 return 0;
2798 case WM_LBUTTONUP:
2799 return 0;
2801 case WM_LBUTTONDBLCLK: {
2802 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
2803 if (lbx) {
2804 lbx->OnDoubleClick();
2807 return 0;
2809 case WM_MBUTTONDOWN:
2810 // disable the scroll wheel button click action
2811 return 0;
2814 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
2815 if (prevWndProc) {
2816 return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
2817 } else {
2818 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2820 } catch (...) {
2822 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
2825 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2826 switch (iMessage) {
2827 case WM_CREATE: {
2828 HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID()));
2829 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
2830 // but has useful side effect of speeding up list population significantly
2831 lb = ::CreateWindowEx(
2832 0, TEXT("listbox"), TEXT(""),
2833 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
2834 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
2835 0, 0, 150,80, hWnd,
2836 reinterpret_cast<HMENU>(ctrlID),
2837 hinstanceParent,
2839 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(lb, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlWndProc)));
2840 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
2842 break;
2844 case WM_SIZE:
2845 if (lb) {
2846 SetRedraw(false);
2847 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
2848 // Ensure the selection remains visible
2849 CentreItem(GetSelection());
2850 SetRedraw(true);
2852 break;
2854 case WM_PAINT: {
2855 PAINTSTRUCT ps;
2856 ::BeginPaint(hWnd, &ps);
2857 ::EndPaint(hWnd, &ps);
2859 break;
2861 case WM_COMMAND:
2862 // This is not actually needed now - the registered double click action is used
2863 // directly to action a choice from the list.
2864 ::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);
2865 break;
2867 case WM_MEASUREITEM: {
2868 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
2869 pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());
2871 break;
2873 case WM_DRAWITEM:
2874 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
2875 break;
2877 case WM_DESTROY:
2878 lb = 0;
2879 ::SetWindowLong(hWnd, 0, 0);
2880 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2882 case WM_ERASEBKGND:
2883 // To reduce flicker we can elide background erasure since this window is
2884 // completely covered by its child.
2885 return TRUE;
2887 case WM_GETMINMAXINFO: {
2888 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
2889 minMax->ptMaxTrackSize = MaxTrackSize();
2890 minMax->ptMinTrackSize = MinTrackSize();
2892 break;
2894 case WM_MOUSEACTIVATE:
2895 return MA_NOACTIVATE;
2897 case WM_NCHITTEST:
2898 return NcHitTest(wParam, lParam);
2900 case WM_NCLBUTTONDOWN:
2901 // We have to implement our own window resizing because the DefWindowProc
2902 // implementation insists on activating the resized window
2903 StartResize(wParam);
2904 return 0;
2906 case WM_MOUSEMOVE: {
2907 if (resizeHit == 0) {
2908 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2909 } else {
2910 ResizeToCursor();
2913 break;
2915 case WM_LBUTTONUP:
2916 case WM_CANCELMODE:
2917 if (resizeHit != 0) {
2918 resizeHit = 0;
2919 ::ReleaseCapture();
2921 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2923 case WM_MOUSEWHEEL:
2924 wheelDelta -= static_cast<short>(HIWORD(wParam));
2925 if (abs(wheelDelta) >= WHEEL_DELTA) {
2926 int nRows = GetVisibleRows();
2927 int linesToScroll = 1;
2928 if (nRows > 1) {
2929 linesToScroll = nRows - 1;
2931 if (linesToScroll > 3) {
2932 linesToScroll = 3;
2934 linesToScroll *= (wheelDelta / WHEEL_DELTA);
2935 int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0) + linesToScroll;
2936 if (top < 0) {
2937 top = 0;
2939 ::SendMessage(lb, LB_SETTOPINDEX, top, 0);
2940 // update wheel delta residue
2941 if (wheelDelta >= 0)
2942 wheelDelta = wheelDelta % WHEEL_DELTA;
2943 else
2944 wheelDelta = - (-wheelDelta % WHEEL_DELTA);
2946 break;
2948 default:
2949 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2952 return 0;
2955 LRESULT PASCAL ListBoxX::StaticWndProc(
2956 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2957 if (iMessage == WM_CREATE) {
2958 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
2959 SetWindowPointer(hWnd, pCreate->lpCreateParams);
2961 // Find C++ object associated with window.
2962 ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(hWnd));
2963 if (lbx) {
2964 return lbx->WndProc(hWnd, iMessage, wParam, lParam);
2965 } else {
2966 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
2970 static bool ListBoxX_Register() {
2971 WNDCLASSEX wndclassc;
2972 wndclassc.cbSize = sizeof(wndclassc);
2973 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
2974 // truncated items in the list and the appearance/disappearance of the vertical scroll bar.
2975 // The list repaint is double-buffered to avoid the flicker this would otherwise cause.
2976 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
2977 wndclassc.cbClsExtra = 0;
2978 wndclassc.cbWndExtra = sizeof(ListBoxX *);
2979 wndclassc.hInstance = hinstPlatformRes;
2980 wndclassc.hIcon = NULL;
2981 wndclassc.hbrBackground = NULL;
2982 wndclassc.lpszMenuName = NULL;
2983 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
2984 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
2985 wndclassc.lpszClassName = ListBoxX_ClassName;
2986 wndclassc.hIconSm = 0;
2988 return ::RegisterClassEx(&wndclassc) != 0;
2991 bool ListBoxX_Unregister() {
2992 return ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes) != 0;
2995 Menu::Menu() : mid(0) {
2998 void Menu::CreatePopUp() {
2999 Destroy();
3000 mid = ::CreatePopupMenu();
3003 void Menu::Destroy() {
3004 if (mid)
3005 ::DestroyMenu(reinterpret_cast<HMENU>(mid));
3006 mid = 0;
3009 void Menu::Show(Point pt, Window &w) {
3010 ::TrackPopupMenu(reinterpret_cast<HMENU>(mid),
3011 0, pt.x - 4, pt.y, 0,
3012 reinterpret_cast<HWND>(w.GetID()), NULL);
3013 Destroy();
3016 static bool initialisedET = false;
3017 static bool usePerformanceCounter = false;
3018 static LARGE_INTEGER frequency;
3020 ElapsedTime::ElapsedTime() {
3021 if (!initialisedET) {
3022 usePerformanceCounter = ::QueryPerformanceFrequency(&frequency) != 0;
3023 initialisedET = true;
3025 if (usePerformanceCounter) {
3026 LARGE_INTEGER timeVal;
3027 ::QueryPerformanceCounter(&timeVal);
3028 bigBit = timeVal.HighPart;
3029 littleBit = timeVal.LowPart;
3030 } else {
3031 bigBit = clock();
3035 double ElapsedTime::Duration(bool reset) {
3036 double result;
3037 long endBigBit;
3038 long endLittleBit;
3040 if (usePerformanceCounter) {
3041 LARGE_INTEGER lEnd;
3042 ::QueryPerformanceCounter(&lEnd);
3043 endBigBit = lEnd.HighPart;
3044 endLittleBit = lEnd.LowPart;
3045 LARGE_INTEGER lBegin;
3046 lBegin.HighPart = bigBit;
3047 lBegin.LowPart = littleBit;
3048 double elapsed = lEnd.QuadPart - lBegin.QuadPart;
3049 result = elapsed / static_cast<double>(frequency.QuadPart);
3050 } else {
3051 endBigBit = clock();
3052 endLittleBit = 0;
3053 double elapsed = endBigBit - bigBit;
3054 result = elapsed / CLOCKS_PER_SEC;
3056 if (reset) {
3057 bigBit = endBigBit;
3058 littleBit = endLittleBit;
3060 return result;
3063 class DynamicLibraryImpl : public DynamicLibrary {
3064 protected:
3065 HMODULE h;
3066 public:
3067 DynamicLibraryImpl(const char *modulePath) {
3068 h = ::LoadLibraryA(modulePath);
3071 virtual ~DynamicLibraryImpl() {
3072 if (h != NULL)
3073 ::FreeLibrary(h);
3076 // Use GetProcAddress to get a pointer to the relevant function.
3077 virtual Function FindFunction(const char *name) {
3078 if (h != NULL) {
3079 // C++ standard doesn't like casts betwen function pointers and void pointers so use a union
3080 union {
3081 FARPROC fp;
3082 Function f;
3083 } fnConv;
3084 fnConv.fp = ::GetProcAddress(h, name);
3085 return fnConv.f;
3086 } else
3087 return NULL;
3090 virtual bool IsValid() {
3091 return h != NULL;
3095 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3096 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3099 ColourDesired Platform::Chrome() {
3100 return ::GetSysColor(COLOR_3DFACE);
3103 ColourDesired Platform::ChromeHighlight() {
3104 return ::GetSysColor(COLOR_3DHIGHLIGHT);
3107 const char *Platform::DefaultFont() {
3108 return "Verdana";
3111 int Platform::DefaultFontSize() {
3112 return 8;
3115 unsigned int Platform::DoubleClickTime() {
3116 return ::GetDoubleClickTime();
3119 bool Platform::MouseButtonBounce() {
3120 return false;
3123 void Platform::DebugDisplay(const char *s) {
3124 ::OutputDebugStringA(s);
3127 bool Platform::IsKeyDown(int key) {
3128 return (::GetKeyState(key) & 0x80000000) != 0;
3131 long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
3132 return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam);
3135 long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
3136 return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
3137 reinterpret_cast<LPARAM>(lParam));
3140 bool Platform::IsDBCSLeadByte(int codePage, char ch) {
3141 return ::IsDBCSLeadByteEx(codePage, ch) != 0;
3144 int Platform::DBCSCharLength(int codePage, const char *s) {
3145 return (::IsDBCSLeadByteEx(codePage, s[0]) != 0) ? 2 : 1;
3148 int Platform::DBCSCharMaxLength() {
3149 return 2;
3152 // These are utility functions not really tied to a platform
3154 int Platform::Minimum(int a, int b) {
3155 if (a < b)
3156 return a;
3157 else
3158 return b;
3161 int Platform::Maximum(int a, int b) {
3162 if (a > b)
3163 return a;
3164 else
3165 return b;
3168 //#define TRACE
3170 #ifdef TRACE
3171 void Platform::DebugPrintf(const char *format, ...) {
3172 char buffer[2000];
3173 va_list pArguments;
3174 va_start(pArguments, format);
3175 vsprintf(buffer,format,pArguments);
3176 va_end(pArguments);
3177 Platform::DebugDisplay(buffer);
3179 #else
3180 void Platform::DebugPrintf(const char *, ...) {
3182 #endif
3184 static bool assertionPopUps = true;
3186 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3187 bool ret = assertionPopUps;
3188 assertionPopUps = assertionPopUps_;
3189 return ret;
3192 void Platform::Assert(const char *c, const char *file, int line) {
3193 char buffer[2000];
3194 sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
3195 if (assertionPopUps) {
3196 int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
3197 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
3198 if (idButton == IDRETRY) {
3199 ::DebugBreak();
3200 } else if (idButton == IDIGNORE) {
3201 // all OK
3202 } else {
3203 abort();
3205 } else {
3206 strcat(buffer, "\r\n");
3207 Platform::DebugDisplay(buffer);
3208 ::DebugBreak();
3209 abort();
3213 int Platform::Clamp(int val, int minVal, int maxVal) {
3214 if (val > maxVal)
3215 val = maxVal;
3216 if (val < minVal)
3217 val = minVal;
3218 return val;
3221 void Platform_Initialise(void *hInstance) {
3222 OSVERSIONINFO osv = {sizeof(OSVERSIONINFO),0,0,0,0,TEXT("")};
3223 ::GetVersionEx(&osv);
3224 onNT = osv.dwPlatformId == VER_PLATFORM_WIN32_NT;
3225 ::InitializeCriticalSection(&crPlatformLock);
3226 hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
3227 // This may be called from DllMain, in which case the call to LoadLibrary
3228 // is bad because it can upset the DLL load order.
3229 if (!hDLLImage) {
3230 hDLLImage = ::LoadLibrary(TEXT("Msimg32"));
3232 if (hDLLImage) {
3233 AlphaBlendFn = (AlphaBlendSig)::GetProcAddress(hDLLImage, "AlphaBlend");
3235 if (!hDLLUser32) {
3236 hDLLUser32 = ::LoadLibrary(TEXT("User32"));
3238 if (hDLLUser32) {
3239 MonitorFromPointFn = (MonitorFromPointSig)::GetProcAddress(hDLLUser32, "MonitorFromPoint");
3240 MonitorFromRectFn = (MonitorFromRectSig)::GetProcAddress(hDLLUser32, "MonitorFromRect");
3241 GetMonitorInfoFn = (GetMonitorInfoSig)::GetProcAddress(hDLLUser32, "GetMonitorInfoA");
3244 ListBoxX_Register();
3247 void Platform_Finalise() {
3248 if (reverseArrowCursor != NULL)
3249 ::DestroyCursor(reverseArrowCursor);
3250 ListBoxX_Unregister();
3251 ::DeleteCriticalSection(&crPlatformLock);