mirror of
https://github.com/quantum5/MusicKeyboard.git
synced 2025-04-24 13:11:58 -04:00
511 lines
16 KiB
C++
511 lines
16 KiB
C++
#include <PianoControl.hpp>
|
|
|
|
#include <windowsx.h>
|
|
#include <stdio.h>
|
|
|
|
BOOL PianoControl::WinRegisterClass(WNDCLASS *pwc)
|
|
{
|
|
return __super::WinRegisterClass(pwc);
|
|
}
|
|
|
|
LRESULT PianoControl::OnCreate()
|
|
{
|
|
NONCLIENTMETRICS ncmMetrics = { sizeof(NONCLIENTMETRICS) };
|
|
RECT client;
|
|
|
|
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncmMetrics, 0);
|
|
GetClientRect(m_hwnd, &client);
|
|
|
|
hFont = CreateFontIndirect(&ncmMetrics.lfMessageFont);
|
|
hwParent = GetParent(m_hwnd);
|
|
|
|
hMemDC = NULL;
|
|
hMemBitmap = NULL;
|
|
bmx = bmy = 0;
|
|
|
|
blackStatus = whiteStatus = NULL;
|
|
blackText = whiteText = NULL;
|
|
|
|
SetOctaves(2);
|
|
return 0;
|
|
}
|
|
|
|
LRESULT PianoControl::OnDestroy()
|
|
{
|
|
if (hMemDC)
|
|
DeleteDC(hMemDC);
|
|
return 0;
|
|
}
|
|
|
|
void PianoControl::SetOctaves(int octaves)
|
|
{
|
|
bool *newBlackStatus, *newWhiteStatus;
|
|
LPCWSTR *newBlackText, *newWhiteText;
|
|
|
|
#define RENEW(type, newname, store) {\
|
|
newname = new type[7 * octaves];\
|
|
if (store) {\
|
|
memcpy(newname, store, min(this->octaves * 7, 7 * octaves) * sizeof(type));\
|
|
delete [] store;\
|
|
} else \
|
|
memset(newname, 0, 7 * octaves * sizeof(type));\
|
|
store = newname;\
|
|
}
|
|
RENEW(bool, newBlackStatus, blackStatus);
|
|
RENEW(bool, newWhiteStatus, whiteStatus);
|
|
RENEW(LPCWSTR, newBlackText, blackText);
|
|
RENEW(LPCWSTR, newWhiteText, whiteText);
|
|
|
|
this->octaves = octaves;
|
|
}
|
|
|
|
void PianoControl::UpdateKey(int key, bool black)
|
|
{
|
|
RECT client;
|
|
int width, height;
|
|
int wwidth, bwidth, bheight, hbwidth;
|
|
|
|
GetClientRect(m_hwnd, &client);
|
|
width = client.right - client.left;
|
|
height = client.bottom - client.top;
|
|
wwidth = width / 7 / octaves; // Displaying 14 buttons.
|
|
bwidth = width / 12 / octaves; // smaller
|
|
bheight = height / 2;
|
|
hbwidth = bwidth / 2;
|
|
|
|
if (black) {
|
|
client.left += (key * wwidth) - hbwidth + 2;
|
|
client.right = client.left + bwidth - 5;
|
|
client.bottom = client.top + bheight;
|
|
InvalidateRect(m_hwnd, &client, FALSE);
|
|
} else {
|
|
client.left += key * wwidth;
|
|
client.right = client.left + wwidth;
|
|
client.bottom = client.top + height;
|
|
InvalidateRect(m_hwnd, &client, FALSE);
|
|
}
|
|
}
|
|
|
|
void PianoControl::SetKeyStatus(int key, bool down)
|
|
{
|
|
bool black;
|
|
int id = keyIDToInternal(key, black);
|
|
|
|
(black ? blackStatus : whiteStatus)[id] = down;
|
|
UpdateKey(id, black);
|
|
}
|
|
|
|
bool PianoControl::GetKeyStatus(int key)
|
|
{
|
|
bool black;
|
|
int id = keyIDToInternal(key, black);
|
|
|
|
return (black ? blackStatus : whiteStatus)[id];
|
|
}
|
|
|
|
void PianoControl::SetKeyText(int key, LPCWSTR text)
|
|
{
|
|
bool black;
|
|
int id = keyIDToInternal(key, black);
|
|
|
|
(black ? blackText : whiteText)[id] = text;
|
|
UpdateKey(id, black);
|
|
}
|
|
|
|
LPCWSTR PianoControl::GetKeyText(int key)
|
|
{
|
|
bool black;
|
|
int id = keyIDToInternal(key, black);
|
|
|
|
return (black ? blackText : whiteText)[id];
|
|
}
|
|
|
|
int PianoControl::keyIDToInternal(int id, bool &black) {
|
|
switch (id % 12) {
|
|
case 0:
|
|
case 2:
|
|
case 4:
|
|
case 7:
|
|
case 9:
|
|
black = true;
|
|
break;
|
|
default:
|
|
black = false;
|
|
}
|
|
|
|
int ret = 0;
|
|
switch (id % 12) {
|
|
case 0:
|
|
case 1:
|
|
ret = 0;
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
ret = 1;
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
ret = 2;
|
|
break;
|
|
case 6:
|
|
ret = 3;
|
|
break;
|
|
case 7:
|
|
case 8:
|
|
ret = 4;
|
|
break;
|
|
case 9:
|
|
case 10:
|
|
ret = 5;
|
|
break;
|
|
case 11:
|
|
ret = 6;
|
|
break;
|
|
}
|
|
|
|
return id / 12 * 7 + ret;
|
|
}
|
|
|
|
bool PianoControl::haveBlackToLeft(int i) {
|
|
switch (i % 7) {
|
|
case 0: // G
|
|
case 1: // A
|
|
case 2: // B
|
|
return true;
|
|
case 3: // C
|
|
return false;
|
|
case 4: // D
|
|
case 5: // E
|
|
return true;
|
|
case 6: // F
|
|
return false;
|
|
}
|
|
return false; // not reached
|
|
}
|
|
|
|
bool PianoControl::haveBlackToRight(int i) {
|
|
switch (i % 7) {
|
|
case 0: // G
|
|
case 1: // A
|
|
return true;
|
|
case 2: // B
|
|
return false;
|
|
case 3: // C
|
|
case 4: // D
|
|
return true;
|
|
case 5: // E
|
|
return false;
|
|
case 6: // F
|
|
return true;
|
|
}
|
|
return false; // not reached
|
|
}
|
|
|
|
void PianoControl::PaintContent(PAINTSTRUCT *pps)
|
|
{
|
|
RECT client, rect;
|
|
int width, height;
|
|
int wwidth, bwidth, bheight, hbwidth;
|
|
HDC hdc = pps->hdc;
|
|
HBRUSH hbFace = GetSysColorBrush(COLOR_3DFACE),
|
|
hbDC = GetStockBrush(DC_BRUSH),
|
|
hbOriginal;
|
|
HPEN hPenOriginal, hPenDC = GetStockPen(DC_PEN);
|
|
HFONT hFontOriginal = SelectFont(hdc, hFont), hFontNew;
|
|
LPWSTR szBuffer = NULL;
|
|
int bufsize = 0;
|
|
COLORREF textColourOriginal = GetTextColor(hdc),
|
|
backgroundOriginal = SetBkMode(hdc, TRANSPARENT);
|
|
LOGFONT lf;
|
|
GetClientRect(m_hwnd, &client);
|
|
width = client.right - client.left;
|
|
height = client.bottom - client.top;
|
|
wwidth = width / 7 / octaves; // Displaying 14 buttons.
|
|
bwidth = width / 12 / octaves; // smaller
|
|
bheight = height / 2;
|
|
bheight = height / 2;
|
|
hbwidth = bwidth / 2;
|
|
|
|
hbOriginal = SelectBrush(hdc, hBackground);
|
|
hPenOriginal = SelectPen(hdc, hPenDC);
|
|
|
|
GetObject(hFont, sizeof(LOGFONT), &lf);
|
|
lf.lfWidth = 0;
|
|
lf.lfHeight = min(bwidth, bheight / 4);
|
|
hFontNew = CreateFontIndirect(&lf);
|
|
SelectFont(hdc, hFontNew);
|
|
|
|
#define MoveTo(hdc, x, y) MoveToEx(hdc, x, y, NULL)
|
|
#define CURVE_SIZE 5
|
|
#define CURVE_CIRCLE (2*CURVE_SIZE)
|
|
#define DRAWLINE(x1, y1, x2, y2) (\
|
|
MoveTo(hdc, x1, y1),\
|
|
LineTo(hdc, x2, y2)\
|
|
)
|
|
#define DRAWVERTICAL(length, x, y, color) (\
|
|
SetDCPenColor(hdc, color),\
|
|
DRAWLINE(x, y, x, y + length)\
|
|
)
|
|
#define DRAWHORIZON(length, x, y, color) (\
|
|
SetDCPenColor(hdc, color),\
|
|
DRAWLINE(x, y, x + length, y)\
|
|
)
|
|
#define DRAWBORDER(length, dx, color) (\
|
|
SetDCPenColor(hdc, color),\
|
|
MoveTo(hdc, sx + dx, 0),\
|
|
LineTo(hdc, sx + dx, length),\
|
|
MoveTo(hdc, ex - dx - 1, 0),\
|
|
LineTo(hdc, ex - dx - 1, length)\
|
|
)
|
|
#define DRAWBOX(start, d, height, color) (\
|
|
SetDCPenColor(hdc, color),\
|
|
RoundRect(hdc, sx + d, start - CURVE_SIZE, ex - d, height - d, CURVE_CIRCLE, CURVE_CIRCLE)\
|
|
)
|
|
#define INITIALIZE_PAINT_TEXT(store) \
|
|
int len = lstrlen(store[i]), bufneed = len * 3 + 6; \
|
|
int bufidx = 0; \
|
|
if (bufsize < bufneed) { \
|
|
if (szBuffer) \
|
|
delete [] szBuffer; \
|
|
szBuffer = new WCHAR[bufneed]; \
|
|
} \
|
|
for (LPCWSTR c = store[i]; *c; c++) { \
|
|
szBuffer[bufidx++] = *c; \
|
|
szBuffer[bufidx++] = L'\r'; \
|
|
szBuffer[bufidx++] = L'\n'; \
|
|
} \
|
|
szBuffer[bufidx] = 0;
|
|
#define GETBORDER0(down) (down ? GetSysColor(COLOR_3DLIGHT) : RGB(0, 0, 0))
|
|
#define GETBORDER1(down) (down ? GetSysColor(COLOR_3DSHADOW) : GetSysColor(COLOR_3DDKSHADOW))
|
|
#define GETBORDER2(down) (down ? GetSysColor(COLOR_3DDKSHADOW) : GetSysColor(COLOR_3DSHADOW))
|
|
|
|
rect.top = height - CURVE_SIZE, rect.bottom = height;
|
|
rect.left = client.left, rect.right = client.right;
|
|
FillRect(hdc, &rect, hBackground);
|
|
|
|
rect.top = client.top, rect.bottom = client.bottom;
|
|
rect.left = client.right - width % (7 * octaves), rect.right = client.right;
|
|
FillRect(hdc, &rect, hBackground);
|
|
for (int i = 0; i < 7 * octaves; ++i) {
|
|
int sx = i * wwidth, ex = i * wwidth + wwidth - 1;
|
|
bool down = whiteStatus[i];
|
|
|
|
SelectBrush(hdc, hbDC);
|
|
SetDCBrushColor(hdc, GETBORDER1(down));
|
|
DRAWBOX(bheight, 0, height, GETBORDER0(down));
|
|
SetDCBrushColor(hdc, GETBORDER2(down));
|
|
DRAWBOX(bheight, 1, height, GETBORDER1(down));
|
|
SelectBrush(hdc, hbFace);
|
|
DRAWBOX(bheight, 2, height, GETBORDER2(down));
|
|
|
|
rect.top = 0, rect.bottom = bheight, rect.left = sx, rect.right = ex;
|
|
FillRect(hdc, &rect, hBackground);
|
|
|
|
switch (haveBlack(i)) {
|
|
case 0: // none
|
|
DRAWBORDER(bheight, 0, GETBORDER0(down));
|
|
DRAWBORDER(bheight, 1, GETBORDER1(down));
|
|
DRAWBORDER(bheight, 2, GETBORDER2(down));
|
|
break;
|
|
case 1: // right
|
|
DRAWVERTICAL(bheight, sx + 0, 0, GETBORDER0(down));
|
|
DRAWVERTICAL(bheight, sx + 1, 0, GETBORDER1(down));
|
|
DRAWVERTICAL(bheight, sx + 2, 0, GETBORDER2(down));
|
|
DRAWVERTICAL(bheight + 0, ex - hbwidth - 0, 0, GETBORDER0(down));
|
|
DRAWVERTICAL(bheight + 1, ex - hbwidth - 1, 0, GETBORDER1(down));
|
|
DRAWVERTICAL(bheight + 2, ex - hbwidth - 2, 0, GETBORDER2(down));
|
|
DRAWHORIZON(hbwidth - 1, ex - hbwidth - 0, bheight + 0, GETBORDER0(down));
|
|
DRAWHORIZON(hbwidth - 1, ex - hbwidth - 1, bheight + 1, GETBORDER1(down));
|
|
DRAWHORIZON(hbwidth - 1, ex - hbwidth - 2, bheight + 2, GETBORDER2(down));
|
|
rect.top = 0, rect.bottom = bheight, rect.left = sx + 3, rect.right = ex - hbwidth - 2;
|
|
FillRect(hdc, &rect, hbFace);
|
|
break;
|
|
case 2: // left
|
|
DRAWVERTICAL(bheight + 0, sx + hbwidth + 0, 0, GETBORDER0(down));
|
|
DRAWVERTICAL(bheight + 1, sx + hbwidth + 1, 0, GETBORDER1(down));
|
|
DRAWVERTICAL(bheight + 2, sx + hbwidth + 2, 0, GETBORDER2(down));
|
|
DRAWVERTICAL(bheight, ex - 1, 0, GETBORDER0(down));
|
|
DRAWVERTICAL(bheight, ex - 2, 0, GETBORDER1(down));
|
|
DRAWVERTICAL(bheight, ex - 3, 0, GETBORDER2(down));
|
|
DRAWHORIZON(hbwidth + 1, sx + 0, bheight + 0, GETBORDER0(down));
|
|
DRAWHORIZON(hbwidth + 1, sx + 1, bheight + 1, GETBORDER1(down));
|
|
DRAWHORIZON(hbwidth + 1, sx + 2, bheight + 2, GETBORDER2(down));
|
|
rect.top = 0, rect.bottom = bheight, rect.left = sx + hbwidth + 3, rect.right = ex - 3;
|
|
FillRect(hdc, &rect, hbFace);
|
|
break;
|
|
case 3: // both
|
|
DRAWVERTICAL(bheight + 0, sx + hbwidth + 0, 0, GETBORDER0(down));
|
|
DRAWVERTICAL(bheight + 1, sx + hbwidth + 1, 0, GETBORDER1(down));
|
|
DRAWVERTICAL(bheight + 2, sx + hbwidth + 2, 0, GETBORDER2(down));
|
|
DRAWVERTICAL(bheight + 0, ex - hbwidth - 0, 0, GETBORDER0(down));
|
|
DRAWVERTICAL(bheight + 1, ex - hbwidth - 1, 0, GETBORDER1(down));
|
|
DRAWVERTICAL(bheight + 2, ex - hbwidth - 2, 0, GETBORDER2(down));
|
|
DRAWHORIZON(hbwidth + 1, sx + 0, bheight + 0, GETBORDER0(down));
|
|
DRAWHORIZON(hbwidth + 1, sx + 1, bheight + 1, GETBORDER1(down));
|
|
DRAWHORIZON(hbwidth + 1, sx + 2, bheight + 2, GETBORDER2(down));
|
|
DRAWHORIZON(hbwidth - 1, ex - hbwidth - 0, bheight + 0, GETBORDER0(down));
|
|
DRAWHORIZON(hbwidth - 1, ex - hbwidth - 1, bheight + 1, GETBORDER1(down));
|
|
DRAWHORIZON(hbwidth - 1, ex - hbwidth - 2, bheight + 2, GETBORDER2(down));
|
|
rect.top = 0, rect.bottom = bheight, rect.left = sx + hbwidth + 3, rect.right = ex - hbwidth - 2;
|
|
FillRect(hdc, &rect, hbFace);
|
|
break;
|
|
}
|
|
|
|
if (whiteText[i]) {
|
|
INITIALIZE_PAINT_TEXT(whiteText);
|
|
rect.top = bheight + bheight / 7, rect.bottom = height - bheight / 7;
|
|
rect.left = sx, rect.right = ex;
|
|
SetTextColor(hdc, RGB(0, 0, 0));
|
|
DrawText(hdc, szBuffer, -1, &rect, DT_CENTER);
|
|
}
|
|
|
|
rect.top = client.top, rect.bottom = client.bottom;
|
|
rect.left = ex, rect.right = ex + 1;
|
|
FillRect(hdc, &rect, hBackground);
|
|
}
|
|
for (int i = 0; i < 7 * octaves; ++i) {
|
|
if (!haveBlackToLeft(i))
|
|
continue;
|
|
int sx = (i * wwidth) - hbwidth + 2, ex = sx + bwidth - 5;
|
|
int kj = bwidth / 4, dc = 128 / kj;
|
|
bool down = blackStatus[i];
|
|
SelectBrush(hdc, hbDC);
|
|
for (int j = 0; j < kj; ++j) {
|
|
int gray = down ? j * dc : (128 - j * dc);
|
|
COLORREF colour = RGB(gray, gray, gray);
|
|
SetDCBrushColor(hdc, colour);
|
|
DRAWBOX(-CURVE_SIZE, j, bheight - 2, colour);
|
|
}
|
|
|
|
if (blackText[i]) {
|
|
INITIALIZE_PAINT_TEXT(blackText);
|
|
rect.top = bheight / 7, rect.bottom = bheight - bheight / 7;
|
|
rect.left = max(0, sx), rect.right = ex;
|
|
SetTextColor(hdc, RGB(255, 255, 255));
|
|
DrawText(hdc, szBuffer, -1, &rect, DT_CENTER);
|
|
}
|
|
}
|
|
#undef MoveTo
|
|
#undef DRAWLINE
|
|
#undef DRAWVERTICAL
|
|
#undef DRAWHORIZON
|
|
#undef DRAWBORDER
|
|
#undef DRAWBOX
|
|
#undef GETBORDER1
|
|
#undef GETBORDER2
|
|
SelectBrush(hdc, hbOriginal);
|
|
SelectPen(hdc, hPenOriginal);
|
|
DeleteObject(hFontNew);
|
|
SelectFont(hdc, hFontOriginal);
|
|
SetTextColor(hdc, textColourOriginal);
|
|
SetBkMode(hdc, backgroundOriginal);
|
|
if (szBuffer)
|
|
delete szBuffer;
|
|
}
|
|
|
|
void PianoControl::OnPaint()
|
|
{
|
|
PAINTSTRUCT ps;
|
|
BeginPaint(m_hwnd, &ps);
|
|
|
|
int x = ps.rcPaint.left;
|
|
int y = ps.rcPaint.top;
|
|
int cx = ps.rcPaint.right - ps.rcPaint.left;
|
|
int cy = ps.rcPaint.bottom - ps.rcPaint.top;
|
|
HDC hdc = ps.hdc;
|
|
|
|
if (!hMemDC)
|
|
hMemDC = CreateCompatibleDC(hdc);
|
|
if (!hMemBitmap)
|
|
hMemBitmap = CreateCompatibleBitmap(hdc, cx + 50, cy + 50);
|
|
if (cx > bmx || cy > bmy) {
|
|
if (hMemBitmap)
|
|
DeleteObject(hMemBitmap);
|
|
hMemBitmap = CreateCompatibleBitmap(hdc, cx + 50, cy + 50);
|
|
}
|
|
if (hMemDC && hMemBitmap) {
|
|
ps.hdc = hMemDC;
|
|
|
|
HBITMAP hbmPrev = SelectBitmap(hMemDC, hMemBitmap);
|
|
SetWindowOrgEx(hMemDC, x, y, NULL);
|
|
|
|
PaintContent(&ps);
|
|
BitBlt(hdc, x, y, cx, cy, hMemDC, x, y, SRCCOPY);
|
|
|
|
SelectObject(hMemDC, hbmPrev);
|
|
} else
|
|
PaintContent(&ps);
|
|
EndPaint(m_hwnd, &ps);
|
|
}
|
|
|
|
LRESULT PianoControl::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg) {
|
|
case WM_CREATE:
|
|
return OnCreate();
|
|
case WM_DESTROY:
|
|
return OnDestroy();
|
|
case WM_NCDESTROY:
|
|
PostQuitMessage(0);
|
|
break;
|
|
case WM_PAINT:
|
|
OnPaint();
|
|
return 0;
|
|
case WM_SIZE:
|
|
InvalidateRect(m_hwnd, NULL, TRUE);
|
|
return 0;
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwParent);
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYUP:
|
|
case WM_CHAR:
|
|
case WM_DEADCHAR:
|
|
case WM_SYSCHAR:
|
|
case WM_SYSDEADCHAR:
|
|
return SendMessage(hwParent, uMsg, wParam, lParam);
|
|
case WM_GETFONT:
|
|
return (LRESULT) GetFont();
|
|
case WM_SETFONT:
|
|
SetFont((HFONT) wParam);
|
|
if (LOWORD(lParam))
|
|
InvalidateRect(m_hwnd, NULL, TRUE);
|
|
case MPCM_GETKEYSTATUS:
|
|
return GetKeyStatus(wParam);
|
|
case MPCM_SETKEYSTATUS:
|
|
SetKeyStatus(wParam, lParam != 0);
|
|
return 0;
|
|
case MPCM_GETOCTAVES:
|
|
return GetOctaves();
|
|
case MPCM_SETOCTAVES:
|
|
SetOctaves(wParam);
|
|
return 0;
|
|
case MPCM_GETKEYTEXT:
|
|
return GetOctaves();
|
|
case MPCM_SETKEYTEXT:
|
|
SetOctaves(wParam);
|
|
return 0;
|
|
case MPCM_GETBACKGROUND:
|
|
return (LRESULT) GetBackground();
|
|
case MPCM_SETBACKGROUND:
|
|
SetBackground((HBRUSH) wParam);
|
|
return 0;
|
|
}
|
|
return __super::HandleMessage(uMsg, wParam, lParam);
|
|
}
|
|
|
|
PianoControl *PianoControl::Create(LPCTSTR szTitle, HWND hwParent,
|
|
DWORD dwStyle, int x, int y, int cx, int cy)
|
|
{
|
|
PianoControl *self = new PianoControl();
|
|
if (self &&
|
|
self->WinCreateWindow(0, szTitle, dwStyle, x, y, cx, cy,
|
|
hwParent, NULL)) {
|
|
return self;
|
|
}
|
|
delete self;
|
|
return NULL;
|
|
}
|