diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 4cee5d68..a896e700 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -52,6 +52,7 @@ list(APPEND _LIBUI_SOURCES windows/tab.cpp windows/table.cpp windows/tableimages.cpp + windows/tabletext.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.cpp b/windows/table.cpp index 93b3c3e0..d3cd97f7 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -79,24 +79,13 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) { static uiprivTableColumnParams *p; - uiTableData *data; - WCHAR *wstr; HRESULT hr; p = (*(t->columns))[nm->item.iSubItem]; - if ((nm->item.mask & LVIF_TEXT) != 0) - if (p->textModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); - // we could just make pszText into a freshly allocated conversion and avoid the limitation of cchTextMax - // but then we would have to keep things around for some amount of time (some pages on MSDN say 2 additional LVN_GETDISPINFO messages) - // and in practice, anything that results in extra LVN_GETDISPINFO messages (such as fillSubitemDrawParams() below) will break this counting - // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) - wcscpy_s(nm->item.pszText, nm->item.cchTextMax, wstr); - uiprivFree(wstr); - } - + hr = uiprivLVN_GETDISPINFOText(t, nm, p); + if (hr != S_OK) { + // TODO + } hr = uiprivLVN_GETDISPINFOImagesCheckboxes(t, nm, p); if (hr != S_OK) { // TODO @@ -126,31 +115,47 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) (BYTE) (bb * 255)); } -static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) +COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int modelColumn, int fallbackSysColorID) { + uiTableData *data; + double r, g, b, a; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, modelColumn); + if (data == NULL) + return GetSysColor(fallbackSysColorID); + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + return blend(nm->clrTextBk, r, g, b, a); +} + +static HRESULT fillSubitemDrawParams(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) +{ + LRESULT state; RECT r; HRESULT hr; - // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages + // note: nm->nmcd.uItemState CDIS_SELECTED is unreliable for the listview configuration we have + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); + dp->selected = (state & LVIS_SELECTED) != 0; if (nm->iSubItem == 0) { ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; - if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { logLastError(L"LVM_GETITEMRECT LVIR_BOUNDS"); return E_FAIL; } dp->bounds = r; ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_ICON; - if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { logLastError(L"LVM_GETITEMRECT LVIR_ICON"); return E_FAIL; } dp->icon = r; ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_LABEL; - if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { logLastError(L"LVM_GETITEMRECT LVIR_LABEL"); return E_FAIL; } @@ -161,7 +166,7 @@ static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubite ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; r.top = nm->iSubItem; - if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { logLastError(L"LVM_GETSUBITEMRECT LVIR_BOUNDS"); return E_FAIL; } @@ -169,7 +174,7 @@ static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubite ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_ICON; r.top = nm->iSubItem; - if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { logLastError(L"LVM_GETSUBITEMRECT LVIR_ICON"); return E_FAIL; } @@ -229,8 +234,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) logLastError(L"DeleteObject()"); } t->clrItemText = nm->clrText; - ret = CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; - break; + return CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; // TODO none of this runs on the first item @@ -245,26 +249,14 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } // TODO draw background on image columns if needed - ret = /*CDRF_SKIPDEFAULT | */CDRF_NEWFONT; + ret = CDRF_SKIPDEFAULT | CDRF_NEWFONT; break; -//case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: -if(0){//nm->iSubItem == 1) { -RECT r, r2; -r.left = LVIR_LABEL; -r.top = 1; -SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM)(&r)); -r2.left = LVIR_ICON; -r2.top = 1; -SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM)(&r2)); -r.left = r2.right + 2; -DrawTextW(nm->nmcd.hdc, L"Part", -1, -&r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL);} default: return CDRF_DODEFAULT; } ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); - hr = fillSubitemDrawParams(t->hwnd, nm, &dp); + hr = fillSubitemDrawParams(t, nm, &dp); if (hr != S_OK) { // TODO } @@ -272,6 +264,10 @@ DrawTextW(nm->nmcd.hdc, L"Part", -1, if (hr != S_OK) { // TODO } + hr = uiprivNM_CUSTOMDRAWText(t, nm, p, &dp); + if (hr != S_OK) { + // TODO + } return ret; } diff --git a/windows/table.hpp b/windows/table.hpp index b1b53c66..8849a833 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -40,11 +40,17 @@ struct uiTable { }; typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; struct uiprivSubitemDrawParams { + bool selected; RECT bounds; RECT icon; RECT label; }; - +extern COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int modelColumn, int fallbackSysColorID); + +// tabletext.cpp +extern HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); +extern HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p, uiprivSubitemDrawParams *dp); + // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 278a88a5..69a23bb5 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -95,8 +95,14 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip uiTableData *data; HRESULT hr; + if (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) { + // having an image list always leaves space for an image on the main item :| + // other places on the internet imply that you should be able to do this but that it shouldn't work + // but it works perfectly (and pixel-perfectly too) for me, so... + nm->item.mask |= LVIF_INDENT; + nm->item.iIndent = -1; + } if ((nm->item.mask & LVIF_IMAGE) == 0) - // TODO we actually need to do the first column fix here too... return S_OK; // nothing to do here if (p->imageModelColumn != -1) { @@ -122,16 +128,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; } - // if we got here, there's no image in this cell - nm->item.mask &= ~LVIF_IMAGE; - // having an image list always leaves space for an image on the main item :| - // other places on the internet imply that you should be able to do this but that it shouldn't work - // but it works perfectly (and pixel-perfectly too) for me, so... - // TODO it doesn't work anymore... - if (nm->item.iSubItem == 0) { - nm->item.mask |= LVIF_INDENT; - nm->item.iIndent = -1; - } + // TODO see if this is correct + nm->item.iImage = -1; return S_OK; } diff --git a/windows/tabletext.cpp b/windows/tabletext.cpp new file mode 100644 index 00000000..9208553d --- /dev/null +++ b/windows/tabletext.cpp @@ -0,0 +1,88 @@ +// 13 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +// This file handles text in tables. + +HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableData *data; + WCHAR *wstr; + HRESULT hr; + + if ((nm->item.mask & LVIF_TEXT) == 0) + return S_OK; + if (p->textModelColumn != -1) + return S_OK; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // we could just make pszText into a freshly allocated conversion and avoid the limitation of cchTextMax + // but then we would have to keep things around for some amount of time (some pages on MSDN say 2 additional LVN_GETDISPINFO messages) + // and in practice, anything that results in extra LVN_GETDISPINFO messages (such as fillSubitemDrawParams() below) will break this counting + // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) + wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); + nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; + uiprivFree(wstr); + return S_OK; +} + +HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p, uiprivSubitemDrawParams *dp) +{ + COLORREF color; + COLORREF prev; + int prevMode; + RECT r; + uiTableData *data; + WCHAR *wstr; + + if (p->textModelColumn == -1) + return S_OK; + + if (dp->selected) + color = GetSysColor(COLOR_HIGHLIGHTTEXT); + else if (p->textParams.ColorModelColumn != -1) + color = uiprivTableBlendedColorFromModel(t, nm, p->textParams.ColorModelColumn, COLOR_WINDOWTEXT); + else + color = GetSysColor(COLOR_WINDOWTEXT); + prev = SetTextColor(nm->nmcd.hdc, color); + if (prev == CLR_INVALID) { + logLastError(L"SetTextColor()"); + return E_FAIL; + } + prevMode = SetBkMode(nm->nmcd.hdc, TRANSPARENT); + if (prevMode == 0) { + logLastError(L"SetBkMode()"); + return E_FAIL; + } + + // text is actually drawn two logical units to the right of the beginning of the text rect + // TODO confirm this for the first column on both image and imageless cases + // TODO actually this whole thing is wrong for imageless columns + r = dp->label; + r.left += 2; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // these flags are a menagerie of flags from various sources: guessing, the Windows 2000 source leak, various custom draw examples on the web, etc. + // TODO find the real correct flags + if (DrawTextW(nm->nmcd.hdc, wstr, -1, &r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { + uiprivFree(wstr); + logLastError(L"DrawTextW()"); + return E_FAIL; + } + uiprivFree(wstr); + + // TODO decide once and for all what to compare to here and with SelectObject() + if (SetBkMode(nm->nmcd.hdc, prevMode) != TRANSPARENT) { + logLastError(L"SetBkMode() prev"); + return E_FAIL; + } + if (SetTextColor(nm->nmcd.hdc, prev) != color) { + logLastError(L"SetTextColor() prev"); + return E_FAIL; + } + return S_OK; +}