Files
2026-05-23 11:01:52 +01:00

1517 lines
44 KiB
C++

#include "alfe/main.h"
#ifndef INCLUDED_USER_H
#define INCLUDED_USER_H
#include "alfe/bitmap.h"
#include "alfe/linked_list.h"
#include "alfe/thread.h"
// TODO: Xlib port
#include <windows.h>
#include <WindowsX.h>
#include <CommCtrl.h>
#include <vector>
#include <functional>
template<class T> class WindowsWindowT;
typedef WindowsWindowT<void> WindowsWindow;
template<class T> class PaintHandleT;
typedef PaintHandleT<void> PaintHandle;
class GDIObject : public ConstHandle
{
public:
GDIObject() { }
GDIObject(HGDIOBJ object) : _object(object)
{
IF_NULL_THROW(object);
ConstHandle::operator=(create<Body>(object));
}
operator HGDIOBJ() const { return _object; }
HGDIOBJ object() const { return _object; }
protected:
class Body : public ConstHandle::Body
{
public:
Body(HGDIOBJ object) : _object(object) { }
~Body() { DeleteObject(_object); }
HGDIOBJ _object;
};
HGDIOBJ _object;
};
class Font : public GDIObject
{
public:
Font() : GDIObject(object()) { }
operator HFONT() const { return static_cast<HFONT>(_object); }
private:
HGDIOBJ object()
{
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS) - sizeof(ncm.iPaddedBorderWidth);
IF_FALSE_THROW(SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
ncm.cbSize, &ncm, 0));
return CreateFontIndirect(&ncm.lfMessageFont);
}
};
template<class T> class WindowsT : Uncopyable
{
public:
WindowsT() : _failed(false) { }
void check()
{
if (!_failed)
return;
_failed = false;
throw _exception;
}
void stashException(Exception exception)
{
_failed = true;
_exception = exception;
}
bool failed() const { return _failed; }
const Font* font() const { return &_font; }
private:
Font _font;
Exception _exception;
bool _failed;
};
typedef WindowsT<void> Windows;
class ContainerWindow;
template<class T> class WindowT;
typedef WindowT<void> Window;
template<class T> class WindowT
: public LinkedListMember<WindowT<T>>
{
public:
WindowT() : _parent(0), _topLeft(Vector(0, 0)) { }
~WindowT() { remove(); }
virtual void create() { layout(); }
virtual void layout() { }
void setParent(ContainerWindow* window) { _parent = window; }
ContainerWindow* parent() const { return _parent; }
// We call these functions to notify derived classes about events
virtual void innerSizeSet(Vector size) { }
virtual void positionSet(Vector topLeft) { }
// Derived classes override these to implement windows with frames
virtual Vector outerSize() const { return _innerSize; }
virtual Vector outerTopLeft() const { return Vector(0, 0); }
virtual void setInnerSize(Vector size)
{
if (size == _innerSize)
return;
_innerSize = size;
innerSizeSet(size);
}
Vector innerSize() const { return _innerSize; }
Vector origin() const { return _topLeft; }
// These all return outer edges/corners in the parent coordinate system,
// useful for laying out.
int left() const { return _topLeft.x + outerTopLeft().x; }
int top() const { return _topLeft.y + outerTopLeft().y; }
int right() const { return _topLeft.x + outerTopLeft().x + outerSize().x; }
int bottom() const
{
return _topLeft.y + outerTopLeft().y + outerSize().y;
}
Vector topLeft() const { return _topLeft + outerTopLeft(); }
Vector topRight() const { return topLeft() + Vector(outerSize().x, 0); }
Vector bottomRight() const { return topLeft() + outerSize(); }
Vector bottomLeft() const { return topLeft() + Vector(0, outerSize().y); }
virtual void setTopLeft(Vector tl)
{
_topLeft = tl - outerTopLeft();
positionSet(_topLeft);
}
void setTopRight(Vector topRight)
{
setTopLeft(topRight - Vector(outerSize().x, 0));
}
void setBottomLeft(Vector bottomLeft)
{
setTopLeft(bottomLeft - Vector(0, outerSize().y));
}
void setBottomRight(Vector bottomRight)
{
setTopLeft(bottomRight - outerSize());
}
virtual void doneResize() { }
virtual void keyboardCharacter(int character) { }
virtual bool keyboardEvent(int key, bool up) { return false; }
//virtual void draw(Bitmap<DWORD> bitmap) { }
// mouseInput() should return true if the mouse should be captured
virtual bool mouseInput(Vector position, int buttons, int wheel)
{
return false;
}
virtual void releaseCapture() { }
void remove()
{
if (_parent != 0) {
_parent->childRemoved(this);
LinkedListMember<WindowT<T>>::remove();
}
}
// The draw() function should do any updates necessary for an animation and
// (for a BitmapWindow) initiate the process of populating the bitmap. It
// should not take a long time though, as it is called on the UI thread. If
// longer is spent in draw() than the time between animation frames,
// WM_PAINT starvation can result.
virtual void draw() { }
private:
ContainerWindow* _parent;
Vector _innerSize;
// _topLeft is the position within the parent window of the origin
// (top-left of inner/client rectangle) of this window.
Vector _topLeft;
};
class ContainerWindow : public Window
{
public:
ContainerWindow() : _focus(0), _capture(0) { }
void create()
{
for (auto& window : _container) {
window.setParent(this);
window.create();
}
Window::create();
}
void remove()
{
// Can't use a range-based for loop here because we're
// removing items and continuing.
auto window = _container.next();
while (window != &_container) {
auto next = window->next();
window->remove();
window = next;
}
Window::remove();
}
void add(Window* window)
{
_container.add(window);
window->setParent(this);
if (_focus == 0)
_focus = window;
}
virtual void childRemoved(Window* child)
{
if (_focus == child) {
_focus = _container.getNext(child);
if (_focus == 0) {
// No more windows after child, try starting from the beginning
// again.
_focus = _container.getNext();
if (_focus == child) {
// It's an only child, so there's nowhere left to put the
// focus.
_focus = 0;
}
}
}
}
//void draw(Bitmap<DWORD> bitmap)
//{
// for (auto& window : _container)
// window.draw(bitmap.subBitmap(window.topLeft(), window.outerSize()));
//}
void keyboardCharacter(int character)
{
if (_focus != 0)
_focus->keyboardCharacter(character);
}
bool keyboardEvent(int key, bool up)
{
if (_focus != 0)
return _focus->keyboardEvent(key, up);
return false;
}
bool mouseInput(Vector position, int buttons, int wheel)
{
// If the mouse is captured, send the input to the capturing window
if (_capture != 0) {
_capture->mouseInput(position - _capture->topLeft(), buttons,
wheel);
if (buttons == 0)
releaseCapture();
return false;
}
// Otherwise, send the input the window under the mouse
Window* window = windowForPosition(position);
if (window != 0) {
bool capture = window->mouseInput(position - window->topLeft(),
buttons, wheel);
if ((buttons & ~_buttons) != 0) {
// A button is newly pressed - put focus on this child window
_focus = window;
}
_buttons = buttons;
if (capture)
_capture = window;
return capture;
}
return false;
}
void releaseCapture() { _capture = 0; }
void repositionChildren()
{
Window* window = _container.getNext();
while (window != 0) {
window->setTopLeft(window->topLeft());
window = _container.getNext(window);
}
}
virtual void enableWindow(bool enabled)
{
for (auto& window : _container)
dynamic_cast<ContainerWindow*>(&window)->enableWindow(enabled);
}
protected:
LinkedList<Window> _container;
Window* _focus;
Window* _capture;
int _buttons;
private:
Window* windowForPosition(Vector p)
{
for (auto& window : _container) {
if ((p - window.topLeft()).inside(window.outerSize()))
return &window;
}
return 0;
}
};
class Menu
{
public:
Menu(WORD resourceId)
{
HMODULE moduleHandle = GetModuleHandle(NULL);
IF_NULL_THROW(moduleHandle);
_menu = LoadMenu(moduleHandle, MAKEINTRESOURCE(resourceId));
IF_NULL_THROW(_menu);
}
~Menu() { DestroyMenu(_menu); }
HMENU hMenu() { return _menu; }
private:
HMENU _menu;
};
class DeviceContext : Uncopyable
{
public:
void setDC(HDC hdc) { _hdc = hdc; }
void NoFailSelectObject(HGDIOBJ hObject) { ::SelectObject(_hdc, hObject); }
HGDIOBJ SelectObject(HGDIOBJ hObject)
{
HGDIOBJ r = ::SelectObject(_hdc, hObject);
IF_NULL_THROW(r);
return r;
}
void SetROP2(int fnDrawMode)
{
IF_ZERO_THROW(::SetROP2(_hdc, fnDrawMode));
}
void Polyline(CONST POINT* lppt, int cPoints)
{
IF_ZERO_THROW(::Polyline(_hdc, lppt, cPoints));
}
operator HDC() const { return _hdc; }
protected:
HDC _hdc;
};
class SelectedObject : public ConstHandle
{
public:
SelectedObject() { }
SelectedObject(DeviceContext* dc, const GDIObject& object)
: ConstHandle(create<Body>(dc, object)) { }
private:
class Body : public ConstHandle::Body
{
public:
Body(DeviceContext* dc, const GDIObject& object) : _dc(dc)
{
_previous = dc->SelectObject(object);
}
~Body()
{
_dc->NoFailSelectObject(_previous);
}
private:
DeviceContext* _dc;
HGDIOBJ _previous;
};
};
class Pen : public GDIObject
{
public:
Pen(int fnPenStyle, int nWidth, COLORREF crColor)
: GDIObject(CreatePen(fnPenStyle, nWidth, crColor)) { }
operator HPEN() { return static_cast<HPEN>(_object); }
};
template<class T> class WindowsWindowT : public ContainerWindow
{
friend class WindowsT<void>;
public:
WindowsWindowT()
: _hWnd(NULL), _resizing(false), _origWndProc(0), _className(0),
_style(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN), _extendedStyle(0),
_menu(NULL), _windowsParent(0), _outerSize(0, 0), _outerTopLeft(0, 0)
{
_dc.setDC(NULL);
ContainerWindow::setInnerSize(Vector(CW_USEDEFAULT, CW_USEDEFAULT));
ContainerWindow::setTopLeft(Vector(CW_USEDEFAULT, CW_USEDEFAULT));
}
void setWindows(Windows* windows) { _windows = windows; }
Vector clientToScreen(Vector client)
{
POINT p;
p.x = client.x;
p.y = client.y;
IF_ZERO_THROW(ClientToScreen(_hWnd, &p));
return Vector(p.x, p.y);
}
Vector mousePosition()
{
POINT point;
IF_ZERO_THROW(GetCursorPos(&point));
return Vector(point.x, point.y) - clientToScreen(Vector(0, 0));
}
virtual HWND hWndParent()
{
if (_windowsParent != 0)
return _windowsParent->hWnd();
return NULL;
}
virtual void create()
{
reset();
Vector s = adjustRect(innerSize());
Vector position = topLeft();
Vector o(0, 0);
Window* p = parent();
while (p != 0) {
auto w = dynamic_cast<WindowsWindow*>(p);
if (w != 0) {
_windowsParent = w;
_windows = w->_windows;
break;
}
else
o += p->origin();
p = p->parent();
}
position += o;
if (topLeft().x == CW_USEDEFAULT)
position.x = CW_USEDEFAULT;
if (topLeft().y == CW_USEDEFAULT)
position.y = CW_USEDEFAULT;
if (innerSize().x == CW_USEDEFAULT)
s.x = CW_USEDEFAULT;
if (innerSize().y == CW_USEDEFAULT)
s.y = CW_USEDEFAULT;
NullTerminatedWideString caption(_text);
HMENU menu = _menu;
if ((_style & WS_CHILD) != 0)
menu = reinterpret_cast<HMENU>(this);
LPCWSTR className = _className;
// We don't really need to register our own window class - we can use
// one of the built-in classes. Some demoscene sizecoded demos use
// "Edit" for example (but that sets the cursor to the I-beam). And
// WC_STATIC doesn't work with SetLayeredWindowAttributes.
if (className == 0)
className = L"MDIClient";
HINSTANCE hInstance = GetModuleHandle(NULL);
IF_ZERO_THROW(hInstance);
_hWnd = CreateWindowEx(
_extendedStyle,
className,
caption,
_style,
position.x,
position.y,
s.x,
s.y,
hWndParent(),
menu,
hInstance,
this);
IF_NULL_THROW(_hWnd);
SetLastError(0);
IF_ZERO_CHECK_THROW_LAST_ERROR(SetWindowLongPtr(_hWnd, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(this)));
if (_className == 0)
_origWndProc = DefWindowProc;
else {
SetLastError(0);
LONG_PTR r = GetWindowLongPtr(_hWnd, GWLP_WNDPROC);
IF_ZERO_CHECK_THROW_LAST_ERROR(r);
_origWndProc = reinterpret_cast<WNDPROC>(r);
}
SetLastError(0);
IF_ZERO_CHECK_THROW_LAST_ERROR(SetWindowLongPtr(_hWnd, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(wndProc)));
HDC hdc = GetDC(_hWnd);
IF_NULL_THROW(hdc);
_dc.setDC(hdc);
RECT clientRect;
IF_ZERO_THROW(GetClientRect(_hWnd, &clientRect));
RECT windowRect;
IF_ZERO_THROW(GetWindowRect(_hWnd, &windowRect));
Vector origin = clientToScreen(Vector(0, 0));
Vector stl(windowRect.left, windowRect.top);
_outerTopLeft = stl - origin;
_outerSize = Vector(windowRect.right, windowRect.bottom) - stl;
Vector pOrigin(0, 0);
if (_windowsParent != 0)
pOrigin = _windowsParent->clientToScreen(Vector(0, 0));
ContainerWindow::setInnerSize(Vector(
clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top));
ContainerWindow::setTopLeft(origin - (pOrigin + o));
SendMessage(_hWnd, WM_SETFONT, reinterpret_cast<WPARAM>(font()),
static_cast<LPARAM>(FALSE));
SelectObject(_dc, font());
ContainerWindow::create();
}
~WindowsWindowT() { reset(); }
void show(int nShowCmd) { ShowWindow(_hWnd, nShowCmd); }
void setText(String text)
{
_text = text;
if (_hWnd != NULL) {
NullTerminatedWideString w(text);
IF_ZERO_THROW(SetWindowText(_hWnd, w));
}
}
String getText()
{
int l = GetWindowTextLength(_hWnd) + 1;
Array<WCHAR> buf(l);
IF_ZERO_CHECK_THROW_LAST_ERROR(GetWindowText(_hWnd, &buf[0], l));
return String(&buf[0]);
}
void setInnerSize(Vector size)
{
_outerSize = adjustRect(size);
if (!_resizing) {
if (_hWnd != NULL) {
IF_ZERO_THROW(SetWindowPos(
_hWnd, // hWnd
NULL, // hWndInsertAfter
0, // X
0, // Y
_outerSize.x, // cx
_outerSize.y, // cy
SWP_NOZORDER | SWP_NOMOVE |
SWP_NOACTIVATE | SWP_NOREPOSITION |
SWP_NOREDRAW | SWP_ASYNCWINDOWPOS)); // uFlags
// ContainerWindow::setInnerSize() will be called via WM_SIZE,
// but we want to make sure our size is set correctly now so it
// can be used for layout before the message loop next runs.
}
}
ContainerWindow::setInnerSize(size);
}
void setTopLeft(Vector position)
{
if (_hWnd != NULL) {
Vector tl = position + _outerTopLeft;
Window* p = parent();
while (p != 0 && p != static_cast<Window*>(_windowsParent)) {
tl += p->origin();
p = p->parent();
}
IF_ZERO_THROW(SetWindowPos(
_hWnd, // hWnd
NULL, // hWndInsertAfter
tl.x, // X
tl.y, // Y
0, // cx
0, // cy
SWP_NOZORDER | SWP_NOSIZE |
SWP_NOACTIVATE | SWP_NOREPOSITION |
SWP_NOREDRAW | SWP_ASYNCWINDOWPOS)); // uFlags
// ContainerWindow::setTopLeft() will be called via WM_MOVE, but we
// want to make sure our position is set correctly now so it can be
// used for layout before the message loop next runs.
}
ContainerWindow::setTopLeft(position);
}
HWND hWnd() const { return _hWnd; }
HDC getDC() { return _dc; }
HFONT font() { return *_windows->font(); }
void postMessage(UINT msg, WPARAM wParam = 0, LPARAM lParam = 0)
{
PostMessage(_hWnd, msg, wParam, lParam);
}
void updateWindow()
{
if (_hWnd != NULL)
IF_ZERO_THROW(UpdateWindow(_hWnd));
}
void redrawWindow(Vector topLeft, Vector size, UINT flags)
{
if (_hWnd == NULL)
return;
RECT rect;
rect.left = topLeft.x;
rect.top = topLeft.y;
rect.right = topLeft.x + size.x;
rect.bottom = topLeft.y + size.y;
IF_ZERO_THROW(RedrawWindow(_hWnd, &rect, NULL, flags));
}
void redrawWindow(UINT flags)
{
redrawWindow(Vector(0, 0), outerSize(), flags);
}
void enableWindow(bool enabled)
{
EnableWindow(_hWnd, enabled ? TRUE : FALSE);
ContainerWindow::enableWindow(enabled);
}
void invalidate() { redrawWindow(RDW_INVALIDATE | RDW_FRAME); }
Vector outerSize() const { return _outerSize; }
Vector outerTopLeft() const { return _outerTopLeft; }
Vector screenToClient(Vector screen)
{
POINT p;
p.x = screen.x;
p.y = screen.y;
IF_ZERO_THROW(ScreenToClient(_hWnd, &p));
return Vector(p.x, p.y);
}
private:
Vector adjustRect(Vector size)
{
RECT rect;
rect.left = 0;
rect.right = size.x;
rect.top = 0;
rect.bottom = size.y;
AdjustWindowRectEx(&rect, _style, FALSE, _extendedStyle);
return Vector(rect.right - rect.left, rect.bottom - rect.top);
}
void destroy()
{
if (getDC() != NULL) {
ReleaseDC(_hWnd, _dc);
_dc.setDC(NULL);
}
_hWnd = NULL;
}
void reset()
{
if (getDC() != NULL) {
ReleaseDC(_hWnd, _dc);
_dc.setDC(NULL);
}
if (_hWnd != NULL) {
DestroyWindow(_hWnd);
_hWnd = NULL;
}
}
void setHwnd(HWND hWnd) { _hWnd = hWnd; }
protected:
virtual bool command(WORD code) { return false; }
virtual bool hScroll(WORD code, WORD position) { return false; }
virtual LRESULT handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int w = static_cast<int>(wParam);
switch (uMsg) {
case WM_SIZING:
{
_resizing = true;
RECT* r = reinterpret_cast<RECT*>(lParam);
Vector requestedSize(r->right - r->left,
r->bottom - r->top);
Vector adjust = adjustRect(innerSize()) - innerSize();
ContainerWindow::setInnerSize(requestedSize - adjust);
Vector adjustedSize = adjustRect(innerSize());
if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_LEFT ||
wParam == WMSZ_BOTTOMLEFT)
r->left = r->right - adjustedSize.x;
if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOP ||
wParam == WMSZ_TOPRIGHT)
r->top = r->bottom - adjustedSize.y;
if (wParam == WMSZ_TOPRIGHT || wParam == WMSZ_RIGHT ||
wParam == WMSZ_BOTTOMRIGHT)
r->right = r->left + adjustedSize.x;
if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOM ||
wParam == WMSZ_BOTTOMRIGHT)
r->bottom = r->top + adjustedSize.y;
}
return TRUE;
case WM_MOVE:
ContainerWindow::setTopLeft(
vectorFromLParam(lParam) + _outerTopLeft);
break;
case WM_EXITSIZEMOVE:
if (_resizing)
doneResize();
_resizing = false;
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
case WM_MOUSEWHEEL:
{
int buttons;
int wheel;
Vector position = vectorFromLParam(lParam);
if (uMsg == WM_MOUSEWHEEL) {
buttons = GET_KEYSTATE_WPARAM(wParam);
wheel = GET_WHEEL_DELTA_WPARAM(wParam);
position = screenToClient(position);
}
else {
buttons = w;
wheel = 0;
}
if (mouseInput(position, buttons, wheel))
SetCapture(_hWnd);
}
break;
case WM_CHAR:
keyboardCharacter(w);
break;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if (keyboardEvent(w, false))
return 0;
break;
case WM_KEYUP:
case WM_SYSKEYUP:
if (keyboardEvent(w, true))
return 0;
break;
case WM_KILLFOCUS:
releaseCapture();
break;
case WM_GETDLGCODE:
return DLGC_WANTMESSAGE;
case WM_COMMAND:
if (lParam != 0) {
WindowsWindow* window = getContext(lParam);
if (window != 0 && window->command(HIWORD(wParam)))
return 0;
}
else {
if (LOWORD(wParam) == IDCANCEL)
keyboardCharacter(VK_ESCAPE);
}
break;
case WM_HSCROLL:
if (lParam != 0) {
if (getContext(lParam)->hScroll(LOWORD(wParam),
HIWORD(wParam)))
return 0;
}
break;
case WM_NCDESTROY:
destroy();
remove();
break;
}
return originalHandleMessage(uMsg, wParam, lParam);
}
void erase(bool invalidateChildren = false)
{
PaintHandle paintHandle(this);
if (paintHandle.zeroArea())
return;
if (!paintHandle.erase())
return;
HBRUSH hBrush = GetSysColorBrush(COLOR_BTNFACE);
SelectedObject bo(&paintHandle, hBrush);
Pen pen(PS_SOLID, 1, GetSysColor(COLOR_BTNFACE));
SelectedObject po(&paintHandle, pen);
Vector tl = paintHandle.topLeft();
Vector br = paintHandle.bottomRight();
Rectangle(paintHandle, tl.x, tl.y, br.x, br.y);
if (invalidateChildren) {
redrawWindow(tl, br - tl,
RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
}
}
LRESULT originalHandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc(_origWndProc, _hWnd, uMsg, wParam, lParam);
}
void releaseCapture()
{
ReleaseCapture();
ContainerWindow::releaseCapture();
}
static Vector vectorFromLParam(LPARAM lParam)
{
return Vector(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
}
void setClassName(LPCWSTR className) { _className = className; }
void setStyle(DWORD style) { _style = style; }
void setExtendedStyle(DWORD extendedStyle)
{
_extendedStyle = extendedStyle;
}
HWND _hWnd;
DeviceContext _dc;
String _text;
DWORD _style;
Vector _outerSize;
Vector _outerTopLeft;
private:
bool _resizing;
Windows* _windows;
WindowsWindow* _windowsParent;
DWORD _extendedStyle;
LPCWSTR _className;
HMENU _menu;
WNDPROC _origWndProc;
static WindowsWindow* getContext(LPARAM lParam)
{
return getContext(reinterpret_cast<HWND>(lParam));
}
static WindowsWindow* getContext(HWND hWnd)
{
SetLastError(0);
LONG_PTR r = GetWindowLongPtr(hWnd, GWLP_USERDATA);
IF_ZERO_CHECK_THROW_LAST_ERROR(r);
return reinterpret_cast<WindowsWindow *>(r);
}
LRESULT windowProcedure(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Avoid reentering and causing a cascade of alerts if an assert fails.
if (!alerting && !_windows->failed()) {
BEGIN_CHECKED {
return handleMessage(uMsg, wParam, lParam);
} END_CHECKED(Exception& e) {
PostQuitMessage(0);
_windows->stashException(e);
}
}
return originalHandleMessage(uMsg, wParam, lParam);
}
static LRESULT CALLBACK wndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam)
{
return getContext(hWnd)->handleMessage(uMsg, wParam, lParam);
}
};
template<class T> class PaintHandleT : public DeviceContext
{
public:
PaintHandleT(const WindowsWindow* window) : _window(window)
{
IF_NULL_THROW(BeginPaint(_window->hWnd(), &_ps));
_hdc = _ps.hdc;
}
~PaintHandleT() { EndPaint(_window->hWnd(), &_ps); }
Vector topLeft() const
{
return Vector(_ps.rcPaint.left, _ps.rcPaint.top);
}
Vector bottomRight() const
{
return Vector(_ps.rcPaint.right, _ps.rcPaint.bottom);
}
bool zeroArea() const { return (topLeft() - bottomRight()).zeroArea(); }
bool erase() const { return _ps.fErase != 0; }
private:
const WindowsWindow* _window;
PAINTSTRUCT _ps;
};
class CompoundWindow : public WindowsWindow
{
public:
LRESULT handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_PAINT) {
erase();
return 0;
}
if (uMsg == WM_ERASEBKGND)
return 0;
if (uMsg == WM_SIZE) {
Vector s = vectorFromLParam(lParam);
if (s != innerSize()) {
ContainerWindow::setInnerSize(s);
redrawWindow(RDW_INVALIDATE | RDW_ALLCHILDREN);
}
return 0;
}
return WindowsWindow::handleMessage(uMsg, wParam, lParam);
}
};
class RootWindow : public CompoundWindow
{
public:
void childRemoved(Window* child)
{
if (_container.getNext(child) == 0 && _container.getNext() == child) {
// Once there are no more child windows left, the thread must
// end.
PostQuitMessage(0);
}
CompoundWindow::childRemoved(child);
}
};
class AnimatedWindow : public WindowsWindow
{
public:
AnimatedWindow()
: _period(50), _timerExpired(true), _delta(0), _lastTickCount(0) { }
void setDrawWindow(Window* window)
{
_drawWindow = window;
}
void setRate(float rate) { _period = 1000.0f/rate; }
void stop() { KillTimer(_hWnd, _timer); _stopped = true; }
void start()
{
stop();
_stopped = false;
_timerExpired = true;
_drawWindow->draw();
}
// Invalidation window needs to call this on paint or invalidate to restart
// timer.
void restart()
{
if (_timerExpired && !_stopped) {
DWORD tickCount = GetTickCount();
_delta += static_cast<float>(tickCount - _lastTickCount);
_lastTickCount = tickCount;
int ms = static_cast<int>(-_delta);
_delta -= _period;
if (ms < -2.0f*_period) {
// Rather than trying to catch up by generating lots of
// events, we'll reset the clock if we're running too
// far behind.
_delta = 0;
ms = 0;
}
if (ms > 2.0f*max(
_period, static_cast<float>(USER_TIMER_MINIMUM))) {
// Chances are we're starting up and the tick count
// wrapped.
_delta = 0;
ms = static_cast<int>(_period) + 1;
}
if (ms < 0)
ms = 0;
if (ms >= USER_TIMER_MINIMUM) {
_timer = SetTimer(_hWnd, 1, static_cast<UINT>(ms), NULL);
IF_ZERO_THROW(_timer);
}
else
postMessage(WM_TIMER, 1, 0);
_timerExpired = false;
}
}
protected:
virtual LRESULT handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_TIMER:
if (wParam == 1)
start();
break;
}
return WindowsWindow::handleMessage(uMsg, wParam, lParam);
}
private:
UINT_PTR _timer;
bool _timerExpired;
float _period;
float _delta;
DWORD _lastTickCount;
bool _stopped;
Window* _drawWindow;
};
class Button : public WindowsWindow
{
public:
Button() : _checked(false)
{
setClassName(WC_BUTTON);
setStyle(BS_PUSHBUTTON | BS_TEXT | WS_CHILD | WS_VISIBLE | WS_TABSTOP);
}
virtual void clicked(bool value) { _clicked(value); }
void setClicked(std::function<void(bool)> clicked) { _clicked = clicked; }
void create()
{
WindowsWindow::create();
SIZE s;
NullTerminatedWideString w(_text);
IF_ZERO_THROW(GetTextExtentPoint32(_dc, w, _text.length(), &s));
Vector size(s.cx + 20, s.cy + 10);
setInnerSize(size);
setCheckState(_checked);
}
bool checked() { return _checked; }
void uncheck()
{
if (_hWnd != NULL)
SendMessage(_hWnd, BM_SETCHECK, static_cast<WPARAM>(FALSE), 0);
_checked = false;
}
void check()
{
if (_hWnd != NULL)
SendMessage(_hWnd, BM_SETCHECK, static_cast<WPARAM>(TRUE), 0);
_checked = true;
}
void setCheckState(bool checked)
{
if (checked)
check();
else
uncheck();
}
bool command(WORD code)
{
if (code == BN_CLICKED) {
clicked(
(Button_GetState(_hWnd) & (BST_CHECKED | BST_PUSHED)) != 0);
return true;
}
return false;
}
private:
bool _checked;
std::function<void(bool)> _clicked;
};
class ToggleButton : public Button
{
public:
void create()
{
setStyle(BS_AUTOCHECKBOX | BS_PUSHLIKE | BS_PUSHBUTTON | BS_TEXT |
WS_CHILD | WS_VISIBLE | WS_TABSTOP);
Button::create();
}
};
class CheckBox : public Button
{
public:
void create()
{
setStyle(BS_AUTOCHECKBOX | BS_TEXT | WS_CHILD | WS_VISIBLE |
WS_TABSTOP);
Button::create();
}
};
class GroupBox : public Button
{
public:
GroupBox()
{
setStyle(BS_GROUPBOX | BS_TEXT | WS_CHILD | WS_VISIBLE);
}
// The code in Button::create() isn't appropriate for GroupBox
void create() { WindowsWindow::create(); }
LRESULT handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_PAINT)
erase(true);
if (uMsg == WM_ERASEBKGND)
return 0;
return WindowsWindow::handleMessage(uMsg, wParam, lParam);
}
};
class TextWindow : public WindowsWindow
{
public:
TextWindow()
{
setClassName(WC_STATIC);
setStyle(WS_CHILD | WS_VISIBLE | /*SS_CENTER |*/ SS_CENTERIMAGE);
}
void layout()
{
if (_hWnd != NULL) {
SIZE s;
NullTerminatedWideString w(_text);
IF_ZERO_THROW(GetTextExtentPoint32(_dc, w, _text.length(), &s));
setInnerSize(Vector(s.cx, s.cy));
}
}
};
class EditWindow : public WindowsWindow
{
public:
EditWindow()
{
setClassName(WC_EDIT);
setStyle(WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL);
}
virtual void changed() { }
void layout()
{
TEXTMETRIC metric;
GetTextMetrics(getDC(), &metric);
setInnerSize(Vector(44, metric.tmHeight));
}
bool command(WORD code)
{
if (code == EN_CHANGE) {
changed();
return true;
}
return false;
}
};
class Slider : public WindowsWindow
{
public:
Slider() : _min(0), _pos(0), _max(1)
{
setClassName(TRACKBAR_CLASS);
setStyle(WS_CHILD | WS_VISIBLE | WS_TABSTOP);
}
virtual void valueSet(double value) { }
void create()
{
WindowsWindow::create();
SendMessage(_hWnd, TBM_SETRANGE, static_cast<WPARAM>(TRUE),
static_cast<LPARAM>(MAKELONG(0, 16384)));
setValue(_pos);
}
void setValue(double pos)
{
_pos = pos;
if (_hWnd != NULL) {
int iPos =
static_cast<int>(((pos - _min) * 16384)/(_max - _min) + 0.5);
SendMessage(_hWnd, TBM_SETPOS, static_cast<WPARAM>(TRUE),
static_cast<LPARAM>(iPos));
}
valueSet(pos);
}
void setRange(double min, double max) { _min = min; _max = max; }
void updateValue(int pos)
{
_pos = pos*(_max - _min) / 16384 + _min;
valueSet(_pos);
}
double getValue() const { return _pos; }
bool hScroll(WORD code, WORD position)
{
updateValue(static_cast<int>(SendMessage(_hWnd, TBM_GETPOS, 0, 0)));
return true;
}
private:
double _min;
double _max;
double _pos;
};
class ProgressBar : public WindowsWindow
{
public:
ProgressBar() : _pos(0)
{
setClassName(PROGRESS_CLASS);
setStyle(WS_CHILD | WS_VISIBLE | PBS_SMOOTH);
}
virtual void valueSet(double value) { }
void create()
{
WindowsWindow::create();
SendMessage(_hWnd, PBM_SETRANGE, static_cast<WPARAM>(TRUE),
static_cast<LPARAM>(MAKELONG(0, 16384)));
setValue(_pos);
}
void setValue(float pos)
{
_pos = pos;
if (_hWnd != NULL) {
int iPos = static_cast<int>(pos*16384 + 0.5);
SendMessage(_hWnd, PBM_SETPOS, static_cast<WPARAM>(iPos),
static_cast<LPARAM>(0));
}
}
private:
float _pos;
};
template<class T> class NumericSliderWindow
{
public:
void setText(String text) { _caption.setText(text); }
void setHost(T* host)
{
_host = host;
host->add(&_slider);
host->add(&_caption);
host->add(&_text);
_slider.setHost(this);
}
void setPositionAndSize(Vector position, Vector size)
{
_slider.setInnerSize(size);
_slider.setTopLeft(position);
_caption.setTopLeft(_slider.bottomLeft() + Vector(0, 15));
_text.setTopLeft(_caption.topRight());
}
Vector bottomLeft() { return _caption.bottomLeft(); }
int right() const { return _slider.right(); }
void setRange(double low, double high)
{
_slider.setRange(positionFromValue(low), positionFromValue(high));
}
void setValue(double value) { _slider.setValue(positionFromValue(value)); }
double getValue() { return valueFromPosition(_slider.getValue()); }
protected:
virtual void create() { }
virtual void valueSet(double value) { }
virtual double positionFromValue(double value) { return value; }
virtual double valueFromPosition(double position) { return position; }
T* _host;
private:
void valueSet1(double value)
{
double v = valueFromPosition(value);
_text.setText(format("%f", v));
_text.layout();
valueSet(v);
}
class NumericSlider : public Slider
{
public:
void setHost(NumericSliderWindow* host) { _host = host; }
void valueSet(double value) { _host->valueSet1(value); }
void create() { _host->create(); Slider::create(); }
private:
NumericSliderWindow* _host;
};
NumericSlider _slider;
TextWindow _caption;
TextWindow _text;
friend class NumericSlider;
};
class DropDownList : public WindowsWindow
{
public:
DropDownList() : _value(0)
{
setClassName(WC_COMBOBOX);
setStyle(WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL |
WS_TABSTOP);
}
virtual void changed(int value) { _changed(value); }
void setChanged(std::function<void(int)> changed) { _changed = changed; }
void create()
{
WindowsWindow::create();
for (auto s : _entries) {
NullTerminatedWideString ns(s);
int r = static_cast<int>(SendMessage(_hWnd, CB_ADDSTRING, 0,
reinterpret_cast<LPARAM>(ns.operator WCHAR *())));
IF_FALSE_THROW(r != CB_ERR && r != CB_ERRSPACE);
}
set(_value);
}
void set(int value)
{
if (_hWnd != NULL)
IF_FALSE_THROW(ComboBox_SetCurSel(_hWnd, value) != CB_ERR);
_value = value;
}
void add(String s) { _entries.add(s); }
void layout()
{
int width = 0;
for (auto s : _entries) {
NullTerminatedWideString ns(s);
SIZE size;
IF_ZERO_THROW(GetTextExtentPoint32(_dc, ns, s.length(), &size));
width = max(width, static_cast<int>(size.cx));
}
COMBOBOXINFO info = { sizeof(COMBOBOXINFO) };
IF_ZERO_THROW(GetComboBoxInfo(_hWnd, &info));
setInnerSize(Vector(10 + width +
info.rcButton.right - info.rcButton.left, 200));
RECT rect;
IF_ZERO_THROW(GetClientRect(info.hwndCombo, &rect));
_outerSize = Vector(rect.right - rect.left, rect.bottom - rect.top);
}
bool command(WORD code)
{
if (code == CBN_SELCHANGE) {
int v = ComboBox_GetCurSel(_hWnd);
IF_FALSE_THROW(v != CB_ERR);
changed(v);
return true;
}
return false;
}
private:
List<String> _entries;
int _value;
std::function<void(int)> _changed;
};
class CaptionedDropDownList : public ContainerWindow
{
public:
CaptionedDropDownList()
{
ContainerWindow::add(&_caption);
ContainerWindow::add(&_list);
}
void setText(String text) { _caption.setText(text); }
void add(String s) { _list.add(s); }
void set(int value) { _list.set(value); }
void layout()
{
int captionHeight = _caption.outerSize().y;
int listHeight = _list.outerSize().y;
int captionY = 0;
int listY = 0;
if (captionHeight > listHeight)
listY = (captionHeight - listHeight)/2;
else
captionY = (listHeight - captionHeight)/2;
_caption.setTopLeft(Vector(0, captionY));
_list.setTopLeft(Vector(_caption.right() + 10, listY));
setInnerSize(
Vector(_list.right(), max(_caption.bottom(), _list.bottom())));
}
void setChanged(std::function<void(int)> changed)
{
_list.setChanged(changed);
}
void setTopLeft(Vector topLeft)
{
ContainerWindow::setTopLeft(topLeft);
repositionChildren();
}
private:
TextWindow _caption;
DropDownList _list;
};
class WindowDeviceContext : public DeviceContext
{
public:
WindowDeviceContext(HWND hWnd) : _hWnd(hWnd)
{
_hdc = GetDC(_hWnd);
IF_NULL_THROW(_hdc);
}
~WindowDeviceContext() { ReleaseDC(_hWnd, _hdc); }
private:
HWND _hWnd;
};
class OwnedDeviceContext : public DeviceContext
{
public:
OwnedDeviceContext(HDC hdc)
{
_hdc = hdc;
IF_NULL_THROW(_hdc);
}
~OwnedDeviceContext() { DeleteDC(_hdc); }
};
class BitmapWindow : public WindowsWindow
{
public:
BitmapWindow()
{
setClassName(WC_STATIC);
setStyle(WS_CHILD | WS_VISIBLE | SS_OWNERDRAW);
}
void create()
{
ZeroMemory(&_bmi, sizeof(BITMAPINFO));
_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
_bmi.bmiHeader.biPlanes = 1;
_bmi.bmiHeader.biBitCount = 32;
_bmi.bmiHeader.biCompression = BI_RGB;
_bmi.bmiHeader.biSizeImage = 0;
_bmi.bmiHeader.biXPelsPerMeter = 0;
_bmi.bmiHeader.biYPelsPerMeter = 0;
_bmi.bmiHeader.biClrUsed = 0;
_bmi.bmiHeader.biClrImportant = 0;
draw();
WindowsWindow::create();
}
// Override this if you just want a simple bitmap that is updated from the
// UI thread.
virtual void draw2() { }
// Override this to have more control, e.g. for a rendering from a
// different thread.
void draw()
{
_bitmap.ensure(innerSize());
draw2();
invalidate();
}
void innerSizeSet(Vector size)
{
if (!size.zeroArea())
draw();
}
virtual LRESULT handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_PAINT:
{
PaintHandle paintHandle(this);
if (paintHandle.zeroArea())
return 0;
if (!_bitmap.valid())
return 0;
Vector ptl = paintHandle.topLeft();
Vector pbr = paintHandle.bottomRight();
Vector br = _bitmap.size();
pbr = Vector(min(pbr.x, br.x), min(pbr.y, br.y));
Vector ps = pbr - ptl;
if (ps.x <= 0 || ps.y <= 0)
return 0;
Vector s = _bitmap.size();
_bmi.bmiHeader.biWidth =
_bitmap.stride() / sizeof(DWORD);
_bmi.bmiHeader.biHeight = -s.y;
paint();
IF_ZERO_THROW(SetDIBitsToDevice(
paintHandle,
ptl.x,
ptl.y,
ps.x,
ps.y,
ptl.x,
s.y - pbr.y,
0,
s.y,
_bitmap.data(),
&_bmi,
DIB_RGB_COLORS));
return 0;
}
case WM_USER:
{
Lock lock(&_mutex);
_lastBitmap = _bitmap;
_bitmap = _nextBitmap;
return 0;
}
}
return WindowsWindow::handleMessage(uMsg, wParam, lParam);
}
// Override this to restart animation only when painting actually happens.
virtual void paint() { };
Bitmap<DWORD> setNextBitmap(Bitmap<DWORD> nextBitmap)
{
Lock lock(&_mutex);
_nextBitmap = nextBitmap;
postMessage(WM_USER);
invalidate();
return _lastBitmap;
}
int stride() const { return _bitmap.stride(); }
const Byte* data() const { return _bitmap.data(); }
Byte* data() { return _bitmap.data(); }
Bitmap<DWORD> bitmap() { return _bitmap; }
protected:
Bitmap<DWORD> _bitmap;
private:
Mutex _mutex;
Bitmap<DWORD> _nextBitmap;
Bitmap<DWORD> _lastBitmap;
BITMAPINFO _bmi;
};
#endif // INCLUDED_USER_H