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
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); |
|
}
|
|
|