summaryrefslogtreecommitdiff
path: root/prev/wintable
diff options
context:
space:
mode:
Diffstat (limited to 'prev/wintable')
-rw-r--r--prev/wintable/accessibility.h1081
-rw-r--r--prev/wintable/api.h110
-rw-r--r--prev/wintable/checkboxes.h247
-rw-r--r--prev/wintable/children.h19
-rw-r--r--prev/wintable/coord.h164
-rw-r--r--prev/wintable/draw.h215
-rw-r--r--prev/wintable/events.h62
-rw-r--r--prev/wintable/header.h80
-rw-r--r--prev/wintable/hscroll.h52
-rw-r--r--prev/wintable/includethis.h73
-rw-r--r--prev/wintable/links3
-rw-r--r--prev/wintable/main.h165
-rw-r--r--prev/wintable/resize.h21
-rw-r--r--prev/wintable/scroll.h137
-rw-r--r--prev/wintable/scrollbarseries24
-rw-r--r--prev/wintable/select.h269
-rw-r--r--prev/wintable/test.c198
-rw-r--r--prev/wintable/update.h53
-rw-r--r--prev/wintable/util.h119
-rw-r--r--prev/wintable/vscroll.h65
20 files changed, 3157 insertions, 0 deletions
diff --git a/prev/wintable/accessibility.h b/prev/wintable/accessibility.h
new file mode 100644
index 0000000..ff7364b
--- /dev/null
+++ b/prev/wintable/accessibility.h
@@ -0,0 +1,1081 @@
+// 24 december 2014
+
+// implement MSAA-conformant accessibility
+// we need to use MSAA because UI Automation is too new for us
+// unfortunately, MSAA's documentation is... very poor. very ambiguous, very inconsistent (just run through this file's commit history and watch the TODO progression to see)
+// resources:
+// http://msdn.microsoft.com/en-us/library/ms971338.aspx
+// http://msdn.microsoft.com/en-us/library/windows/desktop/cc307844.aspx
+// http://msdn.microsoft.com/en-us/library/windows/desktop/cc307847.aspx
+// http://blogs.msdn.com/b/saraford/archive/2004/08/20/which-controls-support-which-msaa-properties-and-how-these-controls-implement-msaa-properties.aspx
+// http://msdn.microsoft.com/en-us/library/ms971325
+// 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
+
+// notes:
+// - TODO figure out what to do about header
+// - a row extends as far right as the right edge of the last cell in the row; anything to the right of that is treated as table space (just like with mouse selection)
+// - this has the added effect that hit-testing can only ever return either the table or a cell, never a row
+// - cells have no children; checkbox cells are themselves the accessible object
+// - TODO if we ever add combobox columns, this will need to change somehow
+// - only Table and Cell can have focus; only Row can have selection
+// - TODO allow selecting a cell?
+
+// TODOs:
+// - make sure E_POINTER and RPC_E_DISCONNECTED are correct returns for IAccessible
+// - return last error on newTableAcc() in all accessible functions
+// - figure out what should be names and what should be values
+// - figure out what to do about that header row
+// - http://acccheck.codeplex.com/
+
+// uncomment this to debug table linked list management
+//#define TABLE_DEBUG_LINKEDLIST
+
+// TODO get rid of this
+typedef struct tableAccWhat tableAccWhat;
+
+struct tableAccWhat {
+ LONG role;
+ intptr_t row;
+ intptr_t column;
+};
+
+struct tableAcc {
+ const IAccessibleVtbl *vtbl;
+ ULONG refcount;
+ struct table *t;
+ IAccessible *std;
+ tableAccWhat what;
+
+ // the list of currently active accessibility objects is a doubly linked list
+ struct tableAcc *prev;
+ struct tableAcc *next;
+};
+
+#ifdef TABLE_DEBUG_LINKEDLIST
+void list(struct table *t)
+{
+ struct tableAcc *ta;
+
+ printf("\n");
+ if (t->firstAcc == NULL) {
+ printf("\tempty\n");
+ return;
+ }
+ printf("\t-> ");
+ for (ta = t->firstAcc; ta != NULL; ta = ta->next)
+ printf("%p ", ta);
+ printf("\n\t<- ");
+ for (ta = t->firstAcc; ta->next != NULL; ta = ta->next)
+ ;
+ for (; ta != NULL; ta = ta->prev)
+ printf("%p ", ta);
+ printf("\n");
+}
+#endif
+
+// called after each allocation
+static struct tableAcc *newTableAcc(struct table *t, LONG role, intptr_t row, intptr_t column);
+
+// common validation for accessibility functions that take varChild
+// also normalizes what as if varChild == CHILDID_SELF
+static HRESULT normalizeWhat(struct tableAcc *ta, VARIANT varChild, tableAccWhat *what)
+{
+ LONG cid;
+
+ if (varChild.vt != VT_I4)
+ return E_INVALIDARG;
+ cid = varChild.lVal;
+ if (cid == CHILDID_SELF)
+ return S_OK;
+ cid--;
+ if (cid < 0)
+ return E_INVALIDARG;
+ switch (what->role) {
+ case ROLE_SYSTEM_TABLE:
+ // TODO +1?
+ if (cid >= ta->t->count)
+ return E_INVALIDARG;
+ what->role = ROLE_SYSTEM_ROW;
+ what->row = (intptr_t) cid;
+ what->column = -1;
+ break;
+ case ROLE_SYSTEM_ROW:
+ case ROLE_SYSTEM_CELL:
+ // TODO
+ // TODO also figure out what to do if the current row/cell become invalid (rows being removed, etc.)
+ break;
+ }
+ return S_OK;
+}
+
+#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)) {
+ IAccessible_AddRef(this);
+ *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) {
+ struct tableAcc *prev, *next;
+
+#ifdef TABLE_DEBUG_LINKEDLIST
+if (TA->t != NULL) { printf("before delete:"); list(TA->t); }
+#endif
+ if (TA->t != NULL && TA->t->firstAcc == TA)
+ TA->t->firstAcc = TA->next;
+ prev = TA->prev;
+ next = TA->next;
+ if (prev != NULL)
+ prev->next = next;
+ if (next != NULL)
+ next->prev = prev;
+#ifdef TABLE_DEBUG_LINKEDLIST
+if (TA->t != NULL) { printf("after delete:"); list(TA->t); }
+#endif
+ if (TA->std != NULL)
+ IAccessible_Release(TA->std);
+ tableFree(TA, "error freeing Table accessibility object");
+ return 0;
+ }
+ return TA->refcount;
+}
+
+// IDispatch
+// TODO make sure relegating these to the standard accessibility object is harmless
+
+static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfoCount(IAccessible *this, UINT *pctinfo)
+{
+ if (TA->t == NULL || TA->std == NULL) {
+ // TODO set values on error
+ return RPC_E_DISCONNECTED;
+ }
+ return IAccessible_GetTypeInfoCount(TA->std, pctinfo);
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfo(IAccessible *this, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
+{
+ if (TA->t == NULL || TA->std == NULL) {
+ // TODO set values on error
+ return RPC_E_DISCONNECTED;
+ }
+ return IAccessible_GetTypeInfo(TA->std, iTInfo, lcid, ppTInfo);
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccGetIDsOfNames(IAccessible *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
+{
+ if (TA->t == NULL || TA->std == NULL) {
+ // TODO set values on error
+ return RPC_E_DISCONNECTED;
+ }
+ return IAccessible_GetIDsOfNames(TA->std, riid, rgszNames, cNames, lcid, rgDispId);
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccInvoke(IAccessible *this, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
+{
+ if (TA->t == NULL || TA->std == NULL) {
+ // TODO set values on error
+ return RPC_E_DISCONNECTED;
+ }
+ return IAccessible_Invoke(TA->std, dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
+}
+
+// IAccessible
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accParent(IAccessible *this, IDispatch **ppdispParent)
+{
+ if (ppdispParent == NULL)
+ return E_POINTER;
+ // TODO set ppdispParent to zero?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ // TODO check if row/column is still valid
+ switch (TA->what.role) {
+ case ROLE_SYSTEM_TABLE:
+ // defer to standard accessible object
+ // TODO [EDGE CASE/POOR DOCUMENTATION?] https://msdn.microsoft.com/en-us/library/ms971325 says "Returns the IDispatch interface of the Table object."; isn't that just returning self?
+ return IAccessible_get_accParent(TA->std, ppdispParent);
+ case ROLE_SYSTEM_ROW:
+ *ppdispParent = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_TABLE, -1, -1);
+ return S_OK;
+ case ROLE_SYSTEM_CELL:
+ *ppdispParent = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_ROW, TA->what.row, -1);
+ return S_OK;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accChildCount(IAccessible *this, long *pcountChildren)
+{
+ if (pcountChildren == NULL)
+ return E_POINTER;
+ // TODO set pcountChildren to zero?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ switch (TA->what.role) {
+ case ROLE_SYSTEM_TABLE:
+ // TODO header row
+ *pcountChildren = (long) (TA->t->count);
+ return S_OK;
+ case ROLE_SYSTEM_ROW:
+ *pcountChildren = (long) (TA->t->nColumns);
+ return S_OK;
+ case ROLE_SYSTEM_CELL:
+ *pcountChildren = 0;
+ return S_OK;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+}
+
+// TODO [EDGE CASE/NOT DOCUMENTED/CHECK SAMPLE] what happens if CHILDID_SELF is passed?
+// TODO [EDGE CASE/NOT DOCUMENTED/CHECK SAMPLE] what SHOULD happen if an out of bounds ID is passed?
+static HRESULT STDMETHODCALLTYPE tableAccget_accChild(IAccessible *this, VARIANT varChild, IDispatch **ppdispChild)
+{
+ LONG cid;
+
+ if (ppdispChild == NULL)
+ return E_POINTER;
+ *ppdispChild = NULL;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ if (varChild.vt != VT_I4)
+ return E_INVALIDARG;
+ cid = varChild.lVal;
+ if (cid < 0)
+ // TODO really?
+ return E_INVALIDARG;
+ if (cid == CHILDID_SELF)
+ return E_FAIL; // TODO
+ cid--;
+ switch (TA->what.role) {
+ case ROLE_SYSTEM_TABLE:
+ // TODO table header
+ if (TA->t->count == 0)
+ return S_FALSE;
+ if (cid > TA->t->count - 1)
+ // TODO really?
+ return E_INVALIDARG;
+ *ppdispChild = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_ROW, cid, -1);
+ return S_OK;
+ case ROLE_SYSTEM_ROW:
+ // TODO verify that row is still valid
+ if (TA->t->nColumns == 0)
+ return S_FALSE;
+ if (cid > TA->t->nColumns - 1)
+ // TODO really?
+ return E_INVALIDARG;
+ *ppdispChild = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_CELL, TA->what.row, cid);
+ case ROLE_SYSTEM_CELL:
+ // TODO verify that row/column are still valid?
+ return S_FALSE;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accName(IAccessible *this, VARIANT varChild, BSTR *pszName)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pszName == NULL)
+ return E_POINTER;
+ // TODO double-check that this must be set to zero
+ *pszName = NULL;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ switch (what.role) {
+ case ROLE_SYSTEM_TABLE:
+ // defer to standard accessible object
+ return IAccessible_get_accName(TA->std, varChild, pszName);
+ case ROLE_SYSTEM_ROW:
+ // TODO
+ return S_FALSE;
+ case ROLE_SYSTEM_CELL:
+ // TODO
+ return S_FALSE;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+}
+
+// this differs quite much from what is described at https://msdn.microsoft.com/en-us/library/ms971325
+static HRESULT STDMETHODCALLTYPE tableAccget_accValue(IAccessible *this, VARIANT varChild, BSTR *pszValue)
+{
+ HRESULT hr;
+ tableAccWhat what;
+ WCHAR *text;
+
+ if (pszValue == NULL)
+ return E_POINTER;
+ // TODO set pszValue to zero?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ switch (what.role) {
+ case ROLE_SYSTEM_TABLE:
+ // TODO really?
+ return IAccessible_get_accValue(TA->std, varChild, pszValue);
+ case ROLE_SYSTEM_ROW:
+ // TODO
+ return DISP_E_MEMBERNOTFOUND;
+ case ROLE_SYSTEM_CELL:
+ switch (TA->t->columnTypes[what.column]) {
+ case tableColumnText:
+ text = getCellText(TA->t, what.row, what.column);
+ // TODO check for error
+ *pszValue = SysAllocString(text);
+ returnCellData(TA->t, what.row, what.column, text);
+ return S_OK;
+ case tableColumnImage:
+ // TODO
+ return DISP_E_MEMBERNOTFOUND;
+ case tableColumnCheckbox:
+ // TODO!!!!!!
+ return DISP_E_MEMBERNOTFOUND;
+ }
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accDescription(IAccessible *this, VARIANT varChild, BSTR *pszDescription)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pszDescription == NULL)
+ return E_POINTER;
+ *pszDescription = NULL;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ // don't support descriptions anyway; do return the above errors just to be safe
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accRole(IAccessible *this, VARIANT varChild, VARIANT *pvarRole)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pvarRole == NULL)
+ return E_POINTER;
+ pvarRole->vt = VT_EMPTY;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ pvarRole->vt = VT_I4;
+ pvarRole->lVal = what.role;
+ return S_OK;
+}
+
+// TODO reason about STATE_SYSTEM_INVISIBLE and STATE_SYSTEM_OFFSCREEN
+static HRESULT STDMETHODCALLTYPE tableAccget_accState(IAccessible *this, VARIANT varChild, VARIANT *pvarState)
+{
+ HRESULT hr;
+ tableAccWhat what;
+ LONG state;
+
+ if (pvarState == NULL)
+ return E_POINTER;
+ pvarState->vt = VT_EMPTY;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+
+ state = 0;
+ switch (what.role) {
+ case ROLE_SYSTEM_TABLE:
+ hr = IAccessible_get_accState(TA->std, varChild, pvarState);
+ if (hr != S_OK)
+ return hr;
+ // TODO make sure pvarState->vt == VT_I4 (what to return otherwise?)
+ state |= pvarState->lVal;
+ break;
+ case ROLE_SYSTEM_ROW:
+ state |= STATE_SYSTEM_SELECTABLE;
+ if (TA->t->selectedRow == what.row)
+ state |= STATE_SYSTEM_SELECTED;
+ break;
+ case ROLE_SYSTEM_CELL:
+ if (TA->t->columnTypes[what.column] == tableColumnCheckbox) {
+ // TODO is there no STATE_SYSTEM_CHECKABLE?
+ if (isCheckboxChecked(TA->t, what.row, what.column))
+ state |= STATE_SYSTEM_CHECKED;
+ }
+ state |= STATE_SYSTEM_FOCUSABLE;
+ if (TA->t->selectedRow == what.row && TA->t->selectedColumn == what.column)
+ state |= STATE_SYSTEM_FOCUSED;
+ if (TA->t->columnTypes[what.column] != tableColumnCheckbox)
+ state |= STATE_SYSTEM_READONLY;
+ break;
+ }
+ pvarState->vt = VT_I4;
+ pvarState->lVal = state;
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accHelp(IAccessible *this, VARIANT varChild, BSTR *pszHelp)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pszHelp == NULL)
+ return E_POINTER;
+ *pszHelp = NULL;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ // don't support help anyway; do return the above errors just to be safe
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+// TODO Inspect.exe seems to ignore the DISP_E_MEMBERNOTFOUND and just tells us the help topic is the empty string; make sure this works right
+static HRESULT STDMETHODCALLTYPE tableAccget_accHelpTopic(IAccessible *this, BSTR *pszHelpFile, VARIANT varChild, long *pidTopic)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pszHelpFile == NULL || pidTopic == NULL)
+ return E_POINTER;
+ // TODO set pszHelpFile and pidTopic to zero?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ // don't support Windows Help (the super-old .hlp help files) topics anyway; do return the above errors just to be safe
+ // TODO [EDGE CASE??] or should we defer back to the standard accessible object? get_accHelp() was explicitly documented as not being supported by the standard/common controls, but this one isn't...
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accKeyboardShortcut(IAccessible *this, VARIANT varChild, BSTR *pszKeyboardShortcut)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pszKeyboardShortcut == NULL)
+ return E_POINTER;
+ // TODO set pszKeyboardShortcut to zero?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ // defer to the standard accessible object for the table itself in case a program assigns an access key somehow (adjacent label?); MSDN says to, anyway
+ if (what.role == ROLE_SYSTEM_TABLE)
+ return IAccessible_get_accKeyboardShortcut(TA->std, varChild, pszKeyboardShortcut);
+ if (what.role == ROLE_SYSTEM_CELL)
+ ; // TODO implement this for checkbox cells?
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+// TODO TEST THIS
+// TODO [EDGE CASE??] no parents?
+static HRESULT STDMETHODCALLTYPE tableAccget_accFocus(IAccessible *this, VARIANT *pvarChild)
+{
+ HRESULT hr;
+
+ if (pvarChild == NULL)
+ return E_POINTER;
+ // TODO set pvarChild to empty?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ // TODO verify that TA is still pointing to a valid row/column
+
+ // first see if the control has the focus
+ // this is why a standard accessible object is needed on all accessible objects
+ hr = IAccessible_get_accFocus(TA->std, pvarChild);
+ // check the pvarChild type instead of hr
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318479%28v=vs.85%29.aspx does this
+ // TODO [EDGE CASE] figure out why
+ if (pvarChild->vt != VT_I4)
+ return hr;
+
+ switch (TA->what.role) {
+ case ROLE_SYSTEM_TABLE:
+ if (TA->t->selectedRow != -1 && TA->t->selectedColumn != -1)
+ goto selectedCell;
+ goto self;
+ case ROLE_SYSTEM_ROW:
+ if (TA->t->selectedRow != TA->what.row)
+ goto nothing;
+ goto selectedCell;
+ case ROLE_SYSTEM_CELL:
+ if (TA->t->selectedRow != TA->what.row)
+ goto nothing;
+ if (TA->t->selectedColumn != TA->what.column)
+ goto nothing;
+ goto self;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+
+nothing:
+ pvarChild->vt = VT_EMPTY;
+ // TODO really this one?
+ return S_FALSE;
+
+self:
+ pvarChild->vt = VT_I4;
+ pvarChild->lVal = CHILDID_SELF;
+ return S_OK;
+
+selectedCell:
+ pvarChild->vt = VT_I4;
+ pvarChild->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_CELL, TA->what.row, TA->what.column);
+ return S_OK;
+}
+
+// note: https://msdn.microsoft.com/en-us/library/ms971325 is geared toward cell-based selection
+// we have row-based selection, so only Tables implement this method, and they return a row
+static HRESULT STDMETHODCALLTYPE tableAccget_accSelection(IAccessible *this, VARIANT *pvarChildren)
+{
+ if (pvarChildren == NULL)
+ return E_POINTER;
+ // TOOD set pvarChildren to VT_EMPTY?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ if (TA->what.role != ROLE_SYSTEM_TABLE)
+ // TODO [EDGE CASE] implement this for row anyway? how?
+ return DISP_E_MEMBERNOTFOUND;
+ if (TA->t->selectedRow == -1) {
+ pvarChildren->vt = VT_EMPTY;
+ return S_OK;
+ }
+ pvarChildren->vt = VT_DISPATCH;
+ pvarChildren->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_ROW, TA->t->selectedRow, -1);
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccget_accDefaultAction(IAccessible *this, VARIANT varChild, BSTR *pszDefaultAction)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (pszDefaultAction == NULL)
+ return E_POINTER;
+ *pszDefaultAction = NULL;
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ if (what.role == ROLE_SYSTEM_CELL)
+ ; // TODO implement this for checkbox cells?
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+// TODO should this method result in an event?
+// TODO [EDGE CASE] how do we deselect? in the table or in the row? wouldn't this go against multiple selection?
+// TODO require cell rows to be selected before focusing?
+static HRESULT STDMETHODCALLTYPE tableAccaccSelect(IAccessible *this, long flagsSelect, VARIANT varChild)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ if (what.role == ROLE_SYSTEM_TABLE) // defer to the standard accessible object
+ return IAccessible_accSelect(TA->std, flagsSelect, varChild);
+ // reject flags that are only applicable to multiple selection
+ if ((flagsSelect & (SELFLAG_EXTENDSELECTION | SELFLAG_ADDSELECTION | SELFLAG_REMOVESELECTION)) != 0)
+ return E_INVALIDARG;
+ // and do nothing if a no-op
+ if (flagsSelect == SELFLAG_NONE)
+ return S_FALSE;
+ // TODO cast ~ expressions to the correct type
+ switch (what.role) {
+ case ROLE_SYSTEM_ROW:
+ // reject any other flag
+ if ((flagsSelect & (~SELFLAG_TAKESELECTION)) != 0)
+ return E_INVALIDARG;
+ if ((flagsSelect & SELFLAG_TAKESELECTION) != 0) {
+ if (TA->t->nColumns == 0) // can't select
+ return S_FALSE;
+ // if no column selected, select first (congruent to behavior of certain keyboard events)
+ // TODO handle GetLastError()
+ if (TA->t->selectedColumn == -1)
+ doselect(TA->t, what.row, TA->t->selectedColumn);
+ else
+ doselect(TA->t, what.row, 0);
+ return S_OK;
+ }
+ return S_FALSE;
+ case ROLE_SYSTEM_CELL:
+ // reject any other flag
+ if ((flagsSelect & (~SELFLAG_TAKEFOCUS)) != 0)
+ return E_INVALIDARG;
+ if ((flagsSelect & SELFLAG_TAKEFOCUS) != 0) {
+ doselect(TA->t, what.row, what.column);
+ return S_OK;
+ }
+ return S_FALSE;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccaccLocation(IAccessible *this, long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild)
+{
+ HRESULT hr;
+ tableAccWhat what;
+ RECT r;
+ POINT pt;
+ struct rowcol rc;
+
+ if (pxLeft == NULL || pyTop == NULL || pcxWidth == NULL || pcyHeight == NULL)
+ return E_POINTER;
+ // TODO set the out parameters to zero?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ switch (what.role) {
+ case ROLE_SYSTEM_TABLE:
+ return IAccessible_accLocation(TA->std, pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
+ case ROLE_SYSTEM_ROW:
+ // TODO actually write this
+ return E_FAIL;
+ case ROLE_SYSTEM_CELL:
+ rc.row = what.row;
+ rc.column = what.column;
+ if (!rowColumnToClientRect(TA->t, rc, &r)) {
+ // TODO [EDGE CASE] what do we do here?
+ // TODO we have to return something indicating that the object is off-screen
+ }
+ // TODO [EDGE CASE] intersect with client rect?
+ break;
+ }
+ pt.x = r.left;
+ pt.y = r.top;
+ if (ClientToScreen(TA->t->hwnd, &pt) == 0)
+ return HRESULT_FROM_WIN32(GetLastError());
+ *pxLeft = pt.x;
+ *pyTop = pt.y;
+ *pcxWidth = r.right - r.left;
+ *pcyHeight = r.bottom - r.top;
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccaccNavigate(IAccessible *this, long navDir, VARIANT varStart, VARIANT *pvarEndUpAt)
+{
+ HRESULT hr;
+ tableAccWhat what;
+ intptr_t row = -1;
+ intptr_t column = -1;
+
+ if (pvarEndUpAt == NULL)
+ return E_POINTER;
+ // TODO set pvarEndUpAt to an invalid value?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varStart, &what);
+ if (hr != S_OK)
+ return hr;
+ switch (what.role) {
+ case ROLE_SYSTEM_TABLE:
+ switch (navDir) {
+ case NAVDIR_FIRSTCHILD:
+ // TODO header row
+ if (TA->t->count == 0)
+ goto nowhere;
+ row = 0;
+ goto specificRow;
+ case NAVDIR_LASTCHILD:
+ // TODO header row
+ if (TA->t->count == 0)
+ goto nowhere;
+ row = TA->t->count - 1;
+ goto specificRow;
+ }
+ // otherwise, defer to the standard accessible object
+ return IAccessible_accNavigate(TA->std, navDir, varStart, pvarEndUpAt);
+ case ROLE_SYSTEM_ROW:
+ row = what.row;
+ switch (navDir) {
+ case NAVDIR_UP:
+ case NAVDIR_PREVIOUS:
+ if (row == 0) // can't go up
+ goto nowhere;
+ row--;
+ // row should still be valid because normalizeWhat() returns an error if the current row is no longer valid, and if that's valid, the row above it should also be valid
+ goto specificRow;
+ case NAVDIR_DOWN:
+ case NAVDIR_NEXT:
+ if (row == TA->t->count - 1) // can't go down
+ goto nowhere;
+ row++;
+ // row should still be valid by the above conjecture
+ goto specificRow;
+ case NAVDIR_LEFT:
+ case NAVDIR_RIGHT:
+ goto nowhere;
+// TODO this doesn't actually exist yet https://msdn.microsoft.com/en-us/library/ms971325 talks about it
+// case NAVDIR_PARENT:
+// goto tableItself;
+ case NAVDIR_FIRSTCHILD:
+ if (TA->t->nColumns == 0)
+ goto nowhere;
+ column = 0;
+ goto specificCell;
+ case NAVDIR_LASTCHILD:
+ if (TA->t->nColumns == 0)
+ goto nowhere;
+ column = TA->t->nColumns - 1;
+ goto specificCell;
+ }
+ // TODO differentiate between unsupported navigation directions and invalid navigation directions
+ goto nowhere;
+ case ROLE_SYSTEM_CELL:
+ row = what.row;
+ column = what.column;
+ switch (navDir) {
+ case NAVDIR_UP:
+ if (row == 0) // can't go up
+ goto nowhere;
+ row--;
+ goto specificCell;
+ case NAVDIR_DOWN:
+ if (row == TA->t->count - 1) // can't go down
+ goto nowhere;
+ row++;
+ goto specificCell;
+ case NAVDIR_LEFT:
+ case NAVDIR_PREVIOUS:
+ if (column == 0) // can't go left
+ goto nowhere;
+ column--;
+ goto specificCell;
+ case NAVDIR_RIGHT:
+ case NAVDIR_NEXT:
+ if (column == TA->t->nColumns - 1) // can't go right
+ goto nowhere;
+ column++;
+ goto specificCell;
+// TODO this doesn't actually exist yet https://msdn.microsoft.com/en-us/library/ms971325 talks about it
+// case NAVDIR_PARENT:
+// goto specificRow;
+ case NAVDIR_FIRSTCHILD:
+ case NAVDIR_LASTCHILD:
+ goto nowhere;
+ }
+ // TODO differentiate between unsupported navigation directions and invalid navigation directions
+ goto nowhere;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+
+nowhere:
+ pvarEndUpAt->vt = VT_EMPTY;
+ return S_FALSE;
+
+tableItself:
+ pvarEndUpAt->vt = VT_DISPATCH;
+ pvarEndUpAt->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_TABLE, -1, -1);
+ return S_OK;
+
+specificRow:
+ pvarEndUpAt->vt = VT_DISPATCH;
+ pvarEndUpAt->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_ROW, row, -1);
+ return S_OK;
+
+specificCell:
+ pvarEndUpAt->vt = VT_DISPATCH;
+ pvarEndUpAt->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_CELL, row, column);
+ return S_OK;
+}
+
+// TODO [EDGE CASE??] should this ever return parents?
+static HRESULT STDMETHODCALLTYPE tableAccaccHitTest(IAccessible *this, long xLeft, long yTop, VARIANT *pvarChild)
+{
+ POINT pt;
+ struct rowcol rc;
+ RECT r;
+
+ if (pvarChild == NULL)
+ return E_POINTER;
+ // TODO set pvarChild to an invalid value?
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+
+ pt.x = xLeft;
+ pt.y = yTop;
+ if (ScreenToClient(TA->t->hwnd, &pt) == 0)
+ return HRESULT_FROM_WIN32(GetLastError());
+
+ // first see if the point is even IN the table
+ if (GetClientRect(TA->t->hwnd, &r) == 0)
+ return HRESULT_FROM_WIN32(GetLastError());
+ r.top += TA->t->headerHeight;
+ if (PtInRect(&r, pt) == 0)
+ goto outside;
+
+ // now see if we're in a cell or in the table
+ // TODO also handle GetLastError() here
+ rc = clientCoordToRowColumn(TA->t, pt);
+ switch (TA->what.role) {
+ case ROLE_SYSTEM_TABLE:
+ // either the table or the cell
+ if (rc.row == -1 || rc.column == -1)
+ goto self;
+ goto specificCell;
+ case ROLE_SYSTEM_ROW:
+ // a specific cell, but only if in the same row
+ // TODO actually do we really need these spurious rc.column ==/!= -1 checks?
+ if (rc.row == TA->what.row) {
+ if (rc.column == -1)
+ // TODO de-GetLastError() this
+ panic("impossible situation TODO write this");
+ goto specificCell;
+ }
+ goto outside;
+ case ROLE_SYSTEM_CELL:
+ if (rc.row == TA->what.row && rc.column == TA->what.column)
+ goto self;
+ goto outside;
+ }
+ // TODO actually do this right
+ // TODO un-GetLastError() this
+ panic("impossible blah blah blah TODO write this");
+ return E_FAIL;
+
+outside:
+ pvarChild->vt = VT_EMPTY;
+ return S_FALSE;
+
+self:
+ pvarChild->vt = VT_I4;
+ pvarChild->lVal = CHILDID_SELF;
+ return S_OK;
+
+specificCell:
+ pvarChild->vt = VT_DISPATCH;
+ // TODO GetLastError() here too
+ pvarChild->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_CELL, rc.row, rc.column);
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccaccDoDefaultAction(IAccessible *this, VARIANT varChild)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ if (what.role == ROLE_SYSTEM_CELL)
+ ; // TODO implement this for checkbox cells?
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+// inconsistencies, inconsistencies
+// https://msdn.microsoft.com/en-us/library/windows/desktop/dd318491%28v=vs.85%29.aspx says to just return E_NOTIMPL and not even bother with an implementation; in fact it doesn't even *have* the documentation anymore
+// http://blogs.msdn.com/b/saraford/archive/2004/08/20/which-controls-support-which-msaa-properties-and-how-these-controls-implement-msaa-properties.aspx says never to return E_NOTIMPL from an IAccessible method (but it also discounts RPC_E_DISCONNECTED (not explicitly), so I'm guessing this is a much older document)
+// let's just do what our put_accValue() does and do full validation, then just return DISP_E_MEMBERNOTFOUND
+// I really hope UI Automation isn't so ambiguous and inconsistent... too bad I'm still choosing to support Windows XP while its market share (compared to *every other OS ever*) is still as large as it is
+static HRESULT STDMETHODCALLTYPE tableAccput_accName(IAccessible *this, VARIANT varChild, BSTR szName)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ // don't support setting values anyway; do return the above errors just to be safe
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+static HRESULT STDMETHODCALLTYPE tableAccput_accValue(IAccessible *this, VARIANT varChild, BSTR szValue)
+{
+ HRESULT hr;
+ tableAccWhat what;
+
+ if (TA->t == NULL || TA->std == NULL)
+ return RPC_E_DISCONNECTED;
+ what = TA->what;
+ hr = normalizeWhat(TA, varChild, &what);
+ if (hr != S_OK)
+ return hr;
+ // don't support setting values anyway; do return the above errors just to be safe
+ // TODO defer ROW_SYSTEM_TABLE to the standard accessible object?
+ // TODO implement for checkboxes?
+ 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, LONG role, intptr_t row, intptr_t column)
+{
+ struct tableAcc *ta;
+ HRESULT hr;
+ IAccessible *std;
+
+ ta = (struct tableAcc *) tableAlloc(sizeof (struct tableAcc), "error creating Table accessibility object");
+ ta->vtbl = &tableAccVtbl;
+ ta->refcount = 1;
+ ta->t = t;
+ // TODO adjust last argument
+ hr = CreateStdAccessibleObject(t->hwnd, OBJID_CLIENT, &IID_IAccessible, (void **) (&std));
+ if (hr != S_OK)
+ // TODO panichresult
+ panic("error creating standard accessible object for Table");
+ ta->std = std;
+ ta->what.role = role;
+ ta->what.row = row;
+ ta->what.column = column;
+
+#ifdef TABLE_DEBUG_LINKEDLIST
+printf("before add:"); list(t);
+#endif
+ ta->next = t->firstAcc;
+ if (t->firstAcc != NULL)
+ t->firstAcc->prev = ta;
+ t->firstAcc = ta;
+#ifdef TABLE_DEBUG_LINKEDLIST
+printf("after add:"); list(t);
+#endif
+
+ return ta;
+}
+
+static void invalidateTableAccs(struct table *t)
+{
+ struct tableAcc *ta;
+
+ for (ta = t->firstAcc; ta != NULL; ta = ta->next) {
+ ta->t = NULL;
+ IAccessible_Release(ta->std);
+ ta->std = NULL;
+ }
+ t->firstAcc = NULL;
+ NotifyWinEvent(EVENT_OBJECT_DESTROY, t->hwnd, OBJID_CLIENT, CHILDID_SELF);
+}
+
+HANDLER(accessibilityHandler)
+{
+ struct tableAcc *ta;
+
+ if (uMsg != WM_GETOBJECT)
+ return FALSE;
+ // OBJID_CLIENT evaluates to an expression of type LONG
+ // the documentation for WM_GETOBJECT says to cast "it" to a DWORD before comparing
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd373624%28v=vs.85%29.aspx casts them both to DWORDs; let's do that
+ // its two siblings only cast lParam, resulting in an erroneous DWORD to LONG comparison
+ // The Old New Thing book does not cast anything
+ // Microsoft's MSAA sample casts lParam to LONG instead!
+ // (As you can probably tell, the biggest problem with MSAA is that its documentation is ambiguous and/or self-contradictory...)
+ if (((DWORD) lParam) != ((DWORD) OBJID_CLIENT))
+ return FALSE;
+ ta = newTableAcc(t, ROLE_SYSTEM_TABLE, -1, -1);
+ *lResult = LresultFromObject(&IID_IAccessible, wParam, (LPUNKNOWN) (ta));
+ // TODO check *lResult
+ // TODO adjust pointer
+ IAccessible_Release((IAccessible *) ta);
+ return TRUE;
+}
diff --git a/prev/wintable/api.h b/prev/wintable/api.h
new file mode 100644
index 0000000..09552e5
--- /dev/null
+++ b/prev/wintable/api.h
@@ -0,0 +1,110 @@
+// 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);
+ update(t, TRUE);
+ // TODO only redraw the part of the client area where the new client went, if any
+ // (TODO when — if — adding autoresize, figure this one out)
+
+ // TODO send a notification for all rows?
+}
+
+// TODO what happens if the currently selected row is lost?
+static void setRowCount(struct table *t, intptr_t rc)
+{
+ intptr_t old, i;
+
+ old = t->count;
+ t->count = rc;
+ // we DO redraw everything because we don't want any rows that should no longer be there to remain on screen!
+ updateAll(t); // DONE
+ // TODO reset checkbox and selection logic if the current row for both no longer exists
+ // TODO really send all these notifications?
+ if (old < t->count) // rows added
+ for (i = old; i < t->count; i++)
+ NotifyWinEvent(EVENT_OBJECT_CREATE, t->hwnd, OBJID_CLIENT, i);
+ else if (old > t->count) // rows removed
+ for (i = old; i > t->count; i++)
+ NotifyWinEvent(EVENT_OBJECT_DESTROY, t->hwnd, OBJID_CLIENT, i);
+ // TODO update existing rows?
+}
+
+HANDLER(apiHandlers)
+{
+ intptr_t *rcp;
+ intptr_t row;
+
+ 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);
+ // if we redraw, we have to redraw ALL of it; after all, the font changed!
+ if (LOWORD(lParam) != FALSE)
+ updateAll(t); // DONE
+ else
+ update(t, FALSE); // DONE
+ *lResult = 0;
+ return TRUE;
+ case WM_GETFONT:
+ *lResult = (LRESULT) (t->font);
+ return TRUE;
+ case tableAddColumn:
+ addColumn(t, wParam, lParam);
+ *lResult = 0;
+ return TRUE;
+ case tableSetRowCount:
+ rcp = (intptr_t *) lParam;
+ setRowCount(t, *rcp);
+ *lResult = 0;
+ return TRUE;
+ case tableGetSelection:
+ rcp = (intptr_t *) wParam;
+ if (rcp != NULL)
+ *rcp = t->selectedRow;
+ rcp = (intptr_t *) lParam;
+ if (rcp != NULL)
+ *rcp = t->selectedColumn;
+ *lResult = 0;
+ return TRUE;
+ case tableSetSelection:
+ // TODO does doselect() do validation?
+ rcp = (intptr_t *) wParam;
+ row = *rcp;
+ rcp = (intptr_t *) lParam;
+ if (rcp == NULL)
+ if (row == -1)
+ doselect(t, -1, -1);
+ else // select column 0, just like keyboard selections; TODO what if there aren't any columns?
+ doselect(t, row, 0);
+ else
+ doselect(t, row, *rcp);
+ *lResult = 0;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static LRESULT notify(struct table *t, UINT code, intptr_t row, intptr_t column, uintptr_t data)
+{
+ tableNM nm;
+
+ ZeroMemory(&nm, sizeof (tableNM));
+ nm.nmhdr.hwndFrom = t->hwnd;
+ // TODO check for error from here? 0 is a valid ID (IDCANCEL)
+ nm.nmhdr.idFrom = GetDlgCtrlID(t->hwnd);
+ nm.nmhdr.code = code;
+ nm.row = row;
+ nm.column = column;
+ nm.columnType = t->columnTypes[nm.column];
+ nm.data = data;
+ // TODO check for error from GetParent()?
+ return SendMessageW(GetParent(t->hwnd), WM_NOTIFY, (WPARAM) (nm.nmhdr.idFrom), (LPARAM) (&nm));
+}
diff --git a/prev/wintable/checkboxes.h b/prev/wintable/checkboxes.h
new file mode 100644
index 0000000..b44a0d0
--- /dev/null
+++ b/prev/wintable/checkboxes.h
@@ -0,0 +1,247 @@
+// 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;
+
+ 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;
+}
+
+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;
+ notify(t, tableNotificationCellCheckboxToggled, rc.row, rc.column, 0);
+ t->checkboxMouseDown = FALSE;
+ // TODO redraw the whole cell?
+ if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
+ panic("error redrawing Table checkbox after mouse up");
+ // TODO really only the row? no way to specify column too?
+ NotifyWinEvent(EVENT_OBJECT_STATECHANGE, t->hwnd, OBJID_CLIENT, rc.row);
+ *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/prev/wintable/children.h b/prev/wintable/children.h
new file mode 100644
index 0000000..6a3aff3
--- /dev/null
+++ b/prev/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/prev/wintable/coord.h b/prev/wintable/coord.h
new file mode 100644
index 0000000..52a70c9
--- /dev/null
+++ b/prev/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/prev/wintable/draw.h b/prev/wintable/draw.h
new file mode 100644
index 0000000..3aeb300
--- /dev/null
+++ b/prev/wintable/draw.h
@@ -0,0 +1,215 @@
+// 8 december 2014
+
+// TODO move to api.h? definitely move somewhere
+static WCHAR *getCellText(struct table *t, intptr_t row, intptr_t column)
+{
+ return (WCHAR *) notify(t, tableNotificationGetCellData, row, column, 0);
+}
+static void returnCellData(struct table *t, intptr_t row, intptr_t column, void *what)
+{
+ notify(t, tableNotificationFinishedWithCellData, row, column, (uintptr_t) what);
+}
+static int isCheckboxChecked(struct table *t, intptr_t row, intptr_t column)
+{
+ return notify(t, tableNotificationGetCellData, row, column, 0) != 0;
+}
+
+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 drawTextCell(struct table *t, HDC dc, struct drawCellParams *p, RECT *r, int textColor)
+{
+ WCHAR *text;
+
+ 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");
+ text = getCellText(t, p->row, p->column);
+ if (DrawTextExW(dc, text, -1, r, DT_END_ELLIPSIS | DT_LEFT | DT_NOPREFIX | DT_SINGLELINE, NULL) == 0)
+ panic("error drawing Table cell text");
+ returnCellData(t, p->row, p->column, text);
+}
+
+static void drawImageCell(struct table *t, HDC dc, struct drawCellParams *p, RECT *r)
+{
+ HBITMAP bitmap;
+ BITMAP bi;
+ HDC idc;
+ HBITMAP previbitmap;
+ BLENDFUNCTION bf;
+
+ // only call tableImageWidth() and tableImageHeight() here in case it changes partway through
+ // we can get the values back out with basic subtraction (r->right - r->left/r->bottom - r->top)
+ toCellContentRect(t, r, p->xoff, tableImageWidth(), tableImageHeight());
+
+ bitmap = (HBITMAP) notify(t, tableNotificationGetCellData, p->row, p->column, 0);
+ ZeroMemory(&bi, sizeof (BITMAP));
+ if (GetObject(bitmap, sizeof (BITMAP), &bi) == 0)
+ panic("error getting Table cell image dimensions for drawing");
+ // is it even possible to enforce the type of bitmap we need here based on the contents of the BITMAP (or even the DIBSECTION) structs?
+
+ idc = CreateCompatibleDC(dc);
+ if (idc == NULL)
+ panic("error creating compatible DC for Table image cell drawing");
+ previbitmap = SelectObject(idc, bitmap);
+ if (previbitmap == NULL)
+ panic("error selecting Table cell image into compatible DC for image drawing");
+
+ ZeroMemory(&bf, sizeof (BLENDFUNCTION));
+ bf.BlendOp = AC_SRC_OVER;
+ bf.BlendFlags = 0;
+ bf.SourceConstantAlpha = 255; // per-pixel alpha values
+ bf.AlphaFormat = AC_SRC_ALPHA;
+ if (AlphaBlend(dc, r->left, r->top, r->right - r->left, r->bottom - r->top,
+ idc, 0, 0, bi.bmWidth, bi.bmHeight, bf) == FALSE)
+ panic("error drawing image into Table cell");
+
+ if (SelectObject(idc, previbitmap) != bitmap)
+ panic("error deselecting Table cell image for drawing image");
+ if (DeleteDC(idc) == 0)
+ panic("error deleting Table compatible DC for image cell drawing");
+
+ returnCellData(t, p->row, p->column, bitmap);
+}
+
+static void drawCheckboxCell(struct table *t, HDC dc, struct drawCellParams *p, RECT *r)
+{
+ POINT pt;
+ int cbState;
+
+ toCheckboxRect(t, r, p->xoff);
+ cbState = 0;
+ if (isCheckboxChecked(t, p->row, p->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);
+}
+
+static void drawCell(struct table *t, HDC dc, struct drawCellParams *p)
+{
+ RECT r;
+ HBRUSH background;
+ int textColor;
+ 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:
+ drawTextCell(t, dc, p, &r, textColor);
+ break;
+ case tableColumnImage:
+ drawImageCell(t, dc, p, &r);
+ break;
+ case tableColumnCheckbox:
+ drawCheckboxCell(t, dc, p, &r);
+ 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;
+ 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/prev/wintable/events.h b/prev/wintable/events.h
new file mode 100644
index 0000000..fbd1522
--- /dev/null
+++ b/prev/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/prev/wintable/header.h b/prev/wintable/header.h
new file mode 100644
index 0000000..b87f7c6
--- /dev/null
+++ b/prev/wintable/header.h
@@ -0,0 +1,80 @@
+// 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");
+}
+
+// to avoid weird bugs, the only functions allowed to call this one are the horizontal scroll functions
+// when we need to reposition the header in a situation other than a user-initiated scroll, we use a dummy scroll (hscrollby(t, 0))
+// see update() in update.h
+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 = &wp;
+ 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 is this triggered if we programmatically move headers (for autosizing)?
+HANDLER(headerNotifyHandler)
+{
+ NMHDR *nmhdr = (NMHDR *) lParam;
+
+ if (nmhdr->hwndFrom != t->header)
+ return FALSE;
+ if (nmhdr->code != HDN_ITEMCHANGED)
+ return FALSE;
+ update(t, TRUE);
+ // TODO make more intelligent
+ // to do this, we have to redraw the column to the left of the divider that was dragged and scroll everything to the right normally (leaving the hole that was scrolled invalidated as well)
+ // of course, this implies that dragging a divider only resizes the column of which it is the right side of and moves all others
+ InvalidateRect(t->hwnd, NULL, TRUE);
+ *lResult = 0;
+ return TRUE;
+}
diff --git a/prev/wintable/hscroll.h b/prev/wintable/hscroll.h
new file mode 100644
index 0000000..1560490
--- /dev/null
+++ b/prev/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/prev/wintable/includethis.h b/prev/wintable/includethis.h
new file mode 100644
index 0000000..422b101
--- /dev/null
+++ b/prev/wintable/includethis.h
@@ -0,0 +1,73 @@
+// 17 february 2015
+
+#ifndef __GO_UI_WINTABLE_INCLUDETHIS_H__
+#define __GO_UI_WINTABLE_INCLUDETHIS_H__
+
+#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)
+// each of these returns nothing unless otherwise indicated
+enum {
+ // wParam - one of the type constants
+ // lParam - column name as a Unicode string
+ tableAddColumn = WM_USER + 20,
+ // wParam - 0
+ // lParam - pointer to intptr_t containing new count
+ tableSetRowCount,
+ // wParam - pointer to intptr_t where selected row will be stored
+ // lParam - pointer to intptr_t where selected column will be stored
+ // both will be -1 for no selection
+ // if either is NULL, that value is not written
+ tableGetSelection,
+ // wParam - pointer to intptr_t containing selected row
+ // lParam - pointer to intptr_t containing selected column
+ // if lParam is NULL, do not change selected column (selects column 0 if nothing previously selected; TODO explicitly document this?)
+ // TODO allow wParam to be NULL too; should both being NULL select nothing or keep the current selection?
+ // this WILL result in a selection changed notification (TODO work into the package ui Table)
+ tableSetSelection,
+};
+
+enum {
+ tableColumnText,
+ tableColumnImage,
+ tableColumnCheckbox,
+ nTableColumnTypes,
+};
+
+// notification codes
+// note that these are positive; see http://blogs.msdn.com/b/oldnewthing/archive/2009/08/21/9877791.aspx
+// each of these is of type tableNM
+// all fields except data will always be set
+enum {
+ // data parameter is always 0
+ // for tableColumnText return should be WCHAR *
+ // for tableColumnImage return should be HBITMAP
+ // for tableColumnCheckbox return is nonzero for checked, zero for unchecked
+ tableNotificationGetCellData,
+ // data parameter is pointer, same as tableNotificationGetCellData
+ // not sent for checkboxes
+ // no return
+ tableNotificationFinishedWithCellData,
+ // data is zero
+ // no return
+ tableNotificationCellCheckboxToggled,
+ // sent even on deselection (in that case, row == -1 and column == -1)
+ // data is zero
+ // no return
+ tableNotificationSelectionChanged,
+};
+
+typedef struct tableNM tableNM;
+
+struct tableNM {
+ NMHDR nmhdr;
+ intptr_t row;
+ intptr_t column;
+ int columnType;
+ uintptr_t data;
+};
+
+// TODO have hInstance passed in
+extern void initTable(void (*panicfunc)(const char *msg, DWORD lastError), BOOL (*WINAPI tme)(LPTRACKMOUSEEVENT));
+
+#endif
diff --git a/prev/wintable/links b/prev/wintable/links
new file mode 100644
index 0000000..f0c9523
--- /dev/null
+++ b/prev/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/prev/wintable/main.h b/prev/wintable/main.h
new file mode 100644
index 0000000..fe49d77
--- /dev/null
+++ b/prev/wintable/main.h
@@ -0,0 +1,165 @@
+// 7 january 2015
+
+// TODO remove
+#include <stdio.h>
+
+// 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
+// - send standard notification codes
+// - seriously figure out how we're going to update everything about the table in one place
+// - redraw on focus change
+// - draw themed WS_EX_CLIENTEDGE (and WS_EX_WINDOWEDGE?)
+// - in-place tooltips for text columns
+// - checkboxes: use capture instead of manual tracking logic? http://blogs.msdn.com/b/oldnewthing/archive/2015/02/25/10595729.aspx
+
+#include "includethis.h"
+
+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 *firstAcc;
+};
+
+// forward declaration (TODO needed?)
+static LRESULT notify(struct table *, UINT, intptr_t, intptr_t, uintptr_t);
+
+// necessary forward declarations
+static void update(struct table *, BOOL);
+static void updateAll(struct table *);
+
+#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"
+#include "update.h"
+
+static const handlerfunc handlers[] = {
+ eventHandlers,
+ childrenHandlers,
+ resizeHandler,
+ drawHandlers,
+ apiHandlers,
+ hscrollHandler,
+ vscrollHandler,
+ accessibilityHandler,
+ NULL,
+};
+
+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);
+ 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
+ invalidateTableAccs(t);
+ 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 %I32u)\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();
+}
+
+// TODO have hInstance passed in
+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");
+}
diff --git a/prev/wintable/resize.h b/prev/wintable/resize.h
new file mode 100644
index 0000000..7805bd9
--- /dev/null
+++ b/prev/wintable/resize.h
@@ -0,0 +1,21 @@
+// 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
+// TODO merge with update.h?
+
+HANDLER(resizeHandler)
+{
+ WINDOWPOS *wp;
+
+ if (uMsg != WM_WINDOWPOSCHANGED)
+ return FALSE;
+ wp = (WINDOWPOS *) lParam;
+ if ((wp->flags & SWP_NOSIZE) != 0)
+ return FALSE;
+ // TODO redraw everything?
+ update(t, TRUE);
+ *lResult = 0;
+ return TRUE;
+}
diff --git a/prev/wintable/scroll.h b/prev/wintable/scroll.h
new file mode 100644
index 0000000..ba5a9f8
--- /dev/null
+++ b/prev/wintable/scroll.h
@@ -0,0 +1,137 @@
+// 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;
+ // TODO this shouldn't have been necessary but alas
+ // TODO the logic is really intended for the whole y origin thing in the scrollbar series; fix that
+ if (pos < 0)
+ pos = 0;
+
+ // 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);
+
+ // EVENT_OBJECT_CONTENTSCROLLED is Vista and up only
+ // TODO send state changes for all affected rows/cells?
+}
+
+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/prev/wintable/scrollbarseries b/prev/wintable/scrollbarseries
new file mode 100644
index 0000000..818b6a8
--- /dev/null
+++ b/prev/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/prev/wintable/select.h b/prev/wintable/select.h
new file mode 100644
index 0000000..6ece240
--- /dev/null
+++ b/prev/wintable/select.h
@@ -0,0 +1,269 @@
+// 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 what about deselect/defocus?
+ // TODO notify on the old row too?
+ NotifyWinEvent(EVENT_OBJECT_SELECTION, t->hwnd, OBJID_CLIENT, t->selectedRow);
+ // TODO send EVENT_OBJECT_STATECHANGED too?
+ // TODO send EVENT_OBJECT_FOCUS
+
+ // TODO before or after NotifyWinEvent()? (see what other things I'm doing)
+ notify(t, tableNotificationSelectionChanged, t->selectedRow, t->selectedColumn, 0);
+}
+
+// TODO make this needless
+HANDLER(checkboxMouseDownHandler);
+
+// TODO which WM_xBUTTONDOWNs?
+HANDLER(mouseDownSelectHandler)
+{
+ struct rowcol rc;
+
+ // TODO separate this from here
+ // TODO other mouse buttons?
+ // don't check SetFocus()'s error (http://stackoverflow.com/questions/24073695/winapi-can-setfocus-return-null-without-an-error-because-thats-what-im-see)
+ // TODO NotifyWinEvent() here?
+ SetFocus(t->hwnd);
+ 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/prev/wintable/test.c b/prev/wintable/test.c
new file mode 100644
index 0000000..942860c
--- /dev/null
+++ b/prev/wintable/test.c
@@ -0,0 +1,198 @@
+// 19 october 2014
+#include "../wininclude_windows.h"
+
+// #qo LIBS: user32 kernel32 gdi32 comctl32 uxtheme ole32 oleaut32 oleacc uuid msimg32
+
+#include "main.h"
+
+HWND tablehwnd = NULL;
+BOOL msgfont = FALSE;
+
+HBITMAP mkbitmap(void);
+
+BOOL mainwinCreate(HWND hwnd, LPCREATESTRUCT lpcs)
+{
+ intptr_t c;
+ intptr_t row, col;
+
+ tablehwnd = CreateWindowExW(0,
+ tableWindowClass, L"Main Window",
+ WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 400, 400,
+ hwnd, NULL, lpcs->hInstance, NULL);
+ if (tablehwnd == NULL)
+ panic("(test program) error creating Table");
+ SendMessageW(tablehwnd, tableAddColumn, tableColumnText, (LPARAM) L"Column");
+ SendMessageW(tablehwnd, tableAddColumn, tableColumnImage, (LPARAM) L"Column 2");
+ SendMessageW(tablehwnd, tableAddColumn, tableColumnCheckbox, (LPARAM) L"Column 3");
+ if (msgfont) {
+ 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(tablehwnd, WM_SETFONT, (WPARAM) font, TRUE);
+ }
+ c = 100;
+ SendMessageW(tablehwnd, tableSetRowCount, 0, (LPARAM) (&c));
+ row = 2;
+ col = 1;
+ SendMessageW(tablehwnd, tableSetSelection, (WPARAM) (&row), (LPARAM) (&col));
+ SetFocus(tablehwnd);
+ return TRUE;
+}
+
+void mainwinDestroy(HWND hwnd)
+{
+ DestroyWindow(tablehwnd);
+ PostQuitMessage(0);
+}
+
+void mainwinResize(HWND hwnd, UINT state, int cx, int cy)
+{
+ if (tablehwnd != NULL)
+ MoveWindow(tablehwnd, 0, 0, cx, cy, TRUE);
+}
+
+BOOL checkboxstates[100];
+
+LRESULT CALLBACK mainwndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ NMHDR *nmhdr = (NMHDR *) lParam;
+ tableNM *nm = (tableNM *) lParam;
+ WCHAR *text;
+ int n;
+
+ if (uMsg == WM_CREATE)
+ ZeroMemory(checkboxstates, 100 * sizeof (BOOL));
+ switch (uMsg) {
+ HANDLE_MSG(hwnd, WM_CREATE, mainwinCreate);
+ HANDLE_MSG(hwnd, WM_SIZE, mainwinResize);
+ HANDLE_MSG(hwnd, WM_DESTROY, mainwinDestroy);
+ case WM_NOTIFY:
+ if (nmhdr->hwndFrom != tablehwnd)
+ break;
+ switch (nmhdr->code) {
+ case tableNotificationGetCellData:
+ switch (nm->columnType) {
+ case tableColumnText:
+ n = _scwprintf(L"mainwin (%d,%d)", nm->row, nm->column);
+ text = (WCHAR *) malloc((n + 1) * sizeof (WCHAR));
+ if (text == NULL)
+ panic("(table program) error allocating string");
+ _swprintf(text, L"mainwin (%d,%d)", nm->row, nm->column);
+ return (LRESULT) text;
+ case tableColumnImage:
+ return (LRESULT) mkbitmap();
+ case tableColumnCheckbox:
+ return (LRESULT) (checkboxstates[nm->row]);
+ }
+ panic("(test program) unreachable");
+ case tableNotificationFinishedWithCellData:
+ switch (nm->columnType) {
+ case tableColumnText:
+ free((void *) (nm->data));
+ break;
+ case tableColumnImage:
+ if (DeleteObject((HBITMAP) (nm->data)) == 0)
+ panic("(test program) error deleting cell image");
+ break;
+ }
+ return 0;
+ case tableNotificationCellCheckboxToggled:
+ checkboxstates[nm->row] = !checkboxstates[nm->row];
+ return 0;
+ }
+ break;
+ }
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+int main(int argc, char *argv[])
+{
+ HWND mainwin;
+ MSG msg;
+ INITCOMMONCONTROLSEX icc;
+ WNDCLASSW wc;
+
+ if (argc != 1)
+ msgfont = TRUE;
+ 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);
+ ZeroMemory(&wc, sizeof (WNDCLASSW));
+ wc.lpszClassName = L"mainwin";
+ wc.lpfnWndProc = mainwndproc;
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
+ wc.hInstance = GetModuleHandle(NULL);
+ if (RegisterClassW(&wc) == 0)
+ panic("(test program) error registering main window class");
+ mainwin = CreateWindowExW(0,
+ L"mainwin", L"Main Window",
+ WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 400, 400,
+ NULL, NULL, GetModuleHandle(NULL), NULL);
+ if (mainwin == NULL)
+ panic("(test program) error creating main window");
+ 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;
+}
+
+// from tango-icon-theme-0.8.90/16x16/status/audio-volume-high.png (public domain)
+COLORREF iconpix[] = {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1003060A, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34101210, 0x5020202, 0x0, 0x0, 0x0, 0x0, 0x9E203E66, 0x78182F4D, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x32111110, 0xF051534F, 0x8030303, 0x0, 0x0, 0x0, 0x0, 0x1C050B12, 0xE92F5C96, 0x2B08111B, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x32111110, 0xF1575855, 0xFE545652, 0x8030303, 0x0, 0x0, 0x67152942, 0x440E1B2C, 0x0, 0x6214263F, 0xCB295083, 0x4000102,
+ 0xF3525450, 0xFB545653, 0xF0555653, 0x8830302E, 0xF1575855, 0xFFBEBFBC, 0xFC565854, 0x8030303, 0x0, 0x0, 0x420C192A, 0xE12E5991, 0xF03060A, 0x0, 0xC6284E7F, 0x52102034,
+ 0xFB555653, 0xFFE5E5E3, 0xFFD1D2CF, 0xFE585A56, 0xFFC7C8C5, 0xFFF7F7F6, 0xFC5A5C58, 0x8030303, 0x68142943, 0x1C060B12, 0x0, 0x9A1E3D63, 0x81193353, 0x0, 0x831A3454, 0x891C3658,
+ 0xFB545653, 0xFFFAFAF9, 0xFFFAFAFA, 0xFF585A56, 0xFFF8F8F8, 0xFFFFFFFF, 0xFC5B5D59, 0x8030303, 0x7B19314F, 0xAB23436E, 0x0, 0x4A0F1D30, 0xBA264978, 0x0, 0x4F101F33, 0xBD264B7A,
+ 0xFB545653, 0xFFCFD0CD, 0xFFD3D4D1, 0xFF585A56, 0xFFD2D2D0, 0xFFD7D7D5, 0xFC565854, 0x8030303, 0x1C060B12, 0xEF305F9A, 0x1000001, 0x19050A10, 0xEF305F9A, 0x1000001, 0x1B050A11, 0xF0315F9A,
+ 0xFB545653, 0xFFC2C3C0, 0xFFC6C7C4, 0xFF585A56, 0xFFCDCECB, 0xFFD1D2CF, 0xFC565854, 0x8030303, 0x19050A10, 0xF231609C, 0x1000001, 0x1603090E, 0xF1315F9B, 0x1000001, 0x1804090F, 0xF231609C,
+ 0xFB545653, 0xFFB5B7B3, 0xFFB9BBB7, 0xFF575955, 0xFFC8C8C6, 0xFFCCCCCA, 0xFC565854, 0x8030303, 0x71172D49, 0xB2244772, 0x0, 0x470D1C2E, 0xBE264B7A, 0x0, 0x4C0F1E31, 0xC0274C7B,
+ 0xFB545653, 0xFFA9ABA7, 0xFFA0A29E, 0xFF575955, 0xFFA9ABA8, 0xFFC6C7C4, 0xFC565854, 0x8030303, 0x75172E4B, 0x23070E17, 0x0, 0x921D3A5E, 0x861A3556, 0x0, 0x801A3252, 0x8C1C375A,
+ 0xFA535551, 0xFC555753, 0xF7545652, 0x98353534, 0xF3565854, 0xFFA8A8A6, 0xFC565854, 0x8030303, 0x0, 0x0, 0x390C1725, 0xE62F5B94, 0x1404080D, 0x0, 0xC0274C7B, 0x57112238,
+ 0x7020202, 0x8020202, 0x3010100, 0x0, 0x3C141414, 0xF3565854, 0xFE545652, 0x8030303, 0x0, 0x0, 0x70162C48, 0x4E0F1F32, 0x0, 0x57112238, 0xD32B5388, 0x6010203,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x3C141414, 0xF5515350, 0x8030303, 0x0, 0x0, 0x0, 0x0, 0x1604080E, 0xE72F5B95, 0x330A1420, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F151515, 0x5020202, 0x0, 0x0, 0x0, 0x0, 0x991F3C62, 0x831A3454, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1B050A11, 0x4000102, 0x0, 0x0,
+};
+
+HBITMAP mkbitmap(void)
+{
+ BITMAPINFO bi;
+ void *ppvBits;
+ HBITMAP b;
+
+ ZeroMemory(&bi, sizeof (BITMAPINFO));
+ bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
+ bi.bmiHeader.biWidth = 16;
+ bi.bmiHeader.biHeight = -16; // negative height to force top-down drawing
+ bi.bmiHeader.biPlanes = 1;
+ bi.bmiHeader.biBitCount = 32;
+ bi.bmiHeader.biCompression = BI_RGB;
+ bi.bmiHeader.biSizeImage = 16 * 16 * 4;
+ b = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &ppvBits, 0, 0);
+ if (b == 0)
+ panic("test bitmap creation failed");
+ memcpy(ppvBits, iconpix, bi.bmiHeader.biSizeImage);
+ return b;
+}
diff --git a/prev/wintable/update.h b/prev/wintable/update.h
new file mode 100644
index 0000000..dae4811
--- /dev/null
+++ b/prev/wintable/update.h
@@ -0,0 +1,53 @@
+// 8 january 2015
+
+// Whenever a number of things in the Table changes, the update() function needs to be called to update any metrics and scrolling positions.
+// The control font changing is the big one, as that comes with a flag that decides whether or not to redraw everything. We'll need to respect that here.
+
+// For my personal convenience, each invocation of update() and updateAll() will be suffixed with a DONE comment once I have reasoned that the chosen function is correct and that no additional redrawing is necessary.
+
+// TODO actually use redraw here
+static void update(struct table *t, BOOL redraw)
+{
+ RECT client;
+ intptr_t i;
+ intptr_t height;
+
+ // before we do anything we need the client rect
+ if (GetClientRect(t->hwnd, &client) == 0)
+ panic("error getting Table client rect in update()");
+
+ // the first step is to figure out how wide the whole table is
+ // TODO count dividers?
+ t->width = 0;
+ for (i = 0; i < t->nColumns; i++)
+ t->width += columnWidth(t, i);
+
+ // now we need to figure out how much of the width of the table can be seen at once
+ 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
+ // we do this regardless of which of the two has changed, just to be safe
+ if (t->hpagesize > t->width)
+ t->hpagesize = t->width;
+
+ // now we do a dummy horizontal scroll to apply the new width and horizontal page size
+ // this will also reposition and resize the header (the latter in case the font changed), which will be important for the next step
+ hscrollby(t, 0);
+
+ // now that we have the new height of the header, we can fix up vertical scrolling
+ // so let's take the header height away from the client area
+ client.top += t->headerHeight;
+ // and update our page size appropriately
+ height = client.bottom - client.top;
+ t->vpagesize = height / rowht(t);
+ // and do a dummy vertical scroll to apply that
+ vscrollby(t, 0);
+}
+
+// this is the same as update(), but also redraws /everything/
+// as such, redraw is TRUE
+static void updateAll(struct table *t)
+{
+ update(t, TRUE);
+ if (InvalidateRect(t->hwnd, NULL, TRUE) == 0)
+ panic("error queueing all of Table for redraw in updateAll()");
+}
diff --git a/prev/wintable/util.h b/prev/wintable/util.h
new file mode 100644
index 0000000..164ca72
--- /dev/null
+++ b/prev/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)
+{
+ const 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/prev/wintable/vscroll.h b/prev/wintable/vscroll.h
new file mode 100644
index 0000000..ca34957
--- /dev/null
+++ b/prev/wintable/vscroll.h
@@ -0,0 +1,65 @@
+// 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);
+}
+
+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;
+}