From 22123fb676d26d309d428a892c453e35e684e367 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 6 Jan 2015 17:27:41 -0500 Subject: Merged wintable/new/ into wintable/. --- wintable/accessibility.h | 287 +++++++++++++++++++++++++++++++++++++++++++ wintable/accessibilitynotes | 3 + wintable/api.h | 35 ++++++ wintable/checkboxes.h | 250 +++++++++++++++++++++++++++++++++++++ wintable/children.h | 19 +++ wintable/coord.h | 164 +++++++++++++++++++++++++ wintable/draw.h | 146 ++++++++++++++++++++++ wintable/events.h | 62 ++++++++++ wintable/header.h | 109 ++++++++++++++++ wintable/hscroll.h | 52 ++++++++ wintable/links | 3 + wintable/main.c | 242 ++++++++++++++++++++++++++++++++++++ wintable/new/accessibility.h | 287 ------------------------------------------- wintable/new/accessnotes | 2 - wintable/new/api.h | 35 ------ wintable/new/checkboxes.h | 250 ------------------------------------- wintable/new/children.h | 19 --- wintable/new/coord.h | 164 ------------------------- wintable/new/draw.h | 146 ---------------------- wintable/new/events.h | 62 ---------- wintable/new/header.h | 109 ---------------- wintable/new/hscroll.h | 52 -------- wintable/new/links | 3 - wintable/new/main.c | 242 ------------------------------------ wintable/new/resize.h | 39 ------ wintable/new/scroll.h | 130 -------------------- wintable/new/scrollbarseries | 24 ---- wintable/new/select.h | 255 -------------------------------------- wintable/new/util.h | 119 ------------------ wintable/new/vscroll.h | 66 ---------- wintable/resize.h | 39 ++++++ wintable/scroll.h | 130 ++++++++++++++++++++ wintable/scrollbarseries | 24 ++++ wintable/select.h | 255 ++++++++++++++++++++++++++++++++++++++ wintable/util.h | 119 ++++++++++++++++++ wintable/vscroll.h | 66 ++++++++++ 36 files changed, 2005 insertions(+), 2004 deletions(-) create mode 100644 wintable/accessibility.h create mode 100644 wintable/api.h create mode 100644 wintable/checkboxes.h create mode 100644 wintable/children.h create mode 100644 wintable/coord.h create mode 100644 wintable/draw.h create mode 100644 wintable/events.h create mode 100644 wintable/header.h create mode 100644 wintable/hscroll.h create mode 100644 wintable/links create mode 100644 wintable/main.c delete mode 100644 wintable/new/accessibility.h delete mode 100644 wintable/new/accessnotes delete mode 100644 wintable/new/api.h delete mode 100644 wintable/new/checkboxes.h delete mode 100644 wintable/new/children.h delete mode 100644 wintable/new/coord.h delete mode 100644 wintable/new/draw.h delete mode 100644 wintable/new/events.h delete mode 100644 wintable/new/header.h delete mode 100644 wintable/new/hscroll.h delete mode 100644 wintable/new/links delete mode 100644 wintable/new/main.c delete mode 100644 wintable/new/resize.h delete mode 100644 wintable/new/scroll.h delete mode 100644 wintable/new/scrollbarseries delete mode 100644 wintable/new/select.h delete mode 100644 wintable/new/util.h delete mode 100644 wintable/new/vscroll.h create mode 100644 wintable/resize.h create mode 100644 wintable/scroll.h create mode 100644 wintable/scrollbarseries create mode 100644 wintable/select.h create mode 100644 wintable/util.h create mode 100644 wintable/vscroll.h (limited to 'wintable') diff --git a/wintable/accessibility.h b/wintable/accessibility.h new file mode 100644 index 0000000..3900cd2 --- /dev/null +++ b/wintable/accessibility.h @@ -0,0 +1,287 @@ +// 24 december 2014 + +struct tableAcc { + IAccessibleVtbl *vtbl; + ULONG refcount; + struct table *t; + IAccessible *std; +}; + +#define TA ((struct tableAcc *) this) + +static HRESULT STDMETHODCALLTYPE tableAccQueryInterface(IAccessible *this, REFIID riid, void **ppvObject) +{ + if (ppvObject == NULL) + return E_POINTER; + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IDispatch) || + IsEqualIID(riid, &IID_IAccessible)) { + // TODO figure out what pointer to use here + TA->vtbl->AddRef(TA); + *ppvObject = (void *) this; + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; +} + +// TODO use InterlockedIncrement()/InterlockedDecrement() for these? + +static ULONG STDMETHODCALLTYPE tableAccAddRef(IAccessible *this) +{ + TA->refcount++; + // TODO correct? + return TA->refcount; +} + +static ULONG STDMETHODCALLTYPE tableAccRelease(IAccessible *this) +{ + TA->refcount--; + if (TA->refcount == 0) { + IAccessible_Release(TA->std); + tableFree(TA, "error freeing Table accessibility object"); + return 0; + } + return TA->refcount; +} + +// disregard IDispatch: http://msdn.microsoft.com/en-us/library/windows/desktop/cc307844.aspx +// TODO DISP_E_MEMBERNOTFOUND? http://blogs.msdn.com/b/saraford/archive/2004/08/20/which-controls-support-which-msaa-properties-and-how-these-controls-implement-msaa-properties.aspx +// TODO relegate these to the standard object? + +static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfoCount(IAccessible *this, UINT *pctinfo) +{ + if (pctinfo == NULL) + return E_INVALIDARG; + // TODO assign something to *pctinfo? + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfo(IAccessible *this, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) +{ + if (ppTInfo == NULL) + return E_INVALIDARG; + *ppTInfo = NULL; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE tableAccGetIDsOfNames(IAccessible *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) +{ + // TODO verify this one + if (rgDispId == NULL) + return E_INVALIDARG; + // TODO overwrite rgDispId? + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE tableAccInvoke(IAccessible *this, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) +{ + // TODO check this one + return E_NOTIMPL; +} + +// IAccessible + +static HRESULT STDMETHODCALLTYPE tableAccget_accParent(IAccessible *this, IDispatch **ppdispParent) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accChildCount(IAccessible *this, long *pcountChildren) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accChild(IAccessible *this, VARIANT varChild, IDispatch **ppdispChild) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accName(IAccessible *this, VARIANT varChild, BSTR *pszName) +{ +printf("tableAccget_accName()\n"); + // TODO check pointer + if (varChild.vt != VT_I4) { +printf("invalid arg\n"); + *pszName = NULL; + return E_INVALIDARG; + } + if (varChild.lVal == CHILDID_SELF) + ; // TODO standard accessible object + // TODO actually get the real name +printf("returning name\n"); + *pszName = SysAllocString(L"This is a test of the accessibility interface."); + // TODO check null pointer + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accValue(IAccessible *this, VARIANT varChild, BSTR *pszValue) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accDescription(IAccessible *this, VARIANT varChild, BSTR *pszDescription) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accRole(IAccessible *this, VARIANT varChild, VARIANT *pvarRole) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accState(IAccessible *this, VARIANT varChild, VARIANT *pvarState) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accHelp(IAccessible *this, VARIANT varChild, BSTR *pszHelp) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accHelpTopic(IAccessible *this, BSTR *pszHelpFile, VARIANT varChild, long *pidTopic) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accKeyboardShortcut(IAccessible *this, VARIANT varChild, BSTR *pszKeyboardShortcut) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accFocus(IAccessible *this, VARIANT *pvarChild) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accSelection(IAccessible *this, VARIANT *pvarChildren) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccget_accDefaultAction(IAccessible *this, VARIANT varChild, BSTR *pszDefaultAction) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccaccSelect(IAccessible *this, long flagsSelect, VARIANT varChild) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccaccLocation(IAccessible *this, long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild) +{ + // TODO + return IAccessible_accLocation(TA->std, pxLeft, pyTop, pcxWidth, pcyHeight, varChild); +} + +static HRESULT STDMETHODCALLTYPE tableAccaccNavigate(IAccessible *this, long navDir, VARIANT varStart, VARIANT *pvarEndUpAt) +{ + // TODO + return IAccessible_accNavigate(TA->std, navDir, varStart, pvarEndUpAt); +} + +static HRESULT STDMETHODCALLTYPE tableAccaccHitTest(IAccessible *this, long xLeft, long yTop, VARIANT *pvarChild) +{ + // TODO + return IAccessible_accHitTest(TA->std, xLeft, yTop, pvarChild); +} + +static HRESULT STDMETHODCALLTYPE tableAccaccDoDefaultAction(IAccessible *this, VARIANT varChild) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccput_accName(IAccessible *this, VARIANT varChild, BSTR szName) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static HRESULT STDMETHODCALLTYPE tableAccput_accValue(IAccessible *this, VARIANT varChild, BSTR szValue) +{ + // TODO + return DISP_E_MEMBERNOTFOUND; +} + +static const IAccessibleVtbl tableAccVtbl = { + .QueryInterface = tableAccQueryInterface, + .AddRef = tableAccAddRef, + .Release = tableAccRelease, + .GetTypeInfoCount = tableAccGetTypeInfoCount, + .GetTypeInfo = tableAccGetTypeInfo, + .GetIDsOfNames = tableAccGetIDsOfNames, + .Invoke = tableAccInvoke, + .get_accParent = tableAccget_accParent, + .get_accChildCount = tableAccget_accChildCount, + .get_accChild = tableAccget_accChild, + .get_accName = tableAccget_accName, + .get_accValue = tableAccget_accValue, + .get_accDescription = tableAccget_accDescription, + .get_accRole = tableAccget_accRole, + .get_accState = tableAccget_accState, + .get_accHelp = tableAccget_accHelp, + .get_accHelpTopic = tableAccget_accHelpTopic, + .get_accKeyboardShortcut = tableAccget_accKeyboardShortcut, + .get_accFocus = tableAccget_accFocus, + .get_accSelection = tableAccget_accSelection, + .get_accDefaultAction = tableAccget_accDefaultAction, + .accSelect = tableAccaccSelect, + .accLocation = tableAccaccLocation, + .accNavigate = tableAccaccNavigate, + .accHitTest = tableAccaccHitTest, + .accDoDefaultAction = tableAccaccDoDefaultAction, + .put_accName = tableAccput_accName, + .put_accValue = tableAccput_accValue, +}; + +static struct tableAcc *newTableAcc(struct table *t) +{ + struct tableAcc *ta; + HRESULT hr; + IAccessible *std; + + ta = (struct tableAcc *) tableAlloc(sizeof (struct tableAcc), "error creating Table accessibility object"); + ta->vtbl = &tableAccVtbl; + ta->vtbl->AddRef(ta); + ta->t = t; + hr = CreateStdAccessibleObject(t->hwnd, OBJID_CLIENT, &IID_IAccessible, &std); + if (hr != S_OK) + // TODO panichresult + panic("error creating standard accessible object for Table"); + ta->std = std; + return ta; +} + +static void freeTableAcc(struct tableAcc *ta) +{ + ta->t = NULL; + ta->vtbl->Release(ta); +} + +HANDLER(accessibilityHandler) +{ + if (uMsg != WM_GETOBJECT) + return FALSE; + if (((DWORD) lParam) != OBJID_CLIENT) + return FALSE; + *lResult = LresultFromObject(&IID_IAccessible, wParam, t->ta); + // TODO check *lResult + return TRUE; +} diff --git a/wintable/accessibilitynotes b/wintable/accessibilitynotes index e90e9cc..0d18cb3 100644 --- a/wintable/accessibilitynotes +++ b/wintable/accessibilitynotes @@ -5,3 +5,6 @@ http://blogs.msdn.com/b/saraford/archive/2004/08/20/which-controls-support-which http://msdn.microsoft.com/en-us/library/ms971325 TODO see if there's a MSAA-centric version of http://acccheck.codeplex.com/ + +http://msdn.microsoft.com/en-us/library/windows/desktop/dd318017%28v=vs.85%29.aspx +http://msdn.microsoft.com/en-us/library/windows/desktop/dd373624%28v=vs.85%29.aspx diff --git a/wintable/api.h b/wintable/api.h new file mode 100644 index 0000000..7ec9afc --- /dev/null +++ b/wintable/api.h @@ -0,0 +1,35 @@ +// 8 december 2014 + +static void addColumn(struct table *t, WPARAM wParam, LPARAM lParam) +{ + t->nColumns++; + t->columnTypes = (int *) tableRealloc(t->columnTypes, t->nColumns * sizeof (int), "adding the new column type to the current Table's list of column types"); + t->columnTypes[t->nColumns - 1] = (int) wParam; + // TODO make a panicNoErrCode() or panicArg() for this + if (t->columnTypes[t->nColumns - 1] >= nTableColumnTypes) + panic("invalid column type passed to tableAddColumn"); + headerAddColumn(t, (WCHAR *) lParam); + updateTableWidth(t); +} + +HANDLER(apiHandlers) +{ + switch (uMsg) { + case WM_SETFONT: + // don't free the old font; see http://blogs.msdn.com/b/oldnewthing/archive/2008/09/12/8945692.aspx + t->font = (HFONT) wParam; + SendMessageW(t->header, WM_SETFONT, wParam, lParam); + // TODO how to properly handle LOWORD(lParam) != FALSE? + // TODO depending on the result of the above, update table width to refresh t->headerHeight? + *lResult = 0; + return TRUE; + case WM_GETFONT: + *lResult = (LRESULT) (t->font); + return TRUE; + case tableAddColumn: + addColumn(t, wParam, lParam); + *lResult = 0; + return TRUE; + } + return FALSE; +} diff --git a/wintable/checkboxes.h b/wintable/checkboxes.h new file mode 100644 index 0000000..67bc745 --- /dev/null +++ b/wintable/checkboxes.h @@ -0,0 +1,250 @@ +// 16 august 2014 + +enum { + checkboxStateChecked = 1 << 0, + checkboxStateHot = 1 << 1, + checkboxStatePushed = 1 << 2, + checkboxnStates = 1 << 3, +}; + +// TODO actually make this +#define panichresult(a, b) panic(a) + +static UINT dfcState(int cbstate) +{ + UINT ret; + + ret = DFCS_BUTTONCHECK; + if ((cbstate & checkboxStateChecked) != 0) + ret |= DFCS_CHECKED; + if ((cbstate & checkboxStateHot) != 0) + ret |= DFCS_HOT; + if ((cbstate & checkboxStatePushed) != 0) + ret |= DFCS_PUSHED; + return ret; +} + +static void drawFrameControlCheckbox(HDC dc, RECT *r, int cbState) +{ + if (DrawFrameControl(dc, r, DFC_BUTTON, dfcState(cbState)) == 0) + panic("error drawing Table checkbox image with DrawFrameControl()"); +} + +static void getFrameControlCheckboxSize(HDC dc, int *width, int *height) +{ + // there's no real metric around + // let's use SM_CX/YSMICON and hope for the best + *width = GetSystemMetrics(SM_CXSMICON); + *height = GetSystemMetrics(SM_CYSMICON); +} + +static int themestates[checkboxnStates] = { + CBS_UNCHECKEDNORMAL, // 0 + CBS_CHECKEDNORMAL, // checked + CBS_UNCHECKEDHOT, // hot + CBS_CHECKEDHOT, // checked | hot + CBS_UNCHECKEDPRESSED, // pushed + CBS_CHECKEDPRESSED, // checked | pushed + CBS_UNCHECKEDPRESSED, // hot | pushed + CBS_CHECKEDPRESSED, // checked | hot | pushed +}; + +static SIZE getStateSize(HDC dc, int cbState, HTHEME theme) +{ + SIZE s; + HRESULT res; + + res = GetThemePartSize(theme, dc, BP_CHECKBOX, themestates[cbState], NULL, TS_DRAW, &s); + if (res != S_OK) + panichresult("error getting theme part size for Table checkboxes", res); + return s; +} + +static void drawThemeCheckbox(HDC dc, RECT *r, int cbState, HTHEME theme) +{ + HRESULT res; + + res = DrawThemeBackground(theme, dc, BP_CHECKBOX, themestates[cbState], r, NULL); + if (res != S_OK) + panichresult("error drawing Table checkbox image from theme", res); +} + +static void getThemeCheckboxSize(HDC dc, int *width, int *height, HTHEME theme) +{ + SIZE size; + int cbState; + + size = getStateSize(dc, 0, theme); + for (cbState = 1; cbState < checkboxnStates; cbState++) { + SIZE against; + + against = getStateSize(dc, cbState, theme); + if (size.cx != against.cx || size.cy != against.cy) + // TODO make this use a no-information (or two ints) panic() + panic("size mismatch in Table checkbox states"); + } + *width = (int) size.cx; + *height = (int) size.cy; +} + +static void drawCheckbox(struct table *t, HDC dc, RECT *r, int cbState) +{ + if (t->theme != NULL) { + drawThemeCheckbox(dc, r, cbState, t->theme); + return; + } + drawFrameControlCheckbox(dc, r, cbState); +} + +static void freeCheckboxThemeData(struct table *t) +{ + if (t->theme != NULL) { + HRESULT res; + + res = CloseThemeData(t->theme); + if (res != S_OK) + panichresult("error closing Table checkbox theme", res); + t->theme = NULL; + } +} + +static void loadCheckboxThemeData(struct table *t) +{ + HDC dc; + + freeCheckboxThemeData(t); + dc = GetDC(t->hwnd); + if (dc == NULL) + panic("error getting Table DC for loading checkbox theme data"); + // ignore error; if it can't be done, we can fall back to DrawFrameControl() + if (t->theme == NULL) // try to open the theme + t->theme = OpenThemeData(t->hwnd, L"button"); + if (t->theme != NULL) // use the theme + getThemeCheckboxSize(dc, &(t->checkboxWidth), &(t->checkboxHeight), t->theme); + else // couldn't open; fall back + getFrameControlCheckboxSize(dc, &(t->checkboxWidth), &(t->checkboxHeight)); + if (ReleaseDC(t->hwnd, dc) == 0) + panic("error releasing Table DC for loading checkbox theme data"); +} + +static void redrawCheckboxRect(struct table *t, LPARAM lParam) +{ + struct rowcol rc; + RECT r; + POINT pt; + + rc = lParamToRowColumn(t, lParam); + if (rc.row == -1 && rc.column == -1) + return; + if (t->columnTypes[rc.column] != tableColumnCheckbox) + return; + if (!rowColumnToClientRect(t, rc, &r)) + return; + // TODO only the checkbox rect? + if (InvalidateRect(t->hwnd, &r, TRUE) == 0) + panic("error redrawing Table checkbox rect for mouse events"); +} + +HANDLER(checkboxMouseMoveHandler) +{ + // don't actually check to see if the mouse is in the checkbox rect + // if there's scrolling without mouse movement, that will change + // instead, drawCell() will handle it + if (!t->checkboxMouseOverLast) { + t->checkboxMouseOverLast = TRUE; + retrack(t); + } else + redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); + t->checkboxMouseOverLastPoint = lParam; + redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); + *lResult = 0; + return TRUE; +} + +HANDLER(checkboxMouseLeaveHandler) +{ + if (t->checkboxMouseOverLast) + redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); + // TODO remember what I wanted to do here in the case of a held mouse button + t->checkboxMouseOverLast = FALSE; + *lResult = 0; + return TRUE; +} + +HANDLER(checkboxMouseDownHandler) +{ + struct rowcol rc; + RECT r; + POINT pt; + + rc = lParamToRowColumn(t, lParam); + if (rc.row == -1 || rc.column == -1) + return FALSE; + if (t->columnTypes[rc.column] != tableColumnCheckbox) + return FALSE; + if (!rowColumnToClientRect(t, rc, &r)) + return FALSE; + toCheckboxRect(t, &r, 0); + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + if (PtInRect(&r, pt) == 0) + return FALSE; + t->checkboxMouseDown = TRUE; + t->checkboxMouseDownRow = rc.row; + t->checkboxMouseDownColumn = rc.column; + // TODO redraw the whole cell? + if (InvalidateRect(t->hwnd, &r, TRUE) == 0) + panic("error redrawing Table checkbox after mouse down"); + *lResult = 0; + return TRUE; +} + +// TODO get rid of this +struct rowcol lastCheckbox; + +HANDLER(checkboxMouseUpHandler) +{ + struct rowcol rc; + RECT r; + POINT pt; + + if (!t->checkboxMouseDown) + return FALSE; + // the logic behind goto wrongUp is that the mouse must be released on the same checkbox + rc = lParamToRowColumn(t, lParam); + if (rc.row == -1 || rc.column == -1) + goto wrongUp; + if (rc.row != t->checkboxMouseDownRow || rc.column != t->checkboxMouseDownColumn) + goto wrongUp; + if (t->columnTypes[rc.column] != tableColumnCheckbox) + goto wrongUp; + if (!rowColumnToClientRect(t, rc, &r)) + goto wrongUp; + toCheckboxRect(t, &r, 0); + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + if (PtInRect(&r, pt) == 0) + goto wrongUp; + // TODO send toggle event +lastCheckbox = rc; + t->checkboxMouseDown = FALSE; + // TODO redraw the whole cell? + if (InvalidateRect(t->hwnd, &r, TRUE) == 0) + panic("error redrawing Table checkbox after mouse up"); + *lResult = 0; + return TRUE; +wrongUp: + if (t->checkboxMouseDown) { + rc.row = t->checkboxMouseDownRow; + rc.column = t->checkboxMouseDownColumn; + if (rowColumnToClientRect(t, rc, &r)) + // TODO only the checkbox rect? + if (InvalidateRect(t->hwnd, &r, TRUE) == 0) + panic("error redrawing Table checkbox rect for aborted mouse up event"); + } + // if we landed on another checkbox, be sure to draw that one too + if (t->checkboxMouseOverLast) + redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); + t->checkboxMouseDown = FALSE; + return FALSE; // TODO really? +} diff --git a/wintable/children.h b/wintable/children.h new file mode 100644 index 0000000..6a3aff3 --- /dev/null +++ b/wintable/children.h @@ -0,0 +1,19 @@ +// 7 december 2014 + +static const handlerfunc commandHandlers[] = { + NULL, +}; + +static const handlerfunc notifyHandlers[] = { + headerNotifyHandler, + NULL, +}; + +HANDLER(childrenHandlers) +{ + if (uMsg == WM_COMMAND) + return runHandlers(commandHandlers, t, uMsg, wParam, lParam, lResult); + if (uMsg == WM_NOTIFY) + return runHandlers(notifyHandlers, t, uMsg, wParam, lParam, lResult); + return FALSE; +} diff --git a/wintable/coord.h b/wintable/coord.h new file mode 100644 index 0000000..52a70c9 --- /dev/null +++ b/wintable/coord.h @@ -0,0 +1,164 @@ +// 4 december 2014 + +// TODO find a better place for these (metrics.h?) +static LONG textHeight(struct table *t, HDC dc, BOOL select) +{ + BOOL release; + HFONT prevfont, newfont; + TEXTMETRICW tm; + + release = FALSE; + if (dc == NULL) { + dc = GetDC(t->hwnd); + if (dc == NULL) + panic("error getting Table DC for rowHeight()"); + release = TRUE; + } + if (select) + prevfont = selectFont(t, dc, &newfont); + if (GetTextMetricsW(dc, &tm) == 0) + panic("error getting text metrics for rowHeight()"); + if (select) + deselectFont(dc, prevfont, newfont); + if (release) + if (ReleaseDC(t->hwnd, dc) == 0) + panic("error releasing Table DC for rowHeight()"); + return tm.tmHeight; +} + +#define tableImageWidth() GetSystemMetrics(SM_CXSMICON) +#define tableImageHeight() GetSystemMetrics(SM_CYSMICON) + +// TODO omit column types that are not present? +static LONG rowHeight(struct table *t, HDC dc, BOOL select) +{ + LONG height; + LONG tmHeight; + + height = tableImageHeight(); // start with this to avoid two function calls + tmHeight = textHeight(t, dc, select); + if (height < tmHeight) + height = tmHeight; + if (height < t->checkboxHeight) + height = t->checkboxHeight; + return height; +} + +#define rowht(t) rowHeight(t, NULL, TRUE) + +struct rowcol { + intptr_t row; + intptr_t column; +}; + +static struct rowcol clientCoordToRowColumn(struct table *t, POINT pt) +{ + RECT r; + struct rowcol rc; + intptr_t i; + + if (GetClientRect(t->hwnd, &r) == 0) + panic("error getting Table client rect in clientCoordToRowColumn()"); + r.top += t->headerHeight; + if (PtInRect(&r, pt) == 0) + goto outside; + + // the row is easy + pt.y -= t->headerHeight; + rc.row = (pt.y / rowht(t)) + t->vscrollpos; + if (rc.row >= t->count) + goto outside; + + // the column... not so much + // we scroll p.x, then subtract column widths until we cross the left edge of the control + pt.x += t->hscrollpos; + rc.column = 0; + for (i = 0; i < t->nColumns; i++) { + pt.x -= columnWidth(t, i); + // use <, not <=, here: + // assume r.left and t->hscrollpos == 0; + // given the first column is 100 wide, + // pt.x == 0 (first pixel of col 0) -> p.x - 100 == -100 < 0 -> break + // pt.x == 99 (last pixel of col 0) -> p.x - 100 == -1 < 0 -> break + // pt.x == 100 (first pixel of col 1) -> p.x - 100 == 0 >= 0 -> next column + if (pt.x < r.left) + break; + rc.column++; + } + if (rc.column >= t->nColumns) + goto outside; + + return rc; + +outside: + rc.row = -1; + rc.column = -1; + return rc; +} + +// same as client coordinates, but stored in a lParam (like the various mouse messages provide) +static struct rowcol lParamToRowColumn(struct table *t, LPARAM lParam) +{ + POINT pt; + + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + return clientCoordToRowColumn(t, pt); +} + +// returns TRUE if the row is visible (even partially visible) and thus has a rectangle in the client area; FALSE otherwise +static BOOL rowColumnToClientRect(struct table *t, struct rowcol rc, RECT *r) +{ + RECT client; + RECT out; // don't change r if we return FALSE + LONG height; + intptr_t xpos; + intptr_t i; + + if (rc.row < t->vscrollpos) + return FALSE; + rc.row -= t->vscrollpos; // align with client.top + + if (GetClientRect(t->hwnd, &client) == 0) + panic("error getting Table client rect in rowColumnToClientRect()"); + client.top += t->headerHeight; + + height = rowht(t); + out.top = client.top + (rc.row * height); + if (out.top >= client.bottom) // >= because RECT.bottom is the first pixel outside the rectangle + return FALSE; + out.bottom = out.top + height; + + // and again the columns are the hard part + // so we start with client.left - t->hscrollpos, then keep adding widths until we get to the column we want + xpos = client.left - t->hscrollpos; + for (i = 0; i < rc.column; i++) + xpos += columnWidth(t, i); + // did we stray too far to the right? if so it's not visible + if (xpos >= client.right) // >= because RECT.right is the first pixel outside the rectangle + return FALSE; + out.left = xpos; + out.right = xpos + columnWidth(t, rc.column); + // and is this too far to the left? + if (out.right < client.left) // < because RECT.left is the first pixel inside the rect + return FALSE; + + *r = out; + return TRUE; +} + +// TODO idealCoordToRowColumn/rowColumnToIdealCoord? + +static void toCellContentRect(struct table *t, RECT *r, LRESULT xoff, intptr_t width, intptr_t height) +{ + if (xoff == 0) + xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0); + r->left += xoff; + if (width != 0) + r->right = r->left + width; + if (height != 0) + // TODO vertical center + r->bottom = r->top + height; +} + +#define toCheckboxRect(t, r, xoff) toCellContentRect(t, r, xoff, t->checkboxWidth, t->checkboxHeight) diff --git a/wintable/draw.h b/wintable/draw.h new file mode 100644 index 0000000..59a1ea1 --- /dev/null +++ b/wintable/draw.h @@ -0,0 +1,146 @@ +// 8 december 2014 + +struct drawCellParams { + intptr_t row; + intptr_t column; + LONG x; + LONG y; + LONG width; // of column + LONG height; // rowHeight() + LRESULT xoff; // result of HDM_GETBITMAPMARGIN +}; + +static void drawCell(struct table *t, HDC dc, struct drawCellParams *p) +{ + RECT r; + WCHAR msg[200]; + int n; + HBRUSH background; + int textColor; + POINT pt; + int cbState; + RECT cellrect; + + // TODO verify these two + background = (HBRUSH) (COLOR_WINDOW + 1); + textColor = COLOR_WINDOWTEXT; + if (t->selectedRow == p->row) { + // these are the colors wine uses (http://source.winehq.org/source/dlls/comctl32/listview.c) + // the two for unfocused are also suggested by http://stackoverflow.com/questions/10428710/windows-forms-inactive-highlight-color + background = (HBRUSH) (COLOR_HIGHLIGHT + 1); + textColor = COLOR_HIGHLIGHTTEXT; + if (GetFocus() != t->hwnd) { + background = (HBRUSH) (COLOR_BTNFACE + 1); + textColor = COLOR_BTNTEXT; + } + // TODO disabled + } + + r.left = p->x; + r.right = p->x + p->width; + r.top = p->y; + r.bottom = p->y + p->height; + if (FillRect(dc, &r, background) == 0) + panic("error filling Table cell background"); + cellrect = r; // save for drawing the focus rect + + switch (t->columnTypes[p->column]) { + case tableColumnText: + case tableColumnImage: // TODO + toCellContentRect(t, &r, p->xoff, 0, 0); // TODO get the text height + if (SetTextColor(dc, GetSysColor(textColor)) == CLR_INVALID) + panic("error setting Table cell text color"); + if (SetBkMode(dc, TRANSPARENT) == 0) + panic("error setting transparent text drawing mode for Table cell"); + n = wsprintf(msg, L"(%d,%d)", p->row, p->column); + if (DrawTextExW(dc, msg, n, &r, DT_END_ELLIPSIS | DT_LEFT | DT_NOPREFIX | DT_SINGLELINE, NULL) == 0) + panic("error drawing Table cell text"); + break; + case tableColumnCheckbox: + toCheckboxRect(t, &r, p->xoff); + cbState = 0; + if (p->row == lastCheckbox.row && p->column == lastCheckbox.column) + cbState |= checkboxStateChecked; + if (t->checkboxMouseDown) + if (p->row == t->checkboxMouseDownRow && p->column == t->checkboxMouseDownColumn) + cbState |= checkboxStatePushed; + if (t->checkboxMouseOverLast) { + pt.x = GET_X_LPARAM(t->checkboxMouseOverLastPoint); + pt.y = GET_Y_LPARAM(t->checkboxMouseOverLastPoint); + if (PtInRect(&r, pt) != 0) + cbState |= checkboxStateHot; + } + drawCheckbox(t, dc, &r, cbState); + break; + } + + // TODO in front of or behind the cell contents? + if (t->selectedRow == p->row && t->selectedColumn == p->column) + if (DrawFocusRect(dc, &cellrect) == 0) + panic("error drawing focus rect on current Table cell"); +} + +// TODO use cliprect +static void draw(struct table *t, HDC dc, RECT cliprect, RECT client) +{ + intptr_t i, j; + int x = 0; + HFONT prevfont, newfont; + struct drawCellParams p; + + prevfont = selectFont(t, dc, &newfont); + + client.top += t->headerHeight; + + ZeroMemory(&p, sizeof (struct drawCellParams)); + p.height = rowHeight(t, dc, FALSE); + p.xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0); + + p.y = client.top; + for (i = t->vscrollpos; i < t->count; i++) { + p.row = i; + p.x = client.left - t->hscrollpos; + for (j = 0; j < t->nColumns; j++) { + p.column = j; + p.width = columnWidth(t, p.column); + drawCell(t, dc, &p); + p.x += p.width; + } + p.y += p.height; + if (p.y >= client.bottom) // >= because RECT.bottom is the first pixel outside the rect + break; + } + + deselectFont(dc, prevfont, newfont); +} + +HANDLER(drawHandlers) +{ + HDC dc; + PAINTSTRUCT ps; + RECT client; + RECT r; + + if (uMsg != WM_PAINT && uMsg != WM_PRINTCLIENT) + return FALSE; + if (GetClientRect(t->hwnd, &client) == 0) + panic("error getting client rect for Table painting"); + if (uMsg == WM_PAINT) { + dc = BeginPaint(t->hwnd, &ps); + if (dc == NULL) + panic("error beginning Table painting"); + r = ps.rcPaint; + } else { + dc = (HDC) wParam; + r = client; + } + draw(t, dc, r, client); + if (uMsg == WM_PAINT) + EndPaint(t->hwnd, &ps); + // this is correct for WM_PRINTCLIENT; see http://stackoverflow.com/a/27362258/3408572 + *lResult = 0; + return TRUE; +} + +// TODO redraw selected row on focus change +// TODO here or in select.h? diff --git a/wintable/events.h b/wintable/events.h new file mode 100644 index 0000000..fbd1522 --- /dev/null +++ b/wintable/events.h @@ -0,0 +1,62 @@ +// 5 december 2014 + +// TODO handler functions don't work here because you can't have more than one for the mouse ones... + +static const handlerfunc keyDownHandlers[] = { + keyDownSelectHandler, + NULL, +}; + +static const handlerfunc keyUpHandlers[] = { + NULL, +}; + +static const handlerfunc charHandlers[] = { + NULL, +}; + +static const handlerfunc mouseMoveHandlers[] = { + checkboxMouseMoveHandler, + NULL, +}; + +static const handlerfunc mouseLeaveHandlers[] = { + checkboxMouseLeaveHandler, + NULL, +}; + +static const handlerfunc lbuttonDownHandlers[] = { + mouseDownSelectHandler, + NULL, +}; + +static const handlerfunc lbuttonUpHandlers[] = { + checkboxMouseUpHandler, + NULL, +}; + +// TODO remove or something? depends on if we implement combobox and how +static const handlerfunc mouseWheelHandlers[] = { + NULL, +}; + +// TODO WM_MOUSEHOVER, other mouse buttons + +HANDLER(eventHandlers) +{ + switch (uMsg) { +#define eventHandler(msg, array) \ + case msg: \ + return runHandlers(array, t, uMsg, wParam, lParam, lResult); + eventHandler(WM_KEYDOWN, keyDownHandlers) + eventHandler(WM_KEYUP, keyUpHandlers) + eventHandler(WM_CHAR, charHandlers) + eventHandler(WM_MOUSEMOVE, mouseMoveHandlers) + eventHandler(WM_MOUSELEAVE, mouseLeaveHandlers) + eventHandler(WM_LBUTTONDOWN, lbuttonDownHandlers) + eventHandler(WM_LBUTTONUP, lbuttonUpHandlers) + eventHandler(WM_MOUSEWHEEL, mouseWheelHandlers) +#undef eventHandler + } + return FALSE; +} diff --git a/wintable/header.h b/wintable/header.h new file mode 100644 index 0000000..5652029 --- /dev/null +++ b/wintable/header.h @@ -0,0 +1,109 @@ +// 7 december 2014 + +// TODO verify header events (double-clicking on a divider, for example) + +static void makeHeader(struct table *t, HINSTANCE hInstance) +{ + t->header = CreateWindowExW(0, + WC_HEADERW, L"", + // don't set WS_VISIBLE; according to MSDN we create the header hidden as part of setting the initial position (http://msdn.microsoft.com/en-us/library/windows/desktop/ff485935%28v=vs.85%29.aspx) + // TODO WS_BORDER? + // TODO is HDS_HOTTRACK needed? + WS_CHILD | HDS_FULLDRAG | HDS_HORZ | HDS_HOTTRACK, + 0, 0, 0, 0, // no initial size + t->hwnd, (HMENU) 100, hInstance, NULL); + if (t->header == NULL) + panic("error creating Table header"); +} + +static void destroyHeader(struct table *t) +{ + if (DestroyWindow(t->header) == 0) + panic("error destroying Table header"); +} + +static void repositionHeader(struct table *t) +{ + RECT r; + WINDOWPOS wp; + HDLAYOUT l; + + if (GetClientRect(t->hwnd, &r) == 0) + panic("error getting client rect for Table header repositioning"); + // we fake horizontal scrolling here by extending the client rect to the left by the scroll position + r.left -= t->hscrollpos; + l.prc = &r; + l.pwpos = ℘ + if (SendMessageW(t->header, HDM_LAYOUT, 0, (LPARAM) (&l)) == FALSE) + panic("error getting new Table header position"); + if (SetWindowPos(t->header, wp.hwndInsertAfter, + wp.x, wp.y, wp.cx, wp.cy, + // see above on showing the header here instead of in the CreateWindowExW() call + wp.flags | SWP_SHOWWINDOW) == 0) + panic("error repositioning Table header"); + t->headerHeight = wp.cy; +} + +static void headerAddColumn(struct table *t, WCHAR *name) +{ + HDITEMW item; + + ZeroMemory(&item, sizeof (HDITEMW)); + item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT; + item.cxy = 200; // TODO + item.pszText = name; + item.fmt = HDF_LEFT | HDF_STRING; + // TODO replace 100 with (t->nColumns - 1) + if (SendMessage(t->header, HDM_INSERTITEM, (WPARAM) (100), (LPARAM) (&item)) == (LRESULT) (-1)) + panic("error adding column to Table header"); +} + +// TODO make a better name for this? +// TODO move to hscroll.h? +// TODO organize this in general... +// TODO because of this function's new extended functionality only hscrollto() is allowed to call repositionHeader() +static void updateTableWidth(struct table *t) +{ + HDITEMW item; + intptr_t i; + RECT client; + + t->width = 0; + // TODO count dividers? + // TODO use columnWidth() + for (i = 0; i < t->nColumns; i++) { + ZeroMemory(&item, sizeof (HDITEMW)); + item.mask = HDI_WIDTH; + if (SendMessageW(t->header, HDM_GETITEM, (WPARAM) i, (LPARAM) (&item)) == FALSE) + panic("error getting Table column width for updateTableWidth()"); + t->width += item.cxy; + } + + if (GetClientRect(t->hwnd, &client) == 0) + panic("error getting Table client rect in updateTableWidth()"); + t->hpagesize = client.right - client.left; + + // this part is critical: if we resize the columns to less than the client area width, then the following hscrollby() will make t->hscrollpos negative, which does very bad things + // note to self: do this regardless of whether the table width or the client area width was changed + if (t->hpagesize > t->width) + t->hpagesize = t->width; + + // do a dummy scroll to update the horizontal scrollbar to use the new width + hscrollby(t, 0); +} + +HANDLER(headerNotifyHandler) +{ + NMHDR *nmhdr = (NMHDR *) lParam; + + if (nmhdr->hwndFrom != t->header) + return FALSE; + if (nmhdr->code != HDN_ITEMCHANGED) + return FALSE; + updateTableWidth(t); + // TODO make more intelligent + InvalidateRect(t->hwnd, NULL, TRUE); + // TODO UpdateWindow()? + *lResult = 0; + return TRUE; +} diff --git a/wintable/hscroll.h b/wintable/hscroll.h new file mode 100644 index 0000000..1560490 --- /dev/null +++ b/wintable/hscroll.h @@ -0,0 +1,52 @@ +// 9 december 2014 + +// forward declaration needed here +static void repositionHeader(struct table *); + +static struct scrollParams hscrollParams(struct table *t) +{ + struct scrollParams p; + + ZeroMemory(&p, sizeof (struct scrollParams)); + p.pos = &(t->hscrollpos); + p.pagesize = t->hpagesize; + p.length = t->width; + p.scale = 1; + p.post = repositionHeader; + p.wheelCarry = &(t->hwheelCarry); + return p; +} + +static void hscrollto(struct table *t, intptr_t pos) +{ + struct scrollParams p; + + p = hscrollParams(t); + scrollto(t, SB_HORZ, &p, pos); +} + +static void hscrollby(struct table *t, intptr_t delta) +{ + struct scrollParams p; + + p = hscrollParams(t); + scrollby(t, SB_HORZ, &p, delta); +} + +static void hscroll(struct table *t, WPARAM wParam, LPARAM lParam) +{ + struct scrollParams p; + + p = hscrollParams(t); + scroll(t, SB_HORZ, &p, wParam, lParam); +} + +// TODO find out if we can indicriminately check for WM_WHEELHSCROLL +HANDLER(hscrollHandler) +{ + if (uMsg != WM_HSCROLL) + return FALSE; + hscroll(t, wParam, lParam); + *lResult = 0; + return TRUE; +} diff --git a/wintable/links b/wintable/links new file mode 100644 index 0000000..f0c9523 --- /dev/null +++ b/wintable/links @@ -0,0 +1,3 @@ +default list view message processing + http://msdn.microsoft.com/en-us/library/windows/desktop/bb774734%28v=vs.85%29.aspx + includes information on handling the edit control and event messages diff --git a/wintable/main.c b/wintable/main.c new file mode 100644 index 0000000..7ee7306 --- /dev/null +++ b/wintable/main.c @@ -0,0 +1,242 @@ +// 19 october 2014 +#define UNICODE +#define _UNICODE +#define STRICT +#define STRICT_TYPED_ITEMIDS +#define CINTERFACE +#define COBJMACROS +// 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 +#include + +// #qo LIBS: user32 kernel32 gdi32 comctl32 uxtheme ole32 oleaut32 oleacc uuid + +// TODO +// - should tablePanic be CALLBACK or some other equivalent macro? and definitely export initTable somehow, but which alias macro to use? +// - make panic messages grammatically correct ("Table error: adding...") +// - make access to column widths consistent; see whether HDITEMW.cxy == (ITEMRECT.right - ITEMRECT.left) +// - make sure all uses of t->headerHeight are ADDED to RECT.top +// - WM_THEMECHANGED, etc. +// - see if vertical centering is really what we want or if we just want to offset by a few pixels or so +// - going right from column 0 to column 2 with the right arrow key deselects +// - make sure all error messages involving InvalidateRect() are consistent with regards to "redrawing" and "queueing for redraw" +// - collect all resize-related tasks in a single function (so things like adding columns will refresh everything, not just horizontal scrolls; also would fix initial coordinates) +// - checkbox columns don't clip to the column width + +#define tableWindowClass L"gouitable" + +// start at WM_USER + 20 just in case for whatever reason we ever get the various dialog manager messages (see also http://blogs.msdn.com/b/oldnewthing/archive/2003/10/21/55384.aspx) +enum { + // wParam - one of the type constants + // lParam - column name as a Unicode string + tableAddColumn = WM_USER + 20, +}; + +enum { + tableColumnText, + tableColumnImage, + tableColumnCheckbox, + nTableColumnTypes, +}; + +static void (*tablePanic)(const char *, DWORD) = NULL; +#define panic(...) (*tablePanic)(__VA_ARGS__, GetLastError()) +#define abort $$$$ // prevent accidental use of abort() + +static BOOL (*WINAPI tableTrackMouseEvent)(LPTRACKMOUSEEVENT); + +// forward declaration +struct tableAcc; + +struct table { + HWND hwnd; + HWND header; + HFONT font; + intptr_t nColumns; + int *columnTypes; + intptr_t width; + intptr_t headerHeight; + intptr_t hscrollpos; // in logical units + intptr_t hpagesize; // in logical units + intptr_t count; + intptr_t vscrollpos; // in rows + intptr_t vpagesize; // in rows + int hwheelCarry; + int vwheelCarry; + intptr_t selectedRow; + intptr_t selectedColumn; + HTHEME theme; + int checkboxWidth; + int checkboxHeight; + BOOL checkboxMouseOverLast; + LPARAM checkboxMouseOverLastPoint; + BOOL checkboxMouseDown; + intptr_t checkboxMouseDownRow; + intptr_t checkboxMouseDownColumn; + struct tableAcc *ta; +}; + +#include "util.h" +#include "coord.h" +#include "scroll.h" +#include "hscroll.h" +#include "vscroll.h" +#include "select.h" +#include "checkboxes.h" +#include "events.h" +#include "header.h" +#include "children.h" +#include "resize.h" +#include "draw.h" +#include "api.h" +#include "accessibility.h" + +static const handlerfunc handlers[] = { + eventHandlers, + childrenHandlers, + resizeHandler, + drawHandlers, + apiHandlers, + hscrollHandler, + vscrollHandler, + accessibilityHandler, + NULL, +}; + +static void initDummyTableStuff(struct table *t) +{ + t->count = 100; + t->selectedRow = 2; + t->selectedColumn = 1; +} + +static LRESULT CALLBACK tableWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + struct table *t; + LRESULT lResult; + + t = (struct table *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); + if (t == NULL) { + // we have to do things this way because creating the header control will fail mysteriously if we create it first thing + // (which is fine; we can get the parent hInstance this way too) + // we use WM_CREATE because we have to use WM_DESTROY to destroy the header; we can't do it in WM_NCDESTROY because Windows will have destroyed it for us by then, and let's match message pairs to be safe + if (uMsg == WM_CREATE) { + CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; + + t = (struct table *) tableAlloc(sizeof (struct table), "error allocating internal Table data structure"); + t->hwnd = hwnd; + makeHeader(t, cs->hInstance); + t->selectedRow = -1; + t->selectedColumn = -1; + loadCheckboxThemeData(t); + t->ta = newTableAcc(t); +initDummyTableStuff(t); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) t); + } + // even if we did the above, fall through + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + if (uMsg == WM_DESTROY) { +printf("destroy\n"); + // TODO free appropriate (after figuring this part out) components of t + // TODO send EVENT_OBJECT_DESTROY events to accessibility listeners (when appropriate); see the note on proxy objects as well + freeTableAcc(t->ta); + t->ta = NULL; + freeCheckboxThemeData(t); + destroyHeader(t); + tableFree(t, "error allocating internal Table data structure"); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + if (runHandlers(handlers, t, uMsg, wParam, lParam, &lResult)) + return lResult; + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +static void deftablePanic(const char *msg, DWORD lastError) +{ + fprintf(stderr, "Table error: %s (last error %d)\n", msg, lastError); + fprintf(stderr, "This is the default Table error handler function; programs that use Table should provide their own instead.\nThe program will now break into the debugger.\n"); + DebugBreak(); +} + +void initTable(void (*panicfunc)(const char *msg, DWORD lastError), BOOL (*WINAPI tme)(LPTRACKMOUSEEVENT)) +{ + WNDCLASSW wc; + + tablePanic = panicfunc; + if (tablePanic == NULL) + tablePanic = deftablePanic; + if (tme == NULL) + // TODO errorless version + panic("must provide a TrackMouseEvent() to initTable()"); + tableTrackMouseEvent = tme; + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpszClassName = tableWindowClass; + wc.lpfnWndProc = tableWndProc; + wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); + wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // TODO correct? + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.hInstance = GetModuleHandle(NULL); + if (RegisterClassW(&wc) == 0) + panic("error registering Table window class"); +} + +int main(int argc, char *argv[]) +{ + HWND mainwin; + MSG msg; + INITCOMMONCONTROLSEX icc; + + ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX)); + icc.dwSize = sizeof (INITCOMMONCONTROLSEX); + icc.dwICC = ICC_LISTVIEW_CLASSES; + if (InitCommonControlsEx(&icc) == 0) + panic("(test program) error initializing comctl32.dll"); + initTable(NULL, _TrackMouseEvent); + mainwin = CreateWindowExW(0, + tableWindowClass, L"Main Window", + WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 400, 400, + NULL, NULL, GetModuleHandle(NULL), NULL); + if (mainwin == NULL) + panic("(test program) error creating Table"); + SendMessageW(mainwin, tableAddColumn, tableColumnText, (LPARAM) L"Column"); + SendMessageW(mainwin, tableAddColumn, tableColumnImage, (LPARAM) L"Column 2"); + SendMessageW(mainwin, tableAddColumn, tableColumnCheckbox, (LPARAM) L"Column 3"); + if (argc > 1) { + NONCLIENTMETRICSW ncm; + HFONT font; + + ZeroMemory(&ncm, sizeof (NONCLIENTMETRICSW)); + ncm.cbSize = sizeof (NONCLIENTMETRICSW); + if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICSW), &ncm, sizeof (NONCLIENTMETRICSW)) == 0) + panic("(test program) error getting non-client metrics"); + font = CreateFontIndirectW(&ncm.lfMessageFont); + if (font == NULL) + panic("(test program) error creating lfMessageFont HFONT"); + SendMessageW(mainwin, WM_SETFONT, (WPARAM) font, TRUE); + } + ShowWindow(mainwin, SW_SHOWDEFAULT); + if (UpdateWindow(mainwin) == 0) + panic("(test program) error updating window"); + while (GetMessageW(&msg, NULL, 0, 0) > 0) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + return 0; +} diff --git a/wintable/new/accessibility.h b/wintable/new/accessibility.h deleted file mode 100644 index 3900cd2..0000000 --- a/wintable/new/accessibility.h +++ /dev/null @@ -1,287 +0,0 @@ -// 24 december 2014 - -struct tableAcc { - IAccessibleVtbl *vtbl; - ULONG refcount; - struct table *t; - IAccessible *std; -}; - -#define TA ((struct tableAcc *) this) - -static HRESULT STDMETHODCALLTYPE tableAccQueryInterface(IAccessible *this, REFIID riid, void **ppvObject) -{ - if (ppvObject == NULL) - return E_POINTER; - if (IsEqualIID(riid, &IID_IUnknown) || - IsEqualIID(riid, &IID_IDispatch) || - IsEqualIID(riid, &IID_IAccessible)) { - // TODO figure out what pointer to use here - TA->vtbl->AddRef(TA); - *ppvObject = (void *) this; - return S_OK; - } - *ppvObject = NULL; - return E_NOINTERFACE; -} - -// TODO use InterlockedIncrement()/InterlockedDecrement() for these? - -static ULONG STDMETHODCALLTYPE tableAccAddRef(IAccessible *this) -{ - TA->refcount++; - // TODO correct? - return TA->refcount; -} - -static ULONG STDMETHODCALLTYPE tableAccRelease(IAccessible *this) -{ - TA->refcount--; - if (TA->refcount == 0) { - IAccessible_Release(TA->std); - tableFree(TA, "error freeing Table accessibility object"); - return 0; - } - return TA->refcount; -} - -// disregard IDispatch: http://msdn.microsoft.com/en-us/library/windows/desktop/cc307844.aspx -// TODO DISP_E_MEMBERNOTFOUND? http://blogs.msdn.com/b/saraford/archive/2004/08/20/which-controls-support-which-msaa-properties-and-how-these-controls-implement-msaa-properties.aspx -// TODO relegate these to the standard object? - -static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfoCount(IAccessible *this, UINT *pctinfo) -{ - if (pctinfo == NULL) - return E_INVALIDARG; - // TODO assign something to *pctinfo? - return E_NOTIMPL; -} - -static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfo(IAccessible *this, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) -{ - if (ppTInfo == NULL) - return E_INVALIDARG; - *ppTInfo = NULL; - return E_NOTIMPL; -} - -static HRESULT STDMETHODCALLTYPE tableAccGetIDsOfNames(IAccessible *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) -{ - // TODO verify this one - if (rgDispId == NULL) - return E_INVALIDARG; - // TODO overwrite rgDispId? - return E_NOTIMPL; -} - -static HRESULT STDMETHODCALLTYPE tableAccInvoke(IAccessible *this, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) -{ - // TODO check this one - return E_NOTIMPL; -} - -// IAccessible - -static HRESULT STDMETHODCALLTYPE tableAccget_accParent(IAccessible *this, IDispatch **ppdispParent) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accChildCount(IAccessible *this, long *pcountChildren) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accChild(IAccessible *this, VARIANT varChild, IDispatch **ppdispChild) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accName(IAccessible *this, VARIANT varChild, BSTR *pszName) -{ -printf("tableAccget_accName()\n"); - // TODO check pointer - if (varChild.vt != VT_I4) { -printf("invalid arg\n"); - *pszName = NULL; - return E_INVALIDARG; - } - if (varChild.lVal == CHILDID_SELF) - ; // TODO standard accessible object - // TODO actually get the real name -printf("returning name\n"); - *pszName = SysAllocString(L"This is a test of the accessibility interface."); - // TODO check null pointer - return S_OK; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accValue(IAccessible *this, VARIANT varChild, BSTR *pszValue) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accDescription(IAccessible *this, VARIANT varChild, BSTR *pszDescription) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accRole(IAccessible *this, VARIANT varChild, VARIANT *pvarRole) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accState(IAccessible *this, VARIANT varChild, VARIANT *pvarState) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accHelp(IAccessible *this, VARIANT varChild, BSTR *pszHelp) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accHelpTopic(IAccessible *this, BSTR *pszHelpFile, VARIANT varChild, long *pidTopic) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accKeyboardShortcut(IAccessible *this, VARIANT varChild, BSTR *pszKeyboardShortcut) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accFocus(IAccessible *this, VARIANT *pvarChild) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accSelection(IAccessible *this, VARIANT *pvarChildren) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccget_accDefaultAction(IAccessible *this, VARIANT varChild, BSTR *pszDefaultAction) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccaccSelect(IAccessible *this, long flagsSelect, VARIANT varChild) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccaccLocation(IAccessible *this, long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild) -{ - // TODO - return IAccessible_accLocation(TA->std, pxLeft, pyTop, pcxWidth, pcyHeight, varChild); -} - -static HRESULT STDMETHODCALLTYPE tableAccaccNavigate(IAccessible *this, long navDir, VARIANT varStart, VARIANT *pvarEndUpAt) -{ - // TODO - return IAccessible_accNavigate(TA->std, navDir, varStart, pvarEndUpAt); -} - -static HRESULT STDMETHODCALLTYPE tableAccaccHitTest(IAccessible *this, long xLeft, long yTop, VARIANT *pvarChild) -{ - // TODO - return IAccessible_accHitTest(TA->std, xLeft, yTop, pvarChild); -} - -static HRESULT STDMETHODCALLTYPE tableAccaccDoDefaultAction(IAccessible *this, VARIANT varChild) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccput_accName(IAccessible *this, VARIANT varChild, BSTR szName) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static HRESULT STDMETHODCALLTYPE tableAccput_accValue(IAccessible *this, VARIANT varChild, BSTR szValue) -{ - // TODO - return DISP_E_MEMBERNOTFOUND; -} - -static const IAccessibleVtbl tableAccVtbl = { - .QueryInterface = tableAccQueryInterface, - .AddRef = tableAccAddRef, - .Release = tableAccRelease, - .GetTypeInfoCount = tableAccGetTypeInfoCount, - .GetTypeInfo = tableAccGetTypeInfo, - .GetIDsOfNames = tableAccGetIDsOfNames, - .Invoke = tableAccInvoke, - .get_accParent = tableAccget_accParent, - .get_accChildCount = tableAccget_accChildCount, - .get_accChild = tableAccget_accChild, - .get_accName = tableAccget_accName, - .get_accValue = tableAccget_accValue, - .get_accDescription = tableAccget_accDescription, - .get_accRole = tableAccget_accRole, - .get_accState = tableAccget_accState, - .get_accHelp = tableAccget_accHelp, - .get_accHelpTopic = tableAccget_accHelpTopic, - .get_accKeyboardShortcut = tableAccget_accKeyboardShortcut, - .get_accFocus = tableAccget_accFocus, - .get_accSelection = tableAccget_accSelection, - .get_accDefaultAction = tableAccget_accDefaultAction, - .accSelect = tableAccaccSelect, - .accLocation = tableAccaccLocation, - .accNavigate = tableAccaccNavigate, - .accHitTest = tableAccaccHitTest, - .accDoDefaultAction = tableAccaccDoDefaultAction, - .put_accName = tableAccput_accName, - .put_accValue = tableAccput_accValue, -}; - -static struct tableAcc *newTableAcc(struct table *t) -{ - struct tableAcc *ta; - HRESULT hr; - IAccessible *std; - - ta = (struct tableAcc *) tableAlloc(sizeof (struct tableAcc), "error creating Table accessibility object"); - ta->vtbl = &tableAccVtbl; - ta->vtbl->AddRef(ta); - ta->t = t; - hr = CreateStdAccessibleObject(t->hwnd, OBJID_CLIENT, &IID_IAccessible, &std); - if (hr != S_OK) - // TODO panichresult - panic("error creating standard accessible object for Table"); - ta->std = std; - return ta; -} - -static void freeTableAcc(struct tableAcc *ta) -{ - ta->t = NULL; - ta->vtbl->Release(ta); -} - -HANDLER(accessibilityHandler) -{ - if (uMsg != WM_GETOBJECT) - return FALSE; - if (((DWORD) lParam) != OBJID_CLIENT) - return FALSE; - *lResult = LresultFromObject(&IID_IAccessible, wParam, t->ta); - // TODO check *lResult - return TRUE; -} diff --git a/wintable/new/accessnotes b/wintable/new/accessnotes deleted file mode 100644 index edd5029..0000000 --- a/wintable/new/accessnotes +++ /dev/null @@ -1,2 +0,0 @@ -http://msdn.microsoft.com/en-us/library/windows/desktop/dd318017%28v=vs.85%29.aspx -http://msdn.microsoft.com/en-us/library/windows/desktop/dd373624%28v=vs.85%29.aspx diff --git a/wintable/new/api.h b/wintable/new/api.h deleted file mode 100644 index 7ec9afc..0000000 --- a/wintable/new/api.h +++ /dev/null @@ -1,35 +0,0 @@ -// 8 december 2014 - -static void addColumn(struct table *t, WPARAM wParam, LPARAM lParam) -{ - t->nColumns++; - t->columnTypes = (int *) tableRealloc(t->columnTypes, t->nColumns * sizeof (int), "adding the new column type to the current Table's list of column types"); - t->columnTypes[t->nColumns - 1] = (int) wParam; - // TODO make a panicNoErrCode() or panicArg() for this - if (t->columnTypes[t->nColumns - 1] >= nTableColumnTypes) - panic("invalid column type passed to tableAddColumn"); - headerAddColumn(t, (WCHAR *) lParam); - updateTableWidth(t); -} - -HANDLER(apiHandlers) -{ - switch (uMsg) { - case WM_SETFONT: - // don't free the old font; see http://blogs.msdn.com/b/oldnewthing/archive/2008/09/12/8945692.aspx - t->font = (HFONT) wParam; - SendMessageW(t->header, WM_SETFONT, wParam, lParam); - // TODO how to properly handle LOWORD(lParam) != FALSE? - // TODO depending on the result of the above, update table width to refresh t->headerHeight? - *lResult = 0; - return TRUE; - case WM_GETFONT: - *lResult = (LRESULT) (t->font); - return TRUE; - case tableAddColumn: - addColumn(t, wParam, lParam); - *lResult = 0; - return TRUE; - } - return FALSE; -} diff --git a/wintable/new/checkboxes.h b/wintable/new/checkboxes.h deleted file mode 100644 index 67bc745..0000000 --- a/wintable/new/checkboxes.h +++ /dev/null @@ -1,250 +0,0 @@ -// 16 august 2014 - -enum { - checkboxStateChecked = 1 << 0, - checkboxStateHot = 1 << 1, - checkboxStatePushed = 1 << 2, - checkboxnStates = 1 << 3, -}; - -// TODO actually make this -#define panichresult(a, b) panic(a) - -static UINT dfcState(int cbstate) -{ - UINT ret; - - ret = DFCS_BUTTONCHECK; - if ((cbstate & checkboxStateChecked) != 0) - ret |= DFCS_CHECKED; - if ((cbstate & checkboxStateHot) != 0) - ret |= DFCS_HOT; - if ((cbstate & checkboxStatePushed) != 0) - ret |= DFCS_PUSHED; - return ret; -} - -static void drawFrameControlCheckbox(HDC dc, RECT *r, int cbState) -{ - if (DrawFrameControl(dc, r, DFC_BUTTON, dfcState(cbState)) == 0) - panic("error drawing Table checkbox image with DrawFrameControl()"); -} - -static void getFrameControlCheckboxSize(HDC dc, int *width, int *height) -{ - // there's no real metric around - // let's use SM_CX/YSMICON and hope for the best - *width = GetSystemMetrics(SM_CXSMICON); - *height = GetSystemMetrics(SM_CYSMICON); -} - -static int themestates[checkboxnStates] = { - CBS_UNCHECKEDNORMAL, // 0 - CBS_CHECKEDNORMAL, // checked - CBS_UNCHECKEDHOT, // hot - CBS_CHECKEDHOT, // checked | hot - CBS_UNCHECKEDPRESSED, // pushed - CBS_CHECKEDPRESSED, // checked | pushed - CBS_UNCHECKEDPRESSED, // hot | pushed - CBS_CHECKEDPRESSED, // checked | hot | pushed -}; - -static SIZE getStateSize(HDC dc, int cbState, HTHEME theme) -{ - SIZE s; - HRESULT res; - - res = GetThemePartSize(theme, dc, BP_CHECKBOX, themestates[cbState], NULL, TS_DRAW, &s); - if (res != S_OK) - panichresult("error getting theme part size for Table checkboxes", res); - return s; -} - -static void drawThemeCheckbox(HDC dc, RECT *r, int cbState, HTHEME theme) -{ - HRESULT res; - - res = DrawThemeBackground(theme, dc, BP_CHECKBOX, themestates[cbState], r, NULL); - if (res != S_OK) - panichresult("error drawing Table checkbox image from theme", res); -} - -static void getThemeCheckboxSize(HDC dc, int *width, int *height, HTHEME theme) -{ - SIZE size; - int cbState; - - size = getStateSize(dc, 0, theme); - for (cbState = 1; cbState < checkboxnStates; cbState++) { - SIZE against; - - against = getStateSize(dc, cbState, theme); - if (size.cx != against.cx || size.cy != against.cy) - // TODO make this use a no-information (or two ints) panic() - panic("size mismatch in Table checkbox states"); - } - *width = (int) size.cx; - *height = (int) size.cy; -} - -static void drawCheckbox(struct table *t, HDC dc, RECT *r, int cbState) -{ - if (t->theme != NULL) { - drawThemeCheckbox(dc, r, cbState, t->theme); - return; - } - drawFrameControlCheckbox(dc, r, cbState); -} - -static void freeCheckboxThemeData(struct table *t) -{ - if (t->theme != NULL) { - HRESULT res; - - res = CloseThemeData(t->theme); - if (res != S_OK) - panichresult("error closing Table checkbox theme", res); - t->theme = NULL; - } -} - -static void loadCheckboxThemeData(struct table *t) -{ - HDC dc; - - freeCheckboxThemeData(t); - dc = GetDC(t->hwnd); - if (dc == NULL) - panic("error getting Table DC for loading checkbox theme data"); - // ignore error; if it can't be done, we can fall back to DrawFrameControl() - if (t->theme == NULL) // try to open the theme - t->theme = OpenThemeData(t->hwnd, L"button"); - if (t->theme != NULL) // use the theme - getThemeCheckboxSize(dc, &(t->checkboxWidth), &(t->checkboxHeight), t->theme); - else // couldn't open; fall back - getFrameControlCheckboxSize(dc, &(t->checkboxWidth), &(t->checkboxHeight)); - if (ReleaseDC(t->hwnd, dc) == 0) - panic("error releasing Table DC for loading checkbox theme data"); -} - -static void redrawCheckboxRect(struct table *t, LPARAM lParam) -{ - struct rowcol rc; - RECT r; - POINT pt; - - rc = lParamToRowColumn(t, lParam); - if (rc.row == -1 && rc.column == -1) - return; - if (t->columnTypes[rc.column] != tableColumnCheckbox) - return; - if (!rowColumnToClientRect(t, rc, &r)) - return; - // TODO only the checkbox rect? - if (InvalidateRect(t->hwnd, &r, TRUE) == 0) - panic("error redrawing Table checkbox rect for mouse events"); -} - -HANDLER(checkboxMouseMoveHandler) -{ - // don't actually check to see if the mouse is in the checkbox rect - // if there's scrolling without mouse movement, that will change - // instead, drawCell() will handle it - if (!t->checkboxMouseOverLast) { - t->checkboxMouseOverLast = TRUE; - retrack(t); - } else - redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); - t->checkboxMouseOverLastPoint = lParam; - redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); - *lResult = 0; - return TRUE; -} - -HANDLER(checkboxMouseLeaveHandler) -{ - if (t->checkboxMouseOverLast) - redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); - // TODO remember what I wanted to do here in the case of a held mouse button - t->checkboxMouseOverLast = FALSE; - *lResult = 0; - return TRUE; -} - -HANDLER(checkboxMouseDownHandler) -{ - struct rowcol rc; - RECT r; - POINT pt; - - rc = lParamToRowColumn(t, lParam); - if (rc.row == -1 || rc.column == -1) - return FALSE; - if (t->columnTypes[rc.column] != tableColumnCheckbox) - return FALSE; - if (!rowColumnToClientRect(t, rc, &r)) - return FALSE; - toCheckboxRect(t, &r, 0); - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - if (PtInRect(&r, pt) == 0) - return FALSE; - t->checkboxMouseDown = TRUE; - t->checkboxMouseDownRow = rc.row; - t->checkboxMouseDownColumn = rc.column; - // TODO redraw the whole cell? - if (InvalidateRect(t->hwnd, &r, TRUE) == 0) - panic("error redrawing Table checkbox after mouse down"); - *lResult = 0; - return TRUE; -} - -// TODO get rid of this -struct rowcol lastCheckbox; - -HANDLER(checkboxMouseUpHandler) -{ - struct rowcol rc; - RECT r; - POINT pt; - - if (!t->checkboxMouseDown) - return FALSE; - // the logic behind goto wrongUp is that the mouse must be released on the same checkbox - rc = lParamToRowColumn(t, lParam); - if (rc.row == -1 || rc.column == -1) - goto wrongUp; - if (rc.row != t->checkboxMouseDownRow || rc.column != t->checkboxMouseDownColumn) - goto wrongUp; - if (t->columnTypes[rc.column] != tableColumnCheckbox) - goto wrongUp; - if (!rowColumnToClientRect(t, rc, &r)) - goto wrongUp; - toCheckboxRect(t, &r, 0); - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - if (PtInRect(&r, pt) == 0) - goto wrongUp; - // TODO send toggle event -lastCheckbox = rc; - t->checkboxMouseDown = FALSE; - // TODO redraw the whole cell? - if (InvalidateRect(t->hwnd, &r, TRUE) == 0) - panic("error redrawing Table checkbox after mouse up"); - *lResult = 0; - return TRUE; -wrongUp: - if (t->checkboxMouseDown) { - rc.row = t->checkboxMouseDownRow; - rc.column = t->checkboxMouseDownColumn; - if (rowColumnToClientRect(t, rc, &r)) - // TODO only the checkbox rect? - if (InvalidateRect(t->hwnd, &r, TRUE) == 0) - panic("error redrawing Table checkbox rect for aborted mouse up event"); - } - // if we landed on another checkbox, be sure to draw that one too - if (t->checkboxMouseOverLast) - redrawCheckboxRect(t, t->checkboxMouseOverLastPoint); - t->checkboxMouseDown = FALSE; - return FALSE; // TODO really? -} diff --git a/wintable/new/children.h b/wintable/new/children.h deleted file mode 100644 index 6a3aff3..0000000 --- a/wintable/new/children.h +++ /dev/null @@ -1,19 +0,0 @@ -// 7 december 2014 - -static const handlerfunc commandHandlers[] = { - NULL, -}; - -static const handlerfunc notifyHandlers[] = { - headerNotifyHandler, - NULL, -}; - -HANDLER(childrenHandlers) -{ - if (uMsg == WM_COMMAND) - return runHandlers(commandHandlers, t, uMsg, wParam, lParam, lResult); - if (uMsg == WM_NOTIFY) - return runHandlers(notifyHandlers, t, uMsg, wParam, lParam, lResult); - return FALSE; -} diff --git a/wintable/new/coord.h b/wintable/new/coord.h deleted file mode 100644 index 52a70c9..0000000 --- a/wintable/new/coord.h +++ /dev/null @@ -1,164 +0,0 @@ -// 4 december 2014 - -// TODO find a better place for these (metrics.h?) -static LONG textHeight(struct table *t, HDC dc, BOOL select) -{ - BOOL release; - HFONT prevfont, newfont; - TEXTMETRICW tm; - - release = FALSE; - if (dc == NULL) { - dc = GetDC(t->hwnd); - if (dc == NULL) - panic("error getting Table DC for rowHeight()"); - release = TRUE; - } - if (select) - prevfont = selectFont(t, dc, &newfont); - if (GetTextMetricsW(dc, &tm) == 0) - panic("error getting text metrics for rowHeight()"); - if (select) - deselectFont(dc, prevfont, newfont); - if (release) - if (ReleaseDC(t->hwnd, dc) == 0) - panic("error releasing Table DC for rowHeight()"); - return tm.tmHeight; -} - -#define tableImageWidth() GetSystemMetrics(SM_CXSMICON) -#define tableImageHeight() GetSystemMetrics(SM_CYSMICON) - -// TODO omit column types that are not present? -static LONG rowHeight(struct table *t, HDC dc, BOOL select) -{ - LONG height; - LONG tmHeight; - - height = tableImageHeight(); // start with this to avoid two function calls - tmHeight = textHeight(t, dc, select); - if (height < tmHeight) - height = tmHeight; - if (height < t->checkboxHeight) - height = t->checkboxHeight; - return height; -} - -#define rowht(t) rowHeight(t, NULL, TRUE) - -struct rowcol { - intptr_t row; - intptr_t column; -}; - -static struct rowcol clientCoordToRowColumn(struct table *t, POINT pt) -{ - RECT r; - struct rowcol rc; - intptr_t i; - - if (GetClientRect(t->hwnd, &r) == 0) - panic("error getting Table client rect in clientCoordToRowColumn()"); - r.top += t->headerHeight; - if (PtInRect(&r, pt) == 0) - goto outside; - - // the row is easy - pt.y -= t->headerHeight; - rc.row = (pt.y / rowht(t)) + t->vscrollpos; - if (rc.row >= t->count) - goto outside; - - // the column... not so much - // we scroll p.x, then subtract column widths until we cross the left edge of the control - pt.x += t->hscrollpos; - rc.column = 0; - for (i = 0; i < t->nColumns; i++) { - pt.x -= columnWidth(t, i); - // use <, not <=, here: - // assume r.left and t->hscrollpos == 0; - // given the first column is 100 wide, - // pt.x == 0 (first pixel of col 0) -> p.x - 100 == -100 < 0 -> break - // pt.x == 99 (last pixel of col 0) -> p.x - 100 == -1 < 0 -> break - // pt.x == 100 (first pixel of col 1) -> p.x - 100 == 0 >= 0 -> next column - if (pt.x < r.left) - break; - rc.column++; - } - if (rc.column >= t->nColumns) - goto outside; - - return rc; - -outside: - rc.row = -1; - rc.column = -1; - return rc; -} - -// same as client coordinates, but stored in a lParam (like the various mouse messages provide) -static struct rowcol lParamToRowColumn(struct table *t, LPARAM lParam) -{ - POINT pt; - - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - return clientCoordToRowColumn(t, pt); -} - -// returns TRUE if the row is visible (even partially visible) and thus has a rectangle in the client area; FALSE otherwise -static BOOL rowColumnToClientRect(struct table *t, struct rowcol rc, RECT *r) -{ - RECT client; - RECT out; // don't change r if we return FALSE - LONG height; - intptr_t xpos; - intptr_t i; - - if (rc.row < t->vscrollpos) - return FALSE; - rc.row -= t->vscrollpos; // align with client.top - - if (GetClientRect(t->hwnd, &client) == 0) - panic("error getting Table client rect in rowColumnToClientRect()"); - client.top += t->headerHeight; - - height = rowht(t); - out.top = client.top + (rc.row * height); - if (out.top >= client.bottom) // >= because RECT.bottom is the first pixel outside the rectangle - return FALSE; - out.bottom = out.top + height; - - // and again the columns are the hard part - // so we start with client.left - t->hscrollpos, then keep adding widths until we get to the column we want - xpos = client.left - t->hscrollpos; - for (i = 0; i < rc.column; i++) - xpos += columnWidth(t, i); - // did we stray too far to the right? if so it's not visible - if (xpos >= client.right) // >= because RECT.right is the first pixel outside the rectangle - return FALSE; - out.left = xpos; - out.right = xpos + columnWidth(t, rc.column); - // and is this too far to the left? - if (out.right < client.left) // < because RECT.left is the first pixel inside the rect - return FALSE; - - *r = out; - return TRUE; -} - -// TODO idealCoordToRowColumn/rowColumnToIdealCoord? - -static void toCellContentRect(struct table *t, RECT *r, LRESULT xoff, intptr_t width, intptr_t height) -{ - if (xoff == 0) - xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0); - r->left += xoff; - if (width != 0) - r->right = r->left + width; - if (height != 0) - // TODO vertical center - r->bottom = r->top + height; -} - -#define toCheckboxRect(t, r, xoff) toCellContentRect(t, r, xoff, t->checkboxWidth, t->checkboxHeight) diff --git a/wintable/new/draw.h b/wintable/new/draw.h deleted file mode 100644 index 59a1ea1..0000000 --- a/wintable/new/draw.h +++ /dev/null @@ -1,146 +0,0 @@ -// 8 december 2014 - -struct drawCellParams { - intptr_t row; - intptr_t column; - LONG x; - LONG y; - LONG width; // of column - LONG height; // rowHeight() - LRESULT xoff; // result of HDM_GETBITMAPMARGIN -}; - -static void drawCell(struct table *t, HDC dc, struct drawCellParams *p) -{ - RECT r; - WCHAR msg[200]; - int n; - HBRUSH background; - int textColor; - POINT pt; - int cbState; - RECT cellrect; - - // TODO verify these two - background = (HBRUSH) (COLOR_WINDOW + 1); - textColor = COLOR_WINDOWTEXT; - if (t->selectedRow == p->row) { - // these are the colors wine uses (http://source.winehq.org/source/dlls/comctl32/listview.c) - // the two for unfocused are also suggested by http://stackoverflow.com/questions/10428710/windows-forms-inactive-highlight-color - background = (HBRUSH) (COLOR_HIGHLIGHT + 1); - textColor = COLOR_HIGHLIGHTTEXT; - if (GetFocus() != t->hwnd) { - background = (HBRUSH) (COLOR_BTNFACE + 1); - textColor = COLOR_BTNTEXT; - } - // TODO disabled - } - - r.left = p->x; - r.right = p->x + p->width; - r.top = p->y; - r.bottom = p->y + p->height; - if (FillRect(dc, &r, background) == 0) - panic("error filling Table cell background"); - cellrect = r; // save for drawing the focus rect - - switch (t->columnTypes[p->column]) { - case tableColumnText: - case tableColumnImage: // TODO - toCellContentRect(t, &r, p->xoff, 0, 0); // TODO get the text height - if (SetTextColor(dc, GetSysColor(textColor)) == CLR_INVALID) - panic("error setting Table cell text color"); - if (SetBkMode(dc, TRANSPARENT) == 0) - panic("error setting transparent text drawing mode for Table cell"); - n = wsprintf(msg, L"(%d,%d)", p->row, p->column); - if (DrawTextExW(dc, msg, n, &r, DT_END_ELLIPSIS | DT_LEFT | DT_NOPREFIX | DT_SINGLELINE, NULL) == 0) - panic("error drawing Table cell text"); - break; - case tableColumnCheckbox: - toCheckboxRect(t, &r, p->xoff); - cbState = 0; - if (p->row == lastCheckbox.row && p->column == lastCheckbox.column) - cbState |= checkboxStateChecked; - if (t->checkboxMouseDown) - if (p->row == t->checkboxMouseDownRow && p->column == t->checkboxMouseDownColumn) - cbState |= checkboxStatePushed; - if (t->checkboxMouseOverLast) { - pt.x = GET_X_LPARAM(t->checkboxMouseOverLastPoint); - pt.y = GET_Y_LPARAM(t->checkboxMouseOverLastPoint); - if (PtInRect(&r, pt) != 0) - cbState |= checkboxStateHot; - } - drawCheckbox(t, dc, &r, cbState); - break; - } - - // TODO in front of or behind the cell contents? - if (t->selectedRow == p->row && t->selectedColumn == p->column) - if (DrawFocusRect(dc, &cellrect) == 0) - panic("error drawing focus rect on current Table cell"); -} - -// TODO use cliprect -static void draw(struct table *t, HDC dc, RECT cliprect, RECT client) -{ - intptr_t i, j; - int x = 0; - HFONT prevfont, newfont; - struct drawCellParams p; - - prevfont = selectFont(t, dc, &newfont); - - client.top += t->headerHeight; - - ZeroMemory(&p, sizeof (struct drawCellParams)); - p.height = rowHeight(t, dc, FALSE); - p.xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0); - - p.y = client.top; - for (i = t->vscrollpos; i < t->count; i++) { - p.row = i; - p.x = client.left - t->hscrollpos; - for (j = 0; j < t->nColumns; j++) { - p.column = j; - p.width = columnWidth(t, p.column); - drawCell(t, dc, &p); - p.x += p.width; - } - p.y += p.height; - if (p.y >= client.bottom) // >= because RECT.bottom is the first pixel outside the rect - break; - } - - deselectFont(dc, prevfont, newfont); -} - -HANDLER(drawHandlers) -{ - HDC dc; - PAINTSTRUCT ps; - RECT client; - RECT r; - - if (uMsg != WM_PAINT && uMsg != WM_PRINTCLIENT) - return FALSE; - if (GetClientRect(t->hwnd, &client) == 0) - panic("error getting client rect for Table painting"); - if (uMsg == WM_PAINT) { - dc = BeginPaint(t->hwnd, &ps); - if (dc == NULL) - panic("error beginning Table painting"); - r = ps.rcPaint; - } else { - dc = (HDC) wParam; - r = client; - } - draw(t, dc, r, client); - if (uMsg == WM_PAINT) - EndPaint(t->hwnd, &ps); - // this is correct for WM_PRINTCLIENT; see http://stackoverflow.com/a/27362258/3408572 - *lResult = 0; - return TRUE; -} - -// TODO redraw selected row on focus change -// TODO here or in select.h? diff --git a/wintable/new/events.h b/wintable/new/events.h deleted file mode 100644 index fbd1522..0000000 --- a/wintable/new/events.h +++ /dev/null @@ -1,62 +0,0 @@ -// 5 december 2014 - -// TODO handler functions don't work here because you can't have more than one for the mouse ones... - -static const handlerfunc keyDownHandlers[] = { - keyDownSelectHandler, - NULL, -}; - -static const handlerfunc keyUpHandlers[] = { - NULL, -}; - -static const handlerfunc charHandlers[] = { - NULL, -}; - -static const handlerfunc mouseMoveHandlers[] = { - checkboxMouseMoveHandler, - NULL, -}; - -static const handlerfunc mouseLeaveHandlers[] = { - checkboxMouseLeaveHandler, - NULL, -}; - -static const handlerfunc lbuttonDownHandlers[] = { - mouseDownSelectHandler, - NULL, -}; - -static const handlerfunc lbuttonUpHandlers[] = { - checkboxMouseUpHandler, - NULL, -}; - -// TODO remove or something? depends on if we implement combobox and how -static const handlerfunc mouseWheelHandlers[] = { - NULL, -}; - -// TODO WM_MOUSEHOVER, other mouse buttons - -HANDLER(eventHandlers) -{ - switch (uMsg) { -#define eventHandler(msg, array) \ - case msg: \ - return runHandlers(array, t, uMsg, wParam, lParam, lResult); - eventHandler(WM_KEYDOWN, keyDownHandlers) - eventHandler(WM_KEYUP, keyUpHandlers) - eventHandler(WM_CHAR, charHandlers) - eventHandler(WM_MOUSEMOVE, mouseMoveHandlers) - eventHandler(WM_MOUSELEAVE, mouseLeaveHandlers) - eventHandler(WM_LBUTTONDOWN, lbuttonDownHandlers) - eventHandler(WM_LBUTTONUP, lbuttonUpHandlers) - eventHandler(WM_MOUSEWHEEL, mouseWheelHandlers) -#undef eventHandler - } - return FALSE; -} diff --git a/wintable/new/header.h b/wintable/new/header.h deleted file mode 100644 index 5652029..0000000 --- a/wintable/new/header.h +++ /dev/null @@ -1,109 +0,0 @@ -// 7 december 2014 - -// TODO verify header events (double-clicking on a divider, for example) - -static void makeHeader(struct table *t, HINSTANCE hInstance) -{ - t->header = CreateWindowExW(0, - WC_HEADERW, L"", - // don't set WS_VISIBLE; according to MSDN we create the header hidden as part of setting the initial position (http://msdn.microsoft.com/en-us/library/windows/desktop/ff485935%28v=vs.85%29.aspx) - // TODO WS_BORDER? - // TODO is HDS_HOTTRACK needed? - WS_CHILD | HDS_FULLDRAG | HDS_HORZ | HDS_HOTTRACK, - 0, 0, 0, 0, // no initial size - t->hwnd, (HMENU) 100, hInstance, NULL); - if (t->header == NULL) - panic("error creating Table header"); -} - -static void destroyHeader(struct table *t) -{ - if (DestroyWindow(t->header) == 0) - panic("error destroying Table header"); -} - -static void repositionHeader(struct table *t) -{ - RECT r; - WINDOWPOS wp; - HDLAYOUT l; - - if (GetClientRect(t->hwnd, &r) == 0) - panic("error getting client rect for Table header repositioning"); - // we fake horizontal scrolling here by extending the client rect to the left by the scroll position - r.left -= t->hscrollpos; - l.prc = &r; - l.pwpos = ℘ - if (SendMessageW(t->header, HDM_LAYOUT, 0, (LPARAM) (&l)) == FALSE) - panic("error getting new Table header position"); - if (SetWindowPos(t->header, wp.hwndInsertAfter, - wp.x, wp.y, wp.cx, wp.cy, - // see above on showing the header here instead of in the CreateWindowExW() call - wp.flags | SWP_SHOWWINDOW) == 0) - panic("error repositioning Table header"); - t->headerHeight = wp.cy; -} - -static void headerAddColumn(struct table *t, WCHAR *name) -{ - HDITEMW item; - - ZeroMemory(&item, sizeof (HDITEMW)); - item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT; - item.cxy = 200; // TODO - item.pszText = name; - item.fmt = HDF_LEFT | HDF_STRING; - // TODO replace 100 with (t->nColumns - 1) - if (SendMessage(t->header, HDM_INSERTITEM, (WPARAM) (100), (LPARAM) (&item)) == (LRESULT) (-1)) - panic("error adding column to Table header"); -} - -// TODO make a better name for this? -// TODO move to hscroll.h? -// TODO organize this in general... -// TODO because of this function's new extended functionality only hscrollto() is allowed to call repositionHeader() -static void updateTableWidth(struct table *t) -{ - HDITEMW item; - intptr_t i; - RECT client; - - t->width = 0; - // TODO count dividers? - // TODO use columnWidth() - for (i = 0; i < t->nColumns; i++) { - ZeroMemory(&item, sizeof (HDITEMW)); - item.mask = HDI_WIDTH; - if (SendMessageW(t->header, HDM_GETITEM, (WPARAM) i, (LPARAM) (&item)) == FALSE) - panic("error getting Table column width for updateTableWidth()"); - t->width += item.cxy; - } - - if (GetClientRect(t->hwnd, &client) == 0) - panic("error getting Table client rect in updateTableWidth()"); - t->hpagesize = client.right - client.left; - - // this part is critical: if we resize the columns to less than the client area width, then the following hscrollby() will make t->hscrollpos negative, which does very bad things - // note to self: do this regardless of whether the table width or the client area width was changed - if (t->hpagesize > t->width) - t->hpagesize = t->width; - - // do a dummy scroll to update the horizontal scrollbar to use the new width - hscrollby(t, 0); -} - -HANDLER(headerNotifyHandler) -{ - NMHDR *nmhdr = (NMHDR *) lParam; - - if (nmhdr->hwndFrom != t->header) - return FALSE; - if (nmhdr->code != HDN_ITEMCHANGED) - return FALSE; - updateTableWidth(t); - // TODO make more intelligent - InvalidateRect(t->hwnd, NULL, TRUE); - // TODO UpdateWindow()? - *lResult = 0; - return TRUE; -} diff --git a/wintable/new/hscroll.h b/wintable/new/hscroll.h deleted file mode 100644 index 1560490..0000000 --- a/wintable/new/hscroll.h +++ /dev/null @@ -1,52 +0,0 @@ -// 9 december 2014 - -// forward declaration needed here -static void repositionHeader(struct table *); - -static struct scrollParams hscrollParams(struct table *t) -{ - struct scrollParams p; - - ZeroMemory(&p, sizeof (struct scrollParams)); - p.pos = &(t->hscrollpos); - p.pagesize = t->hpagesize; - p.length = t->width; - p.scale = 1; - p.post = repositionHeader; - p.wheelCarry = &(t->hwheelCarry); - return p; -} - -static void hscrollto(struct table *t, intptr_t pos) -{ - struct scrollParams p; - - p = hscrollParams(t); - scrollto(t, SB_HORZ, &p, pos); -} - -static void hscrollby(struct table *t, intptr_t delta) -{ - struct scrollParams p; - - p = hscrollParams(t); - scrollby(t, SB_HORZ, &p, delta); -} - -static void hscroll(struct table *t, WPARAM wParam, LPARAM lParam) -{ - struct scrollParams p; - - p = hscrollParams(t); - scroll(t, SB_HORZ, &p, wParam, lParam); -} - -// TODO find out if we can indicriminately check for WM_WHEELHSCROLL -HANDLER(hscrollHandler) -{ - if (uMsg != WM_HSCROLL) - return FALSE; - hscroll(t, wParam, lParam); - *lResult = 0; - return TRUE; -} diff --git a/wintable/new/links b/wintable/new/links deleted file mode 100644 index f0c9523..0000000 --- a/wintable/new/links +++ /dev/null @@ -1,3 +0,0 @@ -default list view message processing - http://msdn.microsoft.com/en-us/library/windows/desktop/bb774734%28v=vs.85%29.aspx - includes information on handling the edit control and event messages diff --git a/wintable/new/main.c b/wintable/new/main.c deleted file mode 100644 index 7ee7306..0000000 --- a/wintable/new/main.c +++ /dev/null @@ -1,242 +0,0 @@ -// 19 october 2014 -#define UNICODE -#define _UNICODE -#define STRICT -#define STRICT_TYPED_ITEMIDS -#define CINTERFACE -#define COBJMACROS -// 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 -#include - -// #qo LIBS: user32 kernel32 gdi32 comctl32 uxtheme ole32 oleaut32 oleacc uuid - -// TODO -// - should tablePanic be CALLBACK or some other equivalent macro? and definitely export initTable somehow, but which alias macro to use? -// - make panic messages grammatically correct ("Table error: adding...") -// - make access to column widths consistent; see whether HDITEMW.cxy == (ITEMRECT.right - ITEMRECT.left) -// - make sure all uses of t->headerHeight are ADDED to RECT.top -// - WM_THEMECHANGED, etc. -// - see if vertical centering is really what we want or if we just want to offset by a few pixels or so -// - going right from column 0 to column 2 with the right arrow key deselects -// - make sure all error messages involving InvalidateRect() are consistent with regards to "redrawing" and "queueing for redraw" -// - collect all resize-related tasks in a single function (so things like adding columns will refresh everything, not just horizontal scrolls; also would fix initial coordinates) -// - checkbox columns don't clip to the column width - -#define tableWindowClass L"gouitable" - -// start at WM_USER + 20 just in case for whatever reason we ever get the various dialog manager messages (see also http://blogs.msdn.com/b/oldnewthing/archive/2003/10/21/55384.aspx) -enum { - // wParam - one of the type constants - // lParam - column name as a Unicode string - tableAddColumn = WM_USER + 20, -}; - -enum { - tableColumnText, - tableColumnImage, - tableColumnCheckbox, - nTableColumnTypes, -}; - -static void (*tablePanic)(const char *, DWORD) = NULL; -#define panic(...) (*tablePanic)(__VA_ARGS__, GetLastError()) -#define abort $$$$ // prevent accidental use of abort() - -static BOOL (*WINAPI tableTrackMouseEvent)(LPTRACKMOUSEEVENT); - -// forward declaration -struct tableAcc; - -struct table { - HWND hwnd; - HWND header; - HFONT font; - intptr_t nColumns; - int *columnTypes; - intptr_t width; - intptr_t headerHeight; - intptr_t hscrollpos; // in logical units - intptr_t hpagesize; // in logical units - intptr_t count; - intptr_t vscrollpos; // in rows - intptr_t vpagesize; // in rows - int hwheelCarry; - int vwheelCarry; - intptr_t selectedRow; - intptr_t selectedColumn; - HTHEME theme; - int checkboxWidth; - int checkboxHeight; - BOOL checkboxMouseOverLast; - LPARAM checkboxMouseOverLastPoint; - BOOL checkboxMouseDown; - intptr_t checkboxMouseDownRow; - intptr_t checkboxMouseDownColumn; - struct tableAcc *ta; -}; - -#include "util.h" -#include "coord.h" -#include "scroll.h" -#include "hscroll.h" -#include "vscroll.h" -#include "select.h" -#include "checkboxes.h" -#include "events.h" -#include "header.h" -#include "children.h" -#include "resize.h" -#include "draw.h" -#include "api.h" -#include "accessibility.h" - -static const handlerfunc handlers[] = { - eventHandlers, - childrenHandlers, - resizeHandler, - drawHandlers, - apiHandlers, - hscrollHandler, - vscrollHandler, - accessibilityHandler, - NULL, -}; - -static void initDummyTableStuff(struct table *t) -{ - t->count = 100; - t->selectedRow = 2; - t->selectedColumn = 1; -} - -static LRESULT CALLBACK tableWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - struct table *t; - LRESULT lResult; - - t = (struct table *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); - if (t == NULL) { - // we have to do things this way because creating the header control will fail mysteriously if we create it first thing - // (which is fine; we can get the parent hInstance this way too) - // we use WM_CREATE because we have to use WM_DESTROY to destroy the header; we can't do it in WM_NCDESTROY because Windows will have destroyed it for us by then, and let's match message pairs to be safe - if (uMsg == WM_CREATE) { - CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; - - t = (struct table *) tableAlloc(sizeof (struct table), "error allocating internal Table data structure"); - t->hwnd = hwnd; - makeHeader(t, cs->hInstance); - t->selectedRow = -1; - t->selectedColumn = -1; - loadCheckboxThemeData(t); - t->ta = newTableAcc(t); -initDummyTableStuff(t); - SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) t); - } - // even if we did the above, fall through - return DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - if (uMsg == WM_DESTROY) { -printf("destroy\n"); - // TODO free appropriate (after figuring this part out) components of t - // TODO send EVENT_OBJECT_DESTROY events to accessibility listeners (when appropriate); see the note on proxy objects as well - freeTableAcc(t->ta); - t->ta = NULL; - freeCheckboxThemeData(t); - destroyHeader(t); - tableFree(t, "error allocating internal Table data structure"); - return DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - if (runHandlers(handlers, t, uMsg, wParam, lParam, &lResult)) - return lResult; - return DefWindowProcW(hwnd, uMsg, wParam, lParam); -} - -static void deftablePanic(const char *msg, DWORD lastError) -{ - fprintf(stderr, "Table error: %s (last error %d)\n", msg, lastError); - fprintf(stderr, "This is the default Table error handler function; programs that use Table should provide their own instead.\nThe program will now break into the debugger.\n"); - DebugBreak(); -} - -void initTable(void (*panicfunc)(const char *msg, DWORD lastError), BOOL (*WINAPI tme)(LPTRACKMOUSEEVENT)) -{ - WNDCLASSW wc; - - tablePanic = panicfunc; - if (tablePanic == NULL) - tablePanic = deftablePanic; - if (tme == NULL) - // TODO errorless version - panic("must provide a TrackMouseEvent() to initTable()"); - tableTrackMouseEvent = tme; - ZeroMemory(&wc, sizeof (WNDCLASSW)); - wc.lpszClassName = tableWindowClass; - wc.lpfnWndProc = tableWndProc; - wc.hCursor = LoadCursorW(NULL, IDC_ARROW); - wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); - wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // TODO correct? - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.hInstance = GetModuleHandle(NULL); - if (RegisterClassW(&wc) == 0) - panic("error registering Table window class"); -} - -int main(int argc, char *argv[]) -{ - HWND mainwin; - MSG msg; - INITCOMMONCONTROLSEX icc; - - ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX)); - icc.dwSize = sizeof (INITCOMMONCONTROLSEX); - icc.dwICC = ICC_LISTVIEW_CLASSES; - if (InitCommonControlsEx(&icc) == 0) - panic("(test program) error initializing comctl32.dll"); - initTable(NULL, _TrackMouseEvent); - mainwin = CreateWindowExW(0, - tableWindowClass, L"Main Window", - WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 400, 400, - NULL, NULL, GetModuleHandle(NULL), NULL); - if (mainwin == NULL) - panic("(test program) error creating Table"); - SendMessageW(mainwin, tableAddColumn, tableColumnText, (LPARAM) L"Column"); - SendMessageW(mainwin, tableAddColumn, tableColumnImage, (LPARAM) L"Column 2"); - SendMessageW(mainwin, tableAddColumn, tableColumnCheckbox, (LPARAM) L"Column 3"); - if (argc > 1) { - NONCLIENTMETRICSW ncm; - HFONT font; - - ZeroMemory(&ncm, sizeof (NONCLIENTMETRICSW)); - ncm.cbSize = sizeof (NONCLIENTMETRICSW); - if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICSW), &ncm, sizeof (NONCLIENTMETRICSW)) == 0) - panic("(test program) error getting non-client metrics"); - font = CreateFontIndirectW(&ncm.lfMessageFont); - if (font == NULL) - panic("(test program) error creating lfMessageFont HFONT"); - SendMessageW(mainwin, WM_SETFONT, (WPARAM) font, TRUE); - } - ShowWindow(mainwin, SW_SHOWDEFAULT); - if (UpdateWindow(mainwin) == 0) - panic("(test program) error updating window"); - while (GetMessageW(&msg, NULL, 0, 0) > 0) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - return 0; -} diff --git a/wintable/new/resize.h b/wintable/new/resize.h deleted file mode 100644 index 2ffaad7..0000000 --- a/wintable/new/resize.h +++ /dev/null @@ -1,39 +0,0 @@ -// 7 december 2014 - -// TODO why doesn't this trigger on first show? -// TODO see if there's anything not metaphor related in the last bits of the scrollbar series -// TODO rename this to boot - -HANDLER(resizeHandler) -{ - WINDOWPOS *wp; - RECT client; - intptr_t height; - - if (uMsg != WM_WINDOWPOSCHANGED) - return FALSE; - wp = (WINDOWPOS *) lParam; - if ((wp->flags & SWP_NOSIZE) != 0) - return FALSE; - - // TODO does wp store the window rect or the client rect? - if (GetClientRect(t->hwnd, &client) == 0) - panic("error getting Table client rect in resizeHandler()"); - // TODO do this after calling updateTableWidth() (which calls repositionHeader()?)? - client.top += t->headerHeight; - - // update the width... - // this will call repositionHeader(); there's a good reason... (see comments) - // TODO when I clean that mess up, remove this comment - updateTableWidth(t); - - // ...and the height - // TODO find out if order matters - height = client.bottom - client.top; - t->vpagesize = height / rowht(t); - // do a dummy scroll to reflect those changes - vscrollby(t, 0); - - *lResult = 0; - return TRUE; -} diff --git a/wintable/new/scroll.h b/wintable/new/scroll.h deleted file mode 100644 index 1f333a8..0000000 --- a/wintable/new/scroll.h +++ /dev/null @@ -1,130 +0,0 @@ -// 9 december 2014 - -struct scrollParams { - intptr_t *pos; - intptr_t pagesize; - intptr_t length; - intptr_t scale; - void (*post)(struct table *); - int *wheelCarry; -}; - -static void scrollto(struct table *t, int which, struct scrollParams *p, intptr_t pos) -{ - RECT scrollArea; - SCROLLINFO si; - intptr_t xamount, yamount; - - if (pos < 0) - pos = 0; - if (pos > p->length - p->pagesize) - pos = p->length - p->pagesize; - - // we don't want to scroll the header - if (GetClientRect(t->hwnd, &scrollArea) == 0) - panic("error getting Table client rect for scrollto()"); - scrollArea.top += t->headerHeight; - - // negative because ScrollWindowEx() is "backwards" - xamount = -(pos - *(p->pos)) * p->scale; - yamount = 0; - if (which == SB_VERT) { - yamount = xamount; - xamount = 0; - } - - if (ScrollWindowEx(t->hwnd, xamount, yamount, - &scrollArea, &scrollArea, NULL, NULL, - SW_ERASE | SW_INVALIDATE) == ERROR) -;// TODO failure case ignored for now; see https://bugs.winehq.org/show_bug.cgi?id=37706 -// panic("error scrolling Table"); - // TODO call UpdateWindow()? - - *(p->pos) = pos; - - // now commit our new scrollbar setup... - ZeroMemory(&si, sizeof (SCROLLINFO)); - si.cbSize = sizeof (SCROLLINFO); - si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; - si.nPage = p->pagesize; - si.nMin = 0; - si.nMax = p->length - 1; // endpoint inclusive - si.nPos = *(p->pos); - SetScrollInfo(t->hwnd, which, &si, TRUE); - - if (p->post != NULL) - (*(p->post))(t); -} - -static void scrollby(struct table *t, int which, struct scrollParams *p, intptr_t delta) -{ - scrollto(t, which, p, *(p->pos) + delta); -} - -static void scroll(struct table *t, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam) -{ - intptr_t pos; - SCROLLINFO si; - - pos = *(p->pos); - switch (LOWORD(wParam)) { - case SB_LEFT: // also SB_TOP - pos = 0; - break; - case SB_RIGHT: // also SB_BOTTOM - pos = p->length - p->pagesize; - break; - case SB_LINELEFT: // also SB_LINEUP - pos--; - break; - case SB_LINERIGHT: // also SB_LINEDOWN - pos++; - break; - case SB_PAGELEFT: // also SB_PAGEUP - pos -= p->pagesize; - break; - case SB_PAGERIGHT: // also SB_PAGEDOWN - pos += p->pagesize; - break; - case SB_THUMBPOSITION: - ZeroMemory(&si, sizeof (SCROLLINFO)); - si.cbSize = sizeof (SCROLLINFO); - si.fMask = SIF_POS; - if (GetScrollInfo(t->hwnd, which, &si) == 0) - panic("error getting thumb position for scroll() in Table"); - pos = si.nPos; - break; - case SB_THUMBTRACK: - ZeroMemory(&si, sizeof (SCROLLINFO)); - si.cbSize = sizeof (SCROLLINFO); - si.fMask = SIF_TRACKPOS; - if (GetScrollInfo(t->hwnd, which, &si) == 0) - panic("error getting thumb track position for scroll() in Table"); - pos = si.nTrackPos; - break; - } - scrollto(t, which, p, pos); -} - -static void wheelscroll(struct table *t, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam) -{ - int delta; - int lines; - UINT scrollAmount; - - delta = GET_WHEEL_DELTA_WPARAM(wParam); - // TODO make a note of what the appropriate hscroll constant is - if (SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollAmount, 0) == 0) - // TODO use scrollAmount == 3 instead? - panic("error getting wheel scroll amount in wheelscroll()"); - if (scrollAmount == WHEEL_PAGESCROLL) - scrollAmount = p->pagesize; - if (scrollAmount == 0) // no mouse wheel scrolling (or t->pagesize == 0) - return; - // the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx - // see those pages for information on subtleties - delta += *(p->wheelCarry); - lines = delta * ((int) scrollAmount) / WHEEL_DELTA; - *(p->wheelCarry) = delta - lines * WHEEL_DELTA / ((int) scrollAmount); - scrollby(t, which, p, -lines); -} diff --git a/wintable/new/scrollbarseries b/wintable/new/scrollbarseries deleted file mode 100644 index 818b6a8..0000000 --- a/wintable/new/scrollbarseries +++ /dev/null @@ -1,24 +0,0 @@ - - http://blogs.msdn.com/b/oldnewthing/archive/2003/07/23/54576.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/07/25/54582.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/07/29/54591.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/07/30/54600.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54610.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54617.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54629.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/13/54639.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/15/54647.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/08/18/54668.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/09/54826.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/11/54885.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/13/54917.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/15/54925.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54944.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54945.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54946.aspx - http://blogs.msdn.com/b/oldnewthing/archive/2003/10/16/55344.aspx - not really part of, so to speak, but still http://blogs.msdn.com/b/oldnewthing/archive/2004/05/10/129068.aspx diff --git a/wintable/new/select.h b/wintable/new/select.h deleted file mode 100644 index 0539e52..0000000 --- a/wintable/new/select.h +++ /dev/null @@ -1,255 +0,0 @@ -// 13 december 2014 - -// damn winsock -static void doselect(struct table *t, intptr_t row, intptr_t column) -{ - RECT r, client; - intptr_t oldrow; - LONG width, height; - struct rowcol rc; - BOOL dovscroll; - intptr_t i; - intptr_t xpos; - LONG clientWidth; - - // check existing selection to see if it's valid - if (t->selectedRow == -1 && t->selectedColumn != -1) - panic("sanity check failure: old Table selection invalid (row == -1, column != -1)"); - if (t->selectedRow != -1 && t->selectedColumn == -1) - panic("sanity check failure: old Table selection invalid (row != -1, column == -1)"); - if (t->selectedRow >= t->count) - panic("sanity check failure: old Table selection invalid (row out of range)"); - if (t->selectedColumn >= t->nColumns) - panic("sanity check failure: old Table selection invalid (column out of range)"); - - oldrow = t->selectedRow; - t->selectedRow = row; - t->selectedColumn = column; - - // check new selection to see if it's valid - if (t->selectedRow == -1 && t->selectedColumn != -1) - panic("sanity check failure: new Table selection invalid (row == -1, column != -1)"); - if (t->selectedRow != -1 && t->selectedColumn == -1) - panic("sanity check failure: new Table selection invalid (row != -1, column == -1)"); - if (t->selectedRow >= t->count) - panic("sanity check failure: new Table selection invalid (row out of range)"); - if (t->selectedColumn >= t->nColumns) - panic("sanity check failure: new Table selection invalid (column out of range)"); - - // do this even if we don't scroll before; noScroll depends on it - if (GetClientRect(t->hwnd, &client) == 0) - panic("error getting Table client rect in doselect()"); - client.top += t->headerHeight; - height = rowht(t); - - // only scroll if we selected something - if (t->selectedRow == -1 || t->selectedColumn == -1) - goto noScroll; - - // first vertically scroll to the new row to make it fully visible (or as visible as possible) - if (t->selectedRow < t->vscrollpos) - vscrollto(t, t->selectedRow); - else { - rc.row = t->selectedRow; - rc.column = t->selectedColumn; - // first assume entirely outside the client area - dovscroll = TRUE; - if (rowColumnToClientRect(t, rc, &r)) - // partially outside the client area? - if (r.bottom <= client.bottom) // <= here since we are comparing bottoms (which are the first pixels outside the rectangle) - dovscroll = FALSE; - if (dovscroll) - vscrollto(t, t->selectedRow - t->vpagesize + 1); // + 1 because apparently just t->selectedRow - t->vpagesize results in no scrolling (t->selectedRow - t->vpagesize == t->vscrollpos)... - } - - // now see if the cell we want is to the left of offscreen, in which case scroll to its x-position - xpos = 0; - for (i = 0; i < t->selectedColumn; i++) - xpos += columnWidth(t, i); - if (xpos < t->hscrollpos) - hscrollto(t, xpos); - else { - // if the full cell is not visible, scroll to the right just enough to make it fully visible (or as visible as possible) - width = columnWidth(t, t->selectedColumn); - clientWidth = client.right - client.left; - if (xpos + width > t->hscrollpos + clientWidth) // > because both sides deal with the first pixel outside - // if the column is too wide, then just make it occupy the whole visible area (left-aligned) - if (width > clientWidth) // TODO >= ? - hscrollto(t, xpos); - else - // TODO don't use t->hpagesize here? depends if other code uses it - hscrollto(t, (xpos + width) - t->hpagesize); - } - -noScroll: - // now redraw the old and new /rows/ - // we do this after scrolling so the rectangles to be invalidated make sense - r.left = client.left; - r.right = client.right; - if (oldrow != -1 && oldrow >= t->vscrollpos) { - r.top = client.top + ((oldrow - t->vscrollpos) * height); - r.bottom = r.top + height; - if (InvalidateRect(t->hwnd, &r, TRUE) == 0) - panic("error queueing previously selected row for redraw in doselect()"); - } - // t->selectedRow must be visible by this point; we scrolled to it - if (t->selectedRow != -1 && t->selectedRow != oldrow) { - r.top = client.top + ((t->selectedRow - t->vscrollpos) * height); - r.bottom = r.top + height; - if (InvalidateRect(t->hwnd, &r, TRUE) == 0) - panic("error queueing newly selected row for redraw in doselect()"); - } -} - -// TODO make this needless -HANDLER(checkboxMouseDownHandler); - -// TODO which WM_xBUTTONDOWNs? -HANDLER(mouseDownSelectHandler) -{ - struct rowcol rc; - - rc = lParamToRowColumn(t, lParam); - // don't check if lParamToRowColumn() returned row -1 or column -1; we want deselection behavior - doselect(t, rc.row, rc.column); - // TODO separate this from here - checkboxMouseDownHandler(t, uMsg, wParam, lParam, lResult); - *lResult = 0; - return TRUE; -} - -/* -the routine below is intended to simulate the comctl32.dll listview keyboard navigation rules, at least as far as vertical navigation is concerned. -horizontal scrolling is different because unlike the comctl32 listview, we say that a single column in each row has the keyboard focus, so left and right navigate between columns here, instead of scrolling left/right by pixels. - TODO provide an override for scrolling by pixels? - TODO any other keyboard shortcuts? - TODO browser keys - TODO media navigation keys - TODO XBUTTON1/2? - TODO clear keys? - -keyboard selection behaviors of the windows 7 listview: -with 100 items (0-99), the window currently shows items 30 through 47 as well as having item 48 partially visible -- item 30: - - page up -> item 13 - - page down -> item 47 -- item 31: - - page up -> item 30 - - page down -> item 47 -- item 42: - - page up -> item 30 - - page down -> item 47 -- item 46: - - page up -> item 30 - - page down -> item 47 -- item 47: - - page up: -> item 30 - - page down: -> item 64 - -when nothing is selected: -- down selects item 0 regardless of scroll -- up selects nothing regardless of scroll -- page down selects the last fully visible item depending on scroll - - so with the above configuration: - - item 0 -> item 17 - - item 30 -> item 47 - - item 80 -> item 97 -- page up selects item 0 regardless of scroll -- home selects item 0 regardless of scroll -- end selects the last item regardless of scroll - -for left and right we will simulate up and down, respectively (so right selects row 0 column 0); remember that you can't have either row or column be -1 but not both - -TODO what happens if page up and page down are pressed with an item selected and the scroll in a different position? -*/ - -HANDLER(keyDownSelectHandler) -{ - intptr_t row; - intptr_t column; - - if (t->count == 0 || t->nColumns == 0) // no items to select - return FALSE; - row = t->selectedRow; - column = t->selectedColumn; - switch (wParam) { - case VK_UP: - if (row == -1) - return FALSE; - row--; - if (row < 0) - row = 0; - break; - case VK_DOWN: - if (row == -1) { - row = 0; - column = 0; - } else { - row++; - if (row >= t->count) - row = t->count - 1; - } - break; - case VK_LEFT: - if (column == -1) - return FALSE; - column--; - if (column < 0) - column = 0; - break; - case VK_RIGHT: - if (column == -1) { - row = 0; - column = 0; - } else { - column++; - if (column >= t->nColumns) - column = t->nColumns - 1; - } - break; - case VK_HOME: - row = 0; - if (column == -1) - column = 0; - break; - case VK_END: - row = t->count - 1; - if (column == -1) - column = 0; - break; - case VK_PRIOR: - if (row == -1) { - row = 0; - column = 0; - } else { - row = t->vscrollpos; - if (row == t->selectedRow) - // TODO investigate why the - 1 is needed here and below - // TODO if this is a misunderstanding of how t->vpagesize works, figure out what happens if there is no partially visible row, and what is supposed to happen - row -= t->vpagesize - 1; - if (row < 0) - row = 0; - } - break; - case VK_NEXT: - if (row == -1) { - row = t->vscrollpos + t->vpagesize - 1; - // TODO ensusre this is the case with the real list view - if (row >= t->count) - row = t->count - 1; - column = 0; - } else { - row = t->vscrollpos + t->vpagesize - 1; - if (row == t->selectedRow) - row += t->vpagesize - 1; - if (row >= t->count) - row = t->count - 1; - } - break; - default: - return FALSE; - } - doselect(t, row, column); - *lResult = 0; - return TRUE; -} diff --git a/wintable/new/util.h b/wintable/new/util.h deleted file mode 100644 index 4dd73d6..0000000 --- a/wintable/new/util.h +++ /dev/null @@ -1,119 +0,0 @@ -// 4 december 2014 - -typedef BOOL (*handlerfunc)(struct table *, UINT, WPARAM, LPARAM, LRESULT *); -#define HANDLER(name) static BOOL name(struct table *t, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) - -static BOOL runHandlers(const handlerfunc list[], struct table *t, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) -{ - handlerfunc *p; - - for (p = list; *p != NULL; p++) - if ((*(*p))(t, uMsg, wParam, lParam, lResult)) - return TRUE; - return FALSE; -} - -// memory allocation stuff -// each of these functions do an implicit ZeroMemory() -// these also make tableRealloc(NULL, ...)/tableFree(NULL) act like realloc(NULL, ...)/free(NULL) (that is, same as tableAlloc(...)/malloc(...) and a no-op, respectively) -// we /would/ use LocalAlloc() here because -// - whether the malloc() family supports the last-error code is undefined -// - the HeapAlloc() family DOES NOT support the last-error code; you're supposed to use Windows exceptions, and I can't find a clean way to do this with MinGW-w64 that doesn't rely on inline assembly or external libraries (unless they added __try/__except blocks) -// - there's no VirtualReAlloc() to complement VirtualAlloc() and I'm not sure if we can even get the original allocated size back out reliably to write it ourselves (http://blogs.msdn.com/b/oldnewthing/archive/2012/03/16/10283988.aspx) -// alas, LocalAlloc() doesn't want to work on real Windows 7 after a few times, throwing up ERROR_NOT_ENOUGH_MEMORY after three (3) ints or so :| -// we'll use malloc() until then -// needless to say, TODO - -static void *tableAlloc(size_t size, const char *panicMessage) -{ -// HLOCAL out; - void *out; - -// out = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, size); - out = malloc(size); - if (out == NULL) - panic(panicMessage); - ZeroMemory(out, size); - return (void *) out; -} - -static void *tableRealloc(void *p, size_t size, const char *panicMessage) -{ -// HLOCAL out; - void *out; - - if (p == NULL) - return tableAlloc(size, panicMessage); -// out = LocalReAlloc((HLOCAL) p, size, LMEM_ZEROINIT); - out = realloc(p, size); - if (out == NULL) - panic(panicMessage); - // TODO zero the extra memory - return (void *) out; -} - -static void tableFree(void *p, const char *panicMessage) -{ - if (p == NULL) - return; -// if (LocalFree((HLOCAL) p) != NULL) -// panic(panicMessage); - free(p); -} - -// font selection - -static HFONT selectFont(struct table *t, HDC dc, HFONT *newfont) -{ - HFONT prevfont; - - // copy it in casse we get a WM_SETFONT before this call's respective deselectFont() call - *newfont = t->font; - if (*newfont == NULL) { - // get it on demand in the (unlikely) event it changes while this Table is alive - *newfont = GetStockObject(SYSTEM_FONT); - if (*newfont == NULL) - panic("error getting default font for selecting into Table DC"); - } - prevfont = (HFONT) SelectObject(dc, *newfont); - if (prevfont == NULL) - panic("error selecting Table font into Table DC"); - return prevfont; -} - -static void deselectFont(HDC dc, HFONT prevfont, HFONT newfont) -{ - if (SelectObject(dc, prevfont) != newfont) - panic("error deselecting Table font from Table DC"); - // doin't delete newfont here, even if it is the system font (see http://msdn.microsoft.com/en-us/library/windows/desktop/dd144925%28v=vs.85%29.aspx) -} - -// and back to other functions - -static LONG columnWidth(struct table *t, intptr_t n) -{ - RECT r; - - if (SendMessageW(t->header, HDM_GETITEMRECT, (WPARAM) n, (LPARAM) (&r)) == 0) - panic("error getting Table column width"); - return r.right - r.left; -} - -/* TODO: -http://blogs.msdn.com/b/oldnewthing/archive/2003/10/13/55279.aspx -http://blogs.msdn.com/b/oldnewthing/archive/2003/10/14/55286.aspx -we'll need to make sure that initial edge case works properly -(TODO get the linked article in the latter) -also implement retrack() as so, in the WM_MOUSEMOVE handler -*/ -static void retrack(struct table *t) -{ - TRACKMOUSEEVENT tm; - - ZeroMemory(&tm, sizeof (TRACKMOUSEEVENT)); - tm.cbSize = sizeof (TRACKMOUSEEVENT); - tm.dwFlags = TME_LEAVE; // TODO TME_NONCLIENT as well? - tm.hwndTrack = t->hwnd; - if ((*tableTrackMouseEvent)(&tm) == 0) - panic("error retracking Table mouse events"); -} diff --git a/wintable/new/vscroll.h b/wintable/new/vscroll.h deleted file mode 100644 index 20f0df7..0000000 --- a/wintable/new/vscroll.h +++ /dev/null @@ -1,66 +0,0 @@ -// 9 december 2014 - -// forward declaration needed here -static void repositionHeader(struct table *); - -static struct scrollParams vscrollParams(struct table *t) -{ - struct scrollParams p; - - ZeroMemory(&p, sizeof (struct scrollParams)); - p.pos = &(t->vscrollpos); - p.pagesize = t->vpagesize; - p.length = t->count; - p.scale = rowht(t); - p.post = NULL; - p.wheelCarry = &(t->vwheelCarry); - return p; -} - -static void vscrollto(struct table *t, intptr_t pos) -{ - struct scrollParams p; - - p = vscrollParams(t); - scrollto(t, SB_VERT, &p, pos); -} - -static void vscrollby(struct table *t, intptr_t delta) -{ - struct scrollParams p; - - p = vscrollParams(t); - scrollby(t, SB_VERT, &p, delta); -} - -static void vscroll(struct table *t, WPARAM wParam, LPARAM lParam) -{ - struct scrollParams p; - - p = vscrollParams(t); - scroll(t, SB_VERT, &p, wParam, lParam); -} - -static void vwheelscroll(struct table *t, WPARAM wParam, LPARAM lParam) -{ - struct scrollParams p; - - p = vscrollParams(t); - wheelscroll(t, SB_VERT, &p, wParam, lParam); -} - -// TODO WM_MOUSEWHEEL -HANDLER(vscrollHandler) -{ - switch (uMsg) { - case WM_VSCROLL: - vscroll(t, wParam, lParam); - *lResult = 0; - return TRUE; - case WM_MOUSEWHEEL: - vwheelscroll(t, wParam, lParam); - *lResult = 0; - return TRUE; - } - return FALSE; -} diff --git a/wintable/resize.h b/wintable/resize.h new file mode 100644 index 0000000..2ffaad7 --- /dev/null +++ b/wintable/resize.h @@ -0,0 +1,39 @@ +// 7 december 2014 + +// TODO why doesn't this trigger on first show? +// TODO see if there's anything not metaphor related in the last bits of the scrollbar series +// TODO rename this to boot + +HANDLER(resizeHandler) +{ + WINDOWPOS *wp; + RECT client; + intptr_t height; + + if (uMsg != WM_WINDOWPOSCHANGED) + return FALSE; + wp = (WINDOWPOS *) lParam; + if ((wp->flags & SWP_NOSIZE) != 0) + return FALSE; + + // TODO does wp store the window rect or the client rect? + if (GetClientRect(t->hwnd, &client) == 0) + panic("error getting Table client rect in resizeHandler()"); + // TODO do this after calling updateTableWidth() (which calls repositionHeader()?)? + client.top += t->headerHeight; + + // update the width... + // this will call repositionHeader(); there's a good reason... (see comments) + // TODO when I clean that mess up, remove this comment + updateTableWidth(t); + + // ...and the height + // TODO find out if order matters + height = client.bottom - client.top; + t->vpagesize = height / rowht(t); + // do a dummy scroll to reflect those changes + vscrollby(t, 0); + + *lResult = 0; + return TRUE; +} diff --git a/wintable/scroll.h b/wintable/scroll.h new file mode 100644 index 0000000..1f333a8 --- /dev/null +++ b/wintable/scroll.h @@ -0,0 +1,130 @@ +// 9 december 2014 + +struct scrollParams { + intptr_t *pos; + intptr_t pagesize; + intptr_t length; + intptr_t scale; + void (*post)(struct table *); + int *wheelCarry; +}; + +static void scrollto(struct table *t, int which, struct scrollParams *p, intptr_t pos) +{ + RECT scrollArea; + SCROLLINFO si; + intptr_t xamount, yamount; + + if (pos < 0) + pos = 0; + if (pos > p->length - p->pagesize) + pos = p->length - p->pagesize; + + // we don't want to scroll the header + if (GetClientRect(t->hwnd, &scrollArea) == 0) + panic("error getting Table client rect for scrollto()"); + scrollArea.top += t->headerHeight; + + // negative because ScrollWindowEx() is "backwards" + xamount = -(pos - *(p->pos)) * p->scale; + yamount = 0; + if (which == SB_VERT) { + yamount = xamount; + xamount = 0; + } + + if (ScrollWindowEx(t->hwnd, xamount, yamount, + &scrollArea, &scrollArea, NULL, NULL, + SW_ERASE | SW_INVALIDATE) == ERROR) +;// TODO failure case ignored for now; see https://bugs.winehq.org/show_bug.cgi?id=37706 +// panic("error scrolling Table"); + // TODO call UpdateWindow()? + + *(p->pos) = pos; + + // now commit our new scrollbar setup... + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; + si.nPage = p->pagesize; + si.nMin = 0; + si.nMax = p->length - 1; // endpoint inclusive + si.nPos = *(p->pos); + SetScrollInfo(t->hwnd, which, &si, TRUE); + + if (p->post != NULL) + (*(p->post))(t); +} + +static void scrollby(struct table *t, int which, struct scrollParams *p, intptr_t delta) +{ + scrollto(t, which, p, *(p->pos) + delta); +} + +static void scroll(struct table *t, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam) +{ + intptr_t pos; + SCROLLINFO si; + + pos = *(p->pos); + switch (LOWORD(wParam)) { + case SB_LEFT: // also SB_TOP + pos = 0; + break; + case SB_RIGHT: // also SB_BOTTOM + pos = p->length - p->pagesize; + break; + case SB_LINELEFT: // also SB_LINEUP + pos--; + break; + case SB_LINERIGHT: // also SB_LINEDOWN + pos++; + break; + case SB_PAGELEFT: // also SB_PAGEUP + pos -= p->pagesize; + break; + case SB_PAGERIGHT: // also SB_PAGEDOWN + pos += p->pagesize; + break; + case SB_THUMBPOSITION: + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_POS; + if (GetScrollInfo(t->hwnd, which, &si) == 0) + panic("error getting thumb position for scroll() in Table"); + pos = si.nPos; + break; + case SB_THUMBTRACK: + ZeroMemory(&si, sizeof (SCROLLINFO)); + si.cbSize = sizeof (SCROLLINFO); + si.fMask = SIF_TRACKPOS; + if (GetScrollInfo(t->hwnd, which, &si) == 0) + panic("error getting thumb track position for scroll() in Table"); + pos = si.nTrackPos; + break; + } + scrollto(t, which, p, pos); +} + +static void wheelscroll(struct table *t, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam) +{ + int delta; + int lines; + UINT scrollAmount; + + delta = GET_WHEEL_DELTA_WPARAM(wParam); + // TODO make a note of what the appropriate hscroll constant is + if (SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollAmount, 0) == 0) + // TODO use scrollAmount == 3 instead? + panic("error getting wheel scroll amount in wheelscroll()"); + if (scrollAmount == WHEEL_PAGESCROLL) + scrollAmount = p->pagesize; + if (scrollAmount == 0) // no mouse wheel scrolling (or t->pagesize == 0) + return; + // the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx + // see those pages for information on subtleties + delta += *(p->wheelCarry); + lines = delta * ((int) scrollAmount) / WHEEL_DELTA; + *(p->wheelCarry) = delta - lines * WHEEL_DELTA / ((int) scrollAmount); + scrollby(t, which, p, -lines); +} diff --git a/wintable/scrollbarseries b/wintable/scrollbarseries new file mode 100644 index 0000000..818b6a8 --- /dev/null +++ b/wintable/scrollbarseries @@ -0,0 +1,24 @@ + + http://blogs.msdn.com/b/oldnewthing/archive/2003/07/23/54576.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/07/25/54582.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/07/29/54591.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/07/30/54600.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54610.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54617.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54629.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/13/54639.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/15/54647.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/08/18/54668.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/09/54826.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/11/54885.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/13/54917.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/15/54925.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54944.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54945.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54946.aspx + http://blogs.msdn.com/b/oldnewthing/archive/2003/10/16/55344.aspx + not really part of, so to speak, but still http://blogs.msdn.com/b/oldnewthing/archive/2004/05/10/129068.aspx diff --git a/wintable/select.h b/wintable/select.h new file mode 100644 index 0000000..0539e52 --- /dev/null +++ b/wintable/select.h @@ -0,0 +1,255 @@ +// 13 december 2014 + +// damn winsock +static void doselect(struct table *t, intptr_t row, intptr_t column) +{ + RECT r, client; + intptr_t oldrow; + LONG width, height; + struct rowcol rc; + BOOL dovscroll; + intptr_t i; + intptr_t xpos; + LONG clientWidth; + + // check existing selection to see if it's valid + if (t->selectedRow == -1 && t->selectedColumn != -1) + panic("sanity check failure: old Table selection invalid (row == -1, column != -1)"); + if (t->selectedRow != -1 && t->selectedColumn == -1) + panic("sanity check failure: old Table selection invalid (row != -1, column == -1)"); + if (t->selectedRow >= t->count) + panic("sanity check failure: old Table selection invalid (row out of range)"); + if (t->selectedColumn >= t->nColumns) + panic("sanity check failure: old Table selection invalid (column out of range)"); + + oldrow = t->selectedRow; + t->selectedRow = row; + t->selectedColumn = column; + + // check new selection to see if it's valid + if (t->selectedRow == -1 && t->selectedColumn != -1) + panic("sanity check failure: new Table selection invalid (row == -1, column != -1)"); + if (t->selectedRow != -1 && t->selectedColumn == -1) + panic("sanity check failure: new Table selection invalid (row != -1, column == -1)"); + if (t->selectedRow >= t->count) + panic("sanity check failure: new Table selection invalid (row out of range)"); + if (t->selectedColumn >= t->nColumns) + panic("sanity check failure: new Table selection invalid (column out of range)"); + + // do this even if we don't scroll before; noScroll depends on it + if (GetClientRect(t->hwnd, &client) == 0) + panic("error getting Table client rect in doselect()"); + client.top += t->headerHeight; + height = rowht(t); + + // only scroll if we selected something + if (t->selectedRow == -1 || t->selectedColumn == -1) + goto noScroll; + + // first vertically scroll to the new row to make it fully visible (or as visible as possible) + if (t->selectedRow < t->vscrollpos) + vscrollto(t, t->selectedRow); + else { + rc.row = t->selectedRow; + rc.column = t->selectedColumn; + // first assume entirely outside the client area + dovscroll = TRUE; + if (rowColumnToClientRect(t, rc, &r)) + // partially outside the client area? + if (r.bottom <= client.bottom) // <= here since we are comparing bottoms (which are the first pixels outside the rectangle) + dovscroll = FALSE; + if (dovscroll) + vscrollto(t, t->selectedRow - t->vpagesize + 1); // + 1 because apparently just t->selectedRow - t->vpagesize results in no scrolling (t->selectedRow - t->vpagesize == t->vscrollpos)... + } + + // now see if the cell we want is to the left of offscreen, in which case scroll to its x-position + xpos = 0; + for (i = 0; i < t->selectedColumn; i++) + xpos += columnWidth(t, i); + if (xpos < t->hscrollpos) + hscrollto(t, xpos); + else { + // if the full cell is not visible, scroll to the right just enough to make it fully visible (or as visible as possible) + width = columnWidth(t, t->selectedColumn); + clientWidth = client.right - client.left; + if (xpos + width > t->hscrollpos + clientWidth) // > because both sides deal with the first pixel outside + // if the column is too wide, then just make it occupy the whole visible area (left-aligned) + if (width > clientWidth) // TODO >= ? + hscrollto(t, xpos); + else + // TODO don't use t->hpagesize here? depends if other code uses it + hscrollto(t, (xpos + width) - t->hpagesize); + } + +noScroll: + // now redraw the old and new /rows/ + // we do this after scrolling so the rectangles to be invalidated make sense + r.left = client.left; + r.right = client.right; + if (oldrow != -1 && oldrow >= t->vscrollpos) { + r.top = client.top + ((oldrow - t->vscrollpos) * height); + r.bottom = r.top + height; + if (InvalidateRect(t->hwnd, &r, TRUE) == 0) + panic("error queueing previously selected row for redraw in doselect()"); + } + // t->selectedRow must be visible by this point; we scrolled to it + if (t->selectedRow != -1 && t->selectedRow != oldrow) { + r.top = client.top + ((t->selectedRow - t->vscrollpos) * height); + r.bottom = r.top + height; + if (InvalidateRect(t->hwnd, &r, TRUE) == 0) + panic("error queueing newly selected row for redraw in doselect()"); + } +} + +// TODO make this needless +HANDLER(checkboxMouseDownHandler); + +// TODO which WM_xBUTTONDOWNs? +HANDLER(mouseDownSelectHandler) +{ + struct rowcol rc; + + rc = lParamToRowColumn(t, lParam); + // don't check if lParamToRowColumn() returned row -1 or column -1; we want deselection behavior + doselect(t, rc.row, rc.column); + // TODO separate this from here + checkboxMouseDownHandler(t, uMsg, wParam, lParam, lResult); + *lResult = 0; + return TRUE; +} + +/* +the routine below is intended to simulate the comctl32.dll listview keyboard navigation rules, at least as far as vertical navigation is concerned. +horizontal scrolling is different because unlike the comctl32 listview, we say that a single column in each row has the keyboard focus, so left and right navigate between columns here, instead of scrolling left/right by pixels. + TODO provide an override for scrolling by pixels? + TODO any other keyboard shortcuts? + TODO browser keys + TODO media navigation keys + TODO XBUTTON1/2? + TODO clear keys? + +keyboard selection behaviors of the windows 7 listview: +with 100 items (0-99), the window currently shows items 30 through 47 as well as having item 48 partially visible +- item 30: + - page up -> item 13 + - page down -> item 47 +- item 31: + - page up -> item 30 + - page down -> item 47 +- item 42: + - page up -> item 30 + - page down -> item 47 +- item 46: + - page up -> item 30 + - page down -> item 47 +- item 47: + - page up: -> item 30 + - page down: -> item 64 + +when nothing is selected: +- down selects item 0 regardless of scroll +- up selects nothing regardless of scroll +- page down selects the last fully visible item depending on scroll + - so with the above configuration: + - item 0 -> item 17 + - item 30 -> item 47 + - item 80 -> item 97 +- page up selects item 0 regardless of scroll +- home selects item 0 regardless of scroll +- end selects the last item regardless of scroll + +for left and right we will simulate up and down, respectively (so right selects row 0 column 0); remember that you can't have either row or column be -1 but not both + +TODO what happens if page up and page down are pressed with an item selected and the scroll in a different position? +*/ + +HANDLER(keyDownSelectHandler) +{ + intptr_t row; + intptr_t column; + + if (t->count == 0 || t->nColumns == 0) // no items to select + return FALSE; + row = t->selectedRow; + column = t->selectedColumn; + switch (wParam) { + case VK_UP: + if (row == -1) + return FALSE; + row--; + if (row < 0) + row = 0; + break; + case VK_DOWN: + if (row == -1) { + row = 0; + column = 0; + } else { + row++; + if (row >= t->count) + row = t->count - 1; + } + break; + case VK_LEFT: + if (column == -1) + return FALSE; + column--; + if (column < 0) + column = 0; + break; + case VK_RIGHT: + if (column == -1) { + row = 0; + column = 0; + } else { + column++; + if (column >= t->nColumns) + column = t->nColumns - 1; + } + break; + case VK_HOME: + row = 0; + if (column == -1) + column = 0; + break; + case VK_END: + row = t->count - 1; + if (column == -1) + column = 0; + break; + case VK_PRIOR: + if (row == -1) { + row = 0; + column = 0; + } else { + row = t->vscrollpos; + if (row == t->selectedRow) + // TODO investigate why the - 1 is needed here and below + // TODO if this is a misunderstanding of how t->vpagesize works, figure out what happens if there is no partially visible row, and what is supposed to happen + row -= t->vpagesize - 1; + if (row < 0) + row = 0; + } + break; + case VK_NEXT: + if (row == -1) { + row = t->vscrollpos + t->vpagesize - 1; + // TODO ensusre this is the case with the real list view + if (row >= t->count) + row = t->count - 1; + column = 0; + } else { + row = t->vscrollpos + t->vpagesize - 1; + if (row == t->selectedRow) + row += t->vpagesize - 1; + if (row >= t->count) + row = t->count - 1; + } + break; + default: + return FALSE; + } + doselect(t, row, column); + *lResult = 0; + return TRUE; +} diff --git a/wintable/util.h b/wintable/util.h new file mode 100644 index 0000000..4dd73d6 --- /dev/null +++ b/wintable/util.h @@ -0,0 +1,119 @@ +// 4 december 2014 + +typedef BOOL (*handlerfunc)(struct table *, UINT, WPARAM, LPARAM, LRESULT *); +#define HANDLER(name) static BOOL name(struct table *t, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) + +static BOOL runHandlers(const handlerfunc list[], struct table *t, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) +{ + handlerfunc *p; + + for (p = list; *p != NULL; p++) + if ((*(*p))(t, uMsg, wParam, lParam, lResult)) + return TRUE; + return FALSE; +} + +// memory allocation stuff +// each of these functions do an implicit ZeroMemory() +// these also make tableRealloc(NULL, ...)/tableFree(NULL) act like realloc(NULL, ...)/free(NULL) (that is, same as tableAlloc(...)/malloc(...) and a no-op, respectively) +// we /would/ use LocalAlloc() here because +// - whether the malloc() family supports the last-error code is undefined +// - the HeapAlloc() family DOES NOT support the last-error code; you're supposed to use Windows exceptions, and I can't find a clean way to do this with MinGW-w64 that doesn't rely on inline assembly or external libraries (unless they added __try/__except blocks) +// - there's no VirtualReAlloc() to complement VirtualAlloc() and I'm not sure if we can even get the original allocated size back out reliably to write it ourselves (http://blogs.msdn.com/b/oldnewthing/archive/2012/03/16/10283988.aspx) +// alas, LocalAlloc() doesn't want to work on real Windows 7 after a few times, throwing up ERROR_NOT_ENOUGH_MEMORY after three (3) ints or so :| +// we'll use malloc() until then +// needless to say, TODO + +static void *tableAlloc(size_t size, const char *panicMessage) +{ +// HLOCAL out; + void *out; + +// out = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, size); + out = malloc(size); + if (out == NULL) + panic(panicMessage); + ZeroMemory(out, size); + return (void *) out; +} + +static void *tableRealloc(void *p, size_t size, const char *panicMessage) +{ +// HLOCAL out; + void *out; + + if (p == NULL) + return tableAlloc(size, panicMessage); +// out = LocalReAlloc((HLOCAL) p, size, LMEM_ZEROINIT); + out = realloc(p, size); + if (out == NULL) + panic(panicMessage); + // TODO zero the extra memory + return (void *) out; +} + +static void tableFree(void *p, const char *panicMessage) +{ + if (p == NULL) + return; +// if (LocalFree((HLOCAL) p) != NULL) +// panic(panicMessage); + free(p); +} + +// font selection + +static HFONT selectFont(struct table *t, HDC dc, HFONT *newfont) +{ + HFONT prevfont; + + // copy it in casse we get a WM_SETFONT before this call's respective deselectFont() call + *newfont = t->font; + if (*newfont == NULL) { + // get it on demand in the (unlikely) event it changes while this Table is alive + *newfont = GetStockObject(SYSTEM_FONT); + if (*newfont == NULL) + panic("error getting default font for selecting into Table DC"); + } + prevfont = (HFONT) SelectObject(dc, *newfont); + if (prevfont == NULL) + panic("error selecting Table font into Table DC"); + return prevfont; +} + +static void deselectFont(HDC dc, HFONT prevfont, HFONT newfont) +{ + if (SelectObject(dc, prevfont) != newfont) + panic("error deselecting Table font from Table DC"); + // doin't delete newfont here, even if it is the system font (see http://msdn.microsoft.com/en-us/library/windows/desktop/dd144925%28v=vs.85%29.aspx) +} + +// and back to other functions + +static LONG columnWidth(struct table *t, intptr_t n) +{ + RECT r; + + if (SendMessageW(t->header, HDM_GETITEMRECT, (WPARAM) n, (LPARAM) (&r)) == 0) + panic("error getting Table column width"); + return r.right - r.left; +} + +/* TODO: +http://blogs.msdn.com/b/oldnewthing/archive/2003/10/13/55279.aspx +http://blogs.msdn.com/b/oldnewthing/archive/2003/10/14/55286.aspx +we'll need to make sure that initial edge case works properly +(TODO get the linked article in the latter) +also implement retrack() as so, in the WM_MOUSEMOVE handler +*/ +static void retrack(struct table *t) +{ + TRACKMOUSEEVENT tm; + + ZeroMemory(&tm, sizeof (TRACKMOUSEEVENT)); + tm.cbSize = sizeof (TRACKMOUSEEVENT); + tm.dwFlags = TME_LEAVE; // TODO TME_NONCLIENT as well? + tm.hwndTrack = t->hwnd; + if ((*tableTrackMouseEvent)(&tm) == 0) + panic("error retracking Table mouse events"); +} diff --git a/wintable/vscroll.h b/wintable/vscroll.h new file mode 100644 index 0000000..20f0df7 --- /dev/null +++ b/wintable/vscroll.h @@ -0,0 +1,66 @@ +// 9 december 2014 + +// forward declaration needed here +static void repositionHeader(struct table *); + +static struct scrollParams vscrollParams(struct table *t) +{ + struct scrollParams p; + + ZeroMemory(&p, sizeof (struct scrollParams)); + p.pos = &(t->vscrollpos); + p.pagesize = t->vpagesize; + p.length = t->count; + p.scale = rowht(t); + p.post = NULL; + p.wheelCarry = &(t->vwheelCarry); + return p; +} + +static void vscrollto(struct table *t, intptr_t pos) +{ + struct scrollParams p; + + p = vscrollParams(t); + scrollto(t, SB_VERT, &p, pos); +} + +static void vscrollby(struct table *t, intptr_t delta) +{ + struct scrollParams p; + + p = vscrollParams(t); + scrollby(t, SB_VERT, &p, delta); +} + +static void vscroll(struct table *t, WPARAM wParam, LPARAM lParam) +{ + struct scrollParams p; + + p = vscrollParams(t); + scroll(t, SB_VERT, &p, wParam, lParam); +} + +static void vwheelscroll(struct table *t, WPARAM wParam, LPARAM lParam) +{ + struct scrollParams p; + + p = vscrollParams(t); + wheelscroll(t, SB_VERT, &p, wParam, lParam); +} + +// TODO WM_MOUSEWHEEL +HANDLER(vscrollHandler) +{ + switch (uMsg) { + case WM_VSCROLL: + vscroll(t, wParam, lParam); + *lResult = 0; + return TRUE; + case WM_MOUSEWHEEL: + vwheelscroll(t, wParam, lParam); + *lResult = 0; + return TRUE; + } + return FALSE; +} -- cgit v1.2.3