From b27671740d0685aecfc57c9e3e942d424a0ad35e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 16 Oct 2014 13:53:05 -0400 Subject: [PATCH] Removed the whole store**HWND spiel. No longer works properly (nil pointers woo). --- newctrl/area_windows.c | 500 +++++++++++++++++++++++++++++++++++ newctrl/area_windows.go | 6 - newctrl/common_windows.c | 136 ++++++++++ newctrl/container_windows.c | 96 +++++++ newctrl/container_windows.go | 11 +- newctrl/winapi_windows.h | 154 +++++++++++ newctrl/window_windows.c | 84 ++++++ newctrl/window_windows.go | 12 +- 8 files changed, 972 insertions(+), 27 deletions(-) create mode 100644 newctrl/area_windows.c create mode 100644 newctrl/common_windows.c create mode 100644 newctrl/container_windows.c create mode 100644 newctrl/winapi_windows.h create mode 100644 newctrl/window_windows.c diff --git a/newctrl/area_windows.c b/newctrl/area_windows.c new file mode 100644 index 0000000..31f705c --- /dev/null +++ b/newctrl/area_windows.c @@ -0,0 +1,500 @@ +// 24 march 2014 + +#include "winapi_windows.h" +#include "_cgo_export.h" + +static void getScrollPos(HWND hwnd, int *xpos, int *ypos) +{ + SCROLLINFO si; + + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_POS | SIF_TRACKPOS; + if (GetScrollInfo(hwnd, SB_HORZ, &si) == 0) + xpanic("error getting horizontal scroll position for Area", GetLastError()); + *xpos = si.nPos; + // MSDN example code reinitializes this each time, so we'll do it too just to be safe + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_POS | SIF_TRACKPOS; + if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) + xpanic("error getting vertical scroll position for Area", GetLastError()); + *ypos = si.nPos; +} + +#define areaBackgroundBrush ((HBRUSH) (COLOR_BTNFACE + 1)) + +static void paintArea(HWND hwnd, void *data) +{ + RECT xrect; + PAINTSTRUCT ps; + HDC hdc; + HDC rdc; + HBITMAP rbitmap, prevrbitmap; + RECT rrect; + BITMAPINFO bi; + VOID *ppvBits; + HBITMAP ibitmap; + HDC idc; + HBITMAP previbitmap; + BLENDFUNCTION blendfunc; + void *i; + intptr_t dx, dy; + int hscroll, vscroll; + + // FALSE here indicates don't send WM_ERASEBKGND + if (GetUpdateRect(hwnd, &xrect, FALSE) == 0) + return; // no update rect; do nothing + + getScrollPos(hwnd, &hscroll, &vscroll); + + hdc = BeginPaint(hwnd, &ps); + if (hdc == NULL) + xpanic("error beginning Area repaint", GetLastError()); + + // very big thanks to Ninjifox for suggesting this technique and helping me go through it + + // first let's create the destination image, which we fill with the windows background color + // this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx + rdc = CreateCompatibleDC(hdc); + if (rdc == NULL) + xpanic("error creating off-screen rendering DC", GetLastError()); + // the bitmap has to be compatible with the window + // if we create a bitmap compatible with the DC we just created, it'll be monochrome + // thanks to David Heffernan in http://stackoverflow.com/questions/23033636/winapi-gdi-fillrectcolor-btnface-fills-with-strange-grid-like-brush-on-window + rbitmap = CreateCompatibleBitmap(hdc, xrect.right - xrect.left, xrect.bottom - xrect.top); + if (rbitmap == NULL) + xpanic("error creating off-screen rendering bitmap", GetLastError()); + prevrbitmap = (HBITMAP) SelectObject(rdc, rbitmap); + if (prevrbitmap == NULL) + xpanic("error connecting off-screen rendering bitmap to off-screen rendering DC", GetLastError()); + rrect.left = 0; + rrect.right = xrect.right - xrect.left; + rrect.top = 0; + rrect.bottom = xrect.bottom - xrect.top; + if (FillRect(rdc, &rrect, areaBackgroundBrush) == 0) + xpanic("error filling off-screen rendering bitmap with the system background color", GetLastError()); + + i = doPaint(&xrect, hscroll, vscroll, data, &dx, &dy); + if (i == NULL) // cliprect empty + goto nobitmap; // we need to blit the background no matter what + + // now we need to shove realbits into a bitmap + // technically bitmaps don't know about alpha; they just ignore the alpha byte + // AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx + ZeroMemory(&bi, sizeof (BITMAPINFO)); + bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bi.bmiHeader.biWidth = (LONG) dx; + bi.bmiHeader.biHeight = -((LONG) dy); // negative height to force top-down drawing + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + bi.bmiHeader.biSizeImage = (DWORD) (dx * dy * 4); + // this is all we need, but because this confused me at first, I will say the two pixels-per-meter fields are unused (see http://blogs.msdn.com/b/oldnewthing/archive/2013/05/15/10418646.aspx and page 581 of Charles Petzold's Programming Windows, Fifth Edition) + // now for the trouble: CreateDIBSection() allocates the memory for us... + ibitmap = CreateDIBSection(NULL, // Ninjifox does this, so do some wine tests (http://source.winehq.org/source/dlls/gdi32/tests/bitmap.c#L725, thanks vpovirk in irc.freenode.net/#winehackers) and even Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2006/11/16/1086835.aspx), so. + &bi, DIB_RGB_COLORS, &ppvBits, 0, 0); + if (ibitmap == NULL) + xpanic("error creating HBITMAP for image returned by AreaHandler.Paint()", GetLastError()); + + // now we have to do TWO MORE things before we can finally do alpha blending + // first, we need to load the bitmap memory, because Windows makes it for us + // the pixels are arranged in RGBA order, but GDI requires BGRA + // this turns out to be just ARGB in little endian; let's convert into this memory + dotoARGB(i, (void *) ppvBits, FALSE); // FALSE = not NRGBA + + // the second thing is... make a device context for the bitmap :| + // Ninjifox just makes another compatible DC; we'll do the same + idc = CreateCompatibleDC(hdc); + if (idc == NULL) + xpanic("error creating HDC for image returned by AreaHandler.Paint()", GetLastError()); + previbitmap = (HBITMAP) SelectObject(idc, ibitmap); + if (previbitmap == NULL) + xpanic("error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC", GetLastError()); + + // AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111 + blendfunc.BlendOp = AC_SRC_OVER; + blendfunc.BlendFlags = 0; + blendfunc.SourceConstantAlpha = 255; // only use per-pixel alphas + blendfunc.AlphaFormat = AC_SRC_ALPHA; // premultiplied + if (AlphaBlend(rdc, 0, 0, (int) dx, (int) dy, // destination + idc, 0, 0, (int) dx, (int)dy, // source + blendfunc) == FALSE) + xpanic("error alpha-blending image returned by AreaHandler.Paint() onto background", GetLastError()); + + // clean up after idc/ibitmap here because of the goto nobitmap + if (SelectObject(idc, previbitmap) != ibitmap) + xpanic("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP", GetLastError()); + if (DeleteObject(ibitmap) == 0) + xpanic("error deleting HBITMAP for image returned by AreaHandler.Paint()", GetLastError()); + if (DeleteDC(idc) == 0) + xpanic("error deleting HDC for image returned by AreaHandler.Paint()", GetLastError()); + +nobitmap: + // and finally we can just blit that into the window + if (BitBlt(hdc, xrect.left, xrect.top, xrect.right - xrect.left, xrect.bottom - xrect.top, + rdc, 0, 0, // from the rdc's origin + SRCCOPY) == 0) + xpanic("error blitting Area image to Area", GetLastError()); + + // now to clean up + if (SelectObject(rdc, prevrbitmap) != rbitmap) + xpanic("error reverting HDC for off-screen rendering to original HBITMAP", GetLastError()); + if (DeleteObject(rbitmap) == 0) + xpanic("error deleting HBITMAP for off-screen rendering", GetLastError()); + if (DeleteDC(rdc) == 0) + xpanic("error deleting HDC for off-screen rendering", GetLastError()); + + EndPaint(hwnd, &ps); +} + +static SIZE getAreaControlSize(HWND hwnd) +{ + RECT rect; + SIZE size; + + if (GetClientRect(hwnd, &rect) == 0) + xpanic("error getting size of actual Area control", GetLastError()); + size.cx = (LONG) (rect.right - rect.left); + size.cy = (LONG) (rect.bottom - rect.top); + return size; +} + +static void scrollArea(HWND hwnd, void *data, WPARAM wParam, int which) +{ + SCROLLINFO si; + SIZE size; + LONG cwid, cht; + LONG pagesize, maxsize; + LONG newpos; + LONG delta; + LONG dx, dy; + + size = getAreaControlSize(hwnd); + cwid = size.cx; + cht = size.cy; + if (which == SB_HORZ) { + pagesize = cwid; + maxsize = areaWidthLONG(data); + } else if (which == SB_VERT) { + pagesize = cht; + maxsize = areaHeightLONG(data); + } else + xpanic("invalid which sent to scrollArea()", 0); + + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_POS | SIF_TRACKPOS; + if (GetScrollInfo(hwnd, which, &si) == 0) + xpanic("error getting current scroll position for scrolling", GetLastError()); + + newpos = (LONG) si.nPos; + switch (LOWORD(wParam)) { + case SB_LEFT: // also SB_TOP; C won't let me have both (C89 §6.6.4.2; C99 §6.8.4.2) + newpos = 0; + break; + case SB_RIGHT: // also SB_BOTTOM + // see comment in adjustAreaScrollbars() below + newpos = maxsize - pagesize; + break; + case SB_LINELEFT: // also SB_LINEUP + newpos--; + break; + case SB_LINERIGHT: // also SB_LINEDOWN + newpos++; + break; + case SB_PAGELEFT: // also SB_PAGEUP + newpos -= pagesize; + break; + case SB_PAGERIGHT: // also SB_PAGEDOWN + newpos += pagesize; + break; + case SB_THUMBPOSITION: + // raymond chen says to just set the newpos to the SCROLLINFO nPos for this message; see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx + // do nothing here; newpos already has nPos + break; + case SB_THUMBTRACK: + newpos = (LONG) si.nTrackPos; + } + // otherwise just keep the current position (that's what MSDN example code says, anyway) + + // make sure we're not out of range + if (newpos < 0) + newpos = 0; + if (newpos > (maxsize - pagesize)) + newpos = maxsize - pagesize; + + // this would be where we would put a check to not scroll if the scroll position changed, but see the note about SB_THUMBPOSITION above: Raymond Chen's code always does the scrolling anyway in this case + + delta = -(newpos - si.nPos); // negative because ScrollWindowEx() scrolls in the opposite direction + dx = delta; + dy = 0; + if (which == SB_VERT) { + dx = 0; + dy = delta; + } + + // this automatically scrolls the edit control, if any + if (ScrollWindowEx(hwnd, + (int) dx, (int) dy, + // these four change what is scrolled and record info about the scroll; we're scrolling the whole client area and don't care about the returned information here + NULL, NULL, NULL, NULL, + // mark the remaining rect as needing redraw and erase... + SW_INVALIDATE | SW_ERASE) == ERROR) + xpanic("error scrolling Area", GetLastError()); + // ...but don't redraw the window yet; we need to apply our scroll changes + + // we actually have to commit the change back to the scrollbar; otherwise the scroll position will merely reset itself + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_POS; + si.nPos = (int) newpos; + // this is not expressly documented as returning an error so IDK what the error code is so assume there is none + SetScrollInfo(hwnd, which, &si, TRUE); // redraw scrollbar + + // NOW redraw it + if (UpdateWindow(hwnd) == 0) + xpanic("error updating Area after scrolling", GetLastError()); + if ((HWND) GetWindowLongPtrW(hwnd, 0) != NULL) + if (UpdateWindow((HWND) GetWindowLongPtrW(hwnd, 0)) == 0) + xpanic("error updating Area TextField after scrolling", GetLastError()); +} + +static void adjustAreaScrollbars(HWND hwnd, void *data) +{ + SCROLLINFO si; + SIZE size; + LONG cwid, cht; + + size = getAreaControlSize(hwnd); + cwid = size.cx; + cht = size.cy; + + // the trick is we want a page to be the width/height of the visible area + // so the scroll range would go from [0..image_dimension - control_dimension] + // but judging from the sample code on MSDN, we don't need to do this; the scrollbar will do it for us + // we DO need to handle it when scrolling, though, since the thumb can only go up to this upper limit + + // have to do horizontal and vertical separately + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_RANGE | SIF_PAGE; + si.nMin = 0; + si.nMax = (int) (areaWidthLONG(data) - 1); // the max point is inclusive, so we have to pass in the last valid value, not the first invalid one (see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx); if we don't, we get weird things like the scrollbar sometimes showing one extra scroll position at the end that you can never scroll to + si.nPage = (UINT) cwid; + SetScrollInfo(hwnd, SB_HORZ, &si, TRUE); // redraw the scroll bar + + // MSDN sample code does this a second time; let's do it too to be safe + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_RANGE | SIF_PAGE; + si.nMin = 0; + si.nMax = (int) (areaHeightLONG(data) - 1); + si.nPage = (UINT) cht; + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); +} + +void repaintArea(HWND hwnd, RECT *r) +{ + // NULL - the whole area; TRUE - have windows erase if possible + if (InvalidateRect(hwnd, r, TRUE) == 0) + xpanic("error flagging Area as needing repainting after event", GetLastError()); + if (UpdateWindow(hwnd) == 0) + xpanic("error repainting Area after event", GetLastError()); +} + +void areaMouseEvent(HWND hwnd, void *data, DWORD button, BOOL up, uintptr_t heldButtons, LPARAM lParam) +{ + int xpos, ypos; + + // mouse coordinates are relative to control; make them relative to Area + getScrollPos(hwnd, &xpos, &ypos); + xpos += GET_X_LPARAM(lParam); + ypos += GET_Y_LPARAM(lParam); + finishAreaMouseEvent(data, button, up, heldButtons, xpos, ypos); +} + +static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + void *data; + DWORD which; + uintptr_t heldButtons = (uintptr_t) wParam; + LRESULT lResult; + + data = getWindowData(hwnd, uMsg, wParam, lParam, &lResult); + if (data == NULL) + return lResult; + switch (uMsg) { + case WM_PAINT: + paintArea(hwnd, data); + return 0; + case WM_ERASEBKGND: + // don't draw a background; we'll do so when painting + // this is to make things flicker-free; see http://msdn.microsoft.com/en-us/library/ms969905.aspx + return 1; + case WM_HSCROLL: + scrollArea(hwnd, data, wParam, SB_HORZ); + return 0; + case WM_VSCROLL: + scrollArea(hwnd, data, wParam, SB_VERT); + return 0; + case WM_SIZE: + adjustAreaScrollbars(hwnd, data); + return 0; + case WM_ACTIVATE: + // don't keep the double-click timer running if the user switched programs in between clicks + areaResetClickCounter(data); + return 0; + case WM_MOUSEMOVE: + areaMouseEvent(hwnd, data, 0, FALSE, heldButtons, lParam); + return 0; + case WM_LBUTTONDOWN: + SetFocus(hwnd); + areaMouseEvent(hwnd, data, 1, FALSE, heldButtons, lParam); + return 0; + case WM_LBUTTONUP: + areaMouseEvent(hwnd, data, 1, TRUE, heldButtons, lParam); + return 0; + case WM_MBUTTONDOWN: + SetFocus(hwnd); + areaMouseEvent(hwnd, data, 2, FALSE, heldButtons, lParam); + return 0; + case WM_MBUTTONUP: + areaMouseEvent(hwnd, data, 2, TRUE, heldButtons, lParam); + return 0; + case WM_RBUTTONDOWN: + SetFocus(hwnd); + areaMouseEvent(hwnd, data, 3, FALSE, heldButtons, lParam); + return 0; + case WM_RBUTTONUP: + areaMouseEvent(hwnd, data, 3, TRUE, heldButtons, lParam); + return 0; + case WM_XBUTTONDOWN: + SetFocus(hwnd); + // values start at 1; we want them to start at 4 + which = (DWORD) GET_XBUTTON_WPARAM(wParam) + 3; + heldButtons = (uintptr_t) GET_KEYSTATE_WPARAM(wParam); + areaMouseEvent(hwnd, data, which, FALSE, heldButtons, lParam); + return TRUE; // XBUTTON messages are different! + case WM_XBUTTONUP: + which = (DWORD) GET_XBUTTON_WPARAM(wParam) + 3; + heldButtons = (uintptr_t) GET_KEYSTATE_WPARAM(wParam); + areaMouseEvent(hwnd, data, which, TRUE, heldButtons, lParam); + return TRUE; + case msgAreaKeyDown: + return (LRESULT) areaKeyEvent(data, FALSE, wParam, lParam); + case msgAreaKeyUp: + return (LRESULT) areaKeyEvent(data, TRUE, wParam, lParam); + case msgAreaSizeChanged: + adjustAreaScrollbars(hwnd, data); + repaintArea(hwnd, NULL); // this calls for an update + return 0; + case msgAreaGetScroll: + getScrollPos(hwnd, (int *) wParam, (int *) lParam); + return 0; + case msgAreaRepaint: + repaintArea(hwnd, (RECT *) lParam); + return 0; + case msgAreaRepaintAll: + repaintArea(hwnd, NULL); + return 0; + default: + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + xmissedmsg("Area", "areaWndProc()", uMsg); + return 0; // unreached +} + +DWORD makeAreaWindowClass(char **errmsg) +{ + WNDCLASSW wc; + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.style = CS_HREDRAW | CS_VREDRAW; // no CS_DBLCLKS because do that manually + wc.lpszClassName = areaWindowClass; + wc.lpfnWndProc = areaWndProc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hArrowCursor, + wc.hbrBackground = NULL; // no brush; we handle WM_ERASEBKGND + wc.cbWndExtra = 3 * sizeof (LONG_PTR); // text field handle, text field current x, text field current y + if (RegisterClassW(&wc) == 0) { + *errmsg = "error registering Area window class"; + return GetLastError(); + } + return 0; +} + +HWND newArea(void *data) +{ + HWND hwnd; + + hwnd = CreateWindowExW( + 0, + areaWindowClass, L"", + WS_HSCROLL | WS_VSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, + msgwin, NULL, hInstance, data); + if (hwnd == NULL) + xpanic("container creation failed", GetLastError()); + return hwnd; +} + +static LRESULT CALLBACK areaTextFieldSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) +{ + switch (uMsg) { + case WM_KILLFOCUS: + ShowWindow(hwnd, SW_HIDE); + areaTextFieldDone((void *) data); + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + case WM_NCDESTROY: + if ((*fv_RemoveWindowSubclass)(hwnd, areaTextFieldSubProc, id) == FALSE) + xpanic("error removing Area TextField subclass (which was for handling WM_KILLFOCUS)", GetLastError()); + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + default: + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + } + xmissedmsg("Area TextField", "areaTextFieldSubProc()", uMsg); + return 0; // unreached +} + +HWND newAreaTextField(HWND area, void *goarea) +{ + HWND tf; + + tf = CreateWindowExW(textfieldExtStyle, + L"edit", L"", + textfieldStyle | WS_CHILD, + 0, 0, 0, 0, + area, NULL, hInstance, NULL); + if (tf == NULL) + xpanic("error making Area TextField", GetLastError()); + if ((*fv_SetWindowSubclass)(tf, areaTextFieldSubProc, 0, (DWORD_PTR) goarea) == FALSE) + xpanic("error subclassing Area TextField to give it its own WM_KILLFOCUS handler", GetLastError()); + return tf; +} + +void areaOpenTextField(HWND area, HWND textfield, int x, int y, int width, int height) +{ + int sx, sy; + int baseX, baseY; + LONG unused; + + getScrollPos(area, &sx, &sy); + x += sx; + y += sy; + calculateBaseUnits(textfield, &baseX, &baseY, &unused); + width = MulDiv(width, baseX, 4); + height = MulDiv(height, baseY, 8); + if (MoveWindow(textfield, x, y, width, height, TRUE) == 0) + xpanic("error moving Area TextField in Area.OpenTextFieldAt()", GetLastError()); + ShowWindow(textfield, SW_SHOW); + if (SetFocus(textfield) == NULL) + xpanic("error giving Area TextField focus", GetLastError()); +} + +void areaMarkTextFieldDone(HWND area) +{ + SetWindowLongPtrW(area, 0, (LONG_PTR) NULL); +} diff --git a/newctrl/area_windows.go b/newctrl/area_windows.go index 84be0fd..19c0250 100644 --- a/newctrl/area_windows.go +++ b/newctrl/area_windows.go @@ -324,12 +324,6 @@ var modonlykeys = map[C.WPARAM]Modifiers{ C.VK_RWIN: Super, } -//export storeAreaHWND -func storeAreaHWND(data unsafe.Pointer, hwnd C.HWND) { - a := (*area)(data) - a.hwnd = hwnd -} - //export areaResetClickCounter func areaResetClickCounter(data unsafe.Pointer) { a := (*area)(data) diff --git a/newctrl/common_windows.c b/newctrl/common_windows.c new file mode 100644 index 0000000..3f1a52e --- /dev/null +++ b/newctrl/common_windows.c @@ -0,0 +1,136 @@ +// 17 july 2014 + +#include "winapi_windows.h" +#include "_cgo_export.h" + +LRESULT getWindowTextLen(HWND hwnd) +{ + return SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0); +} + +void getWindowText(HWND hwnd, WPARAM n, LPWSTR buf) +{ + SetLastError(0); + if (SendMessageW(hwnd, WM_GETTEXT, n + 1, (LPARAM) buf) != (LRESULT) n) + xpanic("WM_GETTEXT did not copy the correct number of characters out", GetLastError()); +} + +void setWindowText(HWND hwnd, LPWSTR text) +{ + switch (SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM) text)) { + case FALSE: + xpanic("WM_SETTEXT failed", GetLastError()); + } +} + +void updateWindow(HWND hwnd) +{ + if (UpdateWindow(hwnd) == 0) + xpanic("error calling UpdateWindow()", GetLastError()); +} + +void *getWindowData(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) +{ + CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; + void *data; + + data = (void *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); + if (data == NULL) { + // the lpParam is available during WM_NCCREATE and WM_CREATE + if (uMsg == WM_NCCREATE) + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams)); + // act as if we're not ready yet, even during WM_NCCREATE (nothing important to the switch statement below happens here anyway) + *lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + return data; +} + +/* +all container windows (including the message-only window, hence this is not in container_windows.c) have to call the sharedWndProc() to ensure messages go in the right place and control colors are handled properly +*/ + +/* +all controls that have events receive the events themselves through subclasses +to do this, all container windows (including the message-only window; see http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q104069) forward WM_COMMAND to each control with this function, WM_NOTIFY with forwardNotify, etc. +*/ +static LRESULT forwardCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HWND control = (HWND) lParam; + + // don't generate an event if the control (if there is one) is unparented (a child of the message-only window) + if (control != NULL && IsChild(msgwin, control) == 0) + return SendMessageW(control, msgCOMMAND, wParam, lParam); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +static LRESULT forwardNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + NMHDR *nmhdr = (NMHDR *) lParam; + HWND control = nmhdr->hwndFrom; + + // don't generate an event if the control (if there is one) is unparented (a child of the message-only window) + if (control != NULL && IsChild(msgwin, control) == 0) + return SendMessageW(control, msgNOTIFY, wParam, lParam); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +BOOL sharedWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) +{ + switch (uMsg) { + case WM_COMMAND: + *lResult = forwardCommand(hwnd, uMsg, wParam, lParam); + return TRUE; + case WM_NOTIFY: + *lResult = forwardNotify(hwnd, uMsg, wParam, lParam); + return TRUE; + case WM_CTLCOLORSTATIC: + case WM_CTLCOLORBTN: + if (SetBkMode((HDC) wParam, TRANSPARENT) == 0) + xpanic("error setting transparent background mode to Labels", GetLastError()); + paintControlBackground((HWND) lParam, (HDC) wParam); + *lResult = (LRESULT) hollowBrush; + return TRUE; + } + return FALSE; +} + +void paintControlBackground(HWND hwnd, HDC dc) +{ + HWND parent; + RECT r; + POINT p; + int saved; + WCHAR classname[128] = L""; // more than enough to avoid collisions + + parent = hwnd; + do { + parent = GetParent(parent); + if (parent == NULL) + xpanic("error getting parent container of control in paintControlBackground()", GetLastError()); + // wine sends these messages early, yay... + if (parent == msgwin) + return; + parent = GetParent(parent); + if (parent == NULL) + xpanic("error getting parent control of control in paintControlBackground()", GetLastError()); + if (parent == msgwin) + return; + if (GetClassNameW(parent, classname, 128) == 0) + xpanic("error getting name of focused window class in paintControlBackground()", GetLastError()); + } while (_wcsicmp(classname, L"button") == 0); // skip groupboxes + if (GetWindowRect(hwnd, &r) == 0) + xpanic("error getting control's window rect in paintControlBackground()", GetLastError()); + // the above is a window rect; convert to client rect + p.x = r.left; + p.y = r.top; + if (ScreenToClient(parent, &p) == 0) + xpanic("error getting client origin of control in paintControlBackground()", GetLastError()); + saved = SaveDC(dc); + if (saved == 0) + xpanic("error saving DC info in paintControlBackground()", GetLastError()); + if (SetWindowOrgEx(dc, p.x, p.y, NULL) == 0) + xpanic("error moving window origin in paintControlBackground()", GetLastError()); + SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) dc, PRF_CLIENT); + if (RestoreDC(dc, saved) == 0) + xpanic("error restoring DC info in paintControlBackground()", GetLastError()); +} diff --git a/newctrl/container_windows.c b/newctrl/container_windows.c new file mode 100644 index 0000000..1f9b691 --- /dev/null +++ b/newctrl/container_windows.c @@ -0,0 +1,96 @@ +// 17 july 2014 + +#include "winapi_windows.h" +#include "_cgo_export.h" + +/* +This could all just be part of Window, but doing so just makes things complex. +In this case, I chose to waste a window handle rather than keep things super complex. +If this is seriously an issue in the future, I can roll it back. +*/ + +#define containerclass L"gouicontainer" + +static LRESULT CALLBACK containerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + void *data; + RECT r; + LRESULT lResult; + + data = getWindowData(hwnd, uMsg, wParam, lParam, &lResult); + if (data == NULL) + return lResult; + if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult)) + return lResult; + switch (uMsg) { + case WM_SIZE: + if (GetClientRect(hwnd, &r) == 0) + xpanic("error getting client rect for Window in WM_SIZE", GetLastError()); + containerResize(data, &r); + return 0; + default: + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + xmissedmsg("container", "containerWndProc()", uMsg); + return 0; // unreached +} + +DWORD makeContainerWindowClass(char **errmsg) +{ + WNDCLASSW wc; + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpfnWndProc = containerWndProc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hArrowCursor; + wc.hbrBackground = NULL;//(HBRUSH) (COLOR_BTNFACE + 1); + wc.lpszClassName = containerclass; + if (RegisterClassW(&wc) == 0) { + *errmsg = "error registering container window class"; + return GetLastError(); + } + return 0; +} + +HWND newContainer(void *data) +{ + HWND hwnd; + + hwnd = CreateWindowExW( + WS_EX_CONTROLPARENT | WS_EX_TRANSPARENT, + containerclass, L"", + WS_CHILD | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, + msgwin, NULL, hInstance, data); + if (hwnd == NULL) + xpanic("container creation failed", GetLastError()); + return hwnd; +} + +void calculateBaseUnits(HWND hwnd, int *baseX, int *baseY, LONG *internalLeading) +{ + HDC dc; + HFONT prevFont; + TEXTMETRICW tm; + SIZE size; + + dc = GetDC(hwnd); + if (dc == NULL) + xpanic("error getting DC for preferred size calculations", GetLastError()); + prevFont = (HFONT) SelectObject(dc, controlFont); + if (prevFont == NULL) + xpanic("error loading control font into device context for preferred size calculation", GetLastError()); + if (GetTextMetricsW(dc, &tm) == 0) + xpanic("error getting text metrics for preferred size calculations", GetLastError()); + if (GetTextExtentPoint32W(dc, L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &size) == 0) + xpanic("error getting text extent point for preferred size calculations", GetLastError()); + *baseX = (int) ((size.cx / 26 + 1) / 2); + *baseY = (int) tm.tmHeight; + *internalLeading = tm.tmInternalLeading; + if (SelectObject(dc, prevFont) != controlFont) + xpanic("error restoring previous font into device context after preferred size calculations", GetLastError()); + if (ReleaseDC(hwnd, dc) == 0) + xpanic("error releasing DC for preferred size calculations", GetLastError()); +} diff --git a/newctrl/container_windows.go b/newctrl/container_windows.go index fe40f01..91aee8f 100644 --- a/newctrl/container_windows.go +++ b/newctrl/container_windows.go @@ -39,10 +39,7 @@ func makeContainerWindowClass() error { func newContainer() *container { c := new(container) - hwnd := C.newContainer(unsafe.Pointer(c)) - if hwnd != c.hwnd { - panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in container (%p) differ", hwnd, c.hwnd)) - } + c.controlSingleHWND = newControlSingleHWND(C.newContainer(unsafe.Pointer(c))) // don't set preferredSize(); it should never be called return c } @@ -61,12 +58,6 @@ func (c *container) parent() *controlParent { return &controlParent{c.hwnd} } -//export storeContainerHWND -func storeContainerHWND(data unsafe.Pointer, hwnd C.HWND) { - c := (*container)(data) - c.hwnd = hwnd -} - // For Windows, Microsoft just hands you a list of preferred control sizes as part of the MSDN documentation and tells you to roll with it. // These sizes are given in "dialog units", which are independent of the font in use. // We need to convert these into standard pixels, which requires we get the device context of the OS window. diff --git a/newctrl/winapi_windows.h b/newctrl/winapi_windows.h new file mode 100644 index 0000000..56f950d --- /dev/null +++ b/newctrl/winapi_windows.h @@ -0,0 +1,154 @@ +// 17 july 2014 + +// cgo will include this file multiple times +#ifndef __GO_UI_WINAPI_WINDOWS_H__ +#define __GO_UI_WINAPI_WINDOWS_H__ + +#define UNICODE +#define _UNICODE +#define STRICT +#define STRICT_TYPED_ITEMIDS +// get Windows version right; right now Windows XP +#define WINVER 0x0501 +#define _WIN32_WINNT 0x0501 +#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */ +#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */ +#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// global messages unique to everything +enum { + msgRequest = WM_APP + 1, // + 1 just to be safe + msgCOMMAND, // WM_COMMAND proxy; see forwardCommand() in controls_windows.go + msgNOTIFY, // WM_NOTIFY proxy + msgAreaSizeChanged, + msgAreaGetScroll, + msgAreaRepaint, + msgAreaRepaintAll, + msgTabCurrentTabHasChildren, + msgAreaKeyDown, + msgAreaKeyUp, + msgLoadImageList, + msgTableMakeInitialCheckboxImageList, + msgOpenFileDone, +}; + +// uitask_windows.c +extern void uimsgloop(void); +extern void issue(void *); +extern HWND msgwin; +extern DWORD makemsgwin(char **); + +// comctl32_windows.c +extern DWORD initCommonControls(char **); +// these are listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason +extern BOOL (*WINAPI fv_SetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR); +extern BOOL (*WINAPI fv_RemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR); +extern LRESULT (*WINAPI fv_DefSubclassProc)(HWND, UINT, WPARAM, LPARAM); +extern HIMAGELIST (*WINAPI fv_ImageList_Create)(int, int, UINT, int, int); +extern int (*WINAPI fv_ImageList_Add)(HIMAGELIST, HBITMAP, HBITMAP); +extern BOOL (*WINAPI fv_ImageList_Destroy)(HIMAGELIST); + +// control_windows.c +extern HWND newControl(LPWSTR, DWORD, DWORD); +extern void controlSetParent(HWND, HWND); +extern void controlSetControlFont(HWND); +extern void moveWindow(HWND, int, int, int, int); +extern LONG controlTextLength(HWND, LPWSTR); + +// basicctrls_windows.c +extern void setButtonSubclass(HWND, void *); +extern void setCheckboxSubclass(HWND, void *); +extern BOOL checkboxChecked(HWND); +extern void checkboxSetChecked(HWND, BOOL); +#define textfieldStyle (ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP) +#define textfieldExtStyle (WS_EX_CLIENTEDGE) +extern void setTextFieldSubclass(HWND, void *); +extern void textfieldSetAndShowInvalidBalloonTip(HWND, WCHAR *); +extern void textfieldHideInvalidBalloonTip(HWND); + +// init_windows.c +extern HINSTANCE hInstance; +extern int nCmdShow; +extern HICON hDefaultIcon; +extern HCURSOR hArrowCursor; +extern HFONT controlFont; +extern HFONT titleFont; +extern HFONT smallTitleFont; +extern HFONT menubarFont; +extern HFONT statusbarFont; +extern HBRUSH hollowBrush; +extern DWORD initWindows(char **); + +// window_windows.c +extern DWORD makeWindowWindowClass(char **); +extern HWND newWindow(LPWSTR, int, int, void *); +extern void windowClose(HWND); + +// common_windows.c +extern LRESULT getWindowTextLen(HWND); +extern void getWindowText(HWND, WPARAM, LPWSTR); +extern void setWindowText(HWND, LPWSTR); +extern void updateWindow(HWND); +extern void *getWindowData(HWND, UINT, WPARAM, LPARAM, LRESULT *); +extern BOOL sharedWndProc(HWND, UINT, WPARAM, LPARAM, LRESULT *); +extern void paintControlBackground(HWND, HDC); + +// tab_windows.go +extern LPWSTR xWC_TABCONTROL; +extern void setTabSubclass(HWND, void *); +extern void tabAppend(HWND, LPWSTR); +extern void tabGetContentRect(HWND, RECT *); +extern LONG tabGetTabHeight(HWND); +extern void tabEnterChildren(HWND); +extern void tabLeaveChildren(HWND); + +// table_windows.go +extern LPWSTR xWC_LISTVIEW; +extern void setTableSubclass(HWND, void *); +extern void tableAppendColumn(HWND, int, LPWSTR); +extern void tableUpdate(HWND, int); +extern void tableAddExtendedStyles(HWND, LPARAM); +extern void tableAutosizeColumns(HWND, int); +extern intptr_t tableSelectedItem(HWND); +extern void tableSelectItem(HWND, intptr_t); + +// container_windows.c +extern DWORD makeContainerWindowClass(char **); +extern HWND newContainer(void *); +extern void calculateBaseUnits(HWND, int *, int *, LONG *); + +// area_windows.c +#define areaWindowClass L"gouiarea" +extern void repaintArea(HWND, RECT *); +extern DWORD makeAreaWindowClass(char **); +extern HWND newArea(void *); +extern HWND newAreaTextField(HWND, void *); +extern void areaOpenTextField(HWND, HWND, int, int, int, int); +extern void areaMarkTextFieldDone(HWND); + +// imagelist_windows.c +extern HBITMAP unscaledBitmap(void *, intptr_t, intptr_t); +extern HIMAGELIST newImageList(int, int); +extern void addImage(HIMAGELIST, HWND, HBITMAP, int, int, int, int); +extern void applyImageList(HWND, UINT, WPARAM, HIMAGELIST, HIMAGELIST); +enum { + checkboxStateChecked = 1 << 0, + checkboxStateHot = 1 << 1, + checkboxStatePushed = 1 << 2, + checkboxnStates = 1 << 3, +}; +extern HIMAGELIST makeCheckboxImageList(HWND, HTHEME *); + +// dialog_windows.c +extern void openFile(HWND, void *); + +#endif diff --git a/newctrl/window_windows.c b/newctrl/window_windows.c new file mode 100644 index 0000000..a4eefb3 --- /dev/null +++ b/newctrl/window_windows.c @@ -0,0 +1,84 @@ +// 17 july 2014 + +#include "winapi_windows.h" +#include "_cgo_export.h" + +#define windowclass L"gouiwindow" + +#define windowBackground ((HBRUSH) (COLOR_BTNFACE + 1)) + +static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + void *data; + RECT r; + LRESULT lResult; + + data = (void *) getWindowData(hwnd, uMsg, wParam, lParam, &lResult); + if (data == NULL) + return lResult; + if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult)) + return lResult; + switch (uMsg) { + case WM_PRINTCLIENT: + // the return value of this message is not documented + // just to be safe, do this first, returning its value later + lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam); + if (GetClientRect(hwnd, &r) == 0) + xpanic("error getting client rect for Window in WM_PRINTCLIENT", GetLastError()); + if (FillRect((HDC) wParam, &r, windowBackground) == 0) + xpanic("error filling WM_PRINTCLIENT DC with window background color", GetLastError()); + return lResult; + case WM_SIZE: + if (GetClientRect(hwnd, &r) == 0) + xpanic("error getting client rect for Window in WM_SIZE", GetLastError()); + windowResize(data, &r); + return 0; + case WM_CLOSE: + windowClosing(data); + return 0; + default: + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + xmissedmsg("Window", "windowWndProc()", uMsg); + return 0; // unreached +} + +DWORD makeWindowWindowClass(char **errmsg) +{ + WNDCLASSW wc; + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpfnWndProc = windowWndProc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hArrowCursor; + wc.hbrBackground = windowBackground; + wc.lpszClassName = windowclass; + if (RegisterClassW(&wc) == 0) { + *errmsg = "error registering Window window class"; + return GetLastError(); + } + return 0; +} + +HWND newWindow(LPWSTR title, int width, int height, void *data) +{ + HWND hwnd; + + hwnd = CreateWindowExW( + 0, + windowclass, title, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + width, height, + NULL, NULL, hInstance, data); + if (hwnd == NULL) + xpanic("Window creation failed", GetLastError()); + return hwnd; +} + +void windowClose(HWND hwnd) +{ + if (DestroyWindow(hwnd) == 0) + xpanic("error destroying window", GetLastError()); +} diff --git a/newctrl/window_windows.go b/newctrl/window_windows.go index 2286ade..8537be8 100644 --- a/newctrl/window_windows.go +++ b/newctrl/window_windows.go @@ -33,14 +33,10 @@ func makeWindowWindowClass() error { func newWindow(title string, width int, height int, control Control) *window { w := &window{ - // hwnd set in WM_CREATE handler closing: newEvent(), child: control, } - hwnd := C.newWindow(toUTF16(title), C.int(width), C.int(height), unsafe.Pointer(w)) - if hwnd != w.hwnd { - panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in Window (%p) differ", hwnd, w.hwnd)) - } + w.hwnd = C.newWindow(toUTF16(title), C.int(width), C.int(height), unsafe.Pointer(w)) hresult := C.EnableThemeDialogTexture(w.hwnd, C.ETDT_ENABLE|C.ETDT_USETABTEXTURE) if hresult != C.S_OK { panic(fmt.Errorf("error setting tab background texture on Window; HRESULT: 0x%X", hresult)) @@ -87,12 +83,6 @@ func (w *window) SetMargined(margined bool) { w.margined = margined } -//export storeWindowHWND -func storeWindowHWND(data unsafe.Pointer, hwnd C.HWND) { - w := (*window)(data) - w.hwnd = hwnd -} - //export windowResize func windowResize(data unsafe.Pointer, r *C.RECT) { w := (*window)(data)