diff --git a/Makefile b/Makefile index 5ccff7e..53b1496 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ OUTDIR=build\$(BUILD) DISTDIR=dist\$(BUILD) FILES=$(OUTDIR)\Keyboard.obj \ $(OUTDIR)\MainWindow.obj \ + $(OUTDIR)\PianoControl.obj \ $(OUTDIR)\Window.obj \ $(OUTDIR)\keyboard.res @@ -34,9 +35,10 @@ initdir: if not exist build md dist if not exist $(DISTDIR) md $(DISTDIR) -$(INCDIR)\MainWindow.hpp: $(INCDIR)\Window.hpp +$(INCDIR)\MainWindow.hpp: $(INCDIR)\Window.hpp $(INCDIR)\PianoControl.hpp $(SRCDIR)\MainWindow.cpp: $(INCDIR)\MainWindow.hpp +$(SRCDIR)\PianoControl.cpp: $(INCDIR)\PianoControl.hpp $(SRCDIR)\Keyboard.cpp: $(INCDIR)\MainWindow.hpp $(SRCDIR)\Window.cpp: $(INCDIR)\Window.hpp keyboard.rc: keyboard.ico keymap.bmp diff --git a/include/MainWindow.hpp b/include/MainWindow.hpp index 61bb231..85ec4ac 100644 --- a/include/MainWindow.hpp +++ b/include/MainWindow.hpp @@ -3,6 +3,7 @@ #define id68C60171_0140_4DE1_B7255EFF557A74F9 #include +#include #include #include @@ -32,11 +33,11 @@ protected: HMIDIOUT m_midi; bool isQWERTY; HKL hklQWERTY; + PianoControl *piano; + LPWSTR m_keychars; private: HFONT hFont; HBRUSH hBrush; - HBITMAP hKeyboardLayout; - HDC hdcKeyboard; }; #endif diff --git a/include/PianoControl.hpp b/include/PianoControl.hpp new file mode 100644 index 0000000..075b099 --- /dev/null +++ b/include/PianoControl.hpp @@ -0,0 +1,73 @@ +#pragma once +#ifndef id7A156843_8DC8_4B5A_83D273814FBE10DE +#define id7A156843_8DC8_4B5A_83D273814FBE10DE + +#include + +#include +#include +#include + +#define MPCM_GETKEYSTATUS (WM_USER + 0) +#define MPCM_SETKEYSTATUS (WM_USER + 1) +#define MPCM_GETOCTAVES (WM_USER + 2) +#define MPCM_SETOCTAVES (WM_USER + 3) +#define MPCM_GETKEYTEXT (WM_USER + 4) +#define MPCM_SETKEYTEXT (WM_USER + 5) +#define MPCM_GETBACKGROUND (WM_USER + 6) +#define MPCM_SETBACKGROUND (WM_USER + 7) + +class PianoControl : public Window { +public: + virtual LPCTSTR ClassName() { return TEXT("KeyboardControl"); } + static PianoControl *Create(LPCTSTR szTitle, HWND hwParent, + DWORD dwStyle = 0, + int x = CW_USEDEFAULT, int y = CW_USEDEFAULT, + int cx = CW_USEDEFAULT, int cy = CW_USEDEFAULT); + + virtual void SetOctaves(int octaves); + virtual int GetOctaves() { return octaves; } + + virtual void SetKeyStatus(int key, bool down); + virtual bool GetKeyStatus(int key); + + virtual void SetKeyText(int key, LPCWSTR text); + virtual LPCWSTR GetKeyText(int key); + + virtual void SetBackground(HBRUSH background) { hBackground = background; } + virtual HBRUSH GetBackground() { return hBackground; } + + HFONT GetFont() { return hFont; } + void SetFont(HFONT font) { hFont = font; } +protected: + LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT OnCreate(); + LRESULT OnDestroy(); + void OnPaint(); + virtual void PaintContent(PAINTSTRUCT *pps); + BOOL WinRegisterClass(WNDCLASS *pwc); + + virtual int keyIDToInternal(int id, bool &black); + virtual bool haveBlackToLeft(int id); + virtual bool haveBlackToRight(int id); + int haveBlack(int id) { + return (haveBlackToLeft(id) ? 2 : 0) | (haveBlackToRight(id) ? 1 : 0); + } + virtual void UpdateKey(int key, bool black); + + bool *blackStatus; + bool *whiteStatus; + LPCWSTR *blackText; + LPCWSTR *whiteText; + + int octaves; + HFONT hFont; + HWND hwParent; + + HDC hMemDC; + HBITMAP hMemBitmap; + HBRUSH hBackground; + int bmx, bmy; +}; + +#endif diff --git a/include/resource.h b/include/resource.h index 3b97a01..4501386 100644 --- a/include/resource.h +++ b/include/resource.h @@ -1,2 +1 @@ #define RID_ICON 1 -#define RID_KEYBOARD 2 diff --git a/keyboard.bmp b/keyboard.bmp deleted file mode 100644 index 13cfed2..0000000 Binary files a/keyboard.bmp and /dev/null differ diff --git a/keyboard.rc b/keyboard.rc index def68c3..06cf7a2 100644 --- a/keyboard.rc +++ b/keyboard.rc @@ -2,7 +2,6 @@ #include RID_ICON ICON keyboard.ico -RID_KEYBOARD BITMAP keymap.bmp STRINGTABLE LANGUAGE 0x09, 0x01 // en-US BEGIN diff --git a/keymap.bmp b/keymap.bmp deleted file mode 100644 index 567402a..0000000 Binary files a/keymap.bmp and /dev/null differ diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3e90fd2..26ac9a9 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -21,6 +21,39 @@ DWORD rgbWindowBackground; static char keymap[256]; +static LPWSTR keychars = + L"~`\0" // F#3 + L"\x21c6Q\0" // G3 + L"W\0" // G#3 + L"AZ1\0" // A3 + L"E\0" // A#3 + L"SX2\0" // B3 + L"DC3\0" // C4 + L"R\0" // C#4 + L"FV4\0" // D4 + L"T\0" // D#4 + L"GB5\0" // E4 + L"HN6\0" // F4 + L"U\0" // F#4 + L"JM7\0" // G4 + L"I\0" // G#4 + L"K,8\0" // A4 + L"O\0" // A#4 + L"L.9\0" // B4 + L";/0\0" // C5 + L"[\0" // C#5 + L"\"-\0" // D5 + L"]\0" // D#5 + L"=\x21b5\0" // E5 + L"\x2190\0" // F5 +; + +int dnslen(LPWSTR dns) +{ + LPWSTR i = dns; + for (; lstrlen(i); i += lstrlen(i) + 1); + return i - dns; +} BOOL MainWindow::WinRegisterClass(WNDCLASS *pwc) { @@ -28,19 +61,6 @@ BOOL MainWindow::WinRegisterClass(WNDCLASS *pwc) return __super::WinRegisterClass(pwc); } -BOOL EnableCloseButton(const HWND hwnd, const BOOL bState) -{ - HMENU hMenu; - UINT dwExtra; - - if (hwnd == NULL) - return FALSE; - if ((hMenu = GetSystemMenu(hwnd, FALSE)) == NULL) - return FALSE; - dwExtra = bState ? MF_ENABLED : (MF_DISABLED | MF_GRAYED); - return EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | dwExtra) != -1; -} - LRESULT MainWindow::OnCreate() { NONCLIENTMETRICS ncmMetrics = { sizeof(NONCLIENTMETRICS) }; @@ -52,10 +72,6 @@ LRESULT MainWindow::OnCreate() hFont = CreateFontIndirect(&ncmMetrics.lfMessageFont); rgbWindowBackground = GetSysColor(COLOR_WINDOW); hBrush = GetSysColorBrush(COLOR_WINDOW); - hKeyboardLayout = (HBITMAP) LoadImage(GetInstance(), MAKEINTRESOURCE(RID_KEYBOARD), - IMAGE_BITMAP, 0, 0, LR_LOADTRANSPARENT); - hdcKeyboard = CreateCompatibleDC(GetDC(m_hwnd)); - SelectBitmap(hdcKeyboard, hKeyboardLayout); // Children m_volumeLabel = CreateWindow(WC_STATIC, L"Volume:", @@ -109,58 +125,65 @@ LRESULT MainWindow::OnCreate() if (midiOutOpen(&m_midi, 0, NULL, NULL, CALLBACK_NULL) != MMSYSERR_NOERROR) MessageBox(m_hwnd, L"Failed to open MIDI device!", L"Fatal Error", MB_ICONERROR); - - keymap[VK_OEM_3] = 54; // `~ key - keymap['Q'] = 55; - keymap[VK_TAB] = 55; - keymap['A'] = 57; - keymap['Z'] = 57; - keymap['W'] = 56; - keymap['E'] = 58; - keymap['S'] = 59; - keymap['X'] = 59; - keymap['D'] = 60; - keymap['C'] = 60; - keymap['R'] = 61; - keymap['F'] = 62; - keymap['V'] = 62; - keymap['T'] = 63; - keymap['G'] = 64; - keymap['B'] = 64; - keymap['H'] = 65; - keymap['N'] = 65; - keymap['U'] = 66; - keymap['J'] = 67; - keymap['M'] = 67; - keymap['I'] = 68; - keymap['K'] = 69; - keymap[VK_OEM_COMMA] = 69; - keymap['O'] = 70; - keymap['L'] = 71; - keymap[VK_OEM_PERIOD] = 71; - keymap[VK_OEM_1] = 72; // ;: - keymap[VK_OEM_2] = 72; // /? - keymap[VK_OEM_7] = 74; // '" - keymap[VK_OEM_4] = 73; // [ - keymap[VK_OEM_6] = 75; // ] - keymap[VK_RETURN] = 76; - keymap[VK_OEM_5] = 77; // \| - keymap[VK_BACK] = 77; - keymap[0x31] = 57; - keymap[0x32] = 59; - keymap[0x33] = 60; - keymap[0x34] = 62; - keymap[0x35] = 64; - keymap[0x36] = 65; - keymap[0x37] = 67; - keymap[0x38] = 69; - keymap[0x39] = 71; - keymap[0x30] = 72; - keymap[VK_OEM_MINUS] = 74; - keymap[VK_OEM_PLUS] = 76; - PostMessage(m_hwnd, WM_INPUTLANGCHANGE, 0, 0); - SetTimer(m_hwnd, 0xDE00, 1000, NULL); + this->piano = PianoControl::Create(NULL, m_hwnd, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0); + this->piano->SetBackground( + (HBRUSH) DefWindowProc(m_hwnd, WM_CTLCOLORSTATIC, + (WPARAM) GetDC(m_volumeLabel), + (LPARAM) m_volumeLabel)); + + { + keymap[VK_OEM_3] = 54; // `~ key + keymap['Q'] = 55; + keymap[VK_TAB] = 55; + keymap['A'] = 57; + keymap['Z'] = 57; + keymap['W'] = 56; + keymap['E'] = 58; + keymap['S'] = 59; + keymap['X'] = 59; + keymap['D'] = 60; + keymap['C'] = 60; + keymap['R'] = 61; + keymap['F'] = 62; + keymap['V'] = 62; + keymap['T'] = 63; + keymap['G'] = 64; + keymap['B'] = 64; + keymap['H'] = 65; + keymap['N'] = 65; + keymap['U'] = 66; + keymap['J'] = 67; + keymap['M'] = 67; + keymap['I'] = 68; + keymap['K'] = 69; + keymap[VK_OEM_COMMA] = 69; + keymap['O'] = 70; + keymap['L'] = 71; + keymap[VK_OEM_PERIOD] = 71; + keymap[VK_OEM_1] = 72; // ;: + keymap[VK_OEM_2] = 72; // /? + keymap[VK_OEM_7] = 74; // '" + keymap[VK_OEM_4] = 73; // [ + keymap[VK_OEM_6] = 75; // ] + keymap[VK_RETURN] = 76; + keymap[VK_OEM_5] = 77; // \| + keymap[VK_BACK] = 77; + keymap[0x31] = 57; + keymap[0x32] = 59; + keymap[0x33] = 60; + keymap[0x34] = 62; + keymap[0x35] = 64; + keymap[0x36] = 65; + keymap[0x37] = 67; + keymap[0x38] = 69; + keymap[0x39] = 71; + keymap[0x30] = 72; + keymap[VK_OEM_MINUS] = 74; + keymap[VK_OEM_PLUS] = 76; + } + m_keychars = NULL; + PostMessage(m_hwnd, WM_INPUTLANGCHANGE, 0, 0); return 0; } @@ -183,21 +206,6 @@ HICON MainWindow::GetIcon() void MainWindow::PaintContent(PAINTSTRUCT *pps) { - BITMAP bm; - int cx, cy, tx, ty, x, y; - double ratio; - RECT client; - GetClientRect(m_hwnd, &client); - GetObject(hKeyboardLayout, sizeof(BITMAP), &bm); - cx = client.right - 24, cy = client.bottom - 117; - ratio = ((double) bm.bmWidth) / ((double) bm.bmHeight); - if (cx / ratio > cy) - tx = (int) (cy * ratio), ty = (int) cy; - else - tx = (int) cx, ty = (int) (cx / ratio); - x = 12 + (cx - tx) / 2; - y = 12 + (cy - ty) / 2; - StretchBlt(pps->hdc, x, y, tx, ty, hdcKeyboard, 0, 0, bm.bmWidth, bm.bmHeight, SRCAND); } void MainWindow::OnPaint() @@ -264,16 +272,14 @@ int GetMIDINote(WPARAM wCode) bool MainWindow::Play(WPARAM wParam, LPARAM lParam, bool down) { - int note; + int note, id; WORD wCode = GetQWERTYKeyCode((WORD) wParam); if (wCode > 255 || !keymap[wCode] || (down && (lParam & 0x40000000))) return false; note = GetMIDINote(wCode); if (down) { - int num = note; - while (num > 24) - num -= 24; + int num = note % 24; while (num < 0x7F) { if (num != note) MIDI_MESSAGE(m_midi, 0x90, num, 0); @@ -284,6 +290,7 @@ bool MainWindow::Play(WPARAM wParam, LPARAM lParam, bool down) MIDI_MESSAGE(m_midi, 0x90, note, m_force); else MIDI_MESSAGE(m_midi, 0x90, note, 0); + piano->SetKeyStatus((note - 6) % 24, down); return true; } @@ -314,6 +321,9 @@ LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) REPOS(m_instruSelect, BOTTOM(82, client.bottom - 72, client.right - 94, 25)); EndDeferWindowPos(hdwp); #undef REPOS + //REPOS(piano->GetHWND(), LEFT(12, 12, client.right - 24, client.bottom - 117)); + if (!MoveWindow(piano->GetHWND(), 12, 12, client.right - 24, client.bottom - 117, TRUE)) + MessageBox(m_hwnd, 0, 0, 0); return 0; } case WM_COMMAND: @@ -362,6 +372,31 @@ LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) isQWERTY = lstrcmpi(buf, L"00000409") == 0; if (!isQWERTY && !hklQWERTY) hklQWERTY = LoadKeyboardLayout(L"00000409", KLF_NOTELLSHELL); + + int size = dnslen(keychars) + 1, i; + LPWSTR s; + if (m_keychars) + delete m_keychars; + m_keychars = new WCHAR[size]; + for (i = 0; i < size; ++i) { + WORD scan = VkKeyScanEx(keychars[i], hklQWERTY); + if (LOBYTE(scan) == -1) + goto giveup; + WORD vk = GetRealKeyCode(LOBYTE(scan)); + if (!vk || vk == LOBYTE(scan)) + goto giveup; + WCHAR ch = (WCHAR) MapVirtualKey(vk, MAPVK_VK_TO_CHAR); + if (!ch) + goto giveup; + m_keychars[i] = ch; + continue; + + giveup: + m_keychars[i] = keychars[i]; + } + + for (s = m_keychars, i = 0; i < 24 && lstrlen(s); s += lstrlen(s) + 1, ++i) + piano->SetKeyText(i, s); } case WM_CHAR: case WM_DEADCHAR: diff --git a/src/PianoControl.cpp b/src/PianoControl.cpp new file mode 100644 index 0000000..168d6f3 --- /dev/null +++ b/src/PianoControl.cpp @@ -0,0 +1,540 @@ +#include + +#include +#include + +#define MessageErrorWndTitle(hwnd, e, t) MessageBox(hwnd, e, t, MB_ICONERROR) +#define MessageErrorWnd(hwnd, e) MessageBox(hwnd, e, T("Error!"), MB_ICONERROR) +#define MessageError(e) MessageErrorWnd(NULL, e) + +#define MessageLastErrorWndTitle(hwnd, e, title)\ + do { \ + LPTSTR s; \ + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e, 0, (LPTSTR)&s, 0, NULL) == 0) {\ + TCHAR str[25]; \ + snprintf(str, 25, T("%x"), str); \ + MessageErrorWndTitle(hwnd, str, title); \ + } else { \ + MessageErrorWndTitle(hwnd, s, title); \ + LocalFree(s); \ + } \ + } while (0) +#define MessageLastErrorWnd(hwnd, e) MessageLastErrorWndTitle(hwnd, e, T("Error!")) +#define MessageLastError(e) MessageLastErrorWnd(NULL, e) + +#define MessageIntBox(hwnd, i, title, opt) \ + do { \ + CHAR buf[100]; \ + sprintf_s(buf, 100, "%d", i); \ + MessageBoxA(hwnd, buf, title, opt); \ + } while (0) + +#define MessageIntWndTitle(hwnd, i, title) MessageIntBox(hwnd, i, title, MB_ICONINFORMATION) +#define MessageIntWnd(hwnd, i) MessageIntWndTitle(hwnd, i, "Debug Info") +#define MessageInt(i) MessageIntWnd(NULL, i) + +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; +}