1 // Scintilla source code edit control
3 ** Implementation of platform facilities on Windows.
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.
21 #define _WIN32_WINNT 0x0500
29 #if defined(NTDDI_WIN7) && !defined(DISABLE_D2D)
39 #include "UniConversion.h"
41 #include "FontQuality.h"
44 #define IDC_HAND MAKEINTRESOURCE(32649)
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
));
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
));
64 #define GWLP_USERDATA GWL_USERDATA
68 #define GWLP_WNDPROC GWL_WNDPROC
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
);
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
;
109 using namespace Scintilla
;
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
)};
123 IDWriteFactory
*pIDWriteFactory
= 0;
124 ID2D1Factory
*pD2DFactory
= 0;
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
,
136 hDLLD2D
= ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, 0x00000800 /*LOAD_LIBRARY_SEARCH_SYSTEM32*/);
138 D2D1CFSig fnD2DCF
= (D2D1CFSig
)::GetProcAddress(hDLLD2D
, "D2D1CreateFactory");
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*/);
149 DWriteCFSig fnDWCF
= (DWriteCFSig
)::GetProcAddress(hDLLDWrite
, "DWriteCreateFactory");
151 fnDWCF(DWRITE_FACTORY_TYPE_SHARED
,
152 __uuidof(IDWriteFactory
),
153 reinterpret_cast<IUnknown
**>(&pIDWriteFactory
));
157 triedLoadingD2D
= true;
158 return pIDWriteFactory
&& pD2DFactory
;
162 struct FormatAndMetrics
{
166 IDWriteTextFormat
*pTextFormat
;
171 FLOAT yInternalLeading
;
172 FormatAndMetrics(HFONT hfont_
, int extraFontFlag_
) :
173 technology(SCWIN_TECH_GDI
), hfont(hfont_
),
177 extraFontFlag(extraFontFlag_
), yAscent(2), yDescent(1), yInternalLeading(0) {
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_
) {
184 ~FormatAndMetrics() {
186 ::DeleteObject(hfont
);
189 pTextFormat
->Release();
195 yInternalLeading
= 0;
200 HFONT
FormatAndMetrics::HFont() {
202 memset(&lf
, 0, sizeof(lf
));
204 if (technology
== SCWIN_TECH_GDI
) {
205 if (0 == ::GetObjectW(hfont
, sizeof(lf
), &lf
)) {
209 HRESULT hr
= pTextFormat
->GetFontFamilyName(lf
.lfFaceName
, LF_FACESIZE
);
210 if (!SUCCEEDED(hr
)) {
213 lf
.lfWeight
= pTextFormat
->GetFontWeight();
214 lf
.lfItalic
= pTextFormat
->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC
;
215 lf
.lfHeight
= -static_cast<int>(pTextFormat
->GetFontSize());
218 if (0 == ::GetObjectW(hfont
, sizeof(lf
), &lf
)) {
222 return ::CreateFontIndirectW(&lf
);
225 #ifndef CLEARTYPE_QUALITY
226 #define CLEARTYPE_QUALITY 5
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
;
242 return SC_EFF_QUALITY_DEFAULT
;
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
;
260 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT
;
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
) {
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) ^
292 class FontCached
: Font
{
299 FontCached(const FontParameters
&fp
);
301 bool SameAs(const FontParameters
&fp
);
302 virtual void Release();
304 static FontCached
*first
;
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
;
318 if (technology
== SCWIN_TECH_GDI
) {
319 HFONT hfont
= ::CreateFontIndirectA(&lf
);
320 fid
= reinterpret_cast<void *>(new FormatAndMetrics(hfont
, fp
.extraFontFlag
));
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
),
332 DWRITE_FONT_STRETCH_NORMAL
, fHeight
, L
"en-us", &pTextFormat
);
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
);
346 hr
= pTextLayout
->GetLineMetrics(lineMetrics
, maxLines
, &lineCount
);
348 yAscent
= lineMetrics
[0].baseline
;
349 yDescent
= lineMetrics
[0].height
- lineMetrics
[0].baseline
;
352 hr
= pTextLayout
->GetFontSize(0, &emHeight
);
354 yInternalLeading
= lineMetrics
[0].height
- emHeight
;
357 pTextLayout
->Release();
359 fid
= reinterpret_cast<void *>(new FormatAndMetrics(pTextFormat
, fp
.extraFontFlag
, yAscent
, yDescent
, yInternalLeading
));
366 bool FontCached::SameAs(const FontParameters
&fp
) {
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
);
382 FontID
FontCached::FindOrCreate(const FontParameters
&fp
) {
384 ::EnterCriticalSection(&crPlatformLock
);
385 int hashFind
= HashFont(fp
);
386 for (FontCached
*cur
=first
; cur
; cur
=cur
->next
) {
387 if ((cur
->hash
== hashFind
) &&
394 FontCached
*fc
= new FontCached(fp
);
401 ::LeaveCriticalSection(&crPlatformLock
);
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_
) {
411 if (cur
->usage
== 0) {
421 ::LeaveCriticalSection(&crPlatformLock
);
433 void Font::Create(const FontParameters
&fp
) {
436 fid
= FontCached::FindOrCreate(fp
);
439 void Font::Release() {
441 FontCached::ReleaseId(fid
);
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
>
450 T bufferStandard
[lengthStandard
];
453 VarBuffer(size_t length
) : buffer(0) {
454 if (length
> lengthStandard
) {
455 buffer
= new T
[length
];
457 buffer
= bufferStandard
;
461 if (buffer
!= bufferStandard
) {
468 const int stackBufferLength
= 10000;
469 class TextWide
: public VarBuffer
<wchar_t, stackBufferLength
> {
472 TextWide(const char *s
, int len
, bool unicodeMode
, int codePage
=0) :
473 VarBuffer
<wchar_t, stackBufferLength
>(len
) {
475 tlen
= UTF16FromUTF8(s
, len
, buffer
, len
);
477 // Support Asian string display in 9x English
478 tlen
= ::MultiByteToWideChar(codePage
, 0, s
, len
, buffer
, len
);
482 typedef VarBuffer
<XYPOSITION
, stackBufferLength
> TextPositions
;
485 namespace Scintilla
{
488 class SurfaceGDI
: public Surface
{
504 // If 9x OS and current code page is same as ANSI code page.
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
&);
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
);
523 void PenColour(ColourDesired fore
);
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_
);
561 } //namespace Scintilla
564 SurfaceGDI::SurfaceGDI() :
566 hdc(0), hdcOwned(false),
568 brush(0), brushOld(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;
578 win9xACPSame
= false;
581 SurfaceGDI::~SurfaceGDI() {
585 void SurfaceGDI::Release() {
587 ::SelectObject(reinterpret_cast<HDC
>(hdc
), penOld
);
593 ::SelectObject(reinterpret_cast<HDC
>(hdc
), brushOld
);
594 ::DeleteObject(brush
);
599 // Fonts are not deleted as they are owned by a Font object
600 ::SelectObject(reinterpret_cast<HDC
>(hdc
), fontOld
);
605 ::SelectObject(reinterpret_cast<HDC
>(hdc
), bitmapOld
);
606 ::DeleteObject(bitmap
);
611 ::DeleteDC(reinterpret_cast<HDC
>(hdc
));
617 bool SurfaceGDI::Initialised() {
621 void SurfaceGDI::Init(WindowID
) {
623 hdc
= ::CreateCompatibleDC(NULL
);
625 ::SetTextAlign(reinterpret_cast<HDC
>(hdc
), TA_BASELINE
);
628 void SurfaceGDI::Init(SurfaceID sid
, WindowID
) {
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
) {
636 hdc
= ::CreateCompatibleDC(static_cast<SurfaceGDI
*>(surface_
)->hdc
);
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
) {
645 ::SelectObject(hdc
, penOld
);
650 pen
= ::CreatePen(0,1,fore
.AsLong());
651 penOld
= static_cast<HPEN
>(::SelectObject(reinterpret_cast<HDC
>(hdc
), pen
));
654 void SurfaceGDI::BrushColor(ColourDesired back
) {
656 ::SelectObject(hdc
, brushOld
);
657 ::DeleteObject(brush
);
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
);
672 ::SelectObject(hdc
, pfm
->hfont
);
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
) {
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
) {
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
) {
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
);
732 void SurfaceGDI::RoundedRectangle(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
737 rc
.right
- 1, rc
.bottom
,
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
;
750 #define AC_SRC_OVER 0x00
753 #define AC_SRC_ALPHA 0x01
756 static DWORD
dwordFromBGRA(byte b
, byte g
, byte r
, byte a
) {
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};
778 HBITMAP hbmMem
= CreateDIBSection(reinterpret_cast<HDC
>(hMemDC
), &bpih
,
779 DIB_RGB_COLORS
, &image
, NULL
, 0);
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
;
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
);
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);
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
);
870 void SurfaceGDI::Ellipse(PRectangle rc
, ColourDesired fore
, ColourDesired back
) {
873 ::Ellipse(hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
876 void SurfaceGDI::Copy(PRectangle rc
, Point from
, Surface
&surfaceSource
) {
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
) {
886 RECT rcw
= RectFromPRectangle(rc
);
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
)) {
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);
905 ::GetTextExtentPoint32A(hdc
, s
+pos
, seglen
, &sz
);
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);
920 ::GetTextExtentPoint32W(hdc
, tbuf
.buffer
+pos
, seglen
, &sz
);
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
++) {
947 ::SetTextColor(hdc
, fore
.AsLong());
948 ::SetBkMode(hdc
, TRANSPARENT
);
949 DrawTextCommon(rc
, font_
, ybase
, s
, len
, 0);
950 ::SetBkMode(hdc
, OPAQUE
);
956 XYPOSITION
SurfaceGDI::WidthText(Font
&font_
, const char *s
, int len
) {
959 if ((!unicodeMode
) && (IsNT() || (codePage
==0) || win9xACPSame
)) {
960 ::GetTextExtentPoint32A(hdc
, s
, Platform::Minimum(len
, maxLenText
), &sz
);
962 const TextWide
tbuf(s
, len
, unicodeMode
, codePage
);
963 ::GetTextExtentPoint32W(hdc
, tbuf
.buffer
, tbuf
.tlen
, &sz
);
968 void SurfaceGDI::MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
) {
973 const TextWide
tbuf(s
, len
, unicodeMode
, codePage
);
974 TextPositionsI
poses(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
987 const unsigned char *us
= reinterpret_cast<const unsigned char *>(s
);
990 unsigned char uch
= us
[i
];
991 unsigned int lenChar
= 1;
992 if (uch
>= (0x80 + 0x40 + 0x20 + 0x10)) {
995 } else if (uch
>= (0x80 + 0x40 + 0x20)) {
997 } else if (uch
>= (0x80)) {
1000 for (unsigned int bytePos
=0; (bytePos
<lenChar
) && (i
<len
); bytePos
++) {
1001 positions
[i
++] = poses
.buffer
[ui
];
1007 lastPos
= positions
[i
-1];
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;
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.
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.
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];
1034 positions
+= lenBlock
;
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
;
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
];
1053 positions
[i
] = poses
.buffer
[ui
];
1062 XYPOSITION
SurfaceGDI::WidthChar(Font
&font_
, char ch
) {
1065 ::GetTextExtentPoint32A(hdc
, &ch
, 1, &sz
);
1069 XYPOSITION
SurfaceGDI::Ascent(Font
&font_
) {
1072 ::GetTextMetrics(hdc
, &tm
);
1076 XYPOSITION
SurfaceGDI::Descent(Font
&font_
) {
1079 ::GetTextMetrics(hdc
, &tm
);
1080 return tm
.tmDescent
;
1083 XYPOSITION
SurfaceGDI::InternalLeading(Font
&font_
) {
1086 ::GetTextMetrics(hdc
, &tm
);
1087 return tm
.tmInternalLeading
;
1090 XYPOSITION
SurfaceGDI::ExternalLeading(Font
&font_
) {
1093 ::GetTextMetrics(hdc
, &tm
);
1094 return tm
.tmExternalLeading
;
1097 XYPOSITION
SurfaceGDI::Height(Font
&font_
) {
1100 ::GetTextMetrics(hdc
, &tm
);
1104 XYPOSITION
SurfaceGDI::AverageCharWidth(Font
&font_
) {
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() {
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
{
1137 class SurfaceD2D
: public Surface
{
1143 ID2D1RenderTarget
*pRenderTarget
;
1144 bool ownRenderTarget
;
1147 IDWriteTextFormat
*pTextFormat
;
1150 FLOAT yInternalLeading
;
1152 ID2D1SolidColorBrush
*pBrush
;
1158 void SetFont(Font
&font_
);
1160 // Private so SurfaceD2D objects can not be copied
1161 SurfaceD2D(const SurfaceD2D
&);
1162 SurfaceD2D
&operator=(const SurfaceD2D
&);
1165 virtual ~SurfaceD2D();
1168 void Init(WindowID wid
);
1169 void Init(SurfaceID sid
, WindowID wid
);
1170 void InitPixMap(int width
, int height
, Surface
*surface_
, WindowID wid
);
1175 HRESULT
FlushDrawing();
1177 void PenColour(ColourDesired fore
);
1178 void D2DPenColour(ColourDesired fore
, int alpha
=255);
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
1219 SurfaceD2D::SurfaceD2D() :
1225 pRenderTarget
= NULL
;
1226 ownRenderTarget
= false;
1229 // From selected font
1233 yInternalLeading
= 0;
1242 SurfaceD2D::~SurfaceD2D() {
1246 void SurfaceD2D::Release() {
1251 if (pRenderTarget
) {
1252 while (clipsActive
) {
1253 pRenderTarget
->PopAxisAlignedClip();
1256 if (ownRenderTarget
) {
1257 pRenderTarget
->Release();
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 */) {
1284 void SurfaceD2D::Init(SurfaceID sid
, WindowID
) {
1287 pRenderTarget
= reinterpret_cast<ID2D1RenderTarget
*>(sid
);
1290 void SurfaceD2D::InitPixMap(int width
, int height
, Surface
*surface_
, WindowID
) {
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
) {
1311 void SurfaceD2D::D2DPenColour(ColourDesired fore
, int alpha
) {
1312 if (pRenderTarget
) {
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;
1319 pBrush
->SetColor(col
);
1321 HRESULT hr
= pRenderTarget
->CreateSolidColorBrush(col
, &pBrush
);
1322 if (!SUCCEEDED(hr
) && pBrush
) {
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() {
1346 int SurfaceD2D::DeviceHeightFont(int points
) {
1347 return ::MulDiv(points
, LogPixelsY(), 72);
1350 void SurfaceD2D::MoveTo(int x_
, int y_
) {
1355 static int Delta(int difference
) {
1358 else if (difference
> 0)
1364 static int RoundFloat(float f
) {
1368 void SurfaceD2D::LineTo(int x_
, int y_
) {
1369 if (pRenderTarget
) {
1371 int xDelta
= Delta(xDiff
);
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
))) {
1386 pRenderTarget
->DrawLine(D2D1::Point2F(x
+ 0.5, y
+ 0.5),
1387 D2D1::Point2F(x_
+ 0.5 - xDelta
, y_
+ 0.5 - yDelta
), pBrush
);
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
);
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
);
1417 pRenderTarget
->FillGeometry(geometry
,pBrush
);
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);
1431 pRenderTarget
->FillRectangle(&rectangle1
, pBrush
);
1433 pRenderTarget
->DrawRectangle(&rectangle1
, pBrush
);
1437 void SurfaceD2D::FillRectangle(PRectangle rc
, ColourDesired back
) {
1438 if (pRenderTarget
) {
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
);
1460 if (SUCCEEDED(hr
)) {
1461 pRenderTarget
->FillRectangle(
1462 D2D1::RectF(rc
.left
, rc
.top
, rc
.right
, rc
.bottom
),
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),
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),
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
);
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
);
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
),
1557 pRenderTarget
->FillEllipse(ellipse
, pBrush
);
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();
1580 void SurfaceD2D::DrawTextCommon(PRectangle rc
, Font
&font_
, XYPOSITION ybase
, const char *s
, int len
, UINT fuOptions
) {
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
);
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
);
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
++) {
1630 if (pRenderTarget
) {
1632 DrawTextCommon(rc
, font_
, ybase
, s
, len
, 0);
1639 XYPOSITION
SurfaceD2D::WidthText(Font
&font_
, const char *s
, int len
) {
1642 const TextWide
tbuf(s
, len
, unicodeMode
, codePage
);
1643 if (pIDWriteFactory
&& pTextFormat
) {
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();
1657 void SurfaceD2D::MeasureWidths(Font
&font_
, const char *s
, int len
, XYPOSITION
*positions
) {
1660 const TextWide
tbuf(s
, len
, unicodeMode
, codePage
);
1661 TextPositions
poses(tbuf
.tlen
);
1663 const int clusters
= 1000;
1664 DWRITE_CLUSTER_METRICS clusterMetrics
[clusters
];
1666 if (pIDWriteFactory
&& pTextFormat
) {
1669 IDWriteTextLayout
*pTextLayout
= 0;
1670 HRESULT hr
= pIDWriteFactory
->CreateTextLayout(tbuf
.buffer
, tbuf
.tlen
, pTextFormat
, 10000.0, 1000.0, &pTextLayout
);
1673 // For now, assuming WCHAR == cluster
1674 pTextLayout
->GetClusterMetrics(clusterMetrics
, clusters
, &count
);
1675 FLOAT position
= 0.0f
;
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();
1688 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1690 const unsigned char *us
= reinterpret_cast<const unsigned char *>(s
);
1693 unsigned char uch
= us
[i
];
1694 unsigned int lenChar
= 1;
1695 if (uch
>= (0x80 + 0x40 + 0x20 + 0x10)) {
1698 } else if (uch
>= (0x80 + 0x40 + 0x20)) {
1700 } else if (uch
>= (0x80)) {
1703 for (unsigned int bytePos
=0; (bytePos
<lenChar
) && (i
<len
); bytePos
++) {
1704 positions
[i
++] = poses
.buffer
[ui
];
1710 lastPos
= positions
[i
-1];
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
];
1724 // May be more than one byte per position
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
];
1732 positions
[i
] = poses
.buffer
[ui
];
1741 XYPOSITION
SurfaceD2D::WidthChar(Font
&font_
, char ch
) {
1744 if (pIDWriteFactory
&& pTextFormat
) {
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();
1759 XYPOSITION
SurfaceD2D::Ascent(Font
&font_
) {
1761 return ceil(yAscent
);
1764 XYPOSITION
SurfaceD2D::Descent(Font
&font_
) {
1766 return ceil(yDescent
);
1769 XYPOSITION
SurfaceD2D::InternalLeading(Font
&font_
) {
1771 return floor(yInternalLeading
);
1774 XYPOSITION
SurfaceD2D::ExternalLeading(Font
&) {
1775 // Not implemented, always return one
1779 XYPOSITION
SurfaceD2D::Height(Font
&font_
) {
1780 return Ascent(font_
) + Descent(font_
);
1783 XYPOSITION
SurfaceD2D::AverageCharWidth(Font
&font_
) {
1786 if (pIDWriteFactory
&& pTextFormat
) {
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();
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
);
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_
;
1823 Surface
*Surface::Allocate(int technology
) {
1824 #if defined(USE_D2D)
1825 if (technology
== SCWIN_TECH_GDI
)
1826 return new SurfaceGDI
;
1828 return new SurfaceD2D
;
1830 return new SurfaceGDI
;
1837 void Window::Destroy() {
1839 ::DestroyWindow(reinterpret_cast<HWND
>(wid
));
1843 bool Window::HasFocus() {
1844 return ::GetFocus() == wid
;
1847 PRectangle
Window::GetPosition() {
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
)) {
1866 RECT rc
= {0, 0, 0, 0};
1867 ::SystemParametersInfoA(SPI_GETWORKAREA
, 0, &rc
, 0);
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
);
1905 PRectangle
Window::GetClientPosition() {
1908 ::GetClientRect(reinterpret_cast<HWND
>(wid
), &rc
);
1909 return PRectangle(rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1912 void Window::Show(bool show
) {
1914 ::ShowWindow(reinterpret_cast<HWND
>(wid
), SW_SHOWNOACTIVATE
);
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
);
1940 HGDIOBJ prevBmp
= ::SelectObject(hdc
, bitmap
);
1941 ::StretchBlt(hdc
, width
- 1, 0, -width
, height
, hdc
, 0, 0, width
, height
, SRCCOPY
);
1942 ::SelectObject(hdc
, prevBmp
);
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
);
1956 if (::GetIconInfo(cursor
, &info
)) {
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
);
1978 void Window::SetCursor(Cursor curs
) {
1981 ::SetCursor(::LoadCursor(NULL
,IDC_IBEAM
));
1984 ::SetCursor(::LoadCursor(NULL
,IDC_UPARROW
));
1987 ::SetCursor(::LoadCursor(NULL
,IDC_WAIT
));
1990 ::SetCursor(::LoadCursor(NULL
,IDC_SIZEWE
));
1993 ::SetCursor(::LoadCursor(NULL
,IDC_SIZENS
));
1996 ::SetCursor(::LoadCursor(NULL
,IDC_HAND
));
1998 case cursorReverseArrow
:
1999 ::SetCursor(GetReverseArrowCursor());
2002 case cursorInvalid
: // Should not occur, but just in case.
2003 ::SetCursor(::LoadCursor(NULL
,IDC_ARROW
));
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
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
);
2032 return PRectangle();
2036 struct ListItemData
{
2041 #define _ROUND2(n,pow2) \
2042 ( ( (n) + (pow2) - 1) & ~((pow2) - 1) )
2060 char *AllocWord(const char *word
);
2063 LineToItem() : words(NULL
), wordsCount(0), wordsSize(0), data(NULL
), len(0), count(0) {
2076 ListItemData
*Append(const char *text
, int value
);
2078 ListItemData
Get(int index
) const {
2079 if (index
>= 0 && index
< count
) {
2082 ListItemData missing
= {"", -1};
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
;
2110 char *s
= &words
[wordsCount
];
2111 wordsCount
= newCount
;
2112 strncpy(s
, text
, chars
);
2116 ListItemData
*LineToItem::AllocItem() {
2118 int lenNew
= _ROUND2((count
+1) * 2, 1024);
2119 ListItemData
*dataNew
= new ListItemData
[lenNew
];
2120 memcpy(dataNew
, data
, count
* sizeof(ListItemData
));
2125 ListItemData
*item
= &data
[count
];
2130 ListItemData
*LineToItem::Append(const char *text
, int imageIndex
) {
2131 ListItemData
*item
= AllocItem();
2132 item
->text
= AllocWord(text
);
2133 item
->pixId
= imageIndex
;
2137 const TCHAR ListBoxX_ClassName
[] = TEXT("ListBoxX");
2139 ListBox::ListBox() {
2142 ListBox::~ListBox() {
2145 class ListBoxX
: public ListBox
{
2149 RGBAImageSet images
;
2153 int desiredVisibleRows
;
2154 unsigned int maxItemCharacters
;
2155 unsigned int aveCharWidth
;
2158 CallBackAction doubleClickAction
;
2159 void *doubleClickActionData
;
2160 const char *widestItem
;
2161 unsigned int maxCharWidth
;
2163 PRectangle rcPreSize
;
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);
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
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() {
2198 ::DeleteObject(fontCopy
);
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();
2238 void ListBoxX::Create(Window
&parent_
, int ctrlID_
, Point location_
, int lineHeight_
, bool unicodeMode_
, int technology_
) {
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
,
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
) {
2264 ::DeleteObject(fontCopy
);
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);
2303 const TextWide
tbuf(widestItem
, len
, unicodeMode
);
2304 ::GetTextExtentPoint32W(hdc
, tbuf
.buffer
, tbuf
.tlen
, &textSize
);
2306 ::GetTextExtentPoint32A(hdc
, widestItem
, len
, &textSize
);
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
);
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() {
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;
2344 void ListBoxX::Append(char *s
, int type
) {
2345 int index
= ::SendMessage(lb
, LB_ADDSTRING
, 0, reinterpret_cast<LPARAM
>(s
));
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() {
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
2366 ::SendMessage(lb
, LB_SETCURSEL
, n
, 0);
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 *) {
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() {
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
));
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
);
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
);
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
);
2437 Surface
*surfaceItem
= Surface::Allocate(technology
);
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());
2447 ::SetTextAlign(pDrawItem
->hDC
, TA_TOP
);
2449 #if defined(USE_D2D)
2450 D2D1_RENDER_TARGET_PROPERTIES props
= D2D1::RenderTargetProperties(
2451 D2D1_RENDER_TARGET_TYPE_DEFAULT
,
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
);
2463 GetClientRect(pDrawItem
->hwndItem
, &rcWindow
);
2464 hr
= pDCRT
->BindDC(pDrawItem
->hDC
, &rcWindow
);
2465 if (SUCCEEDED(hr
)) {
2466 surfaceItem
->Init(pDCRT
, pDrawItem
->hwndItem
);
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());
2484 void ListBoxX::AppendListItem(const char *startword
, const char *numword
) {
2485 ListItemData
*item
= lti
.AllocItem();
2486 item
->text
= startword
;
2490 while ((ch
= *++numword
) != '\0') {
2491 pixId
= 10 * pixId
+ (ch
- '0');
2493 item
->pixId
= pixId
;
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.
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
) {
2521 AppendListItem(startword
, numword
);
2522 startword
= words
+ i
+ 1;
2524 } else if (words
[i
] == typesep
) {
2525 numword
= words
+ i
;
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);
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
;
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())};
2569 POINT
ListBoxX::MaxTrackSize() const {
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())};
2579 void ListBoxX::SetRedraw(bool on
) {
2580 ::SendMessage(lb
, WM_SETREDRAW
, static_cast<BOOL
>(on
), 0);
2582 ::InvalidateRect(lb
, NULL
, TRUE
);
2585 void ListBoxX::ResizeToCursor() {
2586 PRectangle rc
= GetPosition();
2588 ::GetCursorPos(&ptw
);
2589 Point
pt(ptw
.x
, ptw
.y
);
2590 pt
.x
+= dragOffset
.x
;
2591 pt
.y
+= dragOffset
.y
;
2593 switch (resizeHit
) {
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
);
2635 void ListBoxX::StartResize(WPARAM hitCode
) {
2636 rcPreSize
= GetPosition();
2638 ::GetCursorPos(&cursorPos
);
2644 dragOffset
.x
= rcPreSize
.right
- cursorPos
.x
;
2645 dragOffset
.y
= rcPreSize
.bottom
- cursorPos
.y
;
2649 dragOffset
.x
= rcPreSize
.right
- cursorPos
.x
;
2650 dragOffset
.y
= rcPreSize
.top
- cursorPos
.y
;
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
2658 dragOffset
.x
= rcPreSize
.left
- cursorPos
.x
;
2659 dragOffset
.y
= rcPreSize
.top
- cursorPos
.y
;
2662 dragOffset
.x
= rcPreSize
.left
- cursorPos
.x
;
2663 dragOffset
.y
= rcPreSize
.bottom
- cursorPos
.y
;
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
2700 PRectangle rc
= const_cast<ListBoxX
*>(this)->GetPosition();
2701 // Valid only if caret below list
2702 if (location
.y
< rc
.top
)
2708 case HTBOTTOMRIGHT
: {
2709 PRectangle rc
= const_cast<ListBoxX
*>(this)->GetPosition();
2710 // Valid only if caret above list
2711 if (rc
.bottom
< location
.y
)
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
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
) {
2775 HDC hDC
= ::BeginPaint(hWnd
, &ps
);
2776 ListBoxX
*lbx
= reinterpret_cast<ListBoxX
*>(PointerFromWindow(::GetParent(hWnd
)));
2779 ::EndPaint(hWnd
, &ps
);
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
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);
2801 case WM_LBUTTONDBLCLK
: {
2802 ListBoxX
*lbx
= reinterpret_cast<ListBoxX
*>(PointerFromWindow(::GetParent(hWnd
)));
2804 lbx
->OnDoubleClick();
2809 case WM_MBUTTONDOWN
:
2810 // disable the scroll wheel button click action
2814 WNDPROC prevWndProc
= reinterpret_cast<WNDPROC
>(GetWindowLongPtr(hWnd
, GWLP_USERDATA
));
2816 return ::CallWindowProc(prevWndProc
, hWnd
, uMsg
, wParam
, lParam
);
2818 return ::DefWindowProc(hWnd
, uMsg
, wParam
, lParam
);
2822 return ::DefWindowProc(hWnd
, uMsg
, wParam
, lParam
);
2825 LRESULT
ListBoxX::WndProc(HWND hWnd
, UINT iMessage
, WPARAM wParam
, LPARAM lParam
) {
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
,
2836 reinterpret_cast<HMENU
>(ctrlID
),
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
));
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());
2856 ::BeginPaint(hWnd
, &ps
);
2857 ::EndPaint(hWnd
, &ps
);
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
);
2867 case WM_MEASUREITEM
: {
2868 MEASUREITEMSTRUCT
*pMeasureItem
= reinterpret_cast<MEASUREITEMSTRUCT
*>(lParam
);
2869 pMeasureItem
->itemHeight
= static_cast<unsigned int>(ItemHeight());
2874 Draw(reinterpret_cast<DRAWITEMSTRUCT
*>(lParam
));
2879 ::SetWindowLong(hWnd
, 0, 0);
2880 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
2883 // To reduce flicker we can elide background erasure since this window is
2884 // completely covered by its child.
2887 case WM_GETMINMAXINFO
: {
2888 MINMAXINFO
*minMax
= reinterpret_cast<MINMAXINFO
*>(lParam
);
2889 minMax
->ptMaxTrackSize
= MaxTrackSize();
2890 minMax
->ptMinTrackSize
= MinTrackSize();
2894 case WM_MOUSEACTIVATE
:
2895 return MA_NOACTIVATE
;
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
);
2906 case WM_MOUSEMOVE
: {
2907 if (resizeHit
== 0) {
2908 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
2917 if (resizeHit
!= 0) {
2921 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
2924 wheelDelta
-= static_cast<short>(HIWORD(wParam
));
2925 if (abs(wheelDelta
) >= WHEEL_DELTA
) {
2926 int nRows
= GetVisibleRows();
2927 int linesToScroll
= 1;
2929 linesToScroll
= nRows
- 1;
2931 if (linesToScroll
> 3) {
2934 linesToScroll
*= (wheelDelta
/ WHEEL_DELTA
);
2935 int top
= ::SendMessage(lb
, LB_GETTOPINDEX
, 0, 0) + linesToScroll
;
2939 ::SendMessage(lb
, LB_SETTOPINDEX
, top
, 0);
2940 // update wheel delta residue
2941 if (wheelDelta
>= 0)
2942 wheelDelta
= wheelDelta
% WHEEL_DELTA
;
2944 wheelDelta
= - (-wheelDelta
% WHEEL_DELTA
);
2949 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
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
));
2964 return lbx
->WndProc(hWnd
, iMessage
, wParam
, lParam
);
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() {
3000 mid
= ::CreatePopupMenu();
3003 void Menu::Destroy() {
3005 ::DestroyMenu(reinterpret_cast<HMENU
>(mid
));
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
);
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
;
3035 double ElapsedTime::Duration(bool reset
) {
3040 if (usePerformanceCounter
) {
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
);
3051 endBigBit
= clock();
3053 double elapsed
= endBigBit
- bigBit
;
3054 result
= elapsed
/ CLOCKS_PER_SEC
;
3058 littleBit
= endLittleBit
;
3063 class DynamicLibraryImpl
: public DynamicLibrary
{
3067 DynamicLibraryImpl(const char *modulePath
) {
3068 h
= ::LoadLibraryA(modulePath
);
3071 virtual ~DynamicLibraryImpl() {
3076 // Use GetProcAddress to get a pointer to the relevant function.
3077 virtual Function
FindFunction(const char *name
) {
3079 // C++ standard doesn't like casts betwen function pointers and void pointers so use a union
3084 fnConv
.fp
= ::GetProcAddress(h
, name
);
3090 virtual bool IsValid() {
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() {
3111 int Platform::DefaultFontSize() {
3115 unsigned int Platform::DoubleClickTime() {
3116 return ::GetDoubleClickTime();
3119 bool Platform::MouseButtonBounce() {
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() {
3152 // These are utility functions not really tied to a platform
3154 int Platform::Minimum(int a
, int b
) {
3161 int Platform::Maximum(int a
, int b
) {
3171 void Platform::DebugPrintf(const char *format
, ...) {
3174 va_start(pArguments
, format
);
3175 vsprintf(buffer
,format
,pArguments
);
3177 Platform::DebugDisplay(buffer
);
3180 void Platform::DebugPrintf(const char *, ...) {
3184 static bool assertionPopUps
= true;
3186 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) {
3187 bool ret
= assertionPopUps
;
3188 assertionPopUps
= assertionPopUps_
;
3192 void Platform::Assert(const char *c
, const char *file
, int line
) {
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
) {
3200 } else if (idButton
== IDIGNORE
) {
3206 strcat(buffer
, "\r\n");
3207 Platform::DebugDisplay(buffer
);
3213 int Platform::Clamp(int val
, int minVal
, int maxVal
) {
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.
3230 hDLLImage
= ::LoadLibrary(TEXT("Msimg32"));
3233 AlphaBlendFn
= (AlphaBlendSig
)::GetProcAddress(hDLLImage
, "AlphaBlend");
3236 hDLLUser32
= ::LoadLibrary(TEXT("User32"));
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
);