You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2176 lines
61 KiB

// Scintilla source code edit control
/** @file PlatWin.cxx
** Implementation of platform facilities on Windows.
**/
// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include <windowsx.h>
#include "Platform.h"
#include "PlatformRes.h"
#include "UniConversion.h"
#include "XPM.h"
#ifndef IDC_HAND
#define IDC_HAND MAKEINTRESOURCE(32649)
#endif
// Take care of 32/64 bit pointers
#ifdef GetWindowLongPtr
static void *PointerFromWindow(HWND hWnd) {
return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
}
static void SetWindowPointer(HWND hWnd, void *ptr) {
::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
}
#else
static void *PointerFromWindow(HWND hWnd) {
return reinterpret_cast<void *>(::GetWindowLong(hWnd, 0));
}
static void SetWindowPointer(HWND hWnd, void *ptr) {
::SetWindowLong(hWnd, 0, reinterpret_cast<LONG>(ptr));
}
#ifndef GWLP_USERDATA
#define GWLP_USERDATA GWL_USERDATA
#endif
#ifndef GWLP_WNDPROC
#define GWLP_WNDPROC GWL_WNDPROC
#endif
#ifndef LONG_PTR
#define LONG_PTR LONG
#endif
static LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong) {
return ::SetWindowLong(hWnd, nIndex, dwNewLong);
}
static LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex) {
return ::GetWindowLong(hWnd, nIndex);
}
#endif
typedef BOOL (WINAPI *AlphaBlendSig)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
static CRITICAL_SECTION crPlatformLock;
static HINSTANCE hinstPlatformRes = 0;
static bool onNT = false;
static HMODULE hDLLImage = 0;
static AlphaBlendSig AlphaBlendFn = 0;
bool IsNT() {
return onNT;
}
Point Point::FromLong(long lpoint) {
return Point(static_cast<short>(LOWORD(lpoint)), static_cast<short>(HIWORD(lpoint)));
}
static RECT RectFromPRectangle(PRectangle prc) {
RECT rc = {prc.left, prc.top, prc.right, prc.bottom};
return rc;
}
Palette::Palette() {
used = 0;
allowRealization = false;
hpal = 0;
size = 100;
entries = new ColourPair[size];
}
Palette::~Palette() {
Release();
delete []entries;
entries = 0;
}
void Palette::Release() {
used = 0;
if (hpal)
::DeleteObject(hpal);
hpal = 0;
delete []entries;
size = 100;
entries = new ColourPair[size];
}
/**
* This method either adds a colour to the list of wanted colours (want==true)
* or retrieves the allocated colour back to the ColourPair.
* This is one method to make it easier to keep the code for wanting and retrieving in sync.
*/
void Palette::WantFind(ColourPair &cp, bool want) {
if (want) {
for (int i=0; i < used; i++) {
if (entries[i].desired == cp.desired)
return;
}
if (used >= size) {
int sizeNew = size * 2;
ColourPair *entriesNew = new ColourPair[sizeNew];
for (int j=0; j<size; j++) {
entriesNew[j] = entries[j];
}
delete []entries;
entries = entriesNew;
size = sizeNew;
}
entries[used].desired = cp.desired;
entries[used].allocated.Set(cp.desired.AsLong());
used++;
} else {
for (int i=0; i < used; i++) {
if (entries[i].desired == cp.desired) {
cp.allocated = entries[i].allocated;
return;
}
}
cp.allocated.Set(cp.desired.AsLong());
}
}
void Palette::Allocate(Window &) {
if (hpal)
::DeleteObject(hpal);
hpal = 0;
if (allowRealization) {
char *pal = new char[sizeof(LOGPALETTE) + (used-1) * sizeof(PALETTEENTRY)];
LOGPALETTE *logpal = reinterpret_cast<LOGPALETTE *>(pal);
logpal->palVersion = 0x300;
logpal->palNumEntries = static_cast<WORD>(used);
for (int iPal=0;iPal<used;iPal++) {
ColourDesired desired = entries[iPal].desired;
logpal->palPalEntry[iPal].peRed = static_cast<BYTE>(desired.GetRed());
logpal->palPalEntry[iPal].peGreen = static_cast<BYTE>(desired.GetGreen());
logpal->palPalEntry[iPal].peBlue = static_cast<BYTE>(desired.GetBlue());
entries[iPal].allocated.Set(
PALETTERGB(desired.GetRed(), desired.GetGreen(), desired.GetBlue()));
// PC_NOCOLLAPSE means exact colours allocated even when in background this means other windows
// are less likely to get their colours and also flashes more when switching windows
logpal->palPalEntry[iPal].peFlags = PC_NOCOLLAPSE;
// 0 allows approximate colours when in background, yielding moe colours to other windows
//logpal->palPalEntry[iPal].peFlags = 0;
}
hpal = ::CreatePalette(logpal);
delete []pal;
}
}
static void SetLogFont(LOGFONT &lf, const char *faceName, int characterSet, int size, bool bold, bool italic) {
memset(&lf, 0, sizeof(lf));
// The negative is to allow for leading
lf.lfHeight = -(abs(size));
lf.lfWeight = bold ? FW_BOLD : FW_NORMAL;
lf.lfItalic = static_cast<BYTE>(italic ? 1 : 0);
lf.lfCharSet = static_cast<BYTE>(characterSet);
strncpy(lf.lfFaceName, faceName, sizeof(lf.lfFaceName));
}
/**
* Create a hash from the parameters for a font to allow easy checking for identity.
* If one font is the same as another, its hash will be the same, but if the hash is the
* same then they may still be different.
*/
static int HashFont(const char *faceName, int characterSet, int size, bool bold, bool italic) {
return
size ^
(characterSet << 10) ^
(bold ? 0x10000000 : 0) ^
(italic ? 0x20000000 : 0) ^
faceName[0];
}
class FontCached : Font {
FontCached *next;
int usage;
LOGFONT lf;
int hash;
FontCached(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_);
~FontCached() {}
bool SameAs(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_);
virtual void Release();
static FontCached *first;
public:
static FontID FindOrCreate(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_);
static void ReleaseId(FontID id_);
};
FontCached *FontCached::first = 0;
FontCached::FontCached(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_) :
next(0), usage(0), hash(0) {
::SetLogFont(lf, faceName_, characterSet_, size_, bold_, italic_);
hash = HashFont(faceName_, characterSet_, size_, bold_, italic_);
id = ::CreateFontIndirect(&lf);
usage = 1;
}
bool FontCached::SameAs(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_) {
return
(lf.lfHeight == -(abs(size_))) &&
(lf.lfWeight == (bold_ ? FW_BOLD : FW_NORMAL)) &&
(lf.lfItalic == static_cast<BYTE>(italic_ ? 1 : 0)) &&
(lf.lfCharSet == characterSet_) &&
0 == strcmp(lf.lfFaceName,faceName_);
}
void FontCached::Release() {
if (id)
::DeleteObject(id);
id = 0;
}
FontID FontCached::FindOrCreate(const char *faceName_, int characterSet_, int size_, bool bold_, bool italic_) {
FontID ret = 0;
::EnterCriticalSection(&crPlatformLock);
int hashFind = HashFont(faceName_, characterSet_, size_, bold_, italic_);
for (FontCached *cur=first; cur; cur=cur->next) {
if ((cur->hash == hashFind) &&
cur->SameAs(faceName_, characterSet_, size_, bold_, italic_)) {
cur->usage++;
ret = cur->id;
}
}
if (ret == 0) {
FontCached *fc = new FontCached(faceName_, characterSet_, size_, bold_, italic_);
if (fc) {
fc->next = first;
first = fc;
ret = fc->id;
}
}
::LeaveCriticalSection(&crPlatformLock);
return ret;
}
void FontCached::ReleaseId(FontID id_) {
::EnterCriticalSection(&crPlatformLock);
FontCached **pcur=&first;
for (FontCached *cur=first; cur; cur=cur->next) {
if (cur->id == id_) {
cur->usage--;
if (cur->usage == 0) {
*pcur = cur->next;
cur->Release();
cur->next = 0;
delete cur;
}
break;
}
pcur=&cur->next;
}
::LeaveCriticalSection(&crPlatformLock);
}
Font::Font() {
id = 0;
}
Font::~Font() {
}
#define FONTS_CACHED
void Font::Create(const char *faceName, int characterSet, int size,
bool bold, bool italic, bool) {
Release();
#ifndef FONTS_CACHED
LOGFONT lf;
::SetLogFont(lf, faceName, characterSet, size, bold, italic);
id = ::CreateFontIndirect(&lf);
#else
id = FontCached::FindOrCreate(faceName, characterSet, size, bold, italic);
#endif
}
void Font::Release() {
#ifndef FONTS_CACHED
if (id)
::DeleteObject(id);
#else
if (id)
FontCached::ReleaseId(id);
#endif
id = 0;
}
class SurfaceImpl : public Surface {
bool unicodeMode;
HDC hdc;
bool hdcOwned;
HPEN pen;
HPEN penOld;
HBRUSH brush;
HBRUSH brushOld;
HFONT font;
HFONT fontOld;
HBITMAP bitmap;
HBITMAP bitmapOld;
HPALETTE paletteOld;
int maxWidthMeasure;
int maxLenText;
int codePage;
// If 9x OS and current code page is same as ANSI code page.
bool win9xACPSame;
void BrushColor(ColourAllocated back);
void SetFont(Font &font_);
// Private so SurfaceImpl objects can not be copied
SurfaceImpl(const SurfaceImpl &) : Surface() {}
SurfaceImpl &operator=(const SurfaceImpl &) { return *this; }
public:
SurfaceImpl();
virtual ~SurfaceImpl();
void Init(WindowID wid);
void Init(SurfaceID sid, WindowID wid);
void InitPixMap(int width, int height, Surface *surface_, WindowID wid);
void Release();
bool Initialised();
void PenColour(ColourAllocated fore);
int LogPixelsY();
int DeviceHeightFont(int points);
void MoveTo(int x_, int y_);
void LineTo(int x_, int y_);
void Polygon(Point *pts, int npts, ColourAllocated fore, ColourAllocated back);
void RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back);
void FillRectangle(PRectangle rc, ColourAllocated back);
void FillRectangle(PRectangle rc, Surface &surfacePattern);
void RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back);
void AlphaRectangle(PRectangle rc, int cornerSize, ColourAllocated fill, int alphaFill,
ColourAllocated outline, int alphaOutline, int flags);
void Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back);
void Copy(PRectangle rc, Point from, Surface &surfaceSource);
void DrawTextCommon(PRectangle rc, Font &font_, int ybase, const char *s, int len, UINT fuOptions);
void DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore, ColourAllocated back);
void DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore, ColourAllocated back);
void DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore);
void MeasureWidths(Font &font_, const char *s, int len, int *positions);
int WidthText(Font &font_, const char *s, int len);
int WidthChar(Font &font_, char ch);
int Ascent(Font &font_);
int Descent(Font &font_);
int InternalLeading(Font &font_);
int ExternalLeading(Font &font_);
int Height(Font &font_);
int AverageCharWidth(Font &font_);
int SetPalette(Palette *pal, bool inBackGround);
void SetClip(PRectangle rc);
void FlushCachedState();
void SetUnicodeMode(bool unicodeMode_);
void SetDBCSMode(int codePage_);
};
SurfaceImpl::SurfaceImpl() :
unicodeMode(false),
hdc(0), hdcOwned(false),
pen(0), penOld(0),
brush(0), brushOld(0),
font(0), fontOld(0),
bitmap(0), bitmapOld(0),
paletteOld(0) {
// Windows 9x has only a 16 bit coordinate system so break after 30000 pixels
maxWidthMeasure = IsNT() ? 1000000 : 30000;
// There appears to be a 16 bit string length limit in GDI on NT and a limit of
// 8192 characters on Windows 95.
maxLenText = IsNT() ? 65535 : 8192;
codePage = 0;
win9xACPSame = false;
}
SurfaceImpl::~SurfaceImpl() {
Release();
}
void SurfaceImpl::Release() {
if (penOld) {
::SelectObject(reinterpret_cast<HDC>(hdc), penOld);
::DeleteObject(pen);
penOld = 0;
}
pen = 0;
if (brushOld) {
::SelectObject(reinterpret_cast<HDC>(hdc), brushOld);
::DeleteObject(brush);
brushOld = 0;
}
brush = 0;
if (fontOld) {
// Fonts are not deleted as they are owned by a Font object
::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);
fontOld = 0;
}
font = 0;
if (bitmapOld) {
::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);
::DeleteObject(bitmap);
bitmapOld = 0;
}
bitmap = 0;
if (paletteOld) {
// Fonts are not deleted as they are owned by a Palette object
::SelectPalette(reinterpret_cast<HDC>(hdc),
reinterpret_cast<HPALETTE>(paletteOld), TRUE);
paletteOld = 0;
}
if (hdcOwned) {
::DeleteDC(reinterpret_cast<HDC>(hdc));
hdc = 0;
hdcOwned = false;
}
}
bool SurfaceImpl::Initialised() {
return hdc != 0;
}
void SurfaceImpl::Init(WindowID) {
Release();
hdc = ::CreateCompatibleDC(NULL);
hdcOwned = true;
::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
}
void SurfaceImpl::Init(SurfaceID sid, WindowID) {
Release();
hdc = reinterpret_cast<HDC>(sid);
::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
}
void SurfaceImpl::InitPixMap(int width, int height, Surface *surface_, WindowID) {
Release();
hdc = ::CreateCompatibleDC(static_cast<SurfaceImpl *>(surface_)->hdc);
hdcOwned = true;
bitmap = ::CreateCompatibleBitmap(static_cast<SurfaceImpl *>(surface_)->hdc, width, height);
bitmapOld = static_cast<HBITMAP>(::SelectObject(hdc, bitmap));
::SetTextAlign(reinterpret_cast<HDC>(hdc), TA_BASELINE);
}
void SurfaceImpl::PenColour(ColourAllocated fore) {
if (pen) {
::SelectObject(hdc, penOld);
::DeleteObject(pen);
pen = 0;
penOld = 0;
}
pen = ::CreatePen(0,1,fore.AsLong());
penOld = static_cast<HPEN>(::SelectObject(reinterpret_cast<HDC>(hdc), pen));
}
void SurfaceImpl::BrushColor(ColourAllocated back) {
if (brush) {
::SelectObject(hdc, brushOld);
::DeleteObject(brush);
brush = 0;
brushOld = 0;
}
// Only ever want pure, non-dithered brushes
ColourAllocated colourNearest = ::GetNearestColor(hdc, back.AsLong());
brush = ::CreateSolidBrush(colourNearest.AsLong());
brushOld = static_cast<HBRUSH>(::SelectObject(hdc, brush));
}
void SurfaceImpl::SetFont(Font &font_) {
if (font_.GetID() != font) {
if (fontOld) {
::SelectObject(hdc, font_.GetID());
} else {
fontOld = static_cast<HFONT>(::SelectObject(hdc, font_.GetID()));
}
font = reinterpret_cast<HFONT>(font_.GetID());
}
}
int SurfaceImpl::LogPixelsY() {
return ::GetDeviceCaps(hdc, LOGPIXELSY);
}
int SurfaceImpl::DeviceHeightFont(int points) {
return ::MulDiv(points, LogPixelsY(), 72);
}
void SurfaceImpl::MoveTo(int x_, int y_) {
::MoveToEx(hdc, x_, y_, 0);
}
void SurfaceImpl::LineTo(int x_, int y_) {
::LineTo(hdc, x_, y_);
}
void SurfaceImpl::Polygon(Point *pts, int npts, ColourAllocated fore, ColourAllocated back) {
PenColour(fore);
BrushColor(back);
::Polygon(hdc, reinterpret_cast<POINT *>(pts), npts);
}
void SurfaceImpl::RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
PenColour(fore);
BrushColor(back);
::Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
}
void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back) {
// Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
// There is no need to allocate a brush either.
RECT rcw = RectFromPRectangle(rc);
::SetBkColor(hdc, back.AsLong());
::ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rcw, TEXT(""), 0, NULL);
}
void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
HBRUSH br;
if (static_cast<SurfaceImpl &>(surfacePattern).bitmap)
br = ::CreatePatternBrush(static_cast<SurfaceImpl &>(surfacePattern).bitmap);
else // Something is wrong so display in red
br = ::CreateSolidBrush(RGB(0xff, 0, 0));
RECT rcw = RectFromPRectangle(rc);
::FillRect(hdc, &rcw, br);
::DeleteObject(br);
}
void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
PenColour(fore);
BrushColor(back);
::RoundRect(hdc,
rc.left + 1, rc.top,
rc.right - 1, rc.bottom,
8, 8);
}
// Plot a point into a DWORD buffer symetrically to all 4 qudrants
static void AllFour(DWORD *pixels, int width, int height, int x, int y, DWORD val) {
pixels[y*width+x] = val;
pixels[y*width+width-1-x] = val;
pixels[(height-1-y)*width+x] = val;
pixels[(height-1-y)*width+width-1-x] = val;
}
#ifndef AC_SRC_OVER
#define AC_SRC_OVER 0x00
#endif
#ifndef AC_SRC_ALPHA
#define AC_SRC_ALPHA 0x01
#endif
void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourAllocated fill, int alphaFill,
ColourAllocated outline, int alphaOutline, int /* flags*/ ) {
if (AlphaBlendFn) {
HDC hMemDC = ::CreateCompatibleDC(reinterpret_cast<HDC>(hdc));
int width = rc.Width();
int height = rc.Height();
// Ensure not distorted too much by corners when small
cornerSize = Platform::Minimum(cornerSize, (Platform::Minimum(width, height) / 2) - 2);
BITMAPINFO bpih = {sizeof(BITMAPINFOHEADER), width, height, 1, 32, BI_RGB, 0, 0, 0, 0, 0};
void *image = 0;
HBITMAP hbmMem = CreateDIBSection(reinterpret_cast<HDC>(hMemDC), &bpih,
DIB_RGB_COLORS, &image, NULL, 0);
HBITMAP hbmOld = SelectBitmap(hMemDC, hbmMem);
byte pixVal[4] = {0};
DWORD valEmpty = *(reinterpret_cast<DWORD *>(pixVal));
pixVal[0] = static_cast<byte>(GetBValue(fill.AsLong()) * alphaFill / 255);
pixVal[1] = static_cast<byte>(GetGValue(fill.AsLong()) * alphaFill / 255);
pixVal[2] = static_cast<byte>(GetRValue(fill.AsLong()) * alphaFill / 255);
pixVal[3] = static_cast<byte>(alphaFill);
DWORD valFill = *(reinterpret_cast<DWORD *>(pixVal));
pixVal[0] = static_cast<byte>(GetBValue(outline.AsLong()) * alphaOutline / 255);
pixVal[1] = static_cast<byte>(GetGValue(outline.AsLong()) * alphaOutline / 255);
pixVal[2] = static_cast<byte>(GetRValue(outline.AsLong()) * alphaOutline / 255);
pixVal[3] = static_cast<byte>(alphaOutline);
DWORD valOutline = *(reinterpret_cast<DWORD *>(pixVal));
DWORD *pixels = reinterpret_cast<DWORD *>(image);
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
if ((x==0) || (x==width-1) || (y == 0) || (y == height-1)) {
pixels[y*width+x] = valOutline;
} else {
pixels[y*width+x] = valFill;
}
}
}
for (int c=0;c<cornerSize; c++) {
for (int x=0;x<c+1; x++) {
AllFour(pixels, width, height, x, c-x, valEmpty);
}
}
for (int x=1;x<cornerSize; x++) {
AllFour(pixels, width, height, x, cornerSize-x, valOutline);
}
BLENDFUNCTION merge = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
AlphaBlendFn(reinterpret_cast<HDC>(hdc), rc.left, rc.top, width, height, hMemDC, 0, 0, width, height, merge);
SelectBitmap(hMemDC, hbmOld);
::DeleteObject(hbmMem);
::DeleteObject(hMemDC);
} else {
RectangleDraw(rc, outline, fill);
}
}
void SurfaceImpl::Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
PenColour(fore);
BrushColor(back);
::Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);
}
void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
::BitBlt(hdc,
rc.left, rc.top, rc.Width(), rc.Height(),
static_cast<SurfaceImpl &>(surfaceSource).hdc, from.x, from.y, SRCCOPY);
}
const int MAX_US_LEN = 10000;
void SurfaceImpl::DrawTextCommon(PRectangle rc, Font &font_, int ybase, const char *s, int len, UINT fuOptions) {
SetFont(font_);
RECT rcw = RectFromPRectangle(rc);
SIZE sz={0,0};
int pos = 0;
int x = rc.left;
// Text drawing may fail if the text is too big.
// If it does fail, slice up into segments and draw each segment.
const int maxSegmentLength = 0x200;
if ((!unicodeMode) && (IsNT() || (codePage==0) || win9xACPSame)) {
// Use ANSI calls
int lenDraw = Platform::Minimum(len, maxLenText);
if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s, lenDraw, NULL)) {
while (lenDraw > pos) {
int seglen = Platform::Minimum(maxSegmentLength, lenDraw - pos);
if (!::ExtTextOutA(hdc, x, ybase, fuOptions, &rcw, s+pos, seglen, NULL)) {
PLATFORM_ASSERT(false);
return;
}
::GetTextExtentPoint32A(hdc, s+pos, seglen, &sz);
x += sz.cx;
pos += seglen;
}
}
} else {
// Use Unicode calls
wchar_t tbuf[MAX_US_LEN];
int tlen;
if (unicodeMode) {
tlen = UCS2FromUTF8(s, len, tbuf, MAX_US_LEN);
} else {
// Support Asian string display in 9x English
tlen = ::MultiByteToWideChar(codePage, 0, s, len, NULL, 0);
if (tlen > MAX_US_LEN)
tlen = MAX_US_LEN;
::MultiByteToWideChar(codePage, 0, s, len, tbuf, tlen);
}
if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf, tlen, NULL)) {
while (tlen > pos) {
int seglen = Platform::Minimum(maxSegmentLength, tlen - pos);
if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rcw, tbuf+pos, seglen, NULL)) {
PLATFORM_ASSERT(false);
return;
}
::GetTextExtentPoint32W(hdc, tbuf+pos, seglen, &sz);
x += sz.cx;
pos += seglen;
}
}
}
}
void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
ColourAllocated fore, ColourAllocated back) {
::SetTextColor(hdc, fore.AsLong());
::SetBkColor(hdc, back.AsLong());
DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE);
}
void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
ColourAllocated fore, ColourAllocated back) {
::SetTextColor(hdc, fore.AsLong());
::SetBkColor(hdc, back.AsLong());
DrawTextCommon(rc, font_, ybase, s, len, ETO_OPAQUE | ETO_CLIPPED);
}
void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len,
ColourAllocated fore) {
// Avoid drawing spaces in transparent mode
for (int i=0;i<len;i++) {
if (s[i] != ' ') {
::SetTextColor(hdc, fore.AsLong());
::SetBkMode(hdc, TRANSPARENT);
DrawTextCommon(rc, font_, ybase, s, len, 0);
::SetBkMode(hdc, OPAQUE);
return;
}
}
}
int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
SetFont(font_);
SIZE sz={0,0};
if (unicodeMode) {
wchar_t tbuf[MAX_US_LEN];
int tlen = UCS2FromUTF8(s, len, tbuf, MAX_US_LEN);
::GetTextExtentPoint32W(hdc, tbuf, tlen, &sz);
} else if (IsNT() || (codePage==0) || win9xACPSame) {
::GetTextExtentPoint32A(hdc, s, Platform::Minimum(len, maxLenText), &sz);
} else {
// Support Asian string display in 9x English
wchar_t tbuf[MAX_US_LEN];
int tlen = ::MultiByteToWideChar(codePage, 0, s, len, NULL, 0);
::MultiByteToWideChar(codePage, 0, s, len, tbuf, tlen);
::GetTextExtentPoint32W(hdc, tbuf, tlen, &sz);
}
return sz.cx;
}
void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions) {
SetFont(font_);
SIZE sz={0,0};
int fit = 0;
if (unicodeMode) {
wchar_t tbuf[MAX_US_LEN];
int tlen = UCS2FromUTF8(s, len, tbuf, MAX_US_LEN);
int poses[MAX_US_LEN];
fit = tlen;
if (!::GetTextExtentExPointW(hdc, tbuf, tlen, maxWidthMeasure, &fit, poses, &sz)) {
// Likely to have failed because on Windows 9x where function not available
// So measure the character widths by measuring each initial substring
// Turns a linear operation into a qudratic but seems fast enough on test files
for (int widthSS=0; widthSS < tlen; widthSS++) {
::GetTextExtentPoint32W(hdc, tbuf, widthSS+1, &sz);
poses[widthSS] = sz.cx;
}
}
// Map the widths given for UCS-2 characters back onto the UTF-8 input string
int ui=0;
const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
int i=0;
while (ui<fit) {
unsigned char uch = us[i];
positions[i++] = poses[ui];
if (uch >= 0x80) {
if (uch < (0x80 + 0x40 + 0x20)) {
positions[i++] = poses[ui];
} else {
positions[i++] = poses[ui];
positions[i++] = poses[ui];
}
}
ui++;
}
int lastPos = 0;
if (i > 0)
lastPos = positions[i-1];
while (i<len) {
positions[i++] = lastPos;
}
} else if (IsNT() || (codePage==0) || win9xACPSame) {
if (!::GetTextExtentExPoint(hdc, s, Platform::Minimum(len, maxLenText),
maxWidthMeasure, &fit, positions, &sz)) {
// Eeek - a NULL DC or other foolishness could cause this.
// The least we can do is set the positions to zero!
memset(positions, 0, len * sizeof(*positions));
} else if (fit < len) {
// For some reason, such as an incomplete DBCS character
// Not all the positions are filled in so make them equal to end.
for (int i=fit;i<len;i++)
positions[i] = positions[fit-1];
}
} else {
// Support Asian string display in 9x English
wchar_t tbuf[MAX_US_LEN];
int tlen = ::MultiByteToWideChar(codePage, 0, s, len, NULL, 0);
::MultiByteToWideChar(codePage, 0, s, len, tbuf, tlen);
int poses[MAX_US_LEN];
for (int widthSS=0; widthSS<tlen; widthSS++) {
::GetTextExtentPoint32W(hdc, tbuf, widthSS+1, &sz);
poses[widthSS] = sz.cx;
}
int ui = 0;
for (int i=0;i<len;) {
if (::IsDBCSLeadByteEx(codePage, s[i])) {
positions[i] = poses[ui];
positions[i+1] = poses[ui];
i += 2;
} else {
positions[i] = poses[ui];
i++;
}
ui++;
}
}
}
int SurfaceImpl::WidthChar(Font &font_, char ch) {
SetFont(font_);
SIZE sz;
::GetTextExtentPoint32(hdc, &ch, 1, &sz);
return sz.cx;
}
int SurfaceImpl::Ascent(Font &font_) {
SetFont(font_);
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
return tm.tmAscent;
}
int SurfaceImpl::Descent(Font &font_) {
SetFont(font_);
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
return tm.tmDescent;
}
int SurfaceImpl::InternalLeading(Font &font_) {
SetFont(font_);
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
return tm.tmInternalLeading;
}
int SurfaceImpl::ExternalLeading(Font &font_) {
SetFont(font_);
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
return tm.tmExternalLeading;
}
int SurfaceImpl::Height(Font &font_) {
SetFont(font_);
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
return tm.tmHeight;
}
int SurfaceImpl::AverageCharWidth(Font &font_) {
SetFont(font_);
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
return tm.tmAveCharWidth;
}
int SurfaceImpl::SetPalette(Palette *pal, bool inBackGround) {
if (paletteOld) {
::SelectPalette(hdc, paletteOld, TRUE);
}
paletteOld = 0;
int changes = 0;
if (pal->allowRealization) {
paletteOld = ::SelectPalette(hdc,
reinterpret_cast<HPALETTE>(pal->hpal), inBackGround);
changes = ::RealizePalette(hdc);
}
return changes;
}
void SurfaceImpl::SetClip(PRectangle rc) {
::IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
}
void SurfaceImpl::FlushCachedState() {
pen = 0;
brush = 0;
font = 0;
}
void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
unicodeMode=unicodeMode_;
}
void SurfaceImpl::SetDBCSMode(int codePage_) {
// No action on window as automatically handled by system.
codePage = codePage_;
win9xACPSame = !IsNT() && ((unsigned int)codePage == ::GetACP());
}
Surface *Surface::Allocate() {
return new SurfaceImpl;
}
Window::~Window() {
}
void Window::Destroy() {
if (id)
::DestroyWindow(reinterpret_cast<HWND>(id));
id = 0;
}
bool Window::HasFocus() {
return ::GetFocus() == id;
}
PRectangle Window::GetPosition() {
RECT rc;
::GetWindowRect(reinterpret_cast<HWND>(id), &rc);
return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
}
void Window::SetPosition(PRectangle rc) {
::SetWindowPos(reinterpret_cast<HWND>(id),
0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER|SWP_NOACTIVATE);
}
void Window::SetPositionRelative(PRectangle rc, Window w) {
LONG style = ::GetWindowLong(reinterpret_cast<HWND>(id), GWL_STYLE);
if (style & WS_POPUP) {
RECT rcOther;
::GetWindowRect(reinterpret_cast<HWND>(w.GetID()), &rcOther);
rc.Move(rcOther.left, rcOther.top);
// Retrieve desktop bounds and make sure window popup's origin isn't left-top of the screen.
RECT rcDesktop = {0, 0, 0, 0};
#ifdef SM_XVIRTUALSCREEN
rcDesktop.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
rcDesktop.top = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
rcDesktop.right = rcDesktop.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
rcDesktop.bottom = rcDesktop.top + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
#endif
if (rc.left < rcDesktop.left) {
rc.Move(rcDesktop.left - rc.left,0);
}
if (rc.top < rcDesktop.top) {
rc.Move(0,rcDesktop.top - rc.top);
}
}
SetPosition(rc);
}
PRectangle Window::GetClientPosition() {
RECT rc={0,0,0,0};
if (id)
::GetClientRect(reinterpret_cast<HWND>(id), &rc);
return PRectangle(rc.left, rc.top, rc.right, rc.bottom);
}
void Window::Show(bool show) {
if (show)
::ShowWindow(reinterpret_cast<HWND>(id), SW_SHOWNOACTIVATE);
else
::ShowWindow(reinterpret_cast<HWND>(id), SW_HIDE);
}
void Window::InvalidateAll() {
::InvalidateRect(reinterpret_cast<HWND>(id), NULL, FALSE);
}
void Window::InvalidateRectangle(PRectangle rc) {
RECT rcw = RectFromPRectangle(rc);
::InvalidateRect(reinterpret_cast<HWND>(id), &rcw, FALSE);
}
static LRESULT Window_SendMessage(Window *w, UINT msg, WPARAM wParam=0, LPARAM lParam=0) {
return ::SendMessage(reinterpret_cast<HWND>(w->GetID()), msg, wParam, lParam);
}
void Window::SetFont(Font &font) {
Window_SendMessage(this, WM_SETFONT,
reinterpret_cast<WPARAM>(font.GetID()), 0);
}
void Window::SetCursor(Cursor curs) {
switch (curs) {
case cursorText:
::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
break;
case cursorUp:
::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
break;
case cursorWait:
::SetCursor(::LoadCursor(NULL,IDC_WAIT));
break;
case cursorHoriz:
::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
break;
case cursorVert:
::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
break;
case cursorHand:
::SetCursor(::LoadCursor(NULL,IDC_HAND));
break;
case cursorReverseArrow: {
if (!hinstPlatformRes)
hinstPlatformRes = ::GetModuleHandle(TEXT("Scintilla"));
if (!hinstPlatformRes)
hinstPlatformRes = ::GetModuleHandle(TEXT("SciLexer"));
if (!hinstPlatformRes)
hinstPlatformRes = ::GetModuleHandle(NULL);
HCURSOR hcursor = ::LoadCursor(hinstPlatformRes, MAKEINTRESOURCE(IDC_MARGIN));
if (hcursor)
::SetCursor(hcursor);
else
::SetCursor(::LoadCursor(NULL,IDC_ARROW));
}
break;
case cursorArrow:
case cursorInvalid: // Should not occur, but just in case.
::SetCursor(::LoadCursor(NULL,IDC_ARROW));
break;
}
}
void Window::SetTitle(const char *s) {
::SetWindowText(reinterpret_cast<HWND>(id), s);
}
struct ListItemData {
const char *text;
int pixId;
};
#define _ROUND2(n,pow2) \
( ( (n) + (pow2) - 1) & ~((pow2) - 1) )
class LineToItem {
char *words;
int wordsCount;
int wordsSize;
ListItemData *data;
int len;
int count;
private:
void FreeWords() {
delete []words;
words = NULL;
wordsCount = 0;
wordsSize = 0;
}
char *AllocWord(const char *word);
public:
LineToItem() : words(NULL), wordsCount(0), wordsSize(0), data(NULL), len(0), count(0) {
}
~LineToItem() {
Clear();
}
void Clear() {
FreeWords();
delete []data;
data = NULL;
len = 0;
count = 0;
}
ListItemData *Append(const char *text, int value);
ListItemData Get(int index) const {
if (index >= 0 && index < count) {
return data[index];
} else {
ListItemData missing = {"", -1};
return missing;
}
}
int Count() const {
return count;
}
ListItemData *AllocItem();
void SetWords(char *s) {
words = s; // N.B. will be deleted on destruction
}
};
char *LineToItem::AllocWord(const char *text) {
int chars = strlen(text) + 1;
int newCount = wordsCount + chars;
if (newCount > wordsSize) {
wordsSize = _ROUND2(newCount * 2, 8192);
char *wordsNew = new char[wordsSize];
memcpy(wordsNew, words, wordsCount);
int offset = wordsNew - words;
for (int i=0; i<count; i++)
data[i].text += offset;
delete []words;
words = wordsNew;
}
char *s = &words[wordsCount];
wordsCount = newCount;
strncpy(s, text, chars);
return s;
}
ListItemData *LineToItem::AllocItem() {
if (count >= len) {
int lenNew = _ROUND2((count+1) * 2, 1024);
ListItemData *dataNew = new ListItemData[lenNew];
memcpy(dataNew, data, count * sizeof(ListItemData));
delete []data;
data = dataNew;
len = lenNew;
}
ListItemData *item = &data[count];
count++;
return item;
}
ListItemData *LineToItem::Append(const char *text, int imageIndex) {
ListItemData *item = AllocItem();
item->text = AllocWord(text);
item->pixId = imageIndex;
return item;
}
const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
ListBox::ListBox() {
}
ListBox::~ListBox() {
}
class ListBoxX : public ListBox {
int lineHeight;
FontID fontCopy;
XPMSet xset;
LineToItem lti;
HWND lb;
bool unicodeMode;
int desiredVisibleRows;
unsigned int maxItemCharacters;
unsigned int aveCharWidth;
Window *parent;
int ctrlID;
CallBackAction doubleClickAction;
void *doubleClickActionData;
const char *widestItem;
unsigned int maxCharWidth;
int resizeHit;
PRectangle rcPreSize;
Point dragOffset;
Point location; // Caret location at which the list is opened
HWND GetHWND() const;
void AppendListItem(const char *startword, const char *numword);
void AdjustWindowRect(PRectangle *rc) const;
int ItemHeight() const;
int MinClientWidth() const;
int TextOffset() const;
Point GetClientExtent() const;
Point MinTrackSize() const;
Point MaxTrackSize() const;
void SetRedraw(bool on);
void OnDoubleClick();
void ResizeToCursor();
void StartResize(WPARAM);
int NcHitTest(WPARAM, LPARAM) const;
void CentreItem(int);
void Paint(HDC);
void Erase(HDC);
static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
static const Point ItemInset; // Padding around whole item
static const Point TextInset; // Padding around text
static const Point ImageInset; // Padding around image
public:
ListBoxX() : lineHeight(10), fontCopy(0), lb(0), unicodeMode(false),
desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8),
parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL),
widestItem(NULL), maxCharWidth(1), resizeHit(0) {
}
virtual ~ListBoxX() {
if (fontCopy) {
::DeleteObject(fontCopy);
fontCopy = 0;
}
}
virtual void SetFont(Font &font);
virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_);
virtual void SetAverageCharWidth(int width);
virtual void SetVisibleRows(int rows);
virtual int GetVisibleRows() const;
virtual PRectangle GetDesiredRect();
virtual int CaretFromEdge();
virtual void Clear();
virtual void Append(char *s, int type = -1);
virtual int Length();
virtual void Select(int n);
virtual int GetSelection();
virtual int Find(const char *prefix);
virtual void GetValue(int n, char *value, int len);
virtual void RegisterImage(int type, const char *xpm_data);
virtual void ClearRegisteredImages();
virtual void SetDoubleClickAction(CallBackAction action, void *data) {
doubleClickAction = action;
doubleClickActionData = data;
}
virtual void SetList(const char *list, char separator, char typesep);
void Draw(DRAWITEMSTRUCT *pDrawItem);
LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
};
const Point ListBoxX::ItemInset(0, 0);
const Point ListBoxX::TextInset(2, 0);
const Point ListBoxX::ImageInset(1, 0);
ListBox *ListBox::Allocate() {
ListBoxX *lb = new ListBoxX();
return lb;
}
void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_) {
parent = &parent_;
ctrlID = ctrlID_;
location = location_;
lineHeight = lineHeight_;
unicodeMode = unicodeMode_;
HWND hwndParent = reinterpret_cast<HWND>(parent->GetID());
HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
// Window created as popup so not clipped within parent client area
id = ::CreateWindowEx(
WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
WS_POPUP | WS_THICKFRAME,
100,100, 150,80, hwndParent,
NULL,
hinstanceParent,
this);
::MapWindowPoints(hwndParent, NULL, reinterpret_cast<POINT*>(&location), 1);
}
void ListBoxX::SetFont(Font &font) {
LOGFONT lf;
if (0 != ::GetObject(font.GetID(), sizeof(lf), &lf)) {
if (fontCopy) {
::DeleteObject(fontCopy);
fontCopy = 0;
}
fontCopy = ::CreateFontIndirect(&lf);
::SendMessage(lb, WM_SETFONT, reinterpret_cast<WPARAM>(fontCopy), 0);
}
}
void ListBoxX::SetAverageCharWidth(int width) {
aveCharWidth = width;
}
void ListBoxX::SetVisibleRows(int rows) {
desiredVisibleRows = rows;
}
int ListBoxX::GetVisibleRows() const {
return desiredVisibleRows;
}
HWND ListBoxX::GetHWND() const {
return reinterpret_cast<HWND>(GetID());
}
PRectangle ListBoxX::GetDesiredRect() {
PRectangle rcDesired = GetPosition();
int rows = Length();
if ((rows == 0) || (rows > desiredVisibleRows))
rows = desiredVisibleRows;
rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
int width = MinClientWidth();
HDC hdc = ::GetDC(lb);
HFONT oldFont = SelectFont(hdc, fontCopy);
SIZE textSize = {0, 0};
int len = widestItem ? strlen(widestItem) : 0;
if (unicodeMode) {
wchar_t tbuf[MAX_US_LEN];
len = UCS2FromUTF8(widestItem, len, tbuf, sizeof(tbuf)/sizeof(wchar_t)-1);
tbuf[len] = L'\0';
::GetTextExtentPoint32W(hdc, tbuf, len, &textSize);
} else {
::GetTextExtentPoint32(hdc, widestItem, len, &textSize);
}
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
maxCharWidth = tm.tmMaxCharWidth;
SelectFont(hdc, oldFont);
::ReleaseDC(lb, hdc);
int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
if (width < widthDesired)
width = widthDesired;
rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
if (Length() > rows)
rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
AdjustWindowRect(&rcDesired);
return rcDesired;
}
int ListBoxX::TextOffset() const {
int pixWidth = const_cast<XPMSet*>(&xset)->GetWidth();
return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2);
}
int ListBoxX::CaretFromEdge() {
PRectangle rc;
AdjustWindowRect(&rc);
return TextOffset() + TextInset.x + (0 - rc.left) - 1;
}
void ListBoxX::Clear() {
::SendMessage(lb, LB_RESETCONTENT, 0, 0);
maxItemCharacters = 0;
widestItem = NULL;
lti.Clear();
}
void ListBoxX::Append(char *s, int type) {
int index = ::SendMessage(lb, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s));
if (index < 0)
return;
ListItemData *newItem = lti.Append(s, type);
unsigned int len = static_cast<unsigned int>(strlen(s));
if (maxItemCharacters < len) {
maxItemCharacters = len;
widestItem = newItem->text;
}
}
int ListBoxX::Length() {
return lti.Count();
}
void ListBoxX::Select(int n) {
// We are going to scroll to centre on the new selection and then select it, so disable
// redraw to avoid flicker caused by a painting new selection twice in unselected and then
// selected states
SetRedraw(false);
CentreItem(n);
::SendMessage(lb, LB_SETCURSEL, n, 0);
SetRedraw(true);
}
int ListBoxX::GetSelection() {
return ::SendMessage(lb, LB_GETCURSEL, 0, 0);
}
// This is not actually called at present
int ListBoxX::Find(const char *) {
return LB_ERR;
}
void ListBoxX::GetValue(int n, char *value, int len) {
ListItemData item = lti.Get(n);
strncpy(value, item.text, len);
value[len-1] = '\0';
}
void ListBoxX::RegisterImage(int type, const char *xpm_data) {
xset.Add(type, xpm_data);
}
void ListBoxX::ClearRegisteredImages() {
xset.Clear();
}
void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
RECT rcBox = pDrawItem->rcItem;
rcBox.left += TextOffset();
if (pDrawItem->itemState & ODS_SELECTED) {
RECT rcImage = pDrawItem->rcItem;
rcImage.right = rcBox.left;
// The image is not highlighted
::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
} else {
::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
}
ListItemData item = lti.Get(pDrawItem->itemID);
int pixId = item.pixId;
const char *text = item.text;
int len = strlen(text);
RECT rcText = rcBox;
::InsetRect(&rcText, TextInset.x, TextInset.y);
if (unicodeMode) {
wchar_t tbuf[MAX_US_LEN];
int tlen = UCS2FromUTF8(text, len, tbuf, sizeof(tbuf)/sizeof(wchar_t)-1);
tbuf[tlen] = L'\0';
::DrawTextW(pDrawItem->hDC, tbuf, tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
} else {
::DrawText(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
}
if (pDrawItem->itemState & ODS_SELECTED) {
::DrawFocusRect(pDrawItem->hDC, &rcBox);
}
// Draw the image, if any
XPM *pxpm = xset.Get(pixId);
if (pxpm) {
Surface *surfaceItem = Surface::Allocate();
if (surfaceItem) {
surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
//surfaceItem->SetUnicodeMode(unicodeMode);
//surfaceItem->SetDBCSMode(codePage);
int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
PRectangle rcImage(left, pDrawItem->rcItem.top,
left + xset.GetWidth(), pDrawItem->rcItem.bottom);
pxpm->Draw(surfaceItem, rcImage);
delete surfaceItem;
::SetTextAlign(pDrawItem->hDC, TA_TOP);
}
}
}
}
void ListBoxX::AppendListItem(const char *startword, const char *numword) {
ListItemData *item = lti.AllocItem();
item->text = startword;
if (numword) {
int pixId = 0;
char ch;
while ( (ch = *++numword) != '\0' ) {
pixId = 10 * pixId + (ch - '0');
}
item->pixId = pixId;
} else {
item->pixId = -1;
}
unsigned int len = static_cast<unsigned int>(strlen(item->text));
if (maxItemCharacters < len) {
maxItemCharacters = len;
widestItem = item->text;
}
}
void ListBoxX::SetList(const char *list, char separator, char typesep) {
// Turn off redraw while populating the list - this has a significant effect, even if
// the listbox is not visible.
SetRedraw(false);
Clear();
int size = strlen(list) + 1;
char *words = new char[size];
if (words) {
lti.SetWords(words);
memcpy(words, list, size);
char *startword = words;
char *numword = NULL;
int i = 0;
for (; words[i]; i++) {
if (words[i] == separator) {
words[i] = '\0';
if (numword)
*numword = '\0';
AppendListItem(startword, numword);
startword = words + i + 1;
numword = NULL;
} else if (words[i] == typesep) {
numword = words + i;
}
}
if (startword) {
if (numword)
*numword = '\0';
AppendListItem(startword, numword);
}
// Finally populate the listbox itself with the correct number of items
int count = lti.Count();
::SendMessage(lb, LB_INITSTORAGE, count, 0);
for (int j=0; j<count; j++) {
::SendMessage(lb, LB_ADDSTRING, 0, j+1);
}
}
SetRedraw(true);
}
void ListBoxX::AdjustWindowRect(PRectangle *rc) const {
::AdjustWindowRectEx(reinterpret_cast<RECT*>(rc), WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
}
int ListBoxX::ItemHeight() const {
int itemHeight = lineHeight + (TextInset.y * 2);
int pixHeight = const_cast<XPMSet*>(&xset)->GetHeight() + (ImageInset.y * 2);
if (itemHeight < pixHeight) {
itemHeight = pixHeight;
}
return itemHeight;
}
int ListBoxX::MinClientWidth() const {
return 12 * (aveCharWidth+aveCharWidth/3);
}
Point ListBoxX::MinTrackSize() const {
PRectangle rc(0, 0, MinClientWidth(), ItemHeight());
AdjustWindowRect(&rc);
return Point(rc.Width(), rc.Height());
}
Point ListBoxX::MaxTrackSize() const {
PRectangle rc(0, 0,
maxCharWidth * maxItemCharacters + TextInset.x * 2 +
TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL),
ItemHeight() * lti.Count());
AdjustWindowRect(&rc);
return Point(rc.Width(), rc.Height());
}
void ListBoxX::SetRedraw(bool on) {
::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
if (on)
::InvalidateRect(lb, NULL, TRUE);
}
void ListBoxX::ResizeToCursor() {
PRectangle rc = GetPosition();
Point pt;
::GetCursorPos(reinterpret_cast<POINT*>(&pt));
pt.x += dragOffset.x;
pt.y += dragOffset.y;
switch (resizeHit) {
case HTLEFT:
rc.left = pt.x;
break;
case HTRIGHT:
rc.right = pt.x;
break;
case HTTOP:
rc.top = pt.y;
break;
case HTTOPLEFT:
rc.top = pt.y;
rc.left = pt.x;
break;
case HTTOPRIGHT:
rc.top = pt.y;
rc.right = pt.x;
break;
case HTBOTTOM:
rc.bottom = pt.y;
break;
case HTBOTTOMLEFT:
rc.bottom = pt.y;
rc.left = pt.x;
break;
case HTBOTTOMRIGHT:
rc.bottom = pt.y;
rc.right = pt.x;
break;
}
Point ptMin = MinTrackSize();
Point ptMax = MaxTrackSize();
// We don't allow the left edge to move at present, but just in case
rc.left = Platform::Maximum(Platform::Minimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
rc.top = Platform::Maximum(Platform::Minimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
rc.right = Platform::Maximum(Platform::Minimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
rc.bottom = Platform::Maximum(Platform::Minimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
SetPosition(rc);
}
void ListBoxX::StartResize(WPARAM hitCode) {
rcPreSize = GetPosition();
POINT cursorPos;
::GetCursorPos(&cursorPos);
switch (hitCode) {
case HTRIGHT:
case HTBOTTOM:
case HTBOTTOMRIGHT:
dragOffset.x = rcPreSize.right - cursorPos.x;
dragOffset.y = rcPreSize.bottom - cursorPos.y;
break;
case HTTOPRIGHT:
dragOffset.x = rcPreSize.right - cursorPos.x;
dragOffset.y = rcPreSize.top - cursorPos.y;
break;
// Note that the current hit test code prevents the left edge cases ever firing
// as we don't want the left edge to be moveable
case HTLEFT:
case HTTOP:
case HTTOPLEFT:
dragOffset.x = rcPreSize.left - cursorPos.x;
dragOffset.y = rcPreSize.top - cursorPos.y;
break;
case HTBOTTOMLEFT:
dragOffset.x = rcPreSize.left - cursorPos.x;
dragOffset.y = rcPreSize.bottom - cursorPos.y;
break;
default:
return;
}
::SetCapture(GetHWND());
resizeHit = hitCode;
}
int ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
int hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
// There is an apparent bug in the DefWindowProc hit test code whereby it will
// return HTTOPXXX if the window in question is shorter than the default
// window caption height + frame, even if one is hovering over the bottom edge of
// the frame, so workaround that here
if (hit >= HTTOP && hit <= HTTOPRIGHT) {
int minHeight = GetSystemMetrics(SM_CYMINTRACK);
PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
int yPos = GET_Y_LPARAM(lParam);
if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
hit += HTBOTTOM - HTTOP;
}
}
// Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
// depending on whether the list is above or below the caret
switch (hit) {
case HTLEFT:
case HTTOPLEFT:
case HTBOTTOMLEFT:
hit = HTERROR;
break;
case HTTOP:
case HTTOPRIGHT: {
PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
// Valid only if caret below list
if (location.y < rc.top)
hit = HTERROR;
}
break;
case HTBOTTOM:
case HTBOTTOMRIGHT: {
PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
// Valid only if caret above list
if (rc.bottom < location.y)
hit = HTERROR;
}
break;
}
return hit;
}
void ListBoxX::OnDoubleClick() {
if (doubleClickAction != NULL) {
doubleClickAction(doubleClickActionData);
}
}
Point ListBoxX::GetClientExtent() const {
PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
return Point(rc.Width(), rc.Height());
}
void ListBoxX::CentreItem(int n) {
// If below mid point, scroll up to centre, but with more items below if uneven
if (n >= 0) {
Point extent = GetClientExtent();
int visible = extent.y/ItemHeight();
if (visible < Length()) {
int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
int half = (visible - 1) / 2;
if (n > (top + half))
::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
}
}
}
// Performs a double-buffered paint operation to avoid flicker
void ListBoxX::Paint(HDC hDC) {
Point extent = GetClientExtent();
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
HDC bitmapDC = ::CreateCompatibleDC(hDC);
HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
// The list background is mainly erased during painting, but can be a small
// unpainted area when at the end of a non-integrally sized list with a
// vertical scroll bar
RECT rc = { 0, 0, extent.x, extent.y };
::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
// Paint the entire client area and vertical scrollbar
::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
// Select a stock brush to prevent warnings from BoundsChecker
::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
SelectBitmap(bitmapDC, hBitmapOld);
::DeleteDC(bitmapDC);
::DeleteObject(hBitmap);
}
LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_ERASEBKGND:
return TRUE;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hDC = ::BeginPaint(hWnd, &ps);
ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
if (lbx)
lbx->Paint(hDC);
::EndPaint(hWnd, &ps);
}
return 0;
case WM_MOUSEACTIVATE:
// This prevents the view activating when the scrollbar is clicked
return MA_NOACTIVATE;
case WM_LBUTTONDOWN: {
// We must take control of selection to prevent the ListBox activating
// the popup
LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
int item = LOWORD(lResult);
if (HIWORD(lResult) == 0 && item >= 0) {
::SendMessage(hWnd, LB_SETCURSEL, item, 0);
}
}
return 0;
case WM_LBUTTONUP:
return 0;
case WM_LBUTTONDBLCLK: {
ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
if (lbx) {
lbx->OnDoubleClick();
}
}
return 0;
}
WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (prevWndProc) {
return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
} else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
switch (iMessage) {
case WM_CREATE: {
HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID()));
// Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
// but has useful side effect of speeding up list population significantly
lb = ::CreateWindowEx(
0, TEXT("listbox"), TEXT(""),
WS_CHILD | WS_VSCROLL | WS_VISIBLE |
LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
0, 0, 150,80, hWnd,
reinterpret_cast<HMENU>(ctrlID),
hinstanceParent,
0);
WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(lb, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlWndProc)));
::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
}
break;
case WM_SIZE:
if (lb) {
SetRedraw(false);
::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
// Ensure the selection remains visible
CentreItem(GetSelection());
SetRedraw(true);
}
break;
case WM_PAINT: {
PAINTSTRUCT ps;
::BeginPaint(hWnd, &ps);
::EndPaint(hWnd, &ps);
}
break;
case WM_COMMAND:
// This is not actually needed now - the registered double click action is used
// directly to action a choice from the list.
::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);
break;
case WM_MEASUREITEM: {
MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());
}
break;
case WM_DRAWITEM:
Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
break;
case WM_DESTROY:
lb = 0;
::SetWindowLong(hWnd, 0, 0);
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
case WM_ERASEBKGND:
// To reduce flicker we can elide background erasure since this window is
// completely covered by its child.
return TRUE;
case WM_GETMINMAXINFO: {
MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
*reinterpret_cast<Point*>(&minMax->ptMaxTrackSize) = MaxTrackSize();
*reinterpret_cast<Point*>(&minMax->ptMinTrackSize) = MinTrackSize();
}
break;
case WM_MOUSEACTIVATE:
return MA_NOACTIVATE;
case WM_NCHITTEST:
return NcHitTest(wParam, lParam);
case WM_NCLBUTTONDOWN:
// We have to implement our own window resizing because the DefWindowProc
// implementation insists on activating the resized window
StartResize(wParam);
return 0;
case WM_MOUSEMOVE: {
if (resizeHit == 0) {
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
} else {
ResizeToCursor();
}
}
break;
case WM_LBUTTONUP:
case WM_CANCELMODE:
if (resizeHit != 0) {
resizeHit = 0;
::ReleaseCapture();
}
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
default:
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
}
return 0;
}
LRESULT PASCAL ListBoxX::StaticWndProc(
HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
if (iMessage == WM_CREATE) {
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
SetWindowPointer(hWnd, pCreate->lpCreateParams);
}
// Find C++ object associated with window.
ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(hWnd));
if (lbx) {
return lbx->WndProc(hWnd, iMessage, wParam, lParam);
} else {
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
}
}
static bool ListBoxX_Register() {
WNDCLASSEX wndclassc;
wndclassc.cbSize = sizeof(wndclassc);
// We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
// truncated items in the list and the appearance/disappearance of the vertical scroll bar.
// The list repaint is double-buffered to avoid the flicker this would otherwise cause.
wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
wndclassc.cbClsExtra = 0;
wndclassc.cbWndExtra = sizeof(ListBoxX *);
wndclassc.hInstance = hinstPlatformRes;
wndclassc.hIcon = NULL;
wndclassc.hbrBackground = NULL;
wndclassc.lpszMenuName = NULL;
wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wndclassc.lpszClassName = ListBoxX_ClassName;
wndclassc.hIconSm = 0;
return ::RegisterClassEx(&wndclassc) != 0;
}
bool ListBoxX_Unregister() {
return ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes) != 0;
}
Menu::Menu() : id(0) {
}
void Menu::CreatePopUp() {
Destroy();
id = ::CreatePopupMenu();
}
void Menu::Destroy() {
if (id)
::DestroyMenu(reinterpret_cast<HMENU>(id));
id = 0;
}
void Menu::Show(Point pt, Window &w) {
::TrackPopupMenu(reinterpret_cast<HMENU>(id),
0, pt.x - 4, pt.y, 0,
reinterpret_cast<HWND>(w.GetID()), NULL);
Destroy();
}
static bool initialisedET = false;
static bool usePerformanceCounter = false;
static LARGE_INTEGER frequency;
ElapsedTime::ElapsedTime() {
if (!initialisedET) {
usePerformanceCounter = ::QueryPerformanceFrequency(&frequency) != 0;
initialisedET = true;
}
if (usePerformanceCounter) {
LARGE_INTEGER timeVal;
::QueryPerformanceCounter(&timeVal);
bigBit = timeVal.HighPart;
littleBit = timeVal.LowPart;
} else {
bigBit = clock();
}
}
double ElapsedTime::Duration(bool reset) {
double result;
long endBigBit;
long endLittleBit;
if (usePerformanceCounter) {
LARGE_INTEGER lEnd;
::QueryPerformanceCounter(&lEnd);
endBigBit = lEnd.HighPart;
endLittleBit = lEnd.LowPart;
LARGE_INTEGER lBegin;
lBegin.HighPart = bigBit;
lBegin.LowPart = littleBit;
double elapsed = lEnd.QuadPart - lBegin.QuadPart;
result = elapsed / static_cast<double>(frequency.QuadPart);
} else {
endBigBit = clock();
endLittleBit = 0;
double elapsed = endBigBit - bigBit;
result = elapsed / CLOCKS_PER_SEC;
}
if (reset) {
bigBit = endBigBit;
littleBit = endLittleBit;
}
return result;
}
class DynamicLibraryImpl : public DynamicLibrary {
protected:
HMODULE h;
public:
DynamicLibraryImpl(const char *modulePath) {
h = ::LoadLibrary(modulePath);
}
virtual ~DynamicLibraryImpl() {
if (h != NULL)
::FreeLibrary(h);
}
// Use GetProcAddress to get a pointer to the relevant function.
virtual Function FindFunction(const char *name) {
if (h != NULL) {
return static_cast<Function>(
(void *)(::GetProcAddress(h, name)));
} else
return NULL;
}
virtual bool IsValid() {
return h != NULL;
}
};
DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
return static_cast<DynamicLibrary *>( new DynamicLibraryImpl(modulePath) );
}
ColourDesired Platform::Chrome() {
return ::GetSysColor(COLOR_3DFACE);
}
ColourDesired Platform::ChromeHighlight() {
return ::GetSysColor(COLOR_3DHIGHLIGHT);
}
const char *Platform::DefaultFont() {
return "Verdana";
}
int Platform::DefaultFontSize() {
return 8;
}
unsigned int Platform::DoubleClickTime() {
return ::GetDoubleClickTime();
}
bool Platform::MouseButtonBounce() {
return false;
}
void Platform::DebugDisplay(const char *s) {
::OutputDebugString(s);
}
bool Platform::IsKeyDown(int key) {
return (::GetKeyState(key) & 0x80000000) != 0;
}
long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam, lParam);
}
long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam) {
return ::SendMessage(reinterpret_cast<HWND>(w), msg, wParam,
reinterpret_cast<LPARAM>(lParam));
}
bool Platform::IsDBCSLeadByte(int codePage, char ch) {
return ::IsDBCSLeadByteEx(codePage, ch) != 0;
}
int Platform::DBCSCharLength(int codePage, const char *s) {
return (::IsDBCSLeadByteEx(codePage, s[0]) != 0) ? 2 : 1;
}
int Platform::DBCSCharMaxLength() {
return 2;
}
// These are utility functions not really tied to a platform
int Platform::Minimum(int a, int b) {
if (a < b)
return a;
else
return b;
}
int Platform::Maximum(int a, int b) {
if (a > b)
return a;
else
return b;
}
//#define TRACE
#ifdef TRACE
void Platform::DebugPrintf(const char *format, ...) {
char buffer[2000];
va_list pArguments;
va_start(pArguments, format);
vsprintf(buffer,format,pArguments);
va_end(pArguments);
Platform::DebugDisplay(buffer);
}
#else
void Platform::DebugPrintf(const char *, ...) {
}
#endif
static bool assertionPopUps = true;
bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
bool ret = assertionPopUps;
assertionPopUps = assertionPopUps_;
return ret;
}
void Platform::Assert(const char *c, const char *file, int line) {
char buffer[2000];
sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
if (assertionPopUps) {
int idButton = ::MessageBox(0, buffer, "Assertion failure",
MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
if (idButton == IDRETRY) {
::DebugBreak();
} else if (idButton == IDIGNORE) {
// all OK
} else {
abort();
}
} else {
strcat(buffer, "\r\n");
Platform::DebugDisplay(buffer);
::DebugBreak();
abort();
}
}
int Platform::Clamp(int val, int minVal, int maxVal) {
if (val > maxVal)
val = maxVal;
if (val < minVal)
val = minVal;
return val;
}
void Platform_Initialise(void *hInstance) {
OSVERSIONINFO osv = {sizeof(OSVERSIONINFO),0,0,0,0,TEXT("")};
::GetVersionEx(&osv);
onNT = osv.dwPlatformId == VER_PLATFORM_WIN32_NT;
::InitializeCriticalSection(&crPlatformLock);
hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
if (!hDLLImage) {
hDLLImage = ::LoadLibrary(TEXT("Msimg32"));
}
if (hDLLImage) {
AlphaBlendFn = (AlphaBlendSig)::GetProcAddress(hDLLImage, "AlphaBlend");
}
ListBoxX_Register();
}
void Platform_Finalise() {
ListBoxX_Unregister();
::DeleteCriticalSection(&crPlatformLock);
}