Handled the finished-editing cases I can right now. It is... mostly good???????????? IListView is very tantalizing now...
This commit is contained in:
parent
ff4b424ab0
commit
25a443f4f2
|
@ -3,6 +3,7 @@
|
|||
|
||||
// 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?
|
||||
// - should clicking on some other column of the same row, even one that doesn't edit, cancel editing?
|
||||
|
||||
static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = {
|
||||
/*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)
|
||||
{
|
||||
uiTable *t = (uiTable *) dwRefData;
|
||||
NMHDR *nmhdr = (NMHDR *) lParam;
|
||||
bool finishEdit, abortEdit;
|
||||
HWND header;
|
||||
LRESULT lResult;
|
||||
HRESULT hr;
|
||||
|
||||
finishEdit = false;
|
||||
abortEdit = false;
|
||||
switch (uMsg) {
|
||||
case WM_TIMER:
|
||||
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);
|
||||
t->inLButtonDown = FALSE;
|
||||
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:
|
||||
if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE)
|
||||
logLastError(L"RemoveWindowSubclass()");
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -157,6 +222,7 @@ int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG
|
|||
return progress;
|
||||
}
|
||||
|
||||
// TODO properly integrate compound statements
|
||||
static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)
|
||||
{
|
||||
uiTable *t = uiTable(c);
|
||||
|
@ -208,12 +274,14 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)
|
|||
{
|
||||
NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr;
|
||||
UINT oldSelected, newSelected;
|
||||
HRESULT hr;
|
||||
|
||||
if (!t->inLButtonDown)
|
||||
// TODO clean up these if cases
|
||||
if (!t->inLButtonDown && t->edit == NULL)
|
||||
return FALSE;
|
||||
oldSelected = nm->uOldState & LVIS_SELECTED;
|
||||
newSelected = nm->uNewState & LVIS_SELECTED;
|
||||
if (oldSelected == 0 && newSelected != 0) {
|
||||
if (t->inLButtonDown && oldSelected == 0 && newSelected != 0) {
|
||||
t->inDoubleClickTimer = TRUE;
|
||||
// TODO check error
|
||||
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;
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
@ -232,7 +321,12 @@ static void uiTableDestroy(uiControl *c)
|
|||
uiTable *t = uiTable(c);
|
||||
uiTableModel *model = t->model;
|
||||
std::vector<uiTable *>::iterator it;
|
||||
HRESULT hr;
|
||||
|
||||
hr = uiprivTableAbortEditingText(t);
|
||||
if (hr != S_OK) {
|
||||
// TODO
|
||||
}
|
||||
uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd);
|
||||
uiWindowsEnsureDestroyWindow(t->hwnd);
|
||||
// detach table from model
|
||||
|
|
|
@ -36,6 +36,8 @@ struct uiTable {
|
|||
BOOL inLButtonDown;
|
||||
BOOL inDoubleClickTimer;
|
||||
HWND edit;
|
||||
int editedItem;
|
||||
int editedSubitem;
|
||||
};
|
||||
extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos);
|
||||
|
||||
|
@ -48,3 +50,5 @@ extern HRESULT uiprivUpdateImageListSize(uiTable *t);
|
|||
|
||||
// tableevents.cpp
|
||||
extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult);
|
||||
extern HRESULT uiprivTableFinishEditingText(uiTable *t);
|
||||
extern HRESULT uiprivTableAbortEditingText(uiTable *t);
|
||||
|
|
|
@ -17,6 +17,41 @@ static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG l
|
|||
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)
|
||||
{
|
||||
RECT itemLabel;
|
||||
|
@ -27,6 +62,11 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC
|
|||
LONG xInflate, yInflate;
|
||||
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
|
||||
// TODO deduplicate this code with tabledraw.cpp
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue