Handled the finished-editing cases I can right now. It is... mostly good???????????? IListView is very tantalizing now...

This commit is contained in:
Pietro Gagliardi 2018-06-19 23:07:24 -04:00
parent ff4b424ab0
commit 25a443f4f2
3 changed files with 161 additions and 2 deletions

View File

@ -3,6 +3,7 @@
// general TODOs: // general TODOs:
// - tooltips don't work properly on columns with icons (the listview always thinks there's enough room for a short label because it's not taking the icon into account); is this a bug in our LVN_GETDISPINFO handler or something else? // - tooltips don't work properly on columns with icons (the listview always thinks there's enough room for a short label because it's not taking the icon into account); is this a bug in our LVN_GETDISPINFO handler or something else?
// - should clicking on some other column of the same row, even one that doesn't edit, cancel editing?
static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = {
/*TODO.ColorModelColumn = */-1, /*TODO.ColorModelColumn = */-1,
@ -83,8 +84,14 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)
static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData)
{ {
uiTable *t = (uiTable *) dwRefData; uiTable *t = (uiTable *) dwRefData;
NMHDR *nmhdr = (NMHDR *) lParam;
bool finishEdit, abortEdit;
HWND header;
LRESULT lResult; LRESULT lResult;
HRESULT hr;
finishEdit = false;
abortEdit = false;
switch (uMsg) { switch (uMsg) {
case WM_TIMER: case WM_TIMER:
if (wParam == (WPARAM) (&(t->inDoubleClickTimer))) { if (wParam == (WPARAM) (&(t->inDoubleClickTimer))) {
@ -107,11 +114,69 @@ static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM
lResult = DefSubclassProc(hwnd, uMsg, wParam, lParam); lResult = DefSubclassProc(hwnd, uMsg, wParam, lParam);
t->inLButtonDown = FALSE; t->inLButtonDown = FALSE;
return lResult; return lResult;
case WM_COMMAND:
if (HIWORD(wParam) == EN_UPDATE) {
// the real list view resizes the edit control on this notification specifically
// TODO
}
// the real list view accepts changes in this case
if (HIWORD(wParam) == EN_KILLFOCUS)
finishEdit = true;
break; // don't override default handling
case WM_NOTIFY:
// list view accepts changes on column resize, but does not provide such notifications :/
header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0);
if (nmhdr->hwndFrom == header) {
NMHEADERW *nm = (NMHEADERW *) nmhdr;
switch (nmhdr->code) {
case HDN_ITEMCHANGED:
if ((nm->pitem->mask & HDI_WIDTH) == 0)
break;
// fall through
case HDN_DIVIDERDBLCLICK:
case HDN_TRACK:
case HDN_ENDTRACK:
finishEdit = true;
}
}
// I think this mirrors the WM_COMMAND one above... TODO
if (nmhdr->code == NM_KILLFOCUS)
finishEdit = true;
break; // don't override default handling
case LVM_CANCELEDITLABEL:
finishEdit = true;
// TODO properly imitate notifiactions
break; // don't override default handling
// TODO finish edit on WM_WINDOWPOSCHANGING and WM_SIZE?
// for the next three: this item is about to go away; don't bother keeping changes
case LVM_SETITEMCOUNT:
if (wParam <= t->editedItem)
abortEdit = true;
break; // don't override default handling
case LVM_DELETEITEM:
if (wParam == t->editedItem)
abortEdit = true;
break; // don't override default handling
case LVM_DELETEALLITEMS:
abortEdit = true;
break; // don't override default handling
case WM_NCDESTROY: case WM_NCDESTROY:
if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE) if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE)
logLastError(L"RemoveWindowSubclass()"); logLastError(L"RemoveWindowSubclass()");
// fall through // fall through
} }
if (finishEdit) {
hr = uiprivTableFinishEditingText(t);
if (hr != S_OK) {
// TODO
}
} else if (abortEdit) {
hr = uiprivTableAbortEditingText(t);
if (hr != S_OK) {
// TODO
}
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam); return DefSubclassProc(hwnd, uMsg, wParam, lParam);
} }
@ -157,6 +222,7 @@ int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG
return progress; return progress;
} }
// TODO properly integrate compound statements
static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)
{ {
uiTable *t = uiTable(c); uiTable *t = uiTable(c);
@ -208,12 +274,14 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)
{ {
NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr; NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr;
UINT oldSelected, newSelected; UINT oldSelected, newSelected;
HRESULT hr;
if (!t->inLButtonDown) // TODO clean up these if cases
if (!t->inLButtonDown && t->edit == NULL)
return FALSE; return FALSE;
oldSelected = nm->uOldState & LVIS_SELECTED; oldSelected = nm->uOldState & LVIS_SELECTED;
newSelected = nm->uNewState & LVIS_SELECTED; newSelected = nm->uNewState & LVIS_SELECTED;
if (oldSelected == 0 && newSelected != 0) { if (t->inLButtonDown && oldSelected == 0 && newSelected != 0) {
t->inDoubleClickTimer = TRUE; t->inDoubleClickTimer = TRUE;
// TODO check error // TODO check error
SetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)), SetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)),
@ -221,8 +289,29 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)
*lResult = 0; *lResult = 0;
return TRUE; return TRUE;
} }
// the nm->iItem == -1 case is because that is used if "the change has been applied to all items in the list view"
if (t->edit != NULL && oldSelected != 0 && newSelected == 0 && (t->editedItem == nm->iItem || nm->iItem == -1)) {
// TODO see if the real list view accepts or rejects changes here; Windows Explorer accepts
hr = uiprivTableFinishEditingText(t);
if (hr != S_OK) {
// TODO
return FALSE;
}
*lResult = 0;
return TRUE;
}
return FALSE; return FALSE;
} }
// the real list view accepts changes when scrolling or clicking column headers
case LVN_BEGINSCROLL:
case LVN_COLUMNCLICK:
hr = uiprivTableFinishEditingText(t);
if (hr != S_OK) {
// TODO
return FALSE;
}
*lResult = 0;
return TRUE;
} }
return FALSE; return FALSE;
} }
@ -232,7 +321,12 @@ static void uiTableDestroy(uiControl *c)
uiTable *t = uiTable(c); uiTable *t = uiTable(c);
uiTableModel *model = t->model; uiTableModel *model = t->model;
std::vector<uiTable *>::iterator it; std::vector<uiTable *>::iterator it;
HRESULT hr;
hr = uiprivTableAbortEditingText(t);
if (hr != S_OK) {
// TODO
}
uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd);
uiWindowsEnsureDestroyWindow(t->hwnd); uiWindowsEnsureDestroyWindow(t->hwnd);
// detach table from model // detach table from model

View File

@ -36,6 +36,8 @@ struct uiTable {
BOOL inLButtonDown; BOOL inLButtonDown;
BOOL inDoubleClickTimer; BOOL inDoubleClickTimer;
HWND edit; HWND edit;
int editedItem;
int editedSubitem;
}; };
extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos); extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos);
@ -48,3 +50,5 @@ extern HRESULT uiprivUpdateImageListSize(uiTable *t);
// tableevents.cpp // tableevents.cpp
extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult);
extern HRESULT uiprivTableFinishEditingText(uiTable *t);
extern HRESULT uiprivTableAbortEditingText(uiTable *t);

View File

@ -17,6 +17,41 @@ static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG l
return S_OK; return S_OK;
} }
// the real list view intercepts these keys to control editing
static LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData)
{
uiTable *t = (uiTable *) dwRefData;
HRESULT hr;
switch (uMsg) {
case WM_KEYDOWN:
switch (wParam) {
// TODO handle VK_TAB and VK_SHIFT+VK_TAB
case VK_RETURN:
hr = uiprivTableFinishEditingText(t);
if (hr != S_OK) {
// TODO
}
return 0; // yes, the real list view just returns here
case VK_ESCAPE:
hr = uiprivTableAbortEditingText(t);
if (hr != S_OK) {
// TODO
}
return 0;
}
break;
// the real list view also forces these flags
case WM_GETDLGCODE:
return DLGC_HASSETSEL | DLGC_WANTALLKEYS;
case WM_NCDESTROY:
if (RemoveWindowSubclass(hwnd, editSubProc, uIDSubclass) == FALSE)
logLastError(L"RemoveWindowSubclass()");
// fall through
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p) static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p)
{ {
RECT itemLabel; RECT itemLabel;
@ -27,6 +62,11 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC
LONG xInflate, yInflate; LONG xInflate, yInflate;
HRESULT hr; HRESULT hr;
// the real list view accepts changes to the existing item when editing a new item
hr = uiprivTableFinishEditingText(t);
if (hr != S_OK)
return hr;
// compute this in advance so we don't have to needlessly call DestroyWindow() later // compute this in advance so we don't have to needlessly call DestroyWindow() later
// TODO deduplicate this code with tabledraw.cpp // TODO deduplicate this code with tabledraw.cpp
// TODO check LRESULT bad parameters here // TODO check LRESULT bad parameters here
@ -132,6 +172,27 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC
ShowWindow(t->edit, SW_SHOWDEFAULT); ShowWindow(t->edit, SW_SHOWDEFAULT);
uiprivFree(wstr); uiprivFree(wstr);
t->editedItem = iItem;
t->editedSubitem = iSubItem;
return S_OK;
}
HRESULT uiprivTableFinishEditingText(uiTable *t)
{
if (t->edit == NULL)
return S_OK;
return uiprivTableAbortEditingText(t);
}
HRESULT uiprivTableAbortEditingText(uiTable *t)
{
if (t->edit == NULL)
return S_OK;
if (DestroyWindow(t->edit) == 0) {
logLastError(L"DestroyWindow()");
return E_FAIL;
}
t->edit = NULL;
return S_OK; return S_OK;
} }