9136 lines
253 KiB
C
9136 lines
253 KiB
C
/*
|
|
* Listview control
|
|
*
|
|
* Copyright 1998, 1999 Eric Kohl
|
|
* Copyright 1999 Luc Tourangeau
|
|
* Copyright 2000 Jason Mawdsley
|
|
* Copyright 2001 Codeweavers Inc.
|
|
* Copyright 2002 Dimitrie O. Paun
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* NOTES
|
|
* Listview control implementation.
|
|
*
|
|
* TODO:
|
|
* -- Drawing optimizations.
|
|
* -- Hot item handling.
|
|
*
|
|
* Notifications:
|
|
* LISTVIEW_Notify : most notifications from children (editbox and header)
|
|
*
|
|
* Data structure:
|
|
* LISTVIEW_SetItemCount : not completed for non OWNERDATA
|
|
*
|
|
* Advanced functionality:
|
|
* LISTVIEW_GetNumberOfWorkAreas : not implemented
|
|
* LISTVIEW_GetISearchString : not implemented
|
|
* LISTVIEW_GetBkImage : not implemented
|
|
* LISTVIEW_SetBkImage : not implemented
|
|
* LISTVIEW_GetColumnOrderArray : simple hack only
|
|
* LISTVIEW_SetColumnOrderArray : simple hack only
|
|
* LISTVIEW_Arrange : empty stub
|
|
* LISTVIEW_ApproximateViewRect : incomplete
|
|
* LISTVIEW_Update : not completed
|
|
*
|
|
* Known differences in message stream from native control (not known if
|
|
* these differences cause problems):
|
|
* LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
|
|
* LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
|
|
* WM_PAINT does LVN_GETDISPINFO in item order 0->n, native does n->0.
|
|
* WM_SETREDRAW(True) native does LVN_GETDISPINFO for all items and
|
|
* does *not* invoke DefWindowProc
|
|
* WM_CREATE does not issue WM_QUERYUISTATE and associated registry
|
|
* processing for "USEDOUBLECLICKTIME".
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
#include "wine/port.h"
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "winbase.h"
|
|
#include "winnt.h"
|
|
#include "heap.h"
|
|
#include "commctrl.h"
|
|
#include "comctl32.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(listview);
|
|
|
|
typedef struct tagCOLUMNCACHE
|
|
{
|
|
RECT rc;
|
|
UINT align;
|
|
} COLUMNCACHE, *LPCOLUMNCACHE;
|
|
|
|
typedef struct tagITEMHDR
|
|
{
|
|
LPWSTR pszText;
|
|
INT iImage;
|
|
} ITEMHDR, *LPITEMHDR;
|
|
|
|
typedef struct tagLISTVIEW_SUBITEM
|
|
{
|
|
ITEMHDR hdr;
|
|
INT iSubItem;
|
|
} LISTVIEW_SUBITEM;
|
|
|
|
typedef struct tagLISTVIEW_ITEM
|
|
{
|
|
ITEMHDR hdr;
|
|
UINT state;
|
|
LPARAM lParam;
|
|
INT iIndent;
|
|
BOOL valid;
|
|
} LISTVIEW_ITEM;
|
|
|
|
typedef struct tagRANGE
|
|
{
|
|
INT lower;
|
|
INT upper;
|
|
} RANGE;
|
|
|
|
typedef struct tagITERATOR
|
|
{
|
|
INT nItem;
|
|
RANGE range;
|
|
HDPA ranges;
|
|
INT index;
|
|
} ITERATOR;
|
|
|
|
typedef struct tagLISTVIEW_INFO
|
|
{
|
|
HWND hwndSelf;
|
|
HBRUSH hBkBrush;
|
|
COLORREF clrBk;
|
|
COLORREF clrText;
|
|
COLORREF clrTextBk;
|
|
HIMAGELIST himlNormal;
|
|
HIMAGELIST himlSmall;
|
|
HIMAGELIST himlState;
|
|
BOOL bLButtonDown;
|
|
BOOL bRButtonDown;
|
|
INT nItemHeight;
|
|
INT nItemWidth;
|
|
HDPA hdpaSelectionRanges;
|
|
INT nSelectionMark;
|
|
BOOL bRemovingAllSelections;
|
|
INT nHotItem;
|
|
SHORT notifyFormat;
|
|
RECT rcList; /* This rectangle is really the window
|
|
* client rectangle possibly reduced by the
|
|
* horizontal scroll bar and/or header - see
|
|
* LISTVIEW_UpdateSize. This rectangle offset
|
|
* by the LISTVIEW_GetOrigin value is in
|
|
* client coordinates */
|
|
RECT rcView; /* This rectangle contains all items -
|
|
* contructed in LISTVIEW_AlignTop and
|
|
* LISTVIEW_AlignLeft */
|
|
SIZE iconSize;
|
|
SIZE iconSpacing;
|
|
SIZE iconStateSize;
|
|
UINT uCallbackMask;
|
|
HWND hwndHeader;
|
|
HFONT hDefaultFont;
|
|
HCURSOR hHotCursor;
|
|
HFONT hFont;
|
|
INT ntmHeight; /* from GetTextMetrics from above font */
|
|
INT ntmAveCharWidth; /* from GetTextMetrics from above font */
|
|
BOOL bFocus;
|
|
INT nFocusedItem;
|
|
RECT rcFocus;
|
|
DWORD dwStyle; /* the cached window GWL_STYLE */
|
|
DWORD dwLvExStyle; /* extended listview style */
|
|
INT nItemCount;
|
|
HDPA hdpaItems;
|
|
HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
|
|
HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
|
|
PFNLVCOMPARE pfnCompare;
|
|
LPARAM lParamSort;
|
|
HWND hwndEdit;
|
|
BOOL bEditing;
|
|
WNDPROC EditWndProc;
|
|
INT nEditLabelItem;
|
|
DWORD dwHoverTime;
|
|
|
|
DWORD lastKeyPressTimestamp;
|
|
WPARAM charCode;
|
|
INT nSearchParamLength;
|
|
WCHAR szSearchParam[ MAX_PATH ];
|
|
BOOL bIsDrawing;
|
|
} LISTVIEW_INFO;
|
|
|
|
DEFINE_COMMON_NOTIFICATIONS(LISTVIEW_INFO, hwndSelf);
|
|
|
|
/*
|
|
* constants
|
|
*/
|
|
/* How many we debug buffer to allocate */
|
|
#define DEBUG_BUFFERS 20
|
|
/* The size of a single debug bbuffer */
|
|
#define DEBUG_BUFFER_SIZE 256
|
|
|
|
/* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
|
|
#define SB_INTERNAL -1
|
|
|
|
/* maximum size of a label */
|
|
#define DISP_TEXT_SIZE 512
|
|
|
|
/* padding for items in list and small icon display modes */
|
|
#define WIDTH_PADDING 12
|
|
|
|
/* padding for items in list, report and small icon display modes */
|
|
#define HEIGHT_PADDING 1
|
|
|
|
/* offset of items in report display mode */
|
|
#define REPORT_MARGINX 2
|
|
|
|
/* padding for icon in large icon display mode
|
|
* ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
|
|
* that HITTEST will see.
|
|
* ICON_TOP_PADDING_HITABLE - spacing between above and icon.
|
|
* ICON_TOP_PADDING - sum of the two above.
|
|
* ICON_BOTTOM_PADDING - between bottom of icon and top of text
|
|
* LABEL_VERT_PADDING - between bottom of text and end of box
|
|
*
|
|
* ICON_LR_PADDING - additional width above icon size.
|
|
* ICON_LR_HALF - half of the above value
|
|
*/
|
|
#define ICON_TOP_PADDING_NOTHITABLE 2
|
|
#define ICON_TOP_PADDING_HITABLE 2
|
|
#define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
|
|
#define ICON_BOTTOM_PADDING 4
|
|
#define LABEL_VERT_PADDING 7
|
|
#define ICON_LR_PADDING 16
|
|
#define ICON_LR_HALF (ICON_LR_PADDING/2)
|
|
|
|
/* default label width for items in list and small icon display modes */
|
|
#define DEFAULT_LABEL_WIDTH 40
|
|
|
|
/* default column width for items in list display mode */
|
|
#define DEFAULT_COLUMN_WIDTH 128
|
|
|
|
/* Size of "line" scroll for V & H scrolls */
|
|
#define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
|
|
|
|
/* Padding betwen image and label */
|
|
#define IMAGE_PADDING 2
|
|
|
|
/* Padding behind the label */
|
|
#define TRAILING_PADDING 5
|
|
|
|
/* Border for the icon caption */
|
|
#define CAPTION_BORDER 2
|
|
|
|
/* Standard DrawText flags */
|
|
#define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
|
|
#define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
|
|
#define LV_SL_DT_FLAGS (DT_TOP | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
|
|
|
|
/* Dump the LISTVIEW_INFO structure to the debug channel */
|
|
#define LISTVIEW_DUMP(iP) do { \
|
|
TRACE("hwndSelf=%08x, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
|
|
iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
|
|
iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
|
|
TRACE("hwndSelf=%08x, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%s\n", \
|
|
iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
|
|
iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, \
|
|
(iP->bFocus) ? "true" : "false"); \
|
|
TRACE("hwndSelf=%08x, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
|
|
iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
|
|
iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
|
|
TRACE("hwndSelf=%08x, rcList=(%d,%d)-(%d,%d), rcView=(%d,%d)-(%d,%d)\n", \
|
|
iP->hwndSelf, \
|
|
iP->rcList.left, iP->rcList.top, iP->rcList.right, iP->rcList.bottom, \
|
|
iP->rcView.left, iP->rcView.top, iP->rcView.right, iP->rcView.bottom); \
|
|
} while(0)
|
|
|
|
|
|
/*
|
|
* forward declarations
|
|
*/
|
|
static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
|
|
static BOOL LISTVIEW_GetItemMeasures(LISTVIEW_INFO *, INT, LPRECT, LPRECT, LPRECT, LPRECT);
|
|
static void LISTVIEW_AlignLeft(LISTVIEW_INFO *);
|
|
static void LISTVIEW_AlignTop(LISTVIEW_INFO *);
|
|
static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *, INT);
|
|
static INT LISTVIEW_GetItemHeight(LISTVIEW_INFO *);
|
|
static BOOL LISTVIEW_GetItemListOrigin(LISTVIEW_INFO *, INT, LPPOINT);
|
|
static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
|
|
static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
|
|
static INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *);
|
|
static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *, INT, LPRECT);
|
|
static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
|
|
static LRESULT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *, INT);
|
|
static BOOL LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
|
|
static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
|
|
static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
|
|
static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
|
|
static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *, INT, POINT);
|
|
static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
|
|
static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
|
|
static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *);
|
|
static void LISTVIEW_UnsupportedStyles(LONG);
|
|
static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
|
|
static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
|
|
static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
|
|
static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
|
|
static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *, WPARAM, LPARAM);
|
|
static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
|
|
static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
|
|
static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, LPLVITEMW);
|
|
static LRESULT LISTVIEW_GetColumnT(LISTVIEW_INFO *, INT, LPLVCOLUMNW, BOOL);
|
|
static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
|
|
static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
|
|
static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
|
|
static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
|
|
static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
|
|
|
|
/******** Defines that LISTVIEW_ProcessLetterKeys uses ****************/
|
|
#define KEY_DELAY 450
|
|
|
|
/******** Text handling functions *************************************/
|
|
|
|
/* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
|
|
* text string. The string may be ANSI or Unicode, in which case
|
|
* the boolean isW tells us the type of the string.
|
|
*
|
|
* The name of the function tell what type of strings it expects:
|
|
* W: Unicode, T: ANSI/Unicode - function of isW
|
|
*/
|
|
|
|
static inline BOOL is_textW(LPCWSTR text)
|
|
{
|
|
return text != NULL && text != LPSTR_TEXTCALLBACKW;
|
|
}
|
|
|
|
static inline BOOL is_textT(LPCWSTR text, BOOL isW)
|
|
{
|
|
/* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
|
|
return is_textW(text);
|
|
}
|
|
|
|
static inline int textlenT(LPCWSTR text, BOOL isW)
|
|
{
|
|
return !is_textT(text, isW) ? 0 :
|
|
isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
|
|
}
|
|
|
|
static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
|
|
{
|
|
if (isDestW)
|
|
if (isSrcW) lstrcpynW(dest, src, max);
|
|
else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
|
|
else
|
|
if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
|
|
else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
|
|
}
|
|
|
|
static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
|
|
{
|
|
LPWSTR wstr = (LPWSTR)text;
|
|
|
|
if (!isW && is_textT(text, isW))
|
|
{
|
|
INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
|
|
wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
|
|
if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
|
|
}
|
|
TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
|
|
return wstr;
|
|
}
|
|
|
|
static inline void textfreeT(LPWSTR wstr, BOOL isW)
|
|
{
|
|
if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
|
|
}
|
|
|
|
/*
|
|
* dest is a pointer to a Unicode string
|
|
* src is a pointer to a string (Unicode if isW, ANSI if !isW)
|
|
*/
|
|
static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
|
|
if (src == LPSTR_TEXTCALLBACKW)
|
|
{
|
|
if (is_textW(*dest)) COMCTL32_Free(*dest);
|
|
*dest = LPSTR_TEXTCALLBACKW;
|
|
}
|
|
else
|
|
{
|
|
LPWSTR pszText = textdupTtoW(src, isW);
|
|
if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
|
|
bResult = Str_SetPtrW(dest, pszText);
|
|
textfreeT(pszText, isW);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
/*
|
|
* compares a Unicode to a Unicode/ANSI text string
|
|
*/
|
|
static inline int textcmpWT(LPWSTR aw, LPWSTR bt, BOOL isW)
|
|
{
|
|
if (!aw) return bt ? -1 : 0;
|
|
if (!bt) return aw ? 1 : 0;
|
|
if (aw == LPSTR_TEXTCALLBACKW)
|
|
return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
|
|
if (bt != LPSTR_TEXTCALLBACKW)
|
|
{
|
|
LPWSTR bw = textdupTtoW(bt, isW);
|
|
int r = bw ? lstrcmpW(aw, bw) : 1;
|
|
textfreeT(bw, isW);
|
|
return r;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
|
|
{
|
|
int res;
|
|
|
|
n = min(min(n, strlenW(s1)), strlenW(s2));
|
|
res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
|
|
return res ? res - sizeof(WCHAR) : res;
|
|
}
|
|
|
|
/******** Debugging functions *****************************************/
|
|
|
|
static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
|
|
{
|
|
if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
|
|
return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
|
|
}
|
|
|
|
static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
|
|
{
|
|
if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
|
|
n = min(textlenT(text, isW), n);
|
|
return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
|
|
}
|
|
|
|
static char* debug_getbuf()
|
|
{
|
|
static int index = 0;
|
|
static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
|
|
return buffers[index++ % DEBUG_BUFFERS];
|
|
}
|
|
|
|
static inline char* debugpoint(const POINT* lppt)
|
|
{
|
|
if (lppt)
|
|
{
|
|
char* buf = debug_getbuf();
|
|
snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
|
|
return buf;
|
|
} else return "(null)";
|
|
}
|
|
|
|
static inline char* debugrect(const RECT* rect)
|
|
{
|
|
if (rect)
|
|
{
|
|
char* buf = debug_getbuf();
|
|
snprintf(buf, DEBUG_BUFFER_SIZE, "[(%d, %d);(%d, %d)]",
|
|
rect->left, rect->top, rect->right, rect->bottom);
|
|
return buf;
|
|
} else return "(null)";
|
|
}
|
|
|
|
static char* debuglvitem_t(LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
char* buf = debug_getbuf(), *text = buf;
|
|
int len, size = DEBUG_BUFFER_SIZE;
|
|
|
|
if (lpLVItem == NULL) return "(null)";
|
|
len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_STATE)
|
|
len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_IMAGE)
|
|
len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_PARAM)
|
|
len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_INDENT)
|
|
len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
goto undo;
|
|
end:
|
|
buf = text + strlen(text);
|
|
undo:
|
|
if (buf - text > 2) buf[-2] = '}'; buf[-1] = 0;
|
|
return text;
|
|
}
|
|
|
|
static char* debuglvcolumn_t(LPLVCOLUMNW lpColumn, BOOL isW)
|
|
{
|
|
char* buf = debug_getbuf(), *text = buf;
|
|
int len, size = DEBUG_BUFFER_SIZE;
|
|
|
|
if (lpColumn == NULL) return "(null)";
|
|
len = snprintf(buf, size, "{");
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_SUBITEM)
|
|
len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
goto undo;
|
|
end:
|
|
buf = text + strlen(text);
|
|
undo:
|
|
if (buf - text > 2) buf[-2] = '}'; buf[-1] = 0;
|
|
return text;
|
|
}
|
|
|
|
|
|
/******** Notification functions i************************************/
|
|
|
|
static inline BOOL notify(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
|
|
{
|
|
pnmh->hwndFrom = infoPtr->hwndSelf;
|
|
pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
|
|
pnmh->code = code;
|
|
return (BOOL)SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFY,
|
|
(WPARAM)pnmh->idFrom, (LPARAM)pnmh);
|
|
}
|
|
|
|
static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
NMHDR nmh;
|
|
notify(infoPtr, LVN_ITEMACTIVATE, &nmh);
|
|
}
|
|
|
|
static inline BOOL notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
|
|
{
|
|
return notify(infoPtr, code, (LPNMHDR)plvnm);
|
|
}
|
|
|
|
static int get_ansi_notification(INT unicodeNotificationCode)
|
|
{
|
|
switch (unicodeNotificationCode)
|
|
{
|
|
case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
|
|
case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
|
|
case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
|
|
case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
|
|
case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
|
|
case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
|
|
}
|
|
ERR("unknown notification %x\n", unicodeNotificationCode);
|
|
return unicodeNotificationCode;
|
|
}
|
|
|
|
/*
|
|
Send notification. depends on dispinfoW having same
|
|
structure as dispinfoA.
|
|
infoPtr : listview struct
|
|
notificationCode : *Unicode* notification code
|
|
pdi : dispinfo structure (can be unicode or ansi)
|
|
isW : TRUE if dispinfo is Unicode
|
|
*/
|
|
static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
|
|
{
|
|
BOOL bResult = FALSE;
|
|
BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
|
|
INT realNotifCode;
|
|
INT cchTempBufMax = 0, savCchTextMax = 0;
|
|
LPWSTR pszTempBuf = NULL, savPszText = NULL;
|
|
|
|
if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
|
|
{
|
|
convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
|
|
convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
|
|
}
|
|
|
|
if (convertToAnsi || convertToUnicode)
|
|
{
|
|
if (notificationCode != LVN_GETDISPINFOW)
|
|
{
|
|
cchTempBufMax = convertToUnicode ?
|
|
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
cchTempBufMax = pdi->item.cchTextMax;
|
|
*pdi->item.pszText = 0; /* make sure we don't process garbage */
|
|
}
|
|
|
|
pszTempBuf = HeapAlloc(GetProcessHeap(), 0,
|
|
(convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
|
|
if (!pszTempBuf) return FALSE;
|
|
if (convertToUnicode)
|
|
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
|
|
pszTempBuf, cchTempBufMax);
|
|
else
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
|
|
cchTempBufMax, NULL, NULL);
|
|
savCchTextMax = pdi->item.cchTextMax;
|
|
savPszText = pdi->item.pszText;
|
|
pdi->item.pszText = pszTempBuf;
|
|
pdi->item.cchTextMax = cchTempBufMax;
|
|
}
|
|
|
|
if (infoPtr->notifyFormat == NFR_ANSI)
|
|
realNotifCode = get_ansi_notification(notificationCode);
|
|
else
|
|
realNotifCode = notificationCode;
|
|
bResult = notify(infoPtr, realNotifCode, (LPNMHDR)pdi);
|
|
|
|
if (convertToUnicode || convertToAnsi)
|
|
{
|
|
if (convertToUnicode) /* note : pointer can be changed by app ! */
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
|
|
savCchTextMax, NULL, NULL);
|
|
else
|
|
MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
|
|
savPszText, savCchTextMax);
|
|
pdi->item.pszText = savPszText; /* restores our buffer */
|
|
pdi->item.cchTextMax = savCchTextMax;
|
|
HeapFree(GetProcessHeap(), 0, pszTempBuf);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
static inline void notify_odcachehint(LISTVIEW_INFO *infoPtr, RANGE range)
|
|
{
|
|
NMLVCACHEHINT nmlv;
|
|
|
|
nmlv.iFrom = range.lower;
|
|
nmlv.iTo = range.upper;
|
|
notify(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
|
|
}
|
|
|
|
static BOOL notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, HDC hdc, RECT rc)
|
|
{
|
|
NMLVCUSTOMDRAW nmlvcd;
|
|
|
|
TRACE("(dwDrawStage=%lx, hdc=%x, rc=?)\n", dwDrawStage, hdc);
|
|
|
|
nmlvcd.nmcd.dwDrawStage = dwDrawStage;
|
|
nmlvcd.nmcd.hdc = hdc;
|
|
nmlvcd.nmcd.rc = rc;
|
|
nmlvcd.nmcd.dwItemSpec = 0;
|
|
nmlvcd.nmcd.uItemState = 0;
|
|
nmlvcd.nmcd.lItemlParam = 0;
|
|
nmlvcd.clrText = infoPtr->clrText;
|
|
nmlvcd.clrTextBk = infoPtr->clrBk;
|
|
|
|
return (BOOL)notify(infoPtr, NM_CUSTOMDRAW, &nmlvcd.nmcd.hdr);
|
|
}
|
|
|
|
/* FIXME: we should inline this where it's called somehow
|
|
* I think we need to pass in the structure
|
|
*/
|
|
static BOOL notify_customdrawitem (LISTVIEW_INFO *infoPtr, HDC hdc, UINT iItem, UINT iSubItem, UINT uItemDrawState)
|
|
{
|
|
NMLVCUSTOMDRAW nmlvcd;
|
|
UINT uItemState;
|
|
RECT itemRect;
|
|
LVITEMW item;
|
|
BOOL bReturn;
|
|
|
|
item.iItem = iItem;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_PARAM;
|
|
if (!LISTVIEW_GetItemT(infoPtr, &item, TRUE)) return FALSE;
|
|
|
|
uItemState = 0;
|
|
|
|
if (LISTVIEW_GetItemState(infoPtr, iItem, LVIS_SELECTED)) uItemState |= CDIS_SELECTED;
|
|
if (LISTVIEW_GetItemState(infoPtr, iItem, LVIS_FOCUSED)) uItemState |= CDIS_FOCUS;
|
|
if (iItem == infoPtr->nHotItem) uItemState |= CDIS_HOT;
|
|
|
|
itemRect.left = LVIR_BOUNDS;
|
|
LISTVIEW_GetItemRect(infoPtr, iItem, &itemRect);
|
|
|
|
nmlvcd.nmcd.dwDrawStage = CDDS_ITEM | uItemDrawState;
|
|
nmlvcd.nmcd.hdc = hdc;
|
|
nmlvcd.nmcd.rc = itemRect;
|
|
nmlvcd.nmcd.dwItemSpec = iItem;
|
|
nmlvcd.nmcd.uItemState = uItemState;
|
|
nmlvcd.nmcd.lItemlParam = item.lParam;
|
|
nmlvcd.clrText = infoPtr->clrText;
|
|
nmlvcd.clrTextBk = infoPtr->clrBk;
|
|
nmlvcd.iSubItem = iSubItem;
|
|
|
|
TRACE("drawstage=%lx hdc=%x item=%lx, itemstate=%x, lItemlParam=%lx\n",
|
|
nmlvcd.nmcd.dwDrawStage, nmlvcd.nmcd.hdc, nmlvcd.nmcd.dwItemSpec,
|
|
nmlvcd.nmcd.uItemState, nmlvcd.nmcd.lItemlParam);
|
|
|
|
bReturn = notify(infoPtr, NM_CUSTOMDRAW, &nmlvcd.nmcd.hdr);
|
|
|
|
infoPtr->clrText = nmlvcd.clrText;
|
|
infoPtr->clrBk = nmlvcd.clrTextBk;
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
/******** Item iterator functions **********************************/
|
|
|
|
static BOOL ranges_add(HDPA ranges, RANGE range);
|
|
static BOOL ranges_del(HDPA ranges, RANGE range);
|
|
static void ranges_dump(HDPA ranges);
|
|
|
|
static inline BOOL iterator_next(ITERATOR* i)
|
|
{
|
|
if (i->nItem == -1)
|
|
{
|
|
if (i->ranges) goto pickarange;
|
|
return (i->nItem = i->range.lower) != -1;
|
|
}
|
|
i->nItem++;
|
|
if (i->nItem <= i->range.upper)
|
|
return TRUE;
|
|
|
|
pickarange:
|
|
if (i->ranges && i->index < i->ranges->nItemCount)
|
|
{
|
|
i->range = *(RANGE*)DPA_GetPtr(i->ranges, i->index++);
|
|
return (i->nItem = i->range.lower) != -1;
|
|
}
|
|
i->nItem = i->range.lower = i->range.upper = -1;
|
|
return FALSE;
|
|
}
|
|
|
|
static RANGE iterator_range(ITERATOR* i)
|
|
{
|
|
RANGE range;
|
|
|
|
if (!i->ranges) return i->range;
|
|
|
|
range.lower = (*(RANGE*)DPA_GetPtr(i->ranges, 0)).lower;
|
|
range.upper = (*(RANGE*)DPA_GetPtr(i->ranges, i->ranges->nItemCount - 1)).upper;
|
|
return range;
|
|
}
|
|
|
|
static inline void iterator_destroy(ITERATOR* i)
|
|
{
|
|
if (i->ranges) DPA_Destroy(i->ranges);
|
|
}
|
|
|
|
static inline BOOL iterator_empty(ITERATOR* i)
|
|
{
|
|
ZeroMemory(i, sizeof(*i));
|
|
i->nItem = i->range.lower = i->range.upper = -1;
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT* lprc)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
INT lower, upper;
|
|
RECT frame = *lprc;
|
|
POINT Origin;
|
|
|
|
/* in case we fail, we want to return an empty iterator */
|
|
if (!iterator_empty(i)) return FALSE;
|
|
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
|
|
|
|
OffsetRect(&frame, -Origin.x, -Origin.y);
|
|
|
|
if (uView == LVS_ICON || uView == LVS_SMALLICON)
|
|
{
|
|
/* FIXME: we got to do better then this */
|
|
i->range.lower = 0;
|
|
i->range.upper = infoPtr->nItemCount - 1;
|
|
return TRUE;
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
if (frame.left >= infoPtr->nItemWidth) return TRUE;
|
|
if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
|
|
|
|
lower = frame.top / infoPtr->nItemHeight;
|
|
upper = (frame.bottom - 1) / infoPtr->nItemHeight;
|
|
}
|
|
else
|
|
{
|
|
INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
|
|
if (frame.top >= infoPtr->nItemHeight * nPerCol) return TRUE;
|
|
lower = (frame.left / infoPtr->nItemWidth) * nPerCol + frame.top / infoPtr->nItemHeight;
|
|
upper = ((frame.right - 1) / infoPtr->nItemWidth) * nPerCol + (frame.bottom - 1) / infoPtr->nItemHeight;
|
|
}
|
|
|
|
if (upper < 0 || lower >= infoPtr->nItemCount) return TRUE;
|
|
lower = max(lower, 0);
|
|
upper = min(upper, infoPtr->nItemCount - 1);
|
|
if (upper < lower) return TRUE;
|
|
i->range.lower = lower;
|
|
i->range.upper = upper;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL iterator_clippeditems(ITERATOR* i, LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
POINT Origin, Position;
|
|
RECT rcItem, rcClip;
|
|
INT nItem, rgntype;
|
|
|
|
rgntype = GetClipBox(hdc, &rcClip);
|
|
if (rgntype == NULLREGION) return iterator_empty(i);
|
|
if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
|
|
if (rgntype == SIMPLEREGION) return TRUE;
|
|
|
|
/* if we can't deal with the region, we'll just go with the simple range */
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return TRUE;
|
|
if (!(i->ranges = DPA_Create(10))) return TRUE;
|
|
if (!ranges_add(i->ranges, i->range))
|
|
{
|
|
DPA_Destroy(i->ranges);
|
|
i->ranges = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
/* no delete the invisible items from the list */
|
|
for (nItem = i->range.lower; nItem <= i->range.upper; nItem++)
|
|
{
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, nItem, &Position)) continue;
|
|
rcItem.left = Position.x + Origin.x;
|
|
rcItem.top = Position.y + Origin.y;
|
|
rcItem.right = rcItem.left + infoPtr->nItemWidth;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
if (!RectVisible(hdc, &rcItem))
|
|
{
|
|
RANGE item_range = { nItem, nItem };
|
|
ranges_del(i->ranges, item_range);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline BOOL iterator_visibleitems(ITERATOR* i, LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return iterator_frameditems(i, infoPtr, &infoPtr->rcList);
|
|
}
|
|
|
|
/******** Misc helper functions ************************************/
|
|
|
|
static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
|
|
WPARAM wParam, LPARAM lParam, BOOL isW)
|
|
{
|
|
if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
|
|
else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
/******** Internal API functions ************************************/
|
|
|
|
/* The Invalidate* are macros, so we preserve the caller location */
|
|
#define LISTVIEW_InvalidateRect(infoPtr, rect) do { \
|
|
TRACE(" invalidating rect=%s\n", debugrect(rect)); \
|
|
InvalidateRect(infoPtr->hwndSelf, rect, TRUE); \
|
|
} while (0)
|
|
|
|
#define LISTVIEW_InvalidateItem(infoPtr, nItem) do { \
|
|
RECT rcItem; \
|
|
rcItem.left = LVIR_BOUNDS; \
|
|
if(LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) \
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcItem); \
|
|
} while (0)
|
|
|
|
#define LISTVIEW_InvalidateList(infoPtr)\
|
|
LISTVIEW_InvalidateRect(infoPtr, NULL)
|
|
|
|
static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
|
|
{
|
|
return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
|
|
}
|
|
|
|
static inline int LISTVIEW_GetType(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return infoPtr->dwStyle & LVS_TYPEMASK;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the number of items that can fit vertically in the client area.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of items per row.
|
|
*/
|
|
static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
|
|
return max(nListWidth/infoPtr->nItemWidth, 1);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the number of items that can fit horizontally in the client
|
|
* area.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of items per column.
|
|
*/
|
|
static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
|
|
return max(nListHeight / infoPtr->nItemHeight, 1);
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
* LISTVIEW_ProcessLetterKeys
|
|
*
|
|
* Processes keyboard messages generated by pressing the letter keys
|
|
* on the keyboard.
|
|
* What this does is perform a case insensitive search from the
|
|
* current position with the following quirks:
|
|
* - If two chars or more are pressed in quick succession we search
|
|
* for the corresponding string (e.g. 'abc').
|
|
* - If there is a delay we wipe away the current search string and
|
|
* restart with just that char.
|
|
* - If the user keeps pressing the same character, whether slowly or
|
|
* fast, so that the search string is entirely composed of this
|
|
* character ('aaaaa' for instance), then we search for first item
|
|
* that starting with that character.
|
|
* - If the user types the above character in quick succession, then
|
|
* we must also search for the corresponding string ('aaaaa'), and
|
|
* go to that string if there is a match.
|
|
*
|
|
* PARAMETERS
|
|
* [I] hwnd : handle to the window
|
|
* [I] charCode : the character code, the actual character
|
|
* [I] keyData : key data
|
|
*
|
|
* RETURNS
|
|
*
|
|
* Zero.
|
|
*
|
|
* BUGS
|
|
*
|
|
* - The current implementation has a list of characters it will
|
|
* accept and it ignores averything else. In particular it will
|
|
* ignore accentuated characters which seems to match what
|
|
* Windows does. But I'm not sure it makes sense to follow
|
|
* Windows there.
|
|
* - We don't sound a beep when the search fails.
|
|
*
|
|
* SEE ALSO
|
|
*
|
|
* TREEVIEW_ProcessLetterKeys
|
|
*/
|
|
static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
|
|
{
|
|
INT nItem;
|
|
INT endidx,idx;
|
|
LVITEMW item;
|
|
WCHAR buffer[MAX_PATH];
|
|
DWORD timestamp,elapsed;
|
|
|
|
/* simple parameter checking */
|
|
if (!charCode || !keyData) return 0;
|
|
|
|
/* only allow the valid WM_CHARs through */
|
|
if (!isalnum(charCode) &&
|
|
charCode != '.' && charCode != '`' && charCode != '!' &&
|
|
charCode != '@' && charCode != '#' && charCode != '$' &&
|
|
charCode != '%' && charCode != '^' && charCode != '&' &&
|
|
charCode != '*' && charCode != '(' && charCode != ')' &&
|
|
charCode != '-' && charCode != '_' && charCode != '+' &&
|
|
charCode != '=' && charCode != '\\'&& charCode != ']' &&
|
|
charCode != '}' && charCode != '[' && charCode != '{' &&
|
|
charCode != '/' && charCode != '?' && charCode != '>' &&
|
|
charCode != '<' && charCode != ',' && charCode != '~')
|
|
return 0;
|
|
|
|
/* if there's one item or less, there is no where to go */
|
|
if (infoPtr->nItemCount <= 1) return 0;
|
|
|
|
/* compute how much time elapsed since last keypress */
|
|
timestamp=GetTickCount();
|
|
if (timestamp > infoPtr->lastKeyPressTimestamp) {
|
|
elapsed=timestamp-infoPtr->lastKeyPressTimestamp;
|
|
} else {
|
|
elapsed=infoPtr->lastKeyPressTimestamp-timestamp;
|
|
}
|
|
|
|
/* update the search parameters */
|
|
infoPtr->lastKeyPressTimestamp=timestamp;
|
|
if (elapsed < KEY_DELAY) {
|
|
if (infoPtr->nSearchParamLength < MAX_PATH) {
|
|
infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
|
|
}
|
|
if (infoPtr->charCode != charCode) {
|
|
infoPtr->charCode=charCode=0;
|
|
}
|
|
} else {
|
|
infoPtr->charCode=charCode;
|
|
infoPtr->szSearchParam[0]=charCode;
|
|
infoPtr->nSearchParamLength=1;
|
|
/* Redundant with the 1 char string */
|
|
charCode=0;
|
|
}
|
|
|
|
/* and search from the current position */
|
|
nItem=-1;
|
|
if (infoPtr->nFocusedItem >= 0) {
|
|
endidx=infoPtr->nFocusedItem;
|
|
idx=endidx;
|
|
/* if looking for single character match,
|
|
* then we must always move forward
|
|
*/
|
|
if (infoPtr->nSearchParamLength == 1)
|
|
idx++;
|
|
} else {
|
|
endidx=infoPtr->nItemCount;
|
|
idx=0;
|
|
}
|
|
do {
|
|
if (idx == infoPtr->nItemCount) {
|
|
if (endidx == infoPtr->nItemCount || endidx == 0)
|
|
break;
|
|
idx=0;
|
|
}
|
|
|
|
/* get item */
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = idx;
|
|
item.iSubItem = 0;
|
|
item.pszText = buffer;
|
|
item.cchTextMax = MAX_PATH;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
|
|
|
|
/* check for a match */
|
|
if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
|
|
nItem=idx;
|
|
break;
|
|
} else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
|
|
(lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
|
|
/* This would work but we must keep looking for a longer match */
|
|
nItem=idx;
|
|
}
|
|
idx++;
|
|
} while (idx != endidx);
|
|
|
|
if (nItem != -1)
|
|
LISTVIEW_KeySelection(infoPtr, nItem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* LISTVIEW_UpdateHeaderSize [Internal]
|
|
*
|
|
* Function to resize the header control
|
|
*
|
|
* PARAMS
|
|
* hwnd [I] handle to a window
|
|
* nNewScrollPos [I] Scroll Pos to Set
|
|
*
|
|
* RETURNS
|
|
* nothing
|
|
*
|
|
* NOTES
|
|
*/
|
|
static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
|
|
{
|
|
RECT winRect;
|
|
POINT point[2];
|
|
|
|
TRACE("nNewScrollPos=%d\n", nNewScrollPos);
|
|
|
|
GetWindowRect(infoPtr->hwndHeader, &winRect);
|
|
point[0].x = winRect.left;
|
|
point[0].y = winRect.top;
|
|
point[1].x = winRect.right;
|
|
point[1].y = winRect.bottom;
|
|
|
|
MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
|
|
point[0].x = -nNewScrollPos;
|
|
point[1].x += nNewScrollPos;
|
|
|
|
SetWindowPos(infoPtr->hwndHeader,0,
|
|
point[0].x,point[0].y,point[1].x,point[1].y,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Update the scrollbars. This functions should be called whenever
|
|
* the content, size or view changes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
if (lStyle & LVS_NOSCROLL) return;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
|
|
if (uView == LVS_LIST)
|
|
{
|
|
/* update horizontal scrollbar */
|
|
INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
INT nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
|
|
|
|
TRACE("items=%d, perColumn=%d, perRow=%d\n",
|
|
infoPtr->nItemCount, nCountPerColumn, nCountPerRow);
|
|
|
|
scrollInfo.nMin = 0;
|
|
scrollInfo.nMax = infoPtr->nItemCount / nCountPerColumn;
|
|
if((infoPtr->nItemCount % nCountPerColumn) == 0)
|
|
scrollInfo.nMax--;
|
|
if (scrollInfo.nMax < 0) scrollInfo.nMax = 0;
|
|
scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf) / nCountPerColumn;
|
|
scrollInfo.nPage = nCountPerRow;
|
|
scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
|
|
SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
BOOL test;
|
|
|
|
/* update vertical scrollbar */
|
|
scrollInfo.nMin = 0;
|
|
scrollInfo.nMax = infoPtr->nItemCount - 1;
|
|
scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf);
|
|
scrollInfo.nPage = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
|
|
test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
|
|
TRACE("LVS_REPORT Vert. nMax=%d, nPage=%d, test=%d\n",
|
|
scrollInfo.nMax, scrollInfo.nPage, test);
|
|
SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_VERT, (test) ? FALSE : TRUE);
|
|
|
|
/* update horizontal scrollbar */
|
|
nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
scrollInfo.fMask = SIF_POS;
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
|
|
|| infoPtr->nItemCount == 0)
|
|
{
|
|
scrollInfo.nPos = 0;
|
|
}
|
|
scrollInfo.nMin = 0;
|
|
scrollInfo.nMax = max(infoPtr->nItemWidth, 0)-1;
|
|
scrollInfo.nPage = nListWidth;
|
|
scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE ;
|
|
test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
|
|
TRACE("LVS_REPORT Horz. nMax=%d, nPage=%d, test=%d\n",
|
|
scrollInfo.nMax, scrollInfo.nPage, test);
|
|
SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, (test) ? FALSE : TRUE);
|
|
|
|
/* Update the Header Control */
|
|
scrollInfo.fMask = SIF_POS;
|
|
GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo);
|
|
LISTVIEW_UpdateHeaderSize(infoPtr, scrollInfo.nPos);
|
|
|
|
}
|
|
else
|
|
{
|
|
RECT rcView;
|
|
|
|
if (LISTVIEW_GetViewRect(infoPtr, &rcView))
|
|
{
|
|
INT nViewWidth = rcView.right - rcView.left;
|
|
INT nViewHeight = rcView.bottom - rcView.top;
|
|
|
|
/* Update Horizontal Scrollbar */
|
|
scrollInfo.fMask = SIF_POS;
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
|
|
|| infoPtr->nItemCount == 0)
|
|
{
|
|
scrollInfo.nPos = 0;
|
|
}
|
|
scrollInfo.nMin = 0;
|
|
scrollInfo.nMax = max(nViewWidth, 0)-1;
|
|
scrollInfo.nPage = nListWidth;
|
|
scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
|
|
TRACE("LVS_ICON/SMALLICON Horz.\n");
|
|
SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
|
|
|
|
/* Update Vertical Scrollbar */
|
|
nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
scrollInfo.fMask = SIF_POS;
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)
|
|
|| infoPtr->nItemCount == 0)
|
|
{
|
|
scrollInfo.nPos = 0;
|
|
}
|
|
scrollInfo.nMin = 0;
|
|
scrollInfo.nMax = max(nViewHeight,0)-1;
|
|
scrollInfo.nPage = nListHeight;
|
|
scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
|
|
TRACE("LVS_ICON/SMALLICON Vert.\n");
|
|
SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Shows/hides the focus rectangle.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fShow : TRUE to show the focus, FALSE to hide it.
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
|
|
{
|
|
HDC hdc;
|
|
|
|
TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
|
|
|
|
if (infoPtr->nFocusedItem < 0) return;
|
|
|
|
/* we need some gymnastics in ICON mode to handle large items */
|
|
if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
|
|
{
|
|
RECT rcBox;
|
|
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, infoPtr->nFocusedItem, &rcBox, 0, 0, 0))
|
|
return;
|
|
if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
|
|
{
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcBox);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
|
|
{
|
|
DRAWITEMSTRUCT dis;
|
|
LVITEMW item;
|
|
|
|
item.iItem = infoPtr->nFocusedItem;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_PARAM;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
|
|
|
|
ZeroMemory(&dis, sizeof(dis));
|
|
dis.CtlType = ODT_LISTVIEW;
|
|
dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
|
|
dis.itemID = item.iItem;
|
|
dis.itemAction = ODA_FOCUS;
|
|
if (fShow) dis.itemState |= ODS_FOCUS;
|
|
dis.hwndItem = infoPtr->hwndSelf;
|
|
dis.hDC = hdc;
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, dis.itemID, &dis.rcItem, 0, 0, 0)) goto done;
|
|
dis.itemData = item.lParam;
|
|
|
|
SendMessageW(GetParent(infoPtr->hwndSelf), WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
|
|
}
|
|
else
|
|
{
|
|
DrawFocusRect(hdc, &infoPtr->rcFocus);
|
|
}
|
|
done:
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
|
|
/***
|
|
* Invalidates all visible selected items.
|
|
*/
|
|
static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
ITERATOR i;
|
|
|
|
iterator_visibleitems(&i, infoPtr);
|
|
while(iterator_next(&i))
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
|
|
LISTVIEW_InvalidateItem(infoPtr, i.nItem);
|
|
}
|
|
iterator_destroy(&i);
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Prints a message for unsupported window styles.
|
|
* A kind of TODO list for window styles.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] LONG : window style
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_UnsupportedStyles(LONG lStyle)
|
|
{
|
|
if ((LVS_TYPESTYLEMASK & lStyle) == LVS_NOSCROLL)
|
|
FIXME(" LVS_NOSCROLL\n");
|
|
|
|
if (lStyle & LVS_NOLABELWRAP)
|
|
FIXME(" LVS_NOLABELWRAP\n");
|
|
|
|
if (lStyle & LVS_SORTASCENDING)
|
|
FIXME(" LVS_SORTASCENDING\n");
|
|
|
|
if (lStyle & LVS_SORTDESCENDING)
|
|
FIXME(" LVS_SORTDESCENDING\n");
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION: [INTERNAL]
|
|
* Computes an item's (left,top) corner, relative to rcView.
|
|
* That is, the position has NOT been made relative to the Origin.
|
|
* This is deliberate, to avoid computing the Origin over, and
|
|
* over again, when this function is call in a loop. Instead,
|
|
* one ca factor the computation of the Origin before the loop,
|
|
* and offset the value retured by this function, on every iteration.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item number
|
|
* [O] lpptOrig : item top, left corner
|
|
*
|
|
* RETURN:
|
|
* TRUE if computations OK
|
|
* FALSE otherwise
|
|
*/
|
|
static BOOL LISTVIEW_GetItemListOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
|
|
lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
|
|
}
|
|
else if (uView == LVS_LIST)
|
|
{
|
|
INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
|
|
lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
|
|
}
|
|
else /* LVS_REPORT */
|
|
{
|
|
lpptPosition->x = REPORT_MARGINX;
|
|
lpptPosition->y = nItem * infoPtr->nItemHeight;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION: [INTERNAL]
|
|
* Compute the rectangles of an item. This is to localize all
|
|
* the computations in one place. If you are not interested in some
|
|
* of these values, simply pass in a NULL -- the fucntion is smart
|
|
* enough to compute only what's necessary. The function computes
|
|
* the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
|
|
* one, the BOX rectangle. This rectangle is very cheap to compute,
|
|
* and is guaranteed to contain all the other rectangles. Computing
|
|
* the ICON rect is also cheap, but all the others are potentaily
|
|
* expensive. This gives an easy and effective optimization when
|
|
* searching (like point inclusion, or rectangle intersection):
|
|
* first test against the BOX, and if TRUE, test agains the desired
|
|
* rectangle. These optimizations are coded in:
|
|
* LISTVIEW_PtInRect, and LISTVIEW_IntersectRect
|
|
* use them wherever is appropriate.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item number
|
|
* [O] lprcBox : ptr to Box rectangle
|
|
* The internal LVIR_BOX rectangle
|
|
* [O] lprcBounds : ptr to Bounds rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_BOUNDS
|
|
* [O] lprcIcon : ptr to Icon rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_ICON
|
|
* [O] lprcLabel : ptr to Label rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_LABEL
|
|
*
|
|
* RETURN:
|
|
* TRUE if computations OK
|
|
* FALSE otherwise
|
|
*/
|
|
static BOOL LISTVIEW_GetItemMeasures(LISTVIEW_INFO *infoPtr, INT nItem,
|
|
LPRECT lprcBox, LPRECT lprcBounds,
|
|
LPRECT lprcIcon, LPRECT lprcLabel)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
BOOL doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
RECT Box, Icon, Label;
|
|
POINT Position, Origin;
|
|
LVITEMW lvItem;
|
|
|
|
/* Be smart and try to figure out the minimum we have to do */
|
|
if (lprcBounds)
|
|
{
|
|
if (uView == LVS_REPORT) doIcon = TRUE;
|
|
else doLabel = TRUE;
|
|
}
|
|
if (uView == LVS_ICON && infoPtr->bFocus &&
|
|
!(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
|
|
LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
|
|
oversizedBox = doLabel = TRUE;
|
|
if (lprcLabel) doLabel = TRUE;
|
|
if (doLabel || lprcIcon) doIcon = TRUE;
|
|
|
|
/* get what we need from the item before hand, so we make
|
|
* only one request. This can speed up things, if data
|
|
* is stored on the app side */
|
|
if (doLabel || (doIcon && uView == LVS_REPORT))
|
|
{
|
|
lvItem.mask = 0;
|
|
if (doIcon) lvItem.mask |= LVIF_INDENT;
|
|
if (doLabel) lvItem.mask |= LVIF_TEXT;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
|
|
}
|
|
|
|
/************************************************************/
|
|
/* compute the box rectangle (it should be cheap to do) */
|
|
/************************************************************/
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, nItem, &Position)) return FALSE;
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
|
|
Box.left = Position.x + Origin.x;
|
|
Box.top = Position.y + Origin.y;
|
|
Box.right = Box.left + infoPtr->nItemWidth;
|
|
Box.bottom = Box.top + infoPtr->nItemHeight;
|
|
|
|
/************************************************************/
|
|
/* compute ICON bounding box (ala LVM_GETITEMRECT) */
|
|
/************************************************************/
|
|
if (doIcon)
|
|
{
|
|
if (uView == LVS_ICON)
|
|
{
|
|
Icon.left = Box.left;
|
|
if (infoPtr->himlNormal)
|
|
Icon.left += (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2 - ICON_LR_HALF;
|
|
Icon.top = Box.top;
|
|
Icon.right = Icon.left;
|
|
Icon.bottom = Icon.top;
|
|
if (infoPtr->himlNormal)
|
|
{
|
|
Icon.right += infoPtr->iconSize.cx + ICON_LR_PADDING;
|
|
Icon.bottom += infoPtr->iconSize.cy + ICON_TOP_PADDING;
|
|
}
|
|
}
|
|
else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
|
|
{
|
|
/* do indent */
|
|
Icon.left = Box.left;
|
|
if (uView == LVS_REPORT) Icon.left += infoPtr->iconSize.cx * lvItem.iIndent;
|
|
if (infoPtr->himlState) Icon.left += infoPtr->iconStateSize.cx;
|
|
Icon.top = Box.top;
|
|
Icon.right = Icon.left;
|
|
if (infoPtr->himlSmall) Icon.right += infoPtr->iconSize.cx;
|
|
Icon.bottom = Icon.top + infoPtr->nItemHeight;
|
|
}
|
|
if(lprcIcon) *lprcIcon = Icon;
|
|
TRACE("hwnd=%x, item=%d, icon=%s\n", infoPtr->hwndSelf, nItem, debugrect(&Icon));
|
|
}
|
|
|
|
/************************************************************/
|
|
/* compute LABEL bounding box (ala LVM_GETITEMRECT) */
|
|
/************************************************************/
|
|
if (doLabel)
|
|
{
|
|
SIZE labelSize = { 0, 0 };
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
|
|
{
|
|
labelSize.cx = infoPtr->nItemWidth;
|
|
labelSize.cy = infoPtr->nItemHeight;
|
|
}
|
|
else if (is_textT(lvItem.pszText, TRUE))
|
|
{
|
|
HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT hOldFont = SelectObject(hdc, hFont);
|
|
UINT uFormat;
|
|
RECT rcText;
|
|
|
|
/* compute rough rectangle where the label will go */
|
|
SetRectEmpty(&rcText);
|
|
rcText.right = infoPtr->nItemWidth - TRAILING_PADDING;
|
|
rcText.bottom = infoPtr->nItemHeight;
|
|
if (uView == LVS_ICON)
|
|
rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
|
|
|
|
/* now figure out the flags */
|
|
if (uView == LVS_ICON)
|
|
uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
|
|
else
|
|
uFormat = LV_SL_DT_FLAGS;
|
|
|
|
DrawTextW (hdc, lvItem.pszText, -1, &rcText, uFormat | DT_CALCRECT);
|
|
|
|
labelSize.cx = min(rcText.right - rcText.left + TRAILING_PADDING, infoPtr->nItemWidth);
|
|
labelSize.cy = rcText.bottom - rcText.top;
|
|
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
|
|
if (uView == LVS_ICON)
|
|
{
|
|
Label.left = Box.left + (infoPtr->iconSpacing.cx - labelSize.cx) / 2;
|
|
Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
|
|
infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
|
|
Label.right = Label.left + labelSize.cx;
|
|
if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
|
|
Label.bottom = Label.top + infoPtr->nItemHeight;
|
|
else
|
|
{
|
|
if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
|
|
{
|
|
labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
|
|
labelSize.cy /= infoPtr->ntmHeight;
|
|
labelSize.cy = max(labelSize.cy, 1);
|
|
labelSize.cy *= infoPtr->ntmHeight;
|
|
}
|
|
Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
|
|
}
|
|
}
|
|
else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
|
|
{
|
|
Label.left = Icon.right;
|
|
Label.top = Box.top;
|
|
Label.right = Label.left + labelSize.cx;
|
|
if (infoPtr->himlSmall) Label.right += IMAGE_PADDING;
|
|
if (Label.right > Box.right) Label.right = Box.right;
|
|
Label.bottom = Label.top + infoPtr->nItemHeight;
|
|
}
|
|
|
|
if (lprcLabel) *lprcLabel = Label;
|
|
TRACE("hwnd=%x, item=%d, label=%s\n", infoPtr->hwndSelf, nItem, debugrect(&Label));
|
|
}
|
|
|
|
/***********************************************************/
|
|
/* compute bounds box for the item (ala LVM_GETITEMRECT) */
|
|
/***********************************************************/
|
|
if (lprcBounds)
|
|
{
|
|
if (uView == LVS_REPORT)
|
|
{
|
|
lprcBounds->left = infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT ? Box.left : Icon.left;
|
|
lprcBounds->top = Box.top;
|
|
lprcBounds->right = min(lprcBounds->left + infoPtr->nItemWidth, Box.right) - REPORT_MARGINX;
|
|
if (lprcBounds->right < lprcBounds->left) lprcBounds->right = lprcBounds->left;
|
|
lprcBounds->bottom = lprcBounds->top + infoPtr->nItemHeight;
|
|
}
|
|
else
|
|
UnionRect(lprcBounds, &Icon, &Label);
|
|
TRACE("hwnd=%x, item=%d, bounds=%s\n", infoPtr->hwndSelf, nItem, debugrect(lprcBounds));
|
|
}
|
|
|
|
if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
|
|
else if (lprcBox) *lprcBox = Box;
|
|
TRACE("hwnd=%x, item=%d, box=%s\n", infoPtr->hwndSelf, nItem, debugrect(&Box));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Aligns the items with the top edge of the window.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_AlignTop(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
POINT ptItem;
|
|
RECT rcView;
|
|
INT i, off_x=0, off_y=0;
|
|
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
/* Since SetItemPosition uses upper-left of icon, and for
|
|
style=LVS_ICON the icon is not left adjusted, get the offset */
|
|
if (uView == LVS_ICON)
|
|
{
|
|
off_y = ICON_TOP_PADDING;
|
|
off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
|
|
}
|
|
ptItem.x = off_x;
|
|
ptItem.y = off_y;
|
|
ZeroMemory(&rcView, sizeof(RECT));
|
|
TRACE("Icon off.x=%d, off.y=%d, left=%d, right=%d\n",
|
|
off_x, off_y,
|
|
infoPtr->rcList.left, infoPtr->rcList.right);
|
|
|
|
if (nListWidth > infoPtr->nItemWidth)
|
|
{
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
if ((ptItem.x-off_x) + infoPtr->nItemWidth > nListWidth)
|
|
{
|
|
ptItem.x = off_x;
|
|
ptItem.y += infoPtr->nItemHeight;
|
|
}
|
|
|
|
LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
|
|
ptItem.x += infoPtr->nItemWidth;
|
|
rcView.right = max(rcView.right, ptItem.x);
|
|
}
|
|
|
|
rcView.right -= off_x;
|
|
rcView.bottom = (ptItem.y-off_y) + infoPtr->nItemHeight;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
|
|
ptItem.y += infoPtr->nItemHeight;
|
|
}
|
|
|
|
rcView.right = infoPtr->nItemWidth;
|
|
rcView.bottom = ptItem.y-off_y;
|
|
}
|
|
|
|
infoPtr->rcView = rcView;
|
|
}
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Aligns the items with the left edge of the window.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_AlignLeft(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
POINT ptItem;
|
|
RECT rcView;
|
|
INT i, off_x=0, off_y=0;
|
|
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
/* Since SetItemPosition uses upper-left of icon, and for
|
|
style=LVS_ICON the icon is not left adjusted, get the offset */
|
|
if (uView == LVS_ICON)
|
|
{
|
|
off_y = ICON_TOP_PADDING;
|
|
off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
|
|
}
|
|
ptItem.x = off_x;
|
|
ptItem.y = off_y;
|
|
ZeroMemory(&rcView, sizeof(RECT));
|
|
TRACE("Icon off.x=%d, off.y=%d\n", off_x, off_y);
|
|
|
|
if (nListHeight > infoPtr->nItemHeight)
|
|
{
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
if (ptItem.y + infoPtr->nItemHeight > nListHeight)
|
|
{
|
|
ptItem.y = off_y;
|
|
ptItem.x += infoPtr->nItemWidth;
|
|
}
|
|
|
|
LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
|
|
ptItem.y += infoPtr->nItemHeight;
|
|
rcView.bottom = max(rcView.bottom, ptItem.y);
|
|
}
|
|
|
|
rcView.right = ptItem.x + infoPtr->nItemWidth;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
|
|
ptItem.x += infoPtr->nItemWidth;
|
|
}
|
|
|
|
rcView.bottom = infoPtr->nItemHeight;
|
|
rcView.right = ptItem.x;
|
|
}
|
|
|
|
infoPtr->rcView = rcView;
|
|
}
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the bounding rectangle of all the items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lprcView : bounding rectangle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
|
|
{
|
|
POINT ptOrigin;
|
|
|
|
TRACE("(lprcView=%p)\n", lprcView);
|
|
|
|
if (!lprcView) return FALSE;
|
|
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &ptOrigin)) return FALSE;
|
|
|
|
*lprcView = infoPtr->rcView;
|
|
OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
|
|
|
|
TRACE("lprcView=%s\n", debugrect(lprcView));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the subitem pointer associated with the subitem index.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] HDPA : DPA handle for a specific item
|
|
* [I] INT : index of subitem
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : subitem pointer
|
|
* FAILURE : NULL
|
|
*/
|
|
static LISTVIEW_SUBITEM* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems,
|
|
INT nSubItem)
|
|
{
|
|
LISTVIEW_SUBITEM *lpSubItem;
|
|
INT i;
|
|
|
|
/* we should binary search here if need be */
|
|
for (i = 1; i < hdpaSubItems->nItemCount; i++)
|
|
{
|
|
lpSubItem = (LISTVIEW_SUBITEM *) DPA_GetPtr(hdpaSubItems, i);
|
|
if (lpSubItem && (lpSubItem->iSubItem == nSubItem))
|
|
return lpSubItem;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the width of a specific item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item to calculate width, or -1 for max of all
|
|
*
|
|
* RETURN:
|
|
* Returns the width of an item width an item.
|
|
*/
|
|
static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nItemWidth = 0, i;
|
|
|
|
if (uView == LVS_ICON)
|
|
nItemWidth = infoPtr->iconSpacing.cx;
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
INT nHeaderItemCount;
|
|
RECT rcHeaderItem;
|
|
|
|
/* calculate width of header */
|
|
nHeaderItemCount = Header_GetItemCount(infoPtr->hwndHeader);
|
|
for (i = 0; i < nHeaderItemCount; i++)
|
|
if (Header_GetItemRect(infoPtr->hwndHeader, i, &rcHeaderItem))
|
|
nItemWidth += (rcHeaderItem.right - rcHeaderItem.left);
|
|
}
|
|
else
|
|
{
|
|
INT nLabelWidth;
|
|
|
|
if (infoPtr->nItemCount == 0) return DEFAULT_COLUMN_WIDTH;
|
|
|
|
/* get width of string */
|
|
if (nItem == -1)
|
|
{
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, i);
|
|
nItemWidth = max(nItemWidth, nLabelWidth);
|
|
}
|
|
}
|
|
else
|
|
nItemWidth = LISTVIEW_GetLabelWidth(infoPtr, nItem);
|
|
if (!nItemWidth) return DEFAULT_COLUMN_WIDTH;
|
|
nItemWidth += WIDTH_PADDING;
|
|
if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
|
|
if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
|
|
if (nItem == -1) nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth);
|
|
}
|
|
|
|
return max(nItemWidth, 1);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the max width of any item in the list.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] LONG : window style
|
|
*
|
|
* RETURN:
|
|
* Returns item width.
|
|
*/
|
|
static inline INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return LISTVIEW_CalculateItemWidth(infoPtr, -1);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves and saves important text metrics info for the current
|
|
* Listview font.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
*/
|
|
static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
TEXTMETRICW tm;
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
|
|
INT oldHeight, oldACW;
|
|
|
|
GetTextMetricsW(hdc, &tm);
|
|
|
|
oldHeight = infoPtr->ntmHeight;
|
|
oldACW = infoPtr->ntmAveCharWidth;
|
|
infoPtr->ntmHeight = tm.tmHeight;
|
|
infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
|
|
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
TRACE("tmHeight old=%d,new=%d; tmAveCharWidth old=%d,new=%d\n",
|
|
oldHeight, infoPtr->ntmHeight, oldACW, infoPtr->ntmAveCharWidth);
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the height of an item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Returns item height.
|
|
*/
|
|
static INT LISTVIEW_GetItemHeight(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nItemHeight;
|
|
|
|
if (LISTVIEW_GetType(infoPtr) == LVS_ICON)
|
|
nItemHeight = infoPtr->iconSpacing.cy;
|
|
else
|
|
{
|
|
nItemHeight = infoPtr->ntmHeight;
|
|
if (infoPtr->himlState)
|
|
nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
|
|
if (infoPtr->himlSmall)
|
|
nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
|
|
if (infoPtr->himlState || infoPtr->himlSmall)
|
|
nItemHeight += HEIGHT_PADDING;
|
|
}
|
|
return nItemHeight;
|
|
}
|
|
|
|
#if 0
|
|
static void LISTVIEW_PrintSelectionRanges(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
ERR("Selections are:\n");
|
|
ranges_dump(infoPtr->hdpaSelectionRanges);
|
|
}
|
|
#endif
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* A compare function for ranges
|
|
*
|
|
* PARAMETER(S)
|
|
* [I] range1 : pointer to range 1;
|
|
* [I] range2 : pointer to range 2;
|
|
* [I] flags : flags
|
|
*
|
|
* RETURNS:
|
|
* >0 : if Item 1 > Item 2
|
|
* <0 : if Item 2 > Item 1
|
|
* 0 : if Item 1 == Item 2
|
|
*/
|
|
static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
|
|
{
|
|
if (((RANGE*)range1)->upper < ((RANGE*)range2)->lower)
|
|
return -1;
|
|
if (((RANGE*)range2)->upper < ((RANGE*)range1)->lower)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void ranges_dump(HDPA ranges)
|
|
{
|
|
INT i;
|
|
|
|
for (i = 0; i < ranges->nItemCount; i++)
|
|
{
|
|
RANGE *selection = DPA_GetPtr(ranges, i);
|
|
ERR(" [%d - %d]\n", selection->lower, selection->upper);
|
|
}
|
|
}
|
|
|
|
static inline BOOL ranges_contain(HDPA ranges, INT nItem)
|
|
{
|
|
RANGE srchrng = { nItem, nItem };
|
|
|
|
return DPA_Search(ranges, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
|
|
}
|
|
|
|
static BOOL ranges_shift(HDPA ranges, INT nItem, INT delta)
|
|
{
|
|
RANGE srchrng, *chkrng;
|
|
INT index;
|
|
|
|
srchrng.upper = nItem;
|
|
srchrng.lower = nItem;
|
|
|
|
index = DPA_Search(ranges, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
|
|
if (index == -1) return TRUE;
|
|
|
|
for (;index < ranges->nItemCount; index++)
|
|
{
|
|
chkrng = DPA_GetPtr(ranges, index);
|
|
if (chkrng->lower >= nItem && chkrng->lower + delta >= 0)
|
|
chkrng->lower += delta;
|
|
if (chkrng->upper >= nItem && chkrng->upper + delta >= 0)
|
|
chkrng->upper += delta;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL ranges_add(HDPA ranges, RANGE range)
|
|
{
|
|
RANGE srchrgn;
|
|
INT index;
|
|
|
|
TRACE("range=(%i - %i)\n", range.lower, range.upper);
|
|
if (TRACE_ON(listview)) ranges_dump(ranges);
|
|
|
|
/* try find overlapping regions first */
|
|
srchrgn.lower = range.lower - 1;
|
|
srchrgn.upper = range.upper + 1;
|
|
index = DPA_Search(ranges, &srchrgn, 0, ranges_cmp, 0, 0);
|
|
|
|
if (index == -1)
|
|
{
|
|
RANGE *newrgn;
|
|
|
|
/* create the brand new range to insert */
|
|
newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
|
|
if(!newrgn) return FALSE;
|
|
*newrgn = range;
|
|
|
|
/* figure out where to insert it */
|
|
index = DPA_Search(ranges, newrgn, 0, ranges_cmp, 0, DPAS_INSERTAFTER);
|
|
if (index == -1) index = 0;
|
|
|
|
/* and get it over with */
|
|
DPA_InsertPtr(ranges, index, newrgn);
|
|
}
|
|
else
|
|
{
|
|
RANGE *chkrgn, *mrgrgn;
|
|
INT fromindex, mergeindex;
|
|
|
|
chkrgn = DPA_GetPtr(ranges, index);
|
|
if (!chkrgn) return FALSE;
|
|
TRACE("Merge with index %i (%d - %d)\n",
|
|
index, chkrgn->lower, chkrgn->upper);
|
|
|
|
chkrgn->lower = min(range.lower, chkrgn->lower);
|
|
chkrgn->upper = max(range.upper, chkrgn->upper);
|
|
|
|
TRACE("New range %i (%d - %d)\n",
|
|
index, chkrgn->lower, chkrgn->upper);
|
|
|
|
/* merge now common anges */
|
|
fromindex = 0;
|
|
srchrgn.lower = chkrgn->lower - 1;
|
|
srchrgn.upper = chkrgn->upper + 1;
|
|
|
|
do
|
|
{
|
|
mergeindex = DPA_Search(ranges, &srchrgn, fromindex, ranges_cmp, 0, 0);
|
|
if (mergeindex == -1) break;
|
|
if (mergeindex == index)
|
|
{
|
|
fromindex = index + 1;
|
|
continue;
|
|
}
|
|
|
|
TRACE("Merge with index %i\n", mergeindex);
|
|
|
|
mrgrgn = DPA_GetPtr(ranges, mergeindex);
|
|
if (!mrgrgn) return FALSE;
|
|
|
|
chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
|
|
chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
|
|
COMCTL32_Free(mrgrgn);
|
|
DPA_DeletePtr(ranges, mergeindex);
|
|
if (mergeindex < index) index --;
|
|
} while(1);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL ranges_del(HDPA ranges, RANGE range)
|
|
{
|
|
RANGE remrgn, tmprgn, *chkrgn;
|
|
BOOL done = FALSE;
|
|
INT index;
|
|
|
|
TRACE("range: (%d - %d)\n", range.lower, range.upper);
|
|
|
|
remrgn = range;
|
|
do
|
|
{
|
|
index = DPA_Search(ranges, &remrgn, 0, ranges_cmp, 0, 0);
|
|
if (index == -1) return TRUE;
|
|
|
|
chkrgn = DPA_GetPtr(ranges, index);
|
|
if (!chkrgn) return FALSE;
|
|
|
|
TRACE("Matches range index %i (%d - %d)\n",
|
|
index, chkrgn->lower, chkrgn->upper);
|
|
|
|
/* case 1: Same range */
|
|
if ( (chkrgn->upper == remrgn.upper) &&
|
|
(chkrgn->lower == remrgn.lower) )
|
|
{
|
|
DPA_DeletePtr(ranges, index);
|
|
done = TRUE;
|
|
}
|
|
/* case 2: engulf */
|
|
else if ( (chkrgn->upper <= remrgn.upper) &&
|
|
(chkrgn->lower >= remrgn.lower) )
|
|
{
|
|
DPA_DeletePtr(ranges, index);
|
|
}
|
|
/* case 3: overlap upper */
|
|
else if ( (chkrgn->upper <= remrgn.upper) &&
|
|
(chkrgn->lower < remrgn.lower) )
|
|
{
|
|
chkrgn->upper = remrgn.lower - 1;
|
|
}
|
|
/* case 4: overlap lower */
|
|
else if ( (chkrgn->upper > remrgn.upper) &&
|
|
(chkrgn->lower >= remrgn.lower) )
|
|
{
|
|
chkrgn->lower = remrgn.upper + 1;
|
|
}
|
|
/* case 5: fully internal */
|
|
else
|
|
{
|
|
RANGE *newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
|
|
if (!newrgn) return FALSE;
|
|
tmprgn = *chkrgn;
|
|
newrgn->lower = chkrgn->lower;
|
|
newrgn->upper = remrgn.lower - 1;
|
|
chkrgn->lower = remrgn.upper + 1;
|
|
DPA_InsertPtr(ranges, index, newrgn);
|
|
chkrgn = &tmprgn;
|
|
}
|
|
}
|
|
while(!done);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* Helper function for LISTVIEW_RemoveSelectionRange, and LISTVIEW_SetItem.
|
|
*/
|
|
static BOOL remove_selection_range(LISTVIEW_INFO *infoPtr, INT lower, INT upper, BOOL adj_sel_only)
|
|
{
|
|
RANGE range = { lower, upper };
|
|
LVITEMW lvItem;
|
|
INT i;
|
|
|
|
if (!ranges_del(infoPtr->hdpaSelectionRanges, range)) return FALSE;
|
|
if (adj_sel_only) return TRUE;
|
|
|
|
/* reset the selection on items */
|
|
lvItem.state = 0;
|
|
lvItem.stateMask = LVIS_SELECTED;
|
|
for(i = lower; i <= upper; i++)
|
|
LISTVIEW_SetItemState(infoPtr, i, &lvItem);
|
|
|
|
return TRUE;
|
|
}
|
|
/***
|
|
* Helper function for LISTVIEW_AddSelectionRange, and LISTVIEW_SetItem.
|
|
*/
|
|
static BOOL add_selection_range(LISTVIEW_INFO *infoPtr, INT lower, INT upper, BOOL adj_sel_only)
|
|
{
|
|
RANGE range = { lower, upper };
|
|
LVITEMW lvItem;
|
|
INT i;
|
|
|
|
if (!ranges_add(infoPtr->hdpaSelectionRanges, range)) return FALSE;
|
|
if (adj_sel_only) return TRUE;
|
|
|
|
/* set the selection on items */
|
|
lvItem.state = LVIS_SELECTED;
|
|
lvItem.stateMask = LVIS_SELECTED;
|
|
for(i = lower; i <= upper; i++)
|
|
LISTVIEW_SetItemState(infoPtr, i, &lvItem);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* DESCRIPTION:
|
|
* Adds a selection range.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lower : lower item index
|
|
* [I] upper : upper item index
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static inline BOOL LISTVIEW_AddSelectionRange(LISTVIEW_INFO *infoPtr, INT lower, INT upper)
|
|
{
|
|
return add_selection_range(infoPtr, lower, upper, FALSE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes a range selections.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lower : lower item index
|
|
* [I] upper : upper item index
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static inline BOOL LISTVIEW_RemoveSelectionRange(LISTVIEW_INFO *infoPtr, INT lower, INT upper)
|
|
{
|
|
return remove_selection_range(infoPtr, lower, upper, FALSE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes all selection ranges
|
|
*
|
|
* Parameters(s):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURNS:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : TRUE
|
|
*/
|
|
static LRESULT LISTVIEW_RemoveAllSelections(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
RANGE *sel;
|
|
|
|
if (infoPtr->bRemovingAllSelections) return TRUE;
|
|
|
|
infoPtr->bRemovingAllSelections = TRUE;
|
|
|
|
TRACE("()\n");
|
|
|
|
do
|
|
{
|
|
sel = DPA_GetPtr(infoPtr->hdpaSelectionRanges, 0);
|
|
if (sel) LISTVIEW_RemoveSelectionRange(infoPtr, sel->lower, sel->upper);
|
|
}
|
|
while (infoPtr->hdpaSelectionRanges->nItemCount > 0);
|
|
|
|
infoPtr->bRemovingAllSelections = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the number of items that are marked as selected.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of items selected.
|
|
*/
|
|
static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT i, nSelectedCount = 0;
|
|
|
|
if (infoPtr->uCallbackMask & LVIS_SELECTED)
|
|
{
|
|
INT i;
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
|
|
nSelectedCount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < infoPtr->hdpaSelectionRanges->nItemCount; i++)
|
|
{
|
|
RANGE *sel = DPA_GetPtr(infoPtr->hdpaSelectionRanges, i);
|
|
nSelectedCount += sel->upper - sel->lower + 1;
|
|
}
|
|
}
|
|
|
|
TRACE("nSelectedCount=%d\n", nSelectedCount);
|
|
return nSelectedCount;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Manages the item focus.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* TRUE : focused item changed
|
|
* FALSE : focused item has NOT changed
|
|
*/
|
|
static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
INT oldFocus = infoPtr->nFocusedItem;
|
|
LVITEMW lvItem;
|
|
|
|
lvItem.state = LVIS_FOCUSED;
|
|
lvItem.stateMask = LVIS_FOCUSED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
|
|
|
|
return oldFocus != infoPtr->nFocusedItem;
|
|
}
|
|
|
|
/**
|
|
* DESCRIPTION:
|
|
* Updates the various indices after an item has been inserted or deleted.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] direction : Direction of shift, +1 or -1.
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
|
|
{
|
|
TRACE("Shifting %iu, %i steps\n", nItem, direction);
|
|
|
|
ranges_shift(infoPtr->hdpaSelectionRanges, nItem, direction);
|
|
|
|
/* Note that the following will fail if direction != +1 and -1 */
|
|
if (infoPtr->nSelectionMark > nItem)
|
|
infoPtr->nSelectionMark += direction;
|
|
else if (infoPtr->nSelectionMark == nItem)
|
|
{
|
|
if (direction > 0)
|
|
infoPtr->nSelectionMark += direction;
|
|
else if (infoPtr->nSelectionMark >= infoPtr->nItemCount)
|
|
infoPtr->nSelectionMark = infoPtr->nItemCount - 1;
|
|
}
|
|
|
|
if (infoPtr->nFocusedItem > nItem)
|
|
infoPtr->nFocusedItem += direction;
|
|
else if (infoPtr->nFocusedItem == nItem)
|
|
{
|
|
if (direction > 0)
|
|
infoPtr->nFocusedItem += direction;
|
|
else
|
|
{
|
|
if (infoPtr->nFocusedItem >= infoPtr->nItemCount)
|
|
infoPtr->nFocusedItem = infoPtr->nItemCount - 1;
|
|
if (infoPtr->nFocusedItem >= 0)
|
|
LISTVIEW_SetItemFocus(infoPtr, infoPtr->nFocusedItem);
|
|
}
|
|
}
|
|
/* But we are not supposed to modify nHotItem! */
|
|
}
|
|
|
|
|
|
/**
|
|
* DESCRIPTION:
|
|
* Adds a block of selections.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
INT nFirst = min(infoPtr->nSelectionMark, nItem);
|
|
INT nLast = max(infoPtr->nSelectionMark, nItem);
|
|
INT i;
|
|
LVITEMW item;
|
|
|
|
if (nFirst == -1)
|
|
nFirst = nItem;
|
|
|
|
item.state = LVIS_SELECTED;
|
|
item.stateMask = LVIS_SELECTED;
|
|
|
|
/* FIXME: this is not correct LVS_OWNERDATA
|
|
* See docu for LVN_ITEMCHANGED. Is there something similar for
|
|
* RemoveGroupSelection (is there such a thing?)?
|
|
*/
|
|
for (i = nFirst; i <= nLast; i++)
|
|
LISTVIEW_SetItemState(infoPtr,i,&item);
|
|
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets a single group selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT i;
|
|
LVITEMW item;
|
|
POINT ptItem;
|
|
RECT rcSel;
|
|
|
|
LISTVIEW_RemoveAllSelections(infoPtr);
|
|
|
|
item.state = LVIS_SELECTED;
|
|
item.stateMask = LVIS_SELECTED;
|
|
|
|
if ((uView == LVS_LIST) || (uView == LVS_REPORT))
|
|
{
|
|
INT nFirst, nLast;
|
|
|
|
if (infoPtr->nSelectionMark == -1)
|
|
infoPtr->nSelectionMark = nFirst = nLast = nItem;
|
|
else
|
|
{
|
|
nFirst = min(infoPtr->nSelectionMark, nItem);
|
|
nLast = max(infoPtr->nSelectionMark, nItem);
|
|
}
|
|
for (i = nFirst; i <= nLast; i++)
|
|
LISTVIEW_SetItemState(infoPtr, i, &item);
|
|
}
|
|
else
|
|
{
|
|
RECT rcItem, rcSelMark;
|
|
ITERATOR i;
|
|
|
|
rcItem.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
|
|
rcSelMark.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
|
|
UnionRect(&rcSel, &rcItem, &rcSelMark);
|
|
iterator_frameditems(&i, infoPtr, &rcSel);
|
|
while(iterator_next(&i))
|
|
{
|
|
LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
|
|
if (PtInRect(&rcSel, ptItem))
|
|
LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
|
|
}
|
|
iterator_destroy(&i);
|
|
}
|
|
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets a single selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
TRACE("nItem=%d\n", nItem);
|
|
|
|
LISTVIEW_RemoveAllSelections(infoPtr);
|
|
|
|
lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
|
|
lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
|
|
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Set selection(s) with keyboard.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE (needs to be repainted)
|
|
* FAILURE : FALSE (nothing has changed)
|
|
*/
|
|
static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
/* FIXME: pass in the state */
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
|
|
WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
|
|
BOOL bResult = FALSE;
|
|
|
|
if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
|
|
{
|
|
if (lStyle & LVS_SINGLESEL)
|
|
{
|
|
bResult = TRUE;
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
if (wShift)
|
|
{
|
|
bResult = TRUE;
|
|
LISTVIEW_SetGroupSelection(infoPtr, nItem);
|
|
}
|
|
else if (wCtrl)
|
|
{
|
|
bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
bResult = TRUE;
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
}
|
|
}
|
|
ListView_EnsureVisible(infoPtr->hwndSelf, nItem, FALSE);
|
|
}
|
|
|
|
UpdateWindow(infoPtr->hwndSelf); /* update client area */
|
|
return bResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Selects an item based on coordinates.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] pt : mouse click ccordinates
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : item index
|
|
* FAILURE : -1
|
|
*/
|
|
/* FIXME: get rid of this function, use HitTest instead */
|
|
static INT LISTVIEW_GetItemAtPt(LISTVIEW_INFO *infoPtr, POINT pt)
|
|
{
|
|
ITERATOR i;
|
|
RECT rcItem;
|
|
|
|
iterator_visibleitems(&i, infoPtr);
|
|
while(iterator_next(&i))
|
|
{
|
|
rcItem.left = LVIR_SELECTBOUNDS;
|
|
if (LISTVIEW_GetItemRect(infoPtr, i.nItem, &rcItem))
|
|
{
|
|
TRACE("i=%d, rcItem=%s\n", i.nItem, debugrect(&rcItem));
|
|
if (PtInRect(&rcItem, pt)) break;
|
|
}
|
|
}
|
|
iterator_destroy(&i);
|
|
|
|
return i.nItem;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Called when the mouse is being actively tracked and has hovered for a specified
|
|
* amount of time
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fwKeys : key indicator
|
|
* [I] pts : mouse position
|
|
*
|
|
* RETURN:
|
|
* 0 if the message was processed, non-zero if there was an error
|
|
*
|
|
* INFO:
|
|
* LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
|
|
* over the item for a certain period of time.
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
|
|
{
|
|
if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
|
|
/* FIXME: select the item!!! */
|
|
/*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Called whenever WM_MOUSEMOVE is received.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fwKeys : key indicator
|
|
* [I] pts : mouse position
|
|
*
|
|
* RETURN:
|
|
* 0 if the message is processed, non-zero if there was an error
|
|
*/
|
|
static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
|
|
{
|
|
TRACKMOUSEEVENT trackinfo;
|
|
|
|
/* see if we are supposed to be tracking mouse hovering */
|
|
if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
|
|
/* fill in the trackinfo struct */
|
|
trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
trackinfo.dwFlags = TME_QUERY;
|
|
trackinfo.hwndTrack = infoPtr->hwndSelf;
|
|
trackinfo.dwHoverTime = infoPtr->dwHoverTime;
|
|
|
|
/* see if we are already tracking this hwnd */
|
|
_TrackMouseEvent(&trackinfo);
|
|
|
|
if(!(trackinfo.dwFlags & TME_HOVER)) {
|
|
trackinfo.dwFlags = TME_HOVER;
|
|
|
|
/* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
|
|
_TrackMouseEvent(&trackinfo);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* Tests wheather the item is assignable to a list with style lStyle
|
|
*/
|
|
static inline BOOL is_assignable_item(LPLVITEMW lpLVItem, LONG lStyle)
|
|
{
|
|
if ( (lpLVItem->mask & LVIF_TEXT) &&
|
|
(lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
|
|
(lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper for LISTVIEW_SetItemT *only*: sets item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : valid pointer to new item atttributes
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL set_owner_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
NMLISTVIEW nmlv;
|
|
INT oldState;
|
|
|
|
/* a virtual listview stores only the state for the main item */
|
|
if (lpLVItem->iSubItem || !(lpLVItem->mask & LVIF_STATE)) return FALSE;
|
|
|
|
oldState = LISTVIEW_GetItemState(infoPtr, lpLVItem->iItem, LVIS_FOCUSED | LVIS_SELECTED);
|
|
TRACE("oldState=%x, newState=%x, uCallbackMask=%x\n",
|
|
oldState, lpLVItem->state, infoPtr->uCallbackMask);
|
|
|
|
/* we're done if we don't need to change anything we handle */
|
|
if ( !((oldState ^ lpLVItem->state) & lpLVItem->stateMask &
|
|
~infoPtr->uCallbackMask & (LVIS_FOCUSED | LVIS_SELECTED))) return TRUE;
|
|
|
|
/*
|
|
* As per MSDN LVN_ITEMCHANGING notifications are _NOT_ sent for
|
|
* by LVS_OWERNDATA list controls
|
|
*/
|
|
|
|
/* if we handle the focus, and we're asked to change it, do it now */
|
|
if ( lpLVItem->stateMask & LVIS_FOCUSED )
|
|
{
|
|
if (lpLVItem->state & LVIS_FOCUSED)
|
|
infoPtr->nFocusedItem = lpLVItem->iItem;
|
|
else if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
infoPtr->nFocusedItem = -1;
|
|
}
|
|
|
|
/* and the selection is the only other state a virtual list may hold */
|
|
if (lpLVItem->stateMask & LVIS_SELECTED)
|
|
{
|
|
if (lpLVItem->state & LVIS_SELECTED)
|
|
{
|
|
if (lStyle & LVS_SINGLESEL) LISTVIEW_RemoveAllSelections(infoPtr);
|
|
add_selection_range(infoPtr, lpLVItem->iItem, lpLVItem->iItem, TRUE);
|
|
}
|
|
else
|
|
remove_selection_range(infoPtr, lpLVItem->iItem, lpLVItem->iItem, TRUE);
|
|
}
|
|
|
|
/* notify the parent now that things have changed */
|
|
ZeroMemory(&nmlv, sizeof(nmlv));
|
|
nmlv.iItem = lpLVItem->iItem;
|
|
nmlv.uNewState = lpLVItem->state;
|
|
nmlv.uOldState = oldState;
|
|
nmlv.uChanged = LVIF_STATE;
|
|
notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper for LISTVIEW_SetItemT *only*: sets item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : valid pointer to new item atttributes
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL set_main_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
HDPA hdpaSubItems;
|
|
LISTVIEW_ITEM *lpItem;
|
|
NMLISTVIEW nmlv;
|
|
UINT uChanged = 0;
|
|
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
|
|
if (!hdpaSubItems && hdpaSubItems != (HDPA)-1) return FALSE;
|
|
|
|
lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
|
|
if (!lpItem) return FALSE;
|
|
|
|
/* determine what fields will change */
|
|
if ((lpLVItem->mask & LVIF_STATE) &&
|
|
((lpItem->state ^ lpLVItem->state) & lpLVItem->stateMask))
|
|
uChanged |= LVIF_STATE;
|
|
|
|
if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
|
|
uChanged |= LVIF_IMAGE;
|
|
|
|
if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
|
|
uChanged |= LVIF_PARAM;
|
|
|
|
if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
|
|
uChanged |= LVIF_INDENT;
|
|
|
|
if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
|
|
uChanged |= LVIF_TEXT;
|
|
|
|
if (!uChanged) return TRUE;
|
|
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = lpLVItem->iItem;
|
|
nmlv.uNewState = lpLVItem->state & lpLVItem->stateMask;
|
|
nmlv.uOldState = lpItem->state & lpLVItem->stateMask;
|
|
nmlv.uChanged = uChanged;
|
|
nmlv.lParam = lpItem->lParam;
|
|
|
|
/* send LVN_ITEMCHANGING notification, if the item is not being inserted */
|
|
if(lpItem->valid && notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
|
|
return FALSE;
|
|
|
|
/* copy information */
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
|
|
|
|
if (lpLVItem->mask & LVIF_IMAGE)
|
|
lpItem->hdr.iImage = lpLVItem->iImage;
|
|
|
|
if (lpLVItem->mask & LVIF_PARAM)
|
|
lpItem->lParam = lpLVItem->lParam;
|
|
|
|
if (lpLVItem->mask & LVIF_INDENT)
|
|
lpItem->iIndent = lpLVItem->iIndent;
|
|
|
|
if (uChanged & LVIF_STATE)
|
|
{
|
|
lpItem->state &= ~lpLVItem->stateMask;
|
|
lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
|
|
if (nmlv.uNewState & LVIS_SELECTED)
|
|
{
|
|
if (lStyle & LVS_SINGLESEL) LISTVIEW_RemoveAllSelections(infoPtr);
|
|
add_selection_range(infoPtr, lpLVItem->iItem, lpLVItem->iItem, TRUE);
|
|
}
|
|
else if (lpLVItem->stateMask & LVIS_SELECTED)
|
|
remove_selection_range(infoPtr, lpLVItem->iItem, lpLVItem->iItem, TRUE);
|
|
|
|
/* if we are asked to change focus, and we manage it, do it */
|
|
if (nmlv.uNewState & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
|
|
{
|
|
if (lpLVItem->state & LVIS_FOCUSED)
|
|
{
|
|
infoPtr->nFocusedItem = lpLVItem->iItem;
|
|
LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, FALSE);
|
|
}
|
|
else if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
infoPtr->nFocusedItem = -1;
|
|
}
|
|
}
|
|
|
|
/* if LVS_LIST or LVS_SMALLICON, update the width of the items */
|
|
if((uChanged & LVIF_TEXT) && ((uView == LVS_LIST) || (uView == LVS_SMALLICON)))
|
|
{
|
|
int item_width = LISTVIEW_CalculateItemWidth(infoPtr, lpLVItem->iItem);
|
|
if(item_width > infoPtr->nItemWidth) infoPtr->nItemWidth = item_width;
|
|
}
|
|
|
|
/* if we're inserting the item, we're done */
|
|
if (!lpItem->valid) return TRUE;
|
|
|
|
/* send LVN_ITEMCHANGED notification */
|
|
nmlv.lParam = lpItem->lParam;
|
|
notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper for LISTVIEW_SetItemT *only*: sets subitem attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : valid pointer to new subitem atttributes
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
HDPA hdpaSubItems;
|
|
LISTVIEW_SUBITEM *lpSubItem;
|
|
BOOL bModified = FALSE;
|
|
|
|
/* set subitem only if column is present */
|
|
if (Header_GetItemCount(infoPtr->hwndHeader) <= lpLVItem->iSubItem)
|
|
return FALSE;
|
|
|
|
/* First do some sanity checks */
|
|
if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
|
|
if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
|
|
|
|
/* get the subitem structure, and create it if not there */
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
|
|
if (!hdpaSubItems) return FALSE;
|
|
|
|
lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
|
|
if (!lpSubItem)
|
|
{
|
|
LISTVIEW_SUBITEM *tmpSubItem;
|
|
INT i;
|
|
|
|
lpSubItem = (LISTVIEW_SUBITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM));
|
|
if (!lpSubItem) return FALSE;
|
|
/* we could binary search here, if need be...*/
|
|
for (i = 1; i < hdpaSubItems->nItemCount; i++)
|
|
{
|
|
tmpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
|
|
if (tmpSubItem && tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
|
|
}
|
|
if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
|
|
{
|
|
COMCTL32_Free(lpSubItem);
|
|
return FALSE;
|
|
}
|
|
lpSubItem->iSubItem = lpLVItem->iSubItem;
|
|
bModified = TRUE;
|
|
}
|
|
|
|
if (lpLVItem->mask & LVIF_IMAGE)
|
|
if (lpSubItem->hdr.iImage != lpLVItem->iImage)
|
|
{
|
|
lpSubItem->hdr.iImage = lpLVItem->iImage;
|
|
bModified = TRUE;
|
|
}
|
|
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
if (lpSubItem->hdr.pszText != lpLVItem->pszText)
|
|
{
|
|
textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
|
|
bModified = TRUE;
|
|
}
|
|
|
|
if (bModified && !infoPtr->bIsDrawing)
|
|
{
|
|
RECT rect;
|
|
|
|
rect.left = LVIR_BOUNDS;
|
|
rect.top = lpLVItem->iSubItem;
|
|
/* GetSubItemRect will fail in non-report mode, so there's
|
|
* gonna be no invalidation then, yay! */
|
|
if (LISTVIEW_GetSubItemRect(infoPtr, lpLVItem->iItem, &rect))
|
|
LISTVIEW_InvalidateRect(infoPtr, &rect);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] LPLVITEM : new item atttributes
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
INT nOldFocus = infoPtr->nFocusedItem;
|
|
LPWSTR pszText = NULL;
|
|
BOOL bResult;
|
|
|
|
TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
|
|
|
|
if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
/* For efficiency, we transform the lpLVItem->pszText to Unicode here */
|
|
if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
|
|
{
|
|
pszText = lpLVItem->pszText;
|
|
lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
|
|
}
|
|
|
|
/* actually set the fields */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
bResult = set_owner_item(infoPtr, lpLVItem, TRUE);
|
|
else
|
|
{
|
|
/* sanity checks first */
|
|
if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
|
|
|
|
if (lpLVItem->iSubItem)
|
|
bResult = set_sub_item(infoPtr, lpLVItem, TRUE);
|
|
else
|
|
bResult = set_main_item(infoPtr, lpLVItem, TRUE);
|
|
}
|
|
|
|
/* redraw item, if necessary */
|
|
if (bResult && !infoPtr->bIsDrawing && lpLVItem->iSubItem == 0)
|
|
{
|
|
if (nOldFocus != infoPtr->nFocusedItem && infoPtr->bFocus)
|
|
LISTVIEW_InvalidateRect(infoPtr, &infoPtr->rcFocus);
|
|
|
|
/* this little optimization eliminates some nasty flicker */
|
|
if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
|
|
!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
|
|
!(infoPtr->dwStyle & LVS_OWNERDRAWFIXED))
|
|
{
|
|
RECT rect;
|
|
|
|
rect.left = LVIR_BOUNDS;
|
|
rect.top = 0;
|
|
if (LISTVIEW_GetSubItemRect(infoPtr, lpLVItem->iItem, &rect))
|
|
LISTVIEW_InvalidateRect(infoPtr, &rect);
|
|
}
|
|
else
|
|
LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
|
|
}
|
|
/* restore text */
|
|
if (pszText)
|
|
{
|
|
textfreeT(lpLVItem->pszText, isW);
|
|
lpLVItem->pszText = pszText;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the index of the item at coordinate (0, 0) of the client area.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* item index
|
|
*/
|
|
static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
INT nItem = 0;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_POS;
|
|
|
|
if (uView == LVS_LIST)
|
|
{
|
|
if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
|
|
nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
nItem = scrollInfo.nPos;
|
|
}
|
|
else
|
|
{
|
|
if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
|
|
}
|
|
|
|
TRACE("nItem=%d\n", nItem);
|
|
|
|
return nItem;
|
|
}
|
|
|
|
/* used by the drawing code */
|
|
typedef struct tagTEXTATTR
|
|
{
|
|
int bkMode;
|
|
COLORREF bkColor;
|
|
COLORREF fgColor;
|
|
} TEXTATTR;
|
|
|
|
/* helper function for the drawing code */
|
|
static inline void set_text_attr(HDC hdc, TEXTATTR *ta)
|
|
{
|
|
ta->bkMode = SetBkMode(hdc, ta->bkMode);
|
|
ta->bkColor = SetBkColor(hdc, ta->bkColor);
|
|
ta->fgColor = SetTextColor(hdc, ta->fgColor);
|
|
}
|
|
|
|
/* helper function for the drawing code */
|
|
static void select_text_attr(LISTVIEW_INFO *infoPtr, HDC hdc, BOOL isSelected, TEXTATTR *ta)
|
|
{
|
|
ta->bkMode = OPAQUE;
|
|
|
|
if (isSelected && infoPtr->bFocus)
|
|
{
|
|
ta->bkColor = comctl32_color.clrHighlight;
|
|
ta->fgColor = comctl32_color.clrHighlightText;
|
|
}
|
|
else if (isSelected && (infoPtr->dwStyle & LVS_SHOWSELALWAYS))
|
|
{
|
|
ta->bkColor = comctl32_color.clr3dFace;
|
|
ta->fgColor = comctl32_color.clrBtnText;
|
|
}
|
|
else if ( (infoPtr->clrTextBk != CLR_DEFAULT) && (infoPtr->clrTextBk != CLR_NONE) )
|
|
{
|
|
ta->bkColor = infoPtr->clrTextBk;
|
|
ta->fgColor = infoPtr->clrText;
|
|
}
|
|
else
|
|
{
|
|
ta->bkMode = TRANSPARENT;
|
|
ta->bkColor = GetBkColor(hdc);
|
|
ta->fgColor = infoPtr->clrText;
|
|
}
|
|
|
|
set_text_attr(hdc, ta);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Erases the background of the given rectangle
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] lprcBox : clipping rectangle
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT* lprcBox)
|
|
{
|
|
if (!infoPtr->hBkBrush) return FALSE;
|
|
|
|
TRACE("(hdc=%x, lprcBox=%s, hBkBrush=%x)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
|
|
|
|
return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws a subitem.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] HDC : device context handle
|
|
* [I] INT : item index
|
|
* [I] INT : subitem index
|
|
* [I] RECT * : clipping rectangle
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DrawSubItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem,
|
|
INT nSubItem, RECT rcItem, UINT align)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE];
|
|
LVITEMW lvItem;
|
|
|
|
TRACE("(hdc=%x, nItem=%d, nSubItem=%d, rcItem=%s)\n",
|
|
hdc, nItem, nSubItem, debugrect(&rcItem));
|
|
|
|
/* get information needed for drawing the item */
|
|
lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = nSubItem;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
lvItem.pszText = szDispText;
|
|
*lvItem.pszText = '\0';
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
|
|
|
|
TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
|
|
|
|
if (lvItem.iImage) FIXME("Draw the image for the subitem\n");
|
|
|
|
DrawTextW(hdc, lvItem.pszText, -1, &rcItem, LV_SL_DT_FLAGS | align);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws an item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] nItem : item index
|
|
* [I] rcItem : item rectangle
|
|
*
|
|
* RETURN:
|
|
* TRUE: if item is focused
|
|
* FALSE: otherwise
|
|
*/
|
|
static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, RECT rcItem)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE];
|
|
INT nLabelWidth, imagePadding = 0;
|
|
RECT* lprcFocus, rcOrig = rcItem;
|
|
LVITEMW lvItem;
|
|
TEXTATTR ta;
|
|
|
|
TRACE("(hdc=%x, nItem=%d)\n", hdc, nItem);
|
|
|
|
/* get information needed for drawing the item */
|
|
lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_INDENT;
|
|
lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
lvItem.pszText = szDispText;
|
|
*lvItem.pszText = '\0';
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
|
|
TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
|
|
|
|
/* now check if we need to update the focus rectangle */
|
|
lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
|
|
if (lprcFocus) SetRectEmpty(lprcFocus);
|
|
|
|
/* do indent */
|
|
rcItem.left += infoPtr->iconSize.cx * lvItem.iIndent;
|
|
|
|
/* state icons */
|
|
if (infoPtr->himlState)
|
|
{
|
|
UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
|
|
if (uStateImage)
|
|
{
|
|
ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
|
|
rcItem.left, rcItem.top, ILD_NORMAL);
|
|
}
|
|
rcItem.left += infoPtr->iconStateSize.cx;
|
|
imagePadding = IMAGE_PADDING;
|
|
}
|
|
|
|
/* small icons */
|
|
if (infoPtr->himlSmall)
|
|
{
|
|
if (lvItem.iImage >= 0)
|
|
{
|
|
UINT mode = (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ?
|
|
ILD_SELECTED : ILD_NORMAL;
|
|
ImageList_SetBkColor(infoPtr->himlSmall, CLR_NONE);
|
|
ImageList_Draw(infoPtr->himlSmall, lvItem.iImage, hdc,
|
|
rcItem.left, rcItem.top, mode);
|
|
}
|
|
rcItem.left += infoPtr->iconSize.cx;
|
|
imagePadding = IMAGE_PADDING;
|
|
}
|
|
|
|
/* Don't bother painting item being edited */
|
|
if (infoPtr->bEditing && lprcFocus)
|
|
return FALSE;
|
|
|
|
select_text_attr(infoPtr, hdc, lvItem.state & LVIS_SELECTED, &ta);
|
|
|
|
nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
|
|
rcItem.left += imagePadding;
|
|
rcItem.right = rcItem.left + nLabelWidth + TRAILING_PADDING;
|
|
if (rcItem.right > rcOrig.right) rcItem.right = rcOrig.right;
|
|
|
|
if (lvItem.pszText)
|
|
{
|
|
TRACE("drawing text=%s, in rect=%s\n", debugstr_w(lvItem.pszText), debugrect(&rcItem));
|
|
if(lprcFocus) *lprcFocus = rcItem;
|
|
if (lvItem.state & LVIS_SELECTED)
|
|
ExtTextOutW(hdc, rcItem.left, rcItem.top, ETO_OPAQUE, &rcItem, 0, 0, 0);
|
|
DrawTextW(hdc, lvItem.pszText, -1, &rcItem, LV_SL_DT_FLAGS | DT_CENTER);
|
|
}
|
|
|
|
set_text_attr(hdc, &ta);
|
|
return lprcFocus != NULL;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws an item when in large icon display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] nItem : item index
|
|
* [I] rcItem : clipping rectangle
|
|
*
|
|
* RETURN:
|
|
* TRUE: if item is focused
|
|
* FALSE: otherwise
|
|
*/
|
|
static BOOL LISTVIEW_DrawLargeItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, RECT rcItem)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
LVITEMW lvItem;
|
|
UINT uFormat;
|
|
RECT rcIcon, rcFocus, rcLabel, *lprcFocus;
|
|
|
|
TRACE("(hdc=%x, nItem=%d, rcItem=%s)\n", hdc, nItem, debugrect(&rcItem));
|
|
|
|
/* get information needed for drawing the item */
|
|
lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
|
|
lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
lvItem.pszText = szDispText;
|
|
*lvItem.pszText = '\0';
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
|
|
TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
|
|
|
|
/* now check if we need to update the focus rectangle */
|
|
lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
|
|
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, nItem, NULL, NULL, &rcIcon, &rcLabel)) return FALSE;
|
|
|
|
/* Set the item to the boundary box for now */
|
|
TRACE("rcIcon=%s, rcLabel=%s\n", debugrect(&rcIcon), debugrect(&rcLabel));
|
|
|
|
/* Figure out text colours etc. depending on state
|
|
* At least the following states exist; there may be more.
|
|
* Many items may be selected
|
|
* At most one item may have the focus
|
|
* The application may not actually be active currently
|
|
* 1. The item is not selected in any way
|
|
* 2. The cursor is flying over the icon or text and the text is being
|
|
* expanded because it is not fully displayed currently.
|
|
* 3. The item is selected and is focussed, i.e. the user has not clicked
|
|
* in the blank area of the window, and the window (or application?)
|
|
* still has the focus.
|
|
* 4. As 3 except that a different window has the focus
|
|
* 5. The item is the selected item of all the items, but the user has
|
|
* clicked somewhere else on the window.
|
|
* Only a few of these are handled currently. In particular 2 is not yet
|
|
* handled since we do not support the functionality currently (or at least
|
|
* we didn't when I wrote this)
|
|
*/
|
|
|
|
if (lvItem.state & LVIS_SELECTED)
|
|
{
|
|
/* set item colors */
|
|
SetBkColor(hdc, comctl32_color.clrHighlight);
|
|
SetTextColor(hdc, comctl32_color.clrHighlightText);
|
|
SetBkMode (hdc, OPAQUE);
|
|
/* set raster mode */
|
|
SetROP2(hdc, R2_XORPEN);
|
|
/* When exactly is it in XOR? while being dragged? */
|
|
}
|
|
else
|
|
{
|
|
/* set item colors */
|
|
if ( (infoPtr->clrTextBk == CLR_DEFAULT) || (infoPtr->clrTextBk == CLR_NONE) )
|
|
{
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
}
|
|
else
|
|
{
|
|
SetBkMode(hdc, OPAQUE);
|
|
SetBkColor(hdc, infoPtr->clrTextBk);
|
|
}
|
|
|
|
SetTextColor(hdc, infoPtr->clrText);
|
|
/* set raster mode */
|
|
SetROP2(hdc, R2_COPYPEN);
|
|
}
|
|
|
|
/* In cases 2,3 and 5 (see above) the full text is displayed, with word
|
|
* wrapping and long words split.
|
|
* In cases 1 and 4 only a portion of the text is displayed with word
|
|
* wrapping and both word and end ellipsis. (I don't yet know about path
|
|
* ellipsis)
|
|
*/
|
|
uFormat = lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
|
|
|
|
/* state icons */
|
|
if (infoPtr->himlState != NULL)
|
|
{
|
|
UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
|
|
INT x, y;
|
|
|
|
x = rcIcon.left - infoPtr->iconStateSize.cx + 10;
|
|
y = rcIcon.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
|
|
if (uStateImage > 0)
|
|
ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, x, y, ILD_NORMAL);
|
|
}
|
|
|
|
/* draw the icon */
|
|
if (infoPtr->himlNormal != NULL)
|
|
{
|
|
if (lvItem.iImage >= 0)
|
|
ImageList_Draw (infoPtr->himlNormal, lvItem.iImage, hdc,
|
|
rcIcon.left + ICON_LR_HALF, rcIcon.top + ICON_TOP_PADDING,
|
|
(lvItem.state & LVIS_SELECTED) ? ILD_SELECTED : ILD_NORMAL);
|
|
}
|
|
|
|
/* Draw the text below the icon */
|
|
|
|
/* Don't bother painting item being edited */
|
|
if ((infoPtr->bEditing && lprcFocus) || !lvItem.pszText || !lstrlenW(lvItem.pszText))
|
|
{
|
|
if(lprcFocus) SetRectEmpty(lprcFocus);
|
|
return FALSE;
|
|
}
|
|
|
|
/* draw label */
|
|
|
|
/* I am sure of most of the uFormat values. However I am not sure about
|
|
* whether we need or do not need the following:
|
|
* DT_EXTERNALLEADING, DT_INTERNAL, DT_CALCRECT, DT_NOFULLWIDTHCHARBREAK,
|
|
* DT_PATH_ELLIPSIS, DT_RTLREADING,
|
|
* We certainly do not need
|
|
* DT_BOTTOM, DT_VCENTER, DT_MODIFYSTRING, DT_LEFT, DT_RIGHT, DT_PREFIXONLY,
|
|
* DT_SINGLELINE, DT_TABSTOP, DT_EXPANDTABS
|
|
*/
|
|
|
|
/* If the text is being drawn without clipping (i.e. the full text) then we
|
|
* need to jump through a few hoops to ensure that it all gets displayed and
|
|
* that the background is complete
|
|
*/
|
|
rcFocus = rcLabel; /* save for focus */
|
|
if (lvItem.state & LVIS_SELECTED)
|
|
ExtTextOutW(hdc, rcLabel.left, rcLabel.top, ETO_OPAQUE, &rcLabel, 0, 0, 0);
|
|
/* else ? What if we are losing the focus? will we not get a complete
|
|
* background?
|
|
*/
|
|
|
|
DrawTextW (hdc, lvItem.pszText, -1, &rcLabel, uFormat);
|
|
TRACE("text at rcLabel=%s is %s\n", debugrect(&rcLabel), debugstr_w(lvItem.pszText));
|
|
|
|
if(lprcFocus) CopyRect(lprcFocus, &rcFocus);
|
|
|
|
return lprcFocus != NULL;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in owner draw mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
|
|
HWND hwndParent = GetParent(infoPtr->hwndSelf);
|
|
POINT Origin, Position;
|
|
DRAWITEMSTRUCT dis;
|
|
LVITEMW item;
|
|
ITERATOR i;
|
|
|
|
TRACE("()\n");
|
|
|
|
ZeroMemory(&dis, sizeof(dis));
|
|
|
|
/* Get scroll info once before loop */
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
|
|
|
|
/* figure out what we need to draw */
|
|
iterator_clippeditems(&i, infoPtr, hdc);
|
|
|
|
/* send cache hint notification */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
notify_odcachehint(infoPtr, iterator_range(&i));
|
|
|
|
/* iterate through the invalidated rows */
|
|
while(iterator_next(&i))
|
|
{
|
|
item.iItem = i.nItem;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_PARAM | LVIF_STATE;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
|
|
|
|
dis.CtlType = ODT_LISTVIEW;
|
|
dis.CtlID = uID;
|
|
dis.itemID = item.iItem;
|
|
dis.itemAction = ODA_DRAWENTIRE;
|
|
dis.itemState = 0;
|
|
if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
|
|
if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
|
|
dis.hwndItem = infoPtr->hwndSelf;
|
|
dis.hDC = hdc;
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, dis.itemID, &Position)) continue;
|
|
dis.rcItem.left = Position.x + Origin.x;
|
|
dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
|
|
dis.rcItem.top = Position.y + Origin.y;
|
|
dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
|
|
dis.itemData = item.lParam;
|
|
|
|
TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
|
|
SendMessageW(hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
|
|
}
|
|
iterator_destroy(&i);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in report display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] cdmode : custom draw mode
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
|
|
{
|
|
INT rgntype, nDrawPosY, j;
|
|
INT nColumnCount, nFirstCol, nLastCol;
|
|
RECT rcItem, rcClip, rcFullSelect;
|
|
BOOL bFullSelected, isFocused;
|
|
DWORD cditemmode = CDRF_DODEFAULT;
|
|
TEXTATTR tmpTa, oldTa;
|
|
COLUMNCACHE *lpCols;
|
|
LVCOLUMNW lvColumn;
|
|
LVITEMW item;
|
|
POINT ptOrig;
|
|
ITERATOR i;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* figure out what to draw */
|
|
rgntype = GetClipBox(hdc, &rcClip);
|
|
if (rgntype == NULLREGION) return;
|
|
|
|
/* cache column info */
|
|
nColumnCount = Header_GetItemCount(infoPtr->hwndHeader);
|
|
lpCols = COMCTL32_Alloc(nColumnCount * sizeof(COLUMNCACHE));
|
|
if (!lpCols) return;
|
|
for (j = 0; j < nColumnCount; j++)
|
|
{
|
|
Header_GetItemRect(infoPtr->hwndHeader, j, &lpCols[j].rc);
|
|
TRACE("lpCols[%d].rc=%s\n", j, debugrect(&lpCols[j].rc));
|
|
}
|
|
|
|
/* Get scroll info once before loop */
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &ptOrig)) return;
|
|
|
|
/* we now narrow the columns as well */
|
|
nLastCol = nColumnCount - 1;
|
|
for(nFirstCol = 0; nFirstCol < nColumnCount; nFirstCol++)
|
|
if (lpCols[nFirstCol].rc.right + ptOrig.x >= rcClip.left) break;
|
|
for(nLastCol = nColumnCount - 1; nLastCol >= 0; nLastCol--)
|
|
if (lpCols[nLastCol].rc.left + ptOrig.x < rcClip.right) break;
|
|
|
|
/* cache the per-column information before we start drawing */
|
|
for (j = nFirstCol; j <= nLastCol; j++)
|
|
{
|
|
lvColumn.mask = LVCF_FMT;
|
|
LISTVIEW_GetColumnT(infoPtr, j, &lvColumn, TRUE);
|
|
TRACE("lvColumn=%s\n", debuglvcolumn_t(&lvColumn, TRUE));
|
|
lpCols[j].align = DT_LEFT;
|
|
if (lvColumn.fmt & LVCFMT_RIGHT)
|
|
lpCols[j].align = DT_RIGHT;
|
|
else if (lvColumn.fmt & LVCFMT_CENTER)
|
|
lpCols[j].align = DT_CENTER;
|
|
}
|
|
|
|
/* save dc values we're gonna trash while drawing */
|
|
oldTa.bkMode = GetBkMode(hdc);
|
|
oldTa.bkColor = GetBkColor(hdc);
|
|
oldTa.fgColor = GetTextColor(hdc);
|
|
|
|
/* figure out what we need to draw */
|
|
iterator_clippeditems(&i, infoPtr, hdc);
|
|
|
|
/* a last few bits before we start drawing */
|
|
bFullSelected = infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT;
|
|
TRACE("Colums=(%di - %d)\n", nFirstCol, nLastCol);
|
|
|
|
/* send cache hint notification */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
notify_odcachehint(infoPtr, iterator_range(&i));
|
|
|
|
/* iterate through the invalidated rows */
|
|
while(iterator_next(&i))
|
|
{
|
|
nDrawPosY = i.nItem * infoPtr->nItemHeight;
|
|
|
|
/* compute the full select rectangle, if needed */
|
|
if (bFullSelected)
|
|
{
|
|
item.mask = LVIF_IMAGE | LVIF_STATE | LVIF_INDENT;
|
|
item.stateMask = LVIS_SELECTED;
|
|
item.iItem = i.nItem;
|
|
item.iSubItem = 0;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
|
|
|
|
rcFullSelect.left = lpCols[0].rc.left + REPORT_MARGINX +
|
|
infoPtr->iconSize.cx * item.iIndent +
|
|
(infoPtr->himlSmall ? infoPtr->iconSize.cx : 0);
|
|
rcFullSelect.right = max(rcFullSelect.left, lpCols[nColumnCount - 1].rc.right - REPORT_MARGINX);
|
|
rcFullSelect.top = nDrawPosY;
|
|
rcFullSelect.bottom = rcFullSelect.top + infoPtr->nItemHeight;
|
|
OffsetRect(&rcFullSelect, ptOrig.x, ptOrig.y);
|
|
}
|
|
|
|
/* draw the background of the selection rectangle, if need be */
|
|
select_text_attr(infoPtr, hdc, bFullSelected && (item.state & LVIS_SELECTED), &tmpTa);
|
|
if (bFullSelected && (item.state & LVIS_SELECTED))
|
|
ExtTextOutW(hdc, rcFullSelect.left, rcFullSelect.top, ETO_OPAQUE, &rcFullSelect, 0, 0, 0);
|
|
|
|
/* iterate through the invalidated columns */
|
|
isFocused = FALSE;
|
|
for (j = nFirstCol; j <= nLastCol; j++)
|
|
{
|
|
rcItem = lpCols[j].rc;
|
|
rcItem.left += REPORT_MARGINX;
|
|
rcItem.right = max(rcItem.left, rcItem.right - REPORT_MARGINX);
|
|
rcItem.top = nDrawPosY;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
|
|
/* Offset the Scroll Bar Pos */
|
|
OffsetRect(&rcItem, ptOrig.x, ptOrig.y);
|
|
|
|
if (rgntype == COMPLEXREGION && !RectVisible(hdc, &rcItem)) continue;
|
|
|
|
if (cdmode & CDRF_NOTIFYITEMDRAW)
|
|
cditemmode = notify_customdrawitem (infoPtr, hdc, i.nItem, j, CDDS_ITEMPREPAINT);
|
|
if (cditemmode & CDRF_SKIPDEFAULT) continue;
|
|
|
|
if (j == 0)
|
|
isFocused = LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, rcItem);
|
|
else
|
|
LISTVIEW_DrawSubItem(infoPtr, hdc, i.nItem, j, rcItem, lpCols[j].align);
|
|
|
|
if (cditemmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_customdrawitem(infoPtr, hdc, i.nItem, 0, CDDS_ITEMPOSTPAINT);
|
|
}
|
|
|
|
/* Adjust focus if we have it, and we are in full select */
|
|
if (bFullSelected && isFocused) infoPtr->rcFocus = rcFullSelect;
|
|
}
|
|
iterator_destroy(&i);
|
|
|
|
/* cleanup the mess */
|
|
set_text_attr(hdc, &oldTa);
|
|
COMCTL32_Free(lpCols);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in list display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] cdmode : custom draw mode
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
|
|
{
|
|
DWORD cditemmode = CDRF_DODEFAULT;
|
|
POINT Origin, Position;
|
|
RECT rcItem;
|
|
ITERATOR i;
|
|
|
|
/* Get scroll info once before loop */
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
|
|
|
|
/* figure out what we need to draw */
|
|
iterator_clippeditems(&i, infoPtr, hdc);
|
|
|
|
while(iterator_next(&i))
|
|
{
|
|
if (cdmode & CDRF_NOTIFYITEMDRAW)
|
|
cditemmode = notify_customdrawitem (infoPtr, hdc, i.nItem, 0, CDDS_ITEMPREPAINT);
|
|
if (cditemmode & CDRF_SKIPDEFAULT) continue;
|
|
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, i.nItem, &Position)) continue;
|
|
rcItem.left = Position.x;
|
|
rcItem.top = Position.y;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
rcItem.right = rcItem.left + infoPtr->nItemWidth;
|
|
OffsetRect(&rcItem, Origin.x, Origin.y);
|
|
|
|
LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, rcItem);
|
|
|
|
if (cditemmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_customdrawitem(infoPtr, hdc, i.nItem, 0, CDDS_ITEMPOSTPAINT);
|
|
|
|
}
|
|
iterator_destroy(&i);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in icon or small icon display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] cdmode : custom draw mode
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshIcon(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
|
|
{
|
|
DWORD cditemmode = CDRF_DODEFAULT;
|
|
POINT Origin, Position;
|
|
RECT rcItem;
|
|
ITERATOR i;
|
|
|
|
/* Get scroll info once before loop */
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
|
|
|
|
/* figure out what we need to draw */
|
|
iterator_clippeditems(&i, infoPtr, hdc);
|
|
|
|
while(iterator_next(&i))
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_FOCUSED))
|
|
continue;
|
|
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, i.nItem, &Position)) continue;
|
|
rcItem.left = Position.x;
|
|
rcItem.top = Position.y;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
rcItem.right = rcItem.left + infoPtr->nItemWidth;
|
|
OffsetRect(&rcItem, Origin.x, Origin.y);
|
|
|
|
if (cdmode & CDRF_NOTIFYITEMDRAW)
|
|
cditemmode = notify_customdrawitem (infoPtr, hdc, i.nItem, 0, CDDS_ITEMPREPAINT);
|
|
if (cditemmode & CDRF_SKIPDEFAULT) continue;
|
|
|
|
LISTVIEW_DrawLargeItem(infoPtr, hdc, i.nItem, rcItem);
|
|
|
|
if (cditemmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_customdrawitem(infoPtr, hdc, i.nItem, 0, CDDS_ITEMPOSTPAINT);
|
|
}
|
|
iterator_destroy(&i);
|
|
|
|
/* draw the focused item last, in case it's oversized */
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, infoPtr->nFocusedItem, &rcItem, 0, 0, 0)) return;
|
|
if (!RectVisible(hdc, &rcItem)) return;
|
|
|
|
if (cdmode & CDRF_NOTIFYITEMDRAW)
|
|
cditemmode = notify_customdrawitem (infoPtr, hdc, infoPtr->nFocusedItem, 0, CDDS_ITEMPREPAINT);
|
|
if (cditemmode & CDRF_SKIPDEFAULT)
|
|
return;
|
|
|
|
LISTVIEW_DrawLargeItem(infoPtr, hdc, infoPtr->nFocusedItem, rcItem);
|
|
|
|
if (cditemmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_customdrawitem(infoPtr, hdc, i.nItem, 0, CDDS_ITEMPOSTPAINT);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] HDC : device context handle
|
|
*
|
|
* RETURN:
|
|
* NoneX
|
|
*/
|
|
static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
HFONT hOldFont;
|
|
DWORD cdmode;
|
|
RECT rcClient;
|
|
|
|
LISTVIEW_DUMP(infoPtr);
|
|
|
|
GetClientRect(infoPtr->hwndSelf, &rcClient);
|
|
|
|
cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, hdc, rcClient);
|
|
if (cdmode == CDRF_SKIPDEFAULT) return;
|
|
|
|
infoPtr->bIsDrawing = TRUE;
|
|
|
|
/* nothing to draw */
|
|
if(infoPtr->nItemCount == 0) goto enddraw;
|
|
|
|
/* select font */
|
|
hOldFont = SelectObject(hdc, infoPtr->hFont);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
|
|
LISTVIEW_RefreshOwnerDraw(infoPtr, hdc);
|
|
else if (uView == LVS_ICON)
|
|
LISTVIEW_RefreshIcon(infoPtr, hdc, cdmode);
|
|
else if (uView == LVS_REPORT)
|
|
LISTVIEW_RefreshReport(infoPtr, hdc, cdmode);
|
|
else /* LVS_LIST or LVS_SMALLICON */
|
|
LISTVIEW_RefreshList(infoPtr, hdc, cdmode);
|
|
|
|
/* if we have a focus rect, draw it */
|
|
if (infoPtr->bFocus && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED))
|
|
DrawFocusRect(hdc, &infoPtr->rcFocus);
|
|
|
|
/* unselect objects */
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
enddraw:
|
|
if (cdmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_customdraw(infoPtr, CDDS_POSTPAINT, hdc, rcClient);
|
|
|
|
infoPtr->bIsDrawing = FALSE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the approximate width and height of a given number of items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : number of items
|
|
* [I] INT : width
|
|
* [I] INT : height
|
|
*
|
|
* RETURN:
|
|
* Returns a DWORD. The width in the low word and the height in high word.
|
|
*/
|
|
static LRESULT LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
|
|
WORD wWidth, WORD wHeight)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nItemCountPerColumn = 1;
|
|
INT nColumnCount = 0;
|
|
DWORD dwViewRect = 0;
|
|
|
|
if (nItemCount == -1)
|
|
nItemCount = infoPtr->nItemCount;
|
|
|
|
if (uView == LVS_LIST)
|
|
{
|
|
if (wHeight == 0xFFFF)
|
|
{
|
|
/* use current height */
|
|
wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
}
|
|
|
|
if (wHeight < infoPtr->nItemHeight)
|
|
wHeight = infoPtr->nItemHeight;
|
|
|
|
if (nItemCount > 0)
|
|
{
|
|
if (infoPtr->nItemHeight > 0)
|
|
{
|
|
nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
|
|
if (nItemCountPerColumn == 0)
|
|
nItemCountPerColumn = 1;
|
|
|
|
if (nItemCount % nItemCountPerColumn != 0)
|
|
nColumnCount = nItemCount / nItemCountPerColumn;
|
|
else
|
|
nColumnCount = nItemCount / nItemCountPerColumn + 1;
|
|
}
|
|
}
|
|
|
|
/* Microsoft padding magic */
|
|
wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
|
|
wWidth = nColumnCount * infoPtr->nItemWidth + 2;
|
|
|
|
dwViewRect = MAKELONG(wWidth, wHeight);
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
FIXME("uView == LVS_REPORT: not implemented\n");
|
|
else if (uView == LVS_SMALLICON)
|
|
FIXME("uView == LVS_SMALLICON: not implemented\n");
|
|
else if (uView == LVS_ICON)
|
|
FIXME("uView == LVS_ICON: not implemented\n");
|
|
|
|
return dwViewRect;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Arranges listview items in icon display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : alignment code
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
BOOL bResult = FALSE;
|
|
|
|
if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
|
|
{
|
|
switch (nAlignCode)
|
|
{
|
|
case LVA_ALIGNLEFT:
|
|
FIXME("nAlignCode=LVA_ALIGNLEFT: not implemented\n");
|
|
break;
|
|
case LVA_ALIGNTOP:
|
|
FIXME("nAlignCode=LVA_ALIGNTOP: not implemented\n");
|
|
break;
|
|
case LVA_DEFAULT:
|
|
FIXME("nAlignCode=LVA_DEFAULT: not implemented\n");
|
|
break;
|
|
case LVA_SNAPTOGRID:
|
|
FIXME("nAlignCode=LVA_SNAPTOGRID: not implemented\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/* << LISTVIEW_CreateDragImage >> */
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes all listview items and subitems.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
LISTVIEW_ITEM *lpItem;
|
|
LISTVIEW_SUBITEM *lpSubItem;
|
|
NMLISTVIEW nmlv;
|
|
BOOL bSuppress;
|
|
BOOL bResult = FALSE;
|
|
HDPA hdpaSubItems;
|
|
|
|
TRACE("()\n");
|
|
|
|
LISTVIEW_RemoveAllSelections(infoPtr);
|
|
infoPtr->nSelectionMark=-1;
|
|
infoPtr->nFocusedItem=-1;
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
/* But we are supposed to leave nHotItem as is! */
|
|
|
|
if (lStyle & LVS_OWNERDATA)
|
|
{
|
|
infoPtr->nItemCount = 0;
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
return TRUE;
|
|
}
|
|
|
|
if (infoPtr->nItemCount > 0)
|
|
{
|
|
INT i, j;
|
|
|
|
/* send LVN_DELETEALLITEMS notification */
|
|
/* verify if subsequent LVN_DELETEITEM notifications should be
|
|
suppressed */
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = -1;
|
|
bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
|
|
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
|
|
if (hdpaSubItems != NULL)
|
|
{
|
|
for (j = 1; j < hdpaSubItems->nItemCount; j++)
|
|
{
|
|
lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, j);
|
|
if (lpSubItem != NULL)
|
|
{
|
|
/* free subitem string */
|
|
if (is_textW(lpSubItem->hdr.pszText))
|
|
COMCTL32_Free(lpSubItem->hdr.pszText);
|
|
|
|
/* free subitem */
|
|
COMCTL32_Free(lpSubItem);
|
|
}
|
|
}
|
|
|
|
lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
|
|
if (lpItem != NULL)
|
|
{
|
|
if (!bSuppress)
|
|
{
|
|
/* send LVN_DELETEITEM notification */
|
|
nmlv.iItem = i;
|
|
nmlv.lParam = lpItem->lParam;
|
|
notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
|
|
}
|
|
|
|
/* free item string */
|
|
if (is_textW(lpItem->hdr.pszText))
|
|
COMCTL32_Free(lpItem->hdr.pszText);
|
|
|
|
/* free item */
|
|
COMCTL32_Free(lpItem);
|
|
}
|
|
|
|
DPA_Destroy(hdpaSubItems);
|
|
}
|
|
}
|
|
|
|
/* reinitialize listview memory */
|
|
bResult = DPA_DeleteAllPtrs(infoPtr->hdpaItems);
|
|
infoPtr->nItemCount = 0;
|
|
DPA_DeleteAllPtrs(infoPtr->hdpaPosX);
|
|
DPA_DeleteAllPtrs(infoPtr->hdpaPosY);
|
|
|
|
/* align items (set position of each item) */
|
|
if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
|
|
{
|
|
if (lStyle & LVS_ALIGNLEFT)
|
|
{
|
|
LISTVIEW_AlignLeft(infoPtr);
|
|
}
|
|
else
|
|
{
|
|
LISTVIEW_AlignTop(infoPtr);
|
|
}
|
|
}
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes a column from the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : column index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
RECT rcCol, rcOld;
|
|
|
|
TRACE("nColumn=%d\n", nColumn);
|
|
|
|
if (nColumn <= 0) return FALSE;
|
|
|
|
if (uView == LVS_REPORT)
|
|
{
|
|
if (!Header_GetItemRect(infoPtr->hwndHeader, nColumn, &rcCol))
|
|
return FALSE;
|
|
|
|
if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA))
|
|
{
|
|
LISTVIEW_SUBITEM *lpSubItem, *lpDelItem;
|
|
HDPA hdpaSubItems;
|
|
INT nItem, nSubItem, i;
|
|
|
|
for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
|
|
{
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
|
|
if (!hdpaSubItems) continue;
|
|
nSubItem = 0;
|
|
lpDelItem = 0;
|
|
for (i = 1; i < hdpaSubItems->nItemCount; i++)
|
|
{
|
|
lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
|
|
if (!lpSubItem) break;
|
|
if (lpSubItem->iSubItem == nColumn)
|
|
{
|
|
nSubItem = i;
|
|
lpDelItem = lpSubItem;
|
|
}
|
|
else if (lpSubItem->iSubItem > nColumn)
|
|
{
|
|
lpSubItem->iSubItem--;
|
|
}
|
|
}
|
|
|
|
/* if we found our subitem, zapp it */
|
|
if (nSubItem > 0)
|
|
{
|
|
/* free string */
|
|
if (is_textW(lpDelItem->hdr.pszText))
|
|
COMCTL32_Free(lpDelItem->hdr.pszText);
|
|
|
|
/* free item */
|
|
COMCTL32_Free(lpDelItem);
|
|
|
|
/* free dpa memory */
|
|
DPA_DeletePtr(hdpaSubItems, nSubItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* we need to worry about display issues in report mode only */
|
|
if (uView != LVS_REPORT) return TRUE;
|
|
|
|
/* if we have a focus, must first erase the focus rect */
|
|
if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
|
|
|
|
/* Need to reset the item width when deleting a column */
|
|
infoPtr->nItemWidth -= rcCol.right - rcCol.left;
|
|
|
|
/* update scrollbar(s) */
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* scroll to cover the deleted column, and invalidate for redraw */
|
|
rcOld = infoPtr->rcList;
|
|
rcOld.left = rcCol.left;
|
|
ScrollWindowEx(infoPtr->hwndSelf, -(rcCol.right - rcCol.left), 0,
|
|
&rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
|
|
|
|
/* we can restore focus now */
|
|
if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes an item from the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
LONG lCtrlId = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
|
|
NMLISTVIEW nmlv;
|
|
BOOL bResult = FALSE;
|
|
HDPA hdpaSubItems;
|
|
LISTVIEW_ITEM *lpItem;
|
|
LISTVIEW_SUBITEM *lpSubItem;
|
|
INT i;
|
|
LVITEMW item;
|
|
|
|
TRACE("(nItem=%d)\n", nItem);
|
|
|
|
|
|
/* First, send LVN_DELETEITEM notification. */
|
|
memset(&nmlv, 0, sizeof (NMLISTVIEW));
|
|
nmlv.hdr.hwndFrom = infoPtr->hwndSelf;
|
|
nmlv.hdr.idFrom = lCtrlId;
|
|
nmlv.hdr.code = LVN_DELETEITEM;
|
|
nmlv.iItem = nItem;
|
|
SendMessageW((infoPtr->hwndSelf), WM_NOTIFY, (WPARAM)lCtrlId,
|
|
(LPARAM)&nmlv);
|
|
|
|
if (nItem == infoPtr->nFocusedItem)
|
|
{
|
|
infoPtr->nFocusedItem = -1;
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
}
|
|
|
|
/* remove it from the selection range */
|
|
item.state = LVIS_SELECTED;
|
|
item.stateMask = LVIS_SELECTED;
|
|
LISTVIEW_SetItemState(infoPtr,nItem,&item);
|
|
|
|
if (lStyle & LVS_OWNERDATA)
|
|
{
|
|
infoPtr->nItemCount--;
|
|
LISTVIEW_InvalidateList(infoPtr); /*FIXME: optimize */
|
|
return TRUE;
|
|
}
|
|
|
|
if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
|
|
{
|
|
/* initialize memory */
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
|
|
hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
|
|
if (hdpaSubItems != NULL)
|
|
{
|
|
infoPtr->nItemCount--;
|
|
for (i = 1; i < hdpaSubItems->nItemCount; i++)
|
|
{
|
|
lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
|
|
if (lpSubItem != NULL)
|
|
{
|
|
/* free item string */
|
|
if (is_textW(lpSubItem->hdr.pszText))
|
|
COMCTL32_Free(lpSubItem->hdr.pszText);
|
|
|
|
/* free item */
|
|
COMCTL32_Free(lpSubItem);
|
|
}
|
|
}
|
|
|
|
lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
|
|
if (lpItem != NULL)
|
|
{
|
|
/* free item string */
|
|
if (is_textW(lpItem->hdr.pszText))
|
|
COMCTL32_Free(lpItem->hdr.pszText);
|
|
|
|
/* free item */
|
|
COMCTL32_Free(lpItem);
|
|
}
|
|
|
|
bResult = DPA_Destroy(hdpaSubItems);
|
|
DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
|
|
DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
|
|
}
|
|
|
|
LISTVIEW_ShiftIndices(infoPtr,nItem,-1);
|
|
|
|
/* align items (set position of each item) */
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
if (lStyle & LVS_ALIGNLEFT)
|
|
LISTVIEW_AlignLeft(infoPtr);
|
|
else
|
|
LISTVIEW_AlignTop(infoPtr);
|
|
}
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Callback implementation for editlabel control
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] pszText : modified text
|
|
* [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
|
|
{
|
|
NMLVDISPINFOW dispInfo;
|
|
|
|
TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
|
|
|
|
infoPtr->bEditing = FALSE;
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
|
|
dispInfo.item.iItem = infoPtr->nEditLabelItem;
|
|
dispInfo.item.iSubItem = 0;
|
|
dispInfo.item.stateMask = ~0;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
|
|
dispInfo.item.pszText = pszText;
|
|
dispInfo.item.cchTextMax = textlenT(pszText, isW);
|
|
|
|
/* Do we need to update the Item Text */
|
|
if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
|
|
if (!pszText) return TRUE;
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
dispInfo.item.mask = LVIF_TEXT;
|
|
dispInfo.item.iItem = infoPtr->nEditLabelItem;
|
|
dispInfo.item.iSubItem = 0;
|
|
dispInfo.item.pszText = pszText;
|
|
dispInfo.item.cchTextMax = textlenT(pszText, isW);
|
|
return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Begin in place editing of specified list view item
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
* [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
|
|
NMLVDISPINFOW dispInfo;
|
|
RECT rect;
|
|
|
|
TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
|
|
|
|
if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
|
|
|
|
infoPtr->nEditLabelItem = nItem;
|
|
|
|
/* Is the EditBox still there, if so remove it */
|
|
if(infoPtr->hwndEdit != 0)
|
|
{
|
|
SetFocus(infoPtr->hwndSelf);
|
|
infoPtr->hwndEdit = 0;
|
|
}
|
|
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
|
|
rect.left = LVIR_LABEL;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
|
|
dispInfo.item.iItem = nItem;
|
|
dispInfo.item.iSubItem = 0;
|
|
dispInfo.item.stateMask = ~0;
|
|
dispInfo.item.pszText = szDispText;
|
|
dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
|
|
if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
|
|
|
|
infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
|
|
rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
|
|
if (!infoPtr->hwndEdit) return 0;
|
|
|
|
if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
|
|
{
|
|
SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
|
|
infoPtr->hwndEdit = 0;
|
|
return 0;
|
|
}
|
|
|
|
infoPtr->bEditing = TRUE;
|
|
ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
|
|
SetFocus(infoPtr->hwndEdit);
|
|
SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
|
|
return infoPtr->hwndEdit;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Ensures the specified item is visible, scrolling into view if necessary.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] bPartial : partially or entirely visible
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nScrollPosHeight = 0;
|
|
INT nScrollPosWidth = 0;
|
|
INT nHorzAdjust = 0;
|
|
INT nVertAdjust = 0;
|
|
INT nHorzDiff = 0;
|
|
INT nVertDiff = 0;
|
|
RECT rcItem;
|
|
|
|
/* FIXME: ALWAYS bPartial == FALSE, FOR NOW! */
|
|
|
|
rcItem.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
|
|
|
|
if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
|
|
{
|
|
/* scroll left/right, but in LVS_REPORT mode */
|
|
if (uView == LVS_LIST)
|
|
nScrollPosWidth = infoPtr->nItemWidth;
|
|
else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
nScrollPosWidth = 1;
|
|
|
|
if (rcItem.left < infoPtr->rcList.left)
|
|
{
|
|
nHorzAdjust = -1;
|
|
if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
|
|
}
|
|
else
|
|
{
|
|
nHorzAdjust = 1;
|
|
if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
|
|
}
|
|
}
|
|
|
|
if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
|
|
{
|
|
/* scroll up/down, but not in LVS_LIST mode */
|
|
if (uView == LVS_REPORT)
|
|
nScrollPosHeight = infoPtr->nItemHeight;
|
|
else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
|
|
nScrollPosHeight = 1;
|
|
|
|
if (rcItem.top < infoPtr->rcList.top)
|
|
{
|
|
nVertAdjust = -1;
|
|
if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
|
|
}
|
|
else
|
|
{
|
|
nVertAdjust = 1;
|
|
if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
|
|
}
|
|
}
|
|
|
|
if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
|
|
|
|
if (nScrollPosWidth)
|
|
{
|
|
INT diff = nHorzDiff / nScrollPosWidth;
|
|
if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
|
|
LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
|
|
}
|
|
|
|
if (nScrollPosHeight)
|
|
{
|
|
INT diff = nVertDiff / nScrollPosHeight;
|
|
if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
|
|
LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Searches for an item with specific characteristics.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nStart : base item index
|
|
* [I] lpFindInfo : item information to look for
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : index of item
|
|
* FAILURE : -1
|
|
*/
|
|
static LRESULT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
|
|
LPLVFINDINFOW lpFindInfo)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
BOOL bWrap = FALSE, bNearest = FALSE;
|
|
INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
|
|
ULONG xdist, ydist, dist, mindist = 0x7fffffff;
|
|
POINT Position, Destination;
|
|
LVITEMW lvItem;
|
|
|
|
if (!lpFindInfo || nItem < 0) return -1;
|
|
|
|
lvItem.mask = 0;
|
|
if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
|
|
{
|
|
lvItem.mask |= LVIF_TEXT;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
}
|
|
|
|
if (lpFindInfo->flags & LVFI_WRAP)
|
|
bWrap = TRUE;
|
|
|
|
if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
|
|
(uView == LVS_ICON || uView ==LVS_SMALLICON))
|
|
{
|
|
POINT Origin;
|
|
|
|
FIXME("LVFI_NEARESTXY is slow.\n");
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return -1;
|
|
Destination.x = lpFindInfo->pt.x - Origin.x;
|
|
Destination.y = lpFindInfo->pt.y - Origin.y;
|
|
switch(lpFindInfo->vkDirection)
|
|
{
|
|
case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
|
|
case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
|
|
case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
|
|
case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
|
|
case VK_HOME: Destination.x = Destination.y = 0; break;
|
|
case VK_END: Destination.x = infoPtr->rcView.right; Destination.y = infoPtr->rcView.bottom; break;
|
|
case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
|
|
case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
|
|
default: FIXME("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
|
|
}
|
|
bNearest = TRUE;
|
|
}
|
|
|
|
/* if LVFI_PARAM is specified, all other flags are ignored */
|
|
if (lpFindInfo->flags & LVFI_PARAM)
|
|
{
|
|
lvItem.mask |= LVIF_PARAM;
|
|
bNearest = FALSE;
|
|
lvItem.mask &= ~LVIF_TEXT;
|
|
}
|
|
|
|
again:
|
|
for (; nItem < nLast; nItem++)
|
|
{
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
|
|
|
|
if (lvItem.mask & LVIF_PARAM && lpFindInfo->lParam == lvItem.lParam)
|
|
return nItem;
|
|
|
|
if (lvItem.mask & LVIF_TEXT)
|
|
{
|
|
if (lpFindInfo->flags & LVFI_PARTIAL)
|
|
{
|
|
if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
|
|
}
|
|
else
|
|
{
|
|
if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
|
|
}
|
|
}
|
|
|
|
if (!bNearest) return nItem;
|
|
|
|
/* This is very inefficient. To do a good job here,
|
|
* we need a sorted array of (x,y) item positions */
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, nItem, &Position)) continue;
|
|
|
|
/* compute the distance^2 to the destination */
|
|
xdist = Destination.x - Position.x;
|
|
ydist = Destination.y - Position.y;
|
|
dist = xdist * xdist + ydist * ydist;
|
|
|
|
/* remember the distance, and item if it's closer */
|
|
if (dist < mindist)
|
|
{
|
|
mindist = dist;
|
|
nNearestItem = nItem;
|
|
}
|
|
}
|
|
|
|
if (bWrap)
|
|
{
|
|
nItem = 0;
|
|
nLast = min(nStart + 1, infoPtr->nItemCount);
|
|
bWrap = FALSE;
|
|
goto again;
|
|
}
|
|
|
|
return nNearestItem;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Searches for an item with specific characteristics.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nStart : base item index
|
|
* [I] lpFindInfo : item information to look for
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : index of item
|
|
* FAILURE : -1
|
|
*/
|
|
static LRESULT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
|
|
LPLVFINDINFOA lpFindInfo)
|
|
{
|
|
BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
|
|
LVFINDINFOW fiw;
|
|
LRESULT res;
|
|
|
|
memcpy(&fiw, lpFindInfo, sizeof(fiw));
|
|
if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
|
|
res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
|
|
if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
|
|
return res;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the background image of the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] LPLVMKBIMAGE : background image attributes
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
/* static LRESULT LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
|
|
/* { */
|
|
/* FIXME (listview, "empty stub!\n"); */
|
|
/* return FALSE; */
|
|
/* } */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves column attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : column index
|
|
* [IO] LPLVCOLUMNW : column information
|
|
* [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
|
|
* otherwise it is in fact a LPLVCOLUMNA
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVCOLUMNW lpColumn, BOOL isW)
|
|
{
|
|
HDITEMW hdi;
|
|
BOOL bResult = FALSE;
|
|
|
|
if (lpColumn != NULL)
|
|
{
|
|
|
|
/* initialize memory */
|
|
ZeroMemory(&hdi, sizeof(hdi));
|
|
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
hdi.mask |= HDI_FORMAT;
|
|
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
hdi.mask |= HDI_WIDTH;
|
|
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
{
|
|
hdi.mask |= HDI_TEXT;
|
|
hdi.cchTextMax = lpColumn->cchTextMax;
|
|
hdi.pszText = lpColumn->pszText;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
hdi.mask |= HDI_IMAGE;
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
hdi.mask |= HDI_ORDER;
|
|
|
|
if (isW)
|
|
bResult = Header_GetItemW(infoPtr->hwndHeader, nItem, &hdi);
|
|
else
|
|
bResult = Header_GetItemA(infoPtr->hwndHeader, nItem, &hdi);
|
|
|
|
if (bResult)
|
|
{
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
{
|
|
lpColumn->fmt = 0;
|
|
|
|
if (hdi.fmt & HDF_LEFT)
|
|
lpColumn->fmt |= LVCFMT_LEFT;
|
|
else if (hdi.fmt & HDF_RIGHT)
|
|
lpColumn->fmt |= LVCFMT_RIGHT;
|
|
else if (hdi.fmt & HDF_CENTER)
|
|
lpColumn->fmt |= LVCFMT_CENTER;
|
|
|
|
if (hdi.fmt & HDF_IMAGE)
|
|
lpColumn->fmt |= LVCFMT_COL_HAS_IMAGES;
|
|
|
|
if (hdi.fmt & HDF_BITMAP_ON_RIGHT)
|
|
lpColumn->fmt |= LVCFMT_BITMAP_ON_RIGHT;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
lpColumn->cx = hdi.cxy;
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
lpColumn->iImage = hdi.iImage;
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
lpColumn->iOrder = hdi.iOrder;
|
|
|
|
TRACE("(col=%d, lpColumn=%s, isW=%d)\n",
|
|
nItem, debuglvcolumn_t(lpColumn, isW), isW);
|
|
|
|
}
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
static LRESULT LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
|
|
{
|
|
INT i;
|
|
|
|
if (!lpiArray)
|
|
return FALSE;
|
|
|
|
/* FIXME: little hack */
|
|
for (i = 0; i < iCount; i++)
|
|
lpiArray[i] = i;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the column width.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] int : column index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : column width
|
|
* FAILURE : zero
|
|
*/
|
|
static LRESULT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
|
|
{
|
|
INT nColumnWidth = 0;
|
|
HDITEMW hdi;
|
|
|
|
TRACE("nColumn=%d\n", nColumn);
|
|
|
|
switch(LISTVIEW_GetType(infoPtr))
|
|
{
|
|
case LVS_LIST:
|
|
nColumnWidth = infoPtr->nItemWidth;
|
|
break;
|
|
case LVS_REPORT:
|
|
hdi.mask = HDI_WIDTH;
|
|
if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
|
|
nColumnWidth = hdi.cxy;
|
|
break;
|
|
default:
|
|
/* we don't have a 'column' in [SMALL]ICON mode */
|
|
}
|
|
|
|
TRACE("nColumnWidth=%d\n", nColumnWidth);
|
|
return nColumnWidth;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* In list or report display mode, retrieves the number of items that can fit
|
|
* vertically in the visible area. In icon or small icon display mode,
|
|
* retrieves the total number of visible items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of fully visible items.
|
|
*/
|
|
static LRESULT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nItemCount = 0;
|
|
|
|
if (uView == LVS_LIST)
|
|
{
|
|
if (infoPtr->rcList.right > infoPtr->nItemWidth)
|
|
{
|
|
nItemCount = LISTVIEW_GetCountPerRow(infoPtr) *
|
|
LISTVIEW_GetCountPerColumn(infoPtr);
|
|
}
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
nItemCount = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
}
|
|
else
|
|
{
|
|
nItemCount = infoPtr->nItemCount;
|
|
}
|
|
|
|
return nItemCount;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves an image list handle.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nImageList : image list identifier
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : image list handle
|
|
* FAILURE : NULL
|
|
*/
|
|
static LRESULT LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
|
|
{
|
|
HIMAGELIST himl = NULL;
|
|
|
|
switch (nImageList)
|
|
{
|
|
case LVSIL_NORMAL:
|
|
himl = infoPtr->himlNormal;
|
|
break;
|
|
case LVSIL_SMALL:
|
|
himl = infoPtr->himlSmall;
|
|
break;
|
|
case LVSIL_STATE:
|
|
himl = infoPtr->himlState;
|
|
break;
|
|
}
|
|
|
|
return (LRESULT)himl;
|
|
}
|
|
|
|
/* LISTVIEW_GetISearchString */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [IO] lpLVItem : item info
|
|
* [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
|
|
* if FALSE, the lpLVItem is a LPLVITEMA.
|
|
*
|
|
* NOTE:
|
|
* This is the internal 'GetItem' interface -- it tries to
|
|
* be smart, and avoids text copies, if possible, by modifing
|
|
* lpLVItem->pszText to point to the text string. Please note
|
|
* that this is not always possible (e.g. OWNERDATA), so on
|
|
* entry you *must* supply valid values for pszText, and cchTextMax.
|
|
* The only difference to the documented interface is that upon
|
|
* return, you should use *only* the lpLVItem->pszText, rather than
|
|
* the buffer pointer you provided on input. Most code already does
|
|
* that, so it's not a problem.
|
|
* For the two cases when the text must be copied (that is,
|
|
* for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
NMLVDISPINFOW dispInfo;
|
|
LISTVIEW_ITEM *lpItem;
|
|
ITEMHDR* pItemHdr;
|
|
HDPA hdpaSubItems;
|
|
|
|
/* In the following:
|
|
* lpLVItem describes the information requested by the user
|
|
* lpItem is what we have
|
|
* dispInfo is a structure we use to request the missing
|
|
* information from the application
|
|
*/
|
|
|
|
TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
|
|
|
|
if (!lpLVItem || (lpLVItem->iItem < 0) ||
|
|
(lpLVItem->iItem >= infoPtr->nItemCount))
|
|
return FALSE;
|
|
|
|
/* a quick optimization if all we're asked is the focus state
|
|
* these queries are worth optimising since they are common,
|
|
* and can be answered in constant time, without the heavy accesses */
|
|
if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIF_STATE) &&
|
|
!(infoPtr->uCallbackMask & LVIS_FOCUSED) )
|
|
{
|
|
lpLVItem->state = 0;
|
|
if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
lpLVItem->state |= LVIS_FOCUSED;
|
|
return TRUE;
|
|
}
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
|
|
/* if the app stores all the data, handle it separately */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
dispInfo.item.state = 0;
|
|
|
|
/* if we need to callback, do it now */
|
|
if ((lpLVItem->mask & ~LVIF_STATE) || infoPtr->uCallbackMask)
|
|
{
|
|
/* NOTE: copy only fields which we _know_ are initialized, some apps
|
|
* depend on the uninitialized fields being 0 */
|
|
dispInfo.item.mask = lpLVItem->mask;
|
|
dispInfo.item.iItem = lpLVItem->iItem;
|
|
dispInfo.item.iSubItem = lpLVItem->iSubItem;
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
{
|
|
dispInfo.item.pszText = lpLVItem->pszText;
|
|
dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
|
|
}
|
|
if (lpLVItem->mask & LVIF_STATE)
|
|
dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
|
|
notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
|
|
dispInfo.item.stateMask = lpLVItem->stateMask;
|
|
*lpLVItem = dispInfo.item;
|
|
TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
|
|
}
|
|
|
|
/* we store only a little state, so if we're not asked, we're done */
|
|
if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
|
|
|
|
/* if focus is handled by us, report it */
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_FOCUSED;
|
|
if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
lpLVItem->state |= LVIS_FOCUSED;
|
|
}
|
|
|
|
/* and do the same for selection, if we handle it */
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_SELECTED;
|
|
if (ranges_contain(infoPtr->hdpaSelectionRanges, lpLVItem->iItem))
|
|
lpLVItem->state |= LVIS_SELECTED;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* find the item and subitem structures before we proceed */
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
|
|
if (hdpaSubItems == NULL) return FALSE;
|
|
|
|
if ( !(lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0)) )
|
|
return FALSE;
|
|
|
|
if (lpLVItem->iSubItem)
|
|
{
|
|
LISTVIEW_SUBITEM *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
|
|
if(!lpSubItem) return FALSE;
|
|
pItemHdr = &lpSubItem->hdr;
|
|
}
|
|
else
|
|
pItemHdr = &lpItem->hdr;
|
|
|
|
/* Do we need to query the state from the app? */
|
|
if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
|
|
{
|
|
dispInfo.item.mask |= LVIF_STATE;
|
|
dispInfo.item.stateMask = infoPtr->uCallbackMask;
|
|
}
|
|
|
|
/* Do we need to enquire about the image? */
|
|
if ((lpLVItem->mask & LVIF_IMAGE) && (pItemHdr->iImage==I_IMAGECALLBACK))
|
|
dispInfo.item.mask |= LVIF_IMAGE;
|
|
|
|
/* Do we need to enquire about the text? */
|
|
if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
|
|
{
|
|
dispInfo.item.mask |= LVIF_TEXT;
|
|
dispInfo.item.pszText = lpLVItem->pszText;
|
|
dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
|
|
if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
|
|
*dispInfo.item.pszText = '\0';
|
|
}
|
|
|
|
/* If we don't have all the requested info, query the application */
|
|
if (dispInfo.item.mask != 0)
|
|
{
|
|
dispInfo.item.iItem = lpLVItem->iItem;
|
|
dispInfo.item.iSubItem = lpLVItem->iSubItem;
|
|
dispInfo.item.lParam = lpItem->lParam;
|
|
notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
|
|
TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
|
|
}
|
|
|
|
/* Now, handle the iImage field */
|
|
if (dispInfo.item.mask & LVIF_IMAGE)
|
|
{
|
|
lpLVItem->iImage = dispInfo.item.iImage;
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM) && (pItemHdr->iImage==I_IMAGECALLBACK))
|
|
pItemHdr->iImage = dispInfo.item.iImage;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_IMAGE)
|
|
lpLVItem->iImage = pItemHdr->iImage;
|
|
|
|
/* The pszText field */
|
|
if (dispInfo.item.mask & LVIF_TEXT)
|
|
{
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
|
|
textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
|
|
|
|
lpLVItem->pszText = dispInfo.item.pszText;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_TEXT)
|
|
{
|
|
if (isW) lpLVItem->pszText = pItemHdr->pszText;
|
|
else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
|
|
}
|
|
|
|
/* if this is a subitem, we're done*/
|
|
if (lpLVItem->iSubItem) return TRUE;
|
|
|
|
/* Next is the lParam field */
|
|
if (dispInfo.item.mask & LVIF_PARAM)
|
|
{
|
|
lpLVItem->lParam = dispInfo.item.lParam;
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM))
|
|
lpItem->lParam = dispInfo.item.lParam;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_PARAM)
|
|
lpLVItem->lParam = lpItem->lParam;
|
|
|
|
/* ... the state field (this one is different due to uCallbackmask) */
|
|
if (lpLVItem->mask & LVIF_STATE)
|
|
{
|
|
lpLVItem->state = lpItem->state;
|
|
if (dispInfo.item.mask & LVIF_STATE)
|
|
{
|
|
lpLVItem->state &= ~dispInfo.item.stateMask;
|
|
lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
|
|
}
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_FOCUSED;
|
|
if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
lpLVItem->state |= LVIS_FOCUSED;
|
|
}
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_SELECTED;
|
|
if (ranges_contain(infoPtr->hdpaSelectionRanges, lpLVItem->iItem))
|
|
lpLVItem->state |= LVIS_SELECTED;
|
|
}
|
|
}
|
|
|
|
/* and last, but not least, the indent field */
|
|
if (lpLVItem->mask & LVIF_INDENT)
|
|
lpLVItem->iIndent = lpItem->iIndent;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [IO] lpLVItem : item info
|
|
* [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
|
|
* if FALSE, the lpLVItem is a LPLVITEMA.
|
|
*
|
|
* NOTE:
|
|
* This is the external 'GetItem' interface -- it properly copies
|
|
* the text in the provided buffer.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
LPWSTR pszText;
|
|
BOOL bResult;
|
|
|
|
if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
pszText = lpLVItem->pszText;
|
|
bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
|
|
if (bResult && lpLVItem->pszText != pszText)
|
|
textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
|
|
lpLVItem->pszText = pszText;
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the position (upper-left) of the listview control item.
|
|
* Note that for LVS_ICON style, the upper-left is that of the icon
|
|
* and not the bounding box.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [O] lpptPosition : coordinate information
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
POINT Origin;
|
|
|
|
TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
|
|
|
|
if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
if (!LISTVIEW_GetItemListOrigin(infoPtr, nItem, lpptPosition)) return FALSE;
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
|
|
|
|
if (uView == LVS_ICON)
|
|
{
|
|
lpptPosition->x += (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
|
|
lpptPosition->y += ICON_TOP_PADDING;
|
|
}
|
|
lpptPosition->x += Origin.x;
|
|
lpptPosition->y += Origin.y;
|
|
|
|
TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the bounding rectangle for a listview control item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [IO] lprc : bounding rectangle coordinates
|
|
* lprc->left specifies the portion of the item for which the bounding
|
|
* rectangle will be retrieved.
|
|
*
|
|
* LVIR_BOUNDS Returns the bounding rectangle of the entire item,
|
|
* including the icon and label.
|
|
* *
|
|
* * For LVS_ICON
|
|
* * Experiment shows that native control returns:
|
|
* * width = min (48, length of text line)
|
|
* * .left = position.x - (width - iconsize.cx)/2
|
|
* * .right = .left + width
|
|
* * height = #lines of text * ntmHeight + icon height + 8
|
|
* * .top = position.y - 2
|
|
* * .bottom = .top + height
|
|
* * separation between items .y = itemSpacing.cy - height
|
|
* * .x = itemSpacing.cx - width
|
|
* LVIR_ICON Returns the bounding rectangle of the icon or small icon.
|
|
* *
|
|
* * For LVS_ICON
|
|
* * Experiment shows that native control returns:
|
|
* * width = iconSize.cx + 16
|
|
* * .left = position.x - (width - iconsize.cx)/2
|
|
* * .right = .left + width
|
|
* * height = iconSize.cy + 4
|
|
* * .top = position.y - 2
|
|
* * .bottom = .top + height
|
|
* * separation between items .y = itemSpacing.cy - height
|
|
* * .x = itemSpacing.cx - width
|
|
* LVIR_LABEL Returns the bounding rectangle of the item text.
|
|
* *
|
|
* * For LVS_ICON
|
|
* * Experiment shows that native control returns:
|
|
* * width = text length
|
|
* * .left = position.x - width/2
|
|
* * .right = .left + width
|
|
* * height = ntmH * linecount + 2
|
|
* * .top = position.y + iconSize.cy + 6
|
|
* * .bottom = .top + height
|
|
* * separation between items .y = itemSpacing.cy - height
|
|
* * .x = itemSpacing.cx - width
|
|
* LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
|
|
* rectangles, but excludes columns in report view.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*
|
|
* NOTES
|
|
* Note that the bounding rectangle of the label in the LVS_ICON view depends
|
|
* upon whether the window has the focus currently and on whether the item
|
|
* is the one with the focus. Ensure that the control's record of which
|
|
* item has the focus agrees with the items' records.
|
|
*/
|
|
static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
|
|
{
|
|
RECT label_rect;
|
|
|
|
TRACE("(hwnd=%x, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
|
|
|
|
if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
switch(lprc->left)
|
|
{
|
|
case LVIR_ICON:
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, nItem, NULL, NULL, lprc, NULL)) return FALSE;
|
|
break;
|
|
|
|
case LVIR_LABEL:
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, nItem, NULL, NULL, NULL, lprc)) return FALSE;
|
|
break;
|
|
|
|
case LVIR_BOUNDS:
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, nItem, NULL, lprc, NULL, NULL)) return FALSE;
|
|
break;
|
|
|
|
case LVIR_SELECTBOUNDS:
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, nItem, NULL, lprc, NULL, &label_rect)) return FALSE;
|
|
if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
|
|
!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) )
|
|
lprc->right = label_rect.right;
|
|
break;
|
|
|
|
default:
|
|
WARN("Unknown value: %d\n", lprc->left);
|
|
return FALSE;
|
|
}
|
|
|
|
TRACE(" rect=%s\n", debugrect(lprc));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the spacing between listview control items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [IO] lprc : rectangle to receive the output
|
|
* on input, lprc->top = nSubItem
|
|
* lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
|
|
*
|
|
* NOTE: this call is succeeds only for REPORT style listviews.
|
|
* Because we can calculate things much faster in report mode,
|
|
* we're gonna do the calculations inline here, instead of
|
|
* calling functions that do heavy lifting.
|
|
*
|
|
* RETURN:
|
|
* TRUE: success
|
|
* FALSE: failure
|
|
*/
|
|
static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
|
|
{
|
|
POINT ptPosition;
|
|
INT nSubItem, flags;
|
|
|
|
if (!lprc || LISTVIEW_GetType(infoPtr) != LVS_REPORT) return FALSE;
|
|
|
|
nSubItem = lprc->top;
|
|
flags = lprc->left;
|
|
|
|
TRACE("(nItem=%d, nSubItem=%d)\n", nItem, nSubItem);
|
|
|
|
if (!Header_GetItemRect(infoPtr->hwndHeader, nSubItem, lprc)) return FALSE;
|
|
if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &ptPosition)) return FALSE;
|
|
lprc->top = ptPosition.y;
|
|
lprc->bottom = lprc->top + infoPtr->nItemHeight;
|
|
|
|
switch(flags)
|
|
{
|
|
case LVIR_ICON:
|
|
FIXME("Unimplemented LVIR_ICON\n");
|
|
return FALSE;
|
|
case LVIR_LABEL:
|
|
case LVIR_BOUNDS:
|
|
/* nothing to do here, we're done */
|
|
break;
|
|
default:
|
|
ERR("Unknown bounds=%d\n", lprc->left);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the width of a label.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : string width (in pixels)
|
|
* FAILURE : zero
|
|
*/
|
|
static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
LVITEMW lvItem;
|
|
|
|
TRACE("(nItem=%d)\n", nItem);
|
|
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
|
|
|
|
return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the spacing between listview control items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] BOOL : flag for small or large icon
|
|
*
|
|
* RETURN:
|
|
* Horizontal + vertical spacing
|
|
*/
|
|
static LRESULT LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
|
|
{
|
|
LONG lResult;
|
|
|
|
if (!bSmall)
|
|
{
|
|
lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
|
|
}
|
|
else
|
|
{
|
|
if (LISTVIEW_GetType(infoPtr) == LVS_ICON)
|
|
lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
|
|
else
|
|
lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
|
|
}
|
|
return lResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the state of a listview control item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] uMask : state mask
|
|
*
|
|
* RETURN:
|
|
* State specified by the mask.
|
|
*/
|
|
static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
|
|
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.mask = LVIF_STATE;
|
|
lvItem.stateMask = uMask;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
|
|
|
|
return lvItem.state & uMask;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the text of a listview control item or subitem.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nItem : item index
|
|
* [IO] lpLVItem : item information
|
|
* [I] isW : TRUE if lpLVItem is Unicode
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : string length
|
|
* FAILURE : 0
|
|
*/
|
|
static LRESULT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
|
|
|
|
lpLVItem->mask = LVIF_TEXT;
|
|
lpLVItem->iItem = nItem;
|
|
if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
|
|
|
|
return textlenT(lpLVItem->pszText, isW);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Searches for an item based on properties + relationships.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] uFlags : relationship flag
|
|
*
|
|
* FIXME:
|
|
* This function is very, very inefficient! Needs work.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : item index
|
|
* FAILURE : -1
|
|
*/
|
|
static LRESULT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
UINT uMask = 0;
|
|
LVFINDINFOW lvFindInfo;
|
|
INT nCountPerColumn;
|
|
INT i;
|
|
|
|
TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
|
|
if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
|
|
|
|
ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
|
|
|
|
if (uFlags & LVNI_CUT)
|
|
uMask |= LVIS_CUT;
|
|
|
|
if (uFlags & LVNI_DROPHILITED)
|
|
uMask |= LVIS_DROPHILITED;
|
|
|
|
if (uFlags & LVNI_FOCUSED)
|
|
uMask |= LVIS_FOCUSED;
|
|
|
|
if (uFlags & LVNI_SELECTED)
|
|
uMask |= LVIS_SELECTED;
|
|
|
|
/* if we're asked for the focused item, that's only one,
|
|
* so it's worth optimizing */
|
|
if (uFlags & LVNI_FOCUSED)
|
|
{
|
|
if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
|
|
return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
|
|
}
|
|
|
|
if (uFlags & LVNI_ABOVE)
|
|
{
|
|
if ((uView == LVS_LIST) || (uView == LVS_REPORT))
|
|
{
|
|
while (nItem >= 0)
|
|
{
|
|
nItem--;
|
|
if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_UP;
|
|
ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
|
|
while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else if (uFlags & LVNI_BELOW)
|
|
{
|
|
if ((uView == LVS_LIST) || (uView == LVS_REPORT))
|
|
{
|
|
while (nItem < infoPtr->nItemCount)
|
|
{
|
|
nItem++;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_DOWN;
|
|
ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
|
|
while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else if (uFlags & LVNI_TOLEFT)
|
|
{
|
|
if (uView == LVS_LIST)
|
|
{
|
|
nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
while (nItem - nCountPerColumn >= 0)
|
|
{
|
|
nItem -= nCountPerColumn;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_LEFT;
|
|
ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
|
|
while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else if (uFlags & LVNI_TORIGHT)
|
|
{
|
|
if (uView == LVS_LIST)
|
|
{
|
|
nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
while (nItem + nCountPerColumn < infoPtr->nItemCount)
|
|
{
|
|
nItem += nCountPerColumn;
|
|
if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_RIGHT;
|
|
ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
|
|
while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nItem++;
|
|
|
|
/* search by index */
|
|
for (i = nItem; i < infoPtr->nItemCount; i++)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* LISTVIEW_GetNumberOfWorkAreas */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the origin coordinates when in icon or small icon display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lpptOrigin : coordinate information
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
|
|
{
|
|
DWORD lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
INT nHorzPos = 0, nVertPos = 0;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
if (!lpptOrigin) return FALSE;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_POS;
|
|
|
|
if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
|
|
nHorzPos = scrollInfo.nPos;
|
|
if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
nVertPos = scrollInfo.nPos;
|
|
|
|
TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
|
|
|
|
lpptOrigin->x = infoPtr->rcList.left;
|
|
lpptOrigin->y = infoPtr->rcList.top;
|
|
if (uView == LVS_LIST)
|
|
nHorzPos *= infoPtr->nItemWidth;
|
|
else if (uView == LVS_REPORT)
|
|
nVertPos *= infoPtr->nItemHeight;
|
|
|
|
lpptOrigin->x -= nHorzPos;
|
|
lpptOrigin->y -= nVertPos;
|
|
|
|
TRACE(" origin=%s\n", debugpoint(lpptOrigin));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the width of a string.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] lpszText : text string to process
|
|
* [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : string width (in pixels)
|
|
* FAILURE : zero
|
|
*/
|
|
static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
|
|
{
|
|
SIZE stringSize;
|
|
|
|
stringSize.cx = 0;
|
|
if (is_textT(lpszText, isW))
|
|
{
|
|
HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT hOldFont = SelectObject(hdc, hFont);
|
|
|
|
if (isW)
|
|
GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
|
|
else
|
|
GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
return stringSize.cx;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Determines which listview item is located at the specified position.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [IO] lpht : hit test information
|
|
* [I] subitem : fill out iSubItem.
|
|
*
|
|
* NOTE:
|
|
* (mm 20001022): We must not allow iSubItem to be touched, for
|
|
* an app might pass only a structure with space up to iItem!
|
|
* (MS Office 97 does that for instance in the file open dialog)
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : item index
|
|
* FAILURE : -1
|
|
*/
|
|
static LRESULT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
RECT rcBounds, rcIcon, rcLabel;
|
|
|
|
TRACE("(x=%ld, y=%ld)\n", lpht->pt.x, lpht->pt.y);
|
|
|
|
lpht->flags = 0;
|
|
lpht->iItem = -1;
|
|
if (subitem) lpht->iSubItem = 0;
|
|
|
|
if (infoPtr->rcList.left > lpht->pt.x)
|
|
lpht->flags |= LVHT_TOLEFT;
|
|
else if (infoPtr->rcList.right < lpht->pt.x)
|
|
lpht->flags |= LVHT_TORIGHT;
|
|
|
|
if (infoPtr->rcList.top > lpht->pt.y)
|
|
lpht->flags |= LVHT_ABOVE;
|
|
else if (infoPtr->rcList.bottom < lpht->pt.y)
|
|
lpht->flags |= LVHT_BELOW;
|
|
|
|
if (lpht->flags) return -1;
|
|
|
|
lpht->flags |= LVHT_NOWHERE;
|
|
|
|
/* first deal with the large items */
|
|
if (uView == LVS_ICON && (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
|
|
PtInRect (&infoPtr->rcFocus, lpht->pt))
|
|
{
|
|
lpht->iItem = infoPtr->nFocusedItem;
|
|
}
|
|
else
|
|
{
|
|
if (uView == LVS_ICON || uView == LVS_SMALLICON)
|
|
{
|
|
RECT rcSearch;
|
|
ITERATOR i;
|
|
|
|
rcSearch.left = lpht->pt.x - infoPtr->nItemWidth;
|
|
rcSearch.top = lpht->pt.y - infoPtr->nItemHeight;
|
|
rcSearch.right = lpht->pt.x + 1;
|
|
rcSearch.bottom = lpht->pt.y + 1;
|
|
|
|
iterator_frameditems(&i, infoPtr, &rcSearch);
|
|
while(iterator_next(&i))
|
|
{
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, i.nItem, &rcBounds, 0, 0, 0)) continue;
|
|
if (PtInRect(&rcBounds, lpht->pt)) break;
|
|
}
|
|
lpht->iItem = i.nItem;
|
|
iterator_destroy(&i);
|
|
}
|
|
else
|
|
{
|
|
POINT Origin, Position;
|
|
INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
|
|
if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return -1;
|
|
Position.x = lpht->pt.x - Origin.x;
|
|
Position.y = lpht->pt.y - Origin.y;
|
|
|
|
if (Position.y < nPerCol * infoPtr->nItemHeight)
|
|
{
|
|
lpht->iItem = (Position.x / infoPtr->nItemWidth) * nPerCol + (Position.y / infoPtr->nItemHeight);
|
|
if (lpht->iItem < 0 || lpht->iItem >= infoPtr->nItemCount) lpht->iItem = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lpht->iItem == -1) return -1;
|
|
|
|
if (!LISTVIEW_GetItemMeasures(infoPtr, lpht->iItem, 0, &rcBounds, &rcIcon, &rcLabel)) return -1;
|
|
|
|
if (!PtInRect(&rcBounds, lpht->pt)) return -1;
|
|
|
|
if (PtInRect(&rcIcon, lpht->pt))
|
|
lpht->flags |= LVHT_ONITEMICON;
|
|
else if (PtInRect(&rcLabel, lpht->pt))
|
|
lpht->flags |= LVHT_ONITEMLABEL;
|
|
else if (infoPtr->himlState)
|
|
{
|
|
/* FIXME: move this to GetItemMeasures */
|
|
LVITEMW lvItem;
|
|
|
|
lvItem.mask = LVIF_STATE;
|
|
lvItem.stateMask = LVIS_STATEIMAGEMASK;
|
|
lvItem.iItem = lpht->iItem;
|
|
lvItem.iSubItem = 0;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
|
|
{
|
|
UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
|
|
RECT rcState;
|
|
|
|
if (uView == LVS_ICON)
|
|
{
|
|
rcState.left = rcIcon.left - infoPtr->iconStateSize.cx + 10;
|
|
rcState.top = rcIcon.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
|
|
}
|
|
else
|
|
{
|
|
rcState.left = rcIcon.left - infoPtr->iconStateSize.cx - IMAGE_PADDING;
|
|
rcState.top = rcIcon.top;
|
|
}
|
|
rcState.right = rcState.left + infoPtr->iconStateSize.cx;
|
|
rcState.bottom = rcState.top + infoPtr->iconStateSize.cy;
|
|
|
|
if (uStateImage > 0 && PtInRect(&rcState, lpht->pt))
|
|
lpht->flags |= LVHT_ONITEMSTATEICON;
|
|
}
|
|
}
|
|
if (lpht->flags & LVHT_ONITEM)
|
|
lpht->flags &= ~LVHT_NOWHERE;
|
|
|
|
if (uView == LVS_REPORT && lpht->iItem != -1 && subitem)
|
|
{
|
|
INT j, nColumnCount = Header_GetItemCount(infoPtr->hwndHeader);
|
|
rcBounds.right = rcBounds.left;
|
|
for (j = 0; j < nColumnCount; j++)
|
|
{
|
|
rcBounds.left = rcBounds.right;
|
|
rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
|
|
if (PtInRect(&rcBounds, lpht->pt))
|
|
{
|
|
lpht->iSubItem = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return lpht->iItem;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Inserts a new column.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : column index
|
|
* [I] LPLVCOLUMNW : column information
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : new column index
|
|
* FAILURE : -1
|
|
*/
|
|
static LRESULT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
|
|
LPLVCOLUMNW lpColumn, BOOL isW)
|
|
{
|
|
RECT rcOld, rcCol;
|
|
INT nNewColumn;
|
|
HDITEMW hdi;
|
|
|
|
TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
|
|
|
|
if (!lpColumn) return -1;
|
|
|
|
hdi.mask = hdi.fmt = 0;
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
{
|
|
/* format member is valid */
|
|
hdi.mask |= HDI_FORMAT;
|
|
|
|
/* set text alignment (leftmost column must be left-aligned) */
|
|
if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
|
|
hdi.fmt |= HDF_LEFT;
|
|
else if (lpColumn->fmt & LVCFMT_RIGHT)
|
|
hdi.fmt |= HDF_RIGHT;
|
|
else if (lpColumn->fmt & LVCFMT_CENTER)
|
|
hdi.fmt |= HDF_CENTER;
|
|
|
|
if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
|
|
hdi.fmt |= HDF_BITMAP_ON_RIGHT;
|
|
|
|
if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
|
|
{
|
|
hdi.fmt |= HDF_IMAGE;
|
|
hdi.iImage = I_IMAGECALLBACK;
|
|
}
|
|
|
|
if (lpColumn->fmt & LVCFMT_IMAGE)
|
|
; /* FIXME: enable images for *(sub)items* this column */
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
{
|
|
hdi.mask |= HDI_WIDTH;
|
|
if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
|
|
{
|
|
/* make it fill the remainder of the controls width */
|
|
HDITEMW hdit;
|
|
RECT rcHeader;
|
|
INT item_index;
|
|
|
|
/* get the width of every item except the current one */
|
|
hdit.mask = HDI_WIDTH;
|
|
hdi.cxy = 0;
|
|
|
|
for(item_index = 0; item_index < (nColumn - 1); item_index++)
|
|
if (Header_GetItemW(infoPtr->hwndHeader, item_index, (LPARAM)(&hdit)))
|
|
hdi.cxy += hdit.cxy;
|
|
|
|
/* retrieve the layout of the header */
|
|
GetClientRect(infoPtr->hwndSelf, &rcHeader);
|
|
TRACE("start cxy=%d rcHeader=%s\n", hdi.cxy, debugrect(&rcHeader));
|
|
|
|
hdi.cxy = (rcHeader.right - rcHeader.left) - hdi.cxy;
|
|
}
|
|
else
|
|
hdi.cxy = lpColumn->cx;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
{
|
|
hdi.mask |= HDI_TEXT | HDI_FORMAT;
|
|
hdi.fmt |= HDF_STRING;
|
|
hdi.pszText = lpColumn->pszText;
|
|
hdi.cchTextMax = textlenT(lpColumn->pszText, isW);
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
{
|
|
hdi.mask |= HDI_IMAGE;
|
|
hdi.iImage = lpColumn->iImage;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
{
|
|
hdi.mask |= HDI_ORDER;
|
|
hdi.iOrder = lpColumn->iOrder;
|
|
}
|
|
|
|
/* insert item in header control */
|
|
nNewColumn = SendMessageW(infoPtr->hwndHeader,
|
|
isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
|
|
(WPARAM)nColumn, (LPARAM)&hdi);
|
|
if (nNewColumn == -1) return -1;
|
|
if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &rcCol)) return -1;
|
|
|
|
/* now we have to actually adjust the data */
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
|
|
{
|
|
LISTVIEW_SUBITEM *lpSubItem, *lpMainItem, **lpNewItems = 0;
|
|
HDPA hdpaSubItems;
|
|
INT nItem, i;
|
|
|
|
/* preallocate memory, so we can fail gracefully */
|
|
if (nNewColumn == 0)
|
|
{
|
|
lpNewItems = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM *) * infoPtr->nItemCount);
|
|
if (!lpNewItems) return -1;
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
if (!(lpNewItems[i] = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM)))) break;
|
|
if (i != infoPtr->nItemCount)
|
|
{
|
|
for(; i >=0; i--) COMCTL32_Free(lpNewItems[i]);
|
|
COMCTL32_Free(lpNewItems);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
|
|
{
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
|
|
if (!hdpaSubItems) continue;
|
|
for (i = 1; i < hdpaSubItems->nItemCount; i++)
|
|
{
|
|
lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
|
|
if (!lpSubItem) break;
|
|
if (lpSubItem->iSubItem >= nNewColumn)
|
|
lpSubItem->iSubItem++;
|
|
}
|
|
|
|
/* if we found our subitem, zapp it */
|
|
if (nNewColumn == 0)
|
|
{
|
|
lpMainItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, 0);
|
|
lpSubItem = lpNewItems[nItem];
|
|
lpSubItem->hdr = lpMainItem->hdr;
|
|
lpSubItem->iSubItem = 1;
|
|
ZeroMemory(&lpMainItem->hdr, sizeof(lpMainItem->hdr));
|
|
lpMainItem->iSubItem = 0;
|
|
DPA_InsertPtr(hdpaSubItems, 1, lpSubItem);
|
|
}
|
|
}
|
|
|
|
COMCTL32_Free(lpNewItems);
|
|
}
|
|
|
|
/* we don't have to worry abiut display issues in non-report mode */
|
|
if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return nNewColumn;
|
|
|
|
/* if we have a focus, must first erase the focus rect */
|
|
if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
|
|
|
|
/* Need to reset the item width when inserting a new column */
|
|
infoPtr->nItemWidth += rcCol.right - rcCol.left;
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* scroll to cover the deleted column, and invalidate for redraw */
|
|
rcOld = infoPtr->rcList;
|
|
rcOld.left = rcCol.left;
|
|
ScrollWindowEx(infoPtr->hwndSelf, rcCol.right - rcCol.left, 0,
|
|
&rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
|
|
|
|
/* we can restore focus now */
|
|
if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
|
|
|
|
return nNewColumn;
|
|
}
|
|
|
|
/* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
|
|
in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
|
|
This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
|
|
and not during the processing of a LVM_SORTITEMS message. Applications should provide
|
|
their own sort proc. when sending LVM_SORTITEMS.
|
|
*/
|
|
/* Platform SDK:
|
|
(remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
|
|
if:
|
|
LVS_SORTXXX must be specified,
|
|
LVS_OWNERDRAW is not set,
|
|
<item>.pszText is not LPSTR_TEXTCALLBACK.
|
|
|
|
(LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
|
|
are sorted based on item text..."
|
|
*/
|
|
static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
|
|
{
|
|
LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
|
|
LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
|
|
INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
|
|
|
|
/* if we're sorting descending, negate the return value */
|
|
return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
|
|
}
|
|
|
|
/***
|
|
* nESCRIPTION:
|
|
* Inserts a new item in the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : item information
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : new item index
|
|
* FAILURE : -1
|
|
*/
|
|
static LRESULT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
INT nItem = -1;
|
|
HDPA hdpaSubItems;
|
|
NMLISTVIEW nmlv;
|
|
LISTVIEW_ITEM *lpItem;
|
|
BOOL is_sorted;
|
|
|
|
TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
|
|
|
|
if (lStyle & LVS_OWNERDATA)
|
|
{
|
|
nItem = infoPtr->nItemCount;
|
|
infoPtr->nItemCount++;
|
|
return nItem;
|
|
}
|
|
|
|
/* make sure it's an item, and not a subitem; cannot insert a subitem */
|
|
if (!lpLVItem || lpLVItem->iSubItem) return -1;
|
|
|
|
if (!is_assignable_item(lpLVItem, lStyle)) return -1;
|
|
|
|
if ( !(lpItem = (LISTVIEW_ITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_ITEM))) )
|
|
return -1;
|
|
|
|
/* insert item in listview control data structure */
|
|
if ( (hdpaSubItems = DPA_Create(8)) )
|
|
nItem = DPA_InsertPtr(hdpaSubItems, 0, lpItem);
|
|
if (nItem == -1) goto fail;
|
|
|
|
is_sorted = (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
|
|
!(lStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
|
|
|
|
nItem = DPA_InsertPtr( infoPtr->hdpaItems,
|
|
is_sorted ? infoPtr->nItemCount + 1 : lpLVItem->iItem,
|
|
hdpaSubItems );
|
|
if (nItem == -1) goto fail;
|
|
infoPtr->nItemCount++;
|
|
|
|
if (!LISTVIEW_SetItemT(infoPtr, lpLVItem, isW))
|
|
goto undo;
|
|
|
|
/* if we're sorted, sort the list, and update the index */
|
|
if (is_sorted)
|
|
{
|
|
DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
|
|
nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
|
|
if (nItem == -1)
|
|
{
|
|
ERR("We can't find the item we just inserted, possible memory corruption.");
|
|
/* we can't remove it from the list if we can't find it, so just fail */
|
|
/* we don't deallocate memory here, as it will probably cause more problems */
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* make room for the position, if we are in the right mode */
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
|
|
goto undo;
|
|
if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
|
|
{
|
|
DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
|
|
goto undo;
|
|
}
|
|
}
|
|
|
|
/* Add the subitem list to the items array. Do this last in case we go to
|
|
* fail during the above.
|
|
*/
|
|
LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
|
|
|
|
lpItem->valid = TRUE;
|
|
|
|
/* send LVN_INSERTITEM notification */
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = nItem;
|
|
nmlv.lParam = lpItem->lParam;
|
|
notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
|
|
|
|
/* align items (set position of each item) */
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
if (lStyle & LVS_ALIGNLEFT) LISTVIEW_AlignLeft(infoPtr);
|
|
else LISTVIEW_AlignTop(infoPtr);
|
|
}
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
|
|
TRACE(" <- %d\n", nItem);
|
|
return nItem;
|
|
|
|
undo:
|
|
DPA_DeletePtr(infoPtr->hdpaItems, nItem);
|
|
infoPtr->nItemCount--;
|
|
fail:
|
|
DPA_DeletePtr(hdpaSubItems, 0);
|
|
DPA_Destroy (hdpaSubItems);
|
|
COMCTL32_Free (lpItem);
|
|
return -1;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Redraws a range of items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : first item
|
|
* [I] INT : last item
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
|
|
{
|
|
INT i;
|
|
|
|
if (nLast < nFirst || min(nFirst, nLast) < 0 ||
|
|
max(nFirst, nLast) >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
for (i = nFirst; i <= nLast; i++)
|
|
LISTVIEW_InvalidateItem(infoPtr, i);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Scroll the content of a listview.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : horizontal scroll amount in pixels
|
|
* [I] INT : vertical scroll amount in pixels
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*
|
|
* COMMENTS:
|
|
* If the control is in report mode (LVS_REPORT) the control can
|
|
* be scrolled only in line increments. "dy" will be rounded to the
|
|
* nearest number of pixels that are a whole line. Ex: if line height
|
|
* is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
|
|
* is passed the the scroll will be 0. (per MSDN 7/2002)
|
|
*
|
|
* For: (per experimentaion with native control and CSpy ListView)
|
|
* LVS_ICON dy=1 = 1 pixel (vertical only)
|
|
* dx ignored
|
|
* LVS_SMALLICON dy=1 = 1 pixel (vertical only)
|
|
* dx ignored
|
|
* LVS_LIST dx=1 = 1 column (horizontal only)
|
|
* but will only scroll 1 column per message
|
|
* no matter what the value.
|
|
* dy must be 0 or FALSE returned.
|
|
* LVS_REPORT dx=1 = 1 pixel
|
|
* dy= see above
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
|
|
{
|
|
switch(LISTVIEW_GetType(infoPtr)) {
|
|
case LVS_REPORT:
|
|
dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
|
|
dy /= infoPtr->nItemHeight;
|
|
break;
|
|
case LVS_LIST:
|
|
if (dy != 0) return FALSE;
|
|
break;
|
|
default: /* icon */
|
|
dx = 0;
|
|
break;
|
|
}
|
|
|
|
if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
|
|
if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the background color.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] COLORREF : background color
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
|
|
{
|
|
TRACE("(clrBk=%lx)\n", clrBk);
|
|
|
|
if(infoPtr->clrBk != clrBk) {
|
|
if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
|
|
infoPtr->clrBk = clrBk;
|
|
if (clrBk == CLR_NONE)
|
|
infoPtr->hBkBrush = GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
|
|
else
|
|
infoPtr->hBkBrush = CreateSolidBrush(clrBk);
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* LISTVIEW_SetBkImage */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the attributes of a header item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : column index
|
|
* [I] LPLVCOLUMNW : column attributes
|
|
* [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW,
|
|
* otherwise it is in fact a LPLVCOLUMNA
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
|
|
LPLVCOLUMNW lpColumn, BOOL isW)
|
|
{
|
|
BOOL bResult = FALSE;
|
|
HDITEMW hdi, hdiget;
|
|
|
|
if ((lpColumn != NULL) && (nColumn >= 0) &&
|
|
(nColumn < Header_GetItemCount(infoPtr->hwndHeader)))
|
|
{
|
|
/* initialize memory */
|
|
ZeroMemory(&hdi, sizeof(hdi));
|
|
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
{
|
|
/* format member is valid */
|
|
hdi.mask |= HDI_FORMAT;
|
|
|
|
/* get current format first */
|
|
hdiget.mask = HDI_FORMAT;
|
|
if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
|
|
/* preserve HDF_STRING if present */
|
|
hdi.fmt = hdiget.fmt & HDF_STRING;
|
|
|
|
/* set text alignment (leftmost column must be left-aligned) */
|
|
if (nColumn == 0)
|
|
{
|
|
hdi.fmt |= HDF_LEFT;
|
|
}
|
|
else
|
|
{
|
|
if (lpColumn->fmt & LVCFMT_LEFT)
|
|
hdi.fmt |= HDF_LEFT;
|
|
else if (lpColumn->fmt & LVCFMT_RIGHT)
|
|
hdi.fmt |= HDF_RIGHT;
|
|
else if (lpColumn->fmt & LVCFMT_CENTER)
|
|
hdi.fmt |= HDF_CENTER;
|
|
}
|
|
|
|
if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
|
|
hdi.fmt |= HDF_BITMAP_ON_RIGHT;
|
|
|
|
if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
|
|
hdi.fmt |= HDF_IMAGE;
|
|
|
|
if (lpColumn->fmt & LVCFMT_IMAGE)
|
|
{
|
|
hdi.fmt |= HDF_IMAGE;
|
|
hdi.iImage = I_IMAGECALLBACK;
|
|
}
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
{
|
|
hdi.mask |= HDI_WIDTH;
|
|
hdi.cxy = lpColumn->cx;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
{
|
|
hdi.mask |= HDI_TEXT | HDI_FORMAT;
|
|
hdi.pszText = lpColumn->pszText;
|
|
hdi.cchTextMax = textlenT(lpColumn->pszText, isW);
|
|
hdi.fmt |= HDF_STRING;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
{
|
|
hdi.mask |= HDI_IMAGE;
|
|
hdi.iImage = lpColumn->iImage;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
{
|
|
hdi.mask |= HDI_ORDER;
|
|
hdi.iOrder = lpColumn->iOrder;
|
|
}
|
|
|
|
/* set header item attributes */
|
|
if (isW)
|
|
bResult = Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
|
|
else
|
|
bResult = Header_SetItemA(infoPtr->hwndHeader, nColumn, &hdi);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the column order array
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : number of elements in column order array
|
|
* [I] INT : pointer to column order array
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
|
|
{
|
|
FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
|
|
|
|
if (!lpiArray)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the width of a column
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : column index
|
|
* [I] INT : column width
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT iCol, INT cx)
|
|
{
|
|
HDITEMW hdi;
|
|
LRESULT lret;
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
HDC hdc;
|
|
HFONT header_font;
|
|
HFONT old_font;
|
|
SIZE size;
|
|
WCHAR text_buffer[DISP_TEXT_SIZE];
|
|
INT header_item_count;
|
|
INT item_index;
|
|
INT nLabelWidth;
|
|
RECT rcHeader;
|
|
LVITEMW lvItem;
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
|
|
|
|
if (!infoPtr->hwndHeader) /* make sure we have a header */
|
|
return (FALSE);
|
|
|
|
/* set column width only if in report or list mode */
|
|
if ((uView != LVS_REPORT) && (uView != LVS_LIST))
|
|
return (FALSE);
|
|
|
|
TRACE("(iCol=%d, cx=%d\n", iCol, cx);
|
|
|
|
/* take care of invalid cx values */
|
|
if((uView == LVS_REPORT) && (cx < -2))
|
|
cx = LVSCW_AUTOSIZE;
|
|
else if (uView == LVS_LIST && (cx < 1))
|
|
return FALSE;
|
|
|
|
/* resize all columns if in LVS_LIST mode */
|
|
if(uView == LVS_LIST) {
|
|
infoPtr->nItemWidth = cx;
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
return TRUE;
|
|
}
|
|
|
|
/* autosize based on listview items width */
|
|
if(cx == LVSCW_AUTOSIZE)
|
|
{
|
|
/* set the width of the column to the width of the widest item */
|
|
if (iCol == 0 || uView == LVS_LIST)
|
|
{
|
|
cx = 0;
|
|
for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
|
|
{
|
|
nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, item_index);
|
|
cx = (nLabelWidth>cx)?nLabelWidth:cx;
|
|
}
|
|
if (infoPtr->himlSmall)
|
|
cx += infoPtr->iconSize.cx + IMAGE_PADDING;
|
|
}
|
|
else
|
|
{
|
|
lvItem.iSubItem = iCol;
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
cx = 0;
|
|
for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
|
|
{
|
|
lvItem.iItem = item_index;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
|
|
nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
|
|
cx = (nLabelWidth>cx)?nLabelWidth:cx;
|
|
}
|
|
}
|
|
cx += TRAILING_PADDING;
|
|
} /* autosize based on listview header width */
|
|
else if(cx == LVSCW_AUTOSIZE_USEHEADER)
|
|
{
|
|
header_item_count = Header_GetItemCount(infoPtr->hwndHeader);
|
|
|
|
/* if iCol is the last column make it fill the remainder of the controls width */
|
|
if(iCol == (header_item_count - 1)) {
|
|
/* get the width of every item except the current one */
|
|
hdi.mask = HDI_WIDTH;
|
|
cx = 0;
|
|
|
|
for(item_index = 0; item_index < (header_item_count - 1); item_index++) {
|
|
Header_GetItemW(infoPtr->hwndHeader, item_index, (LPARAM)(&hdi));
|
|
cx+=hdi.cxy;
|
|
}
|
|
|
|
/* retrieve the layout of the header */
|
|
GetWindowRect(infoPtr->hwndHeader, &rcHeader);
|
|
|
|
cx = (rcHeader.right - rcHeader.left) - cx;
|
|
}
|
|
else
|
|
{
|
|
/* Despite what the MS docs say, if this is not the last
|
|
column, then MS resizes the column to the width of the
|
|
largest text string in the column, including headers
|
|
and items. This is different from LVSCW_AUTOSIZE in that
|
|
LVSCW_AUTOSIZE ignores the header string length.
|
|
*/
|
|
|
|
/* retrieve header font */
|
|
header_font = SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0L, 0L);
|
|
|
|
/* retrieve header text */
|
|
hdi.mask = HDI_TEXT;
|
|
hdi.cchTextMax = sizeof(text_buffer)/sizeof(text_buffer[0]);
|
|
hdi.pszText = text_buffer;
|
|
|
|
Header_GetItemW(infoPtr->hwndHeader, iCol, (LPARAM)(&hdi));
|
|
|
|
/* determine the width of the text in the header */
|
|
hdc = GetDC(infoPtr->hwndSelf);
|
|
old_font = SelectObject(hdc, header_font); /* select the font into hdc */
|
|
|
|
GetTextExtentPoint32W(hdc, text_buffer, lstrlenW(text_buffer), &size);
|
|
|
|
SelectObject(hdc, old_font); /* restore the old font */
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
|
|
lvItem.iSubItem = iCol;
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
cx = size.cx;
|
|
for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
|
|
{
|
|
lvItem.iItem = item_index;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
|
|
nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
|
|
nLabelWidth += TRAILING_PADDING;
|
|
/* While it is possible for subitems to have icons, even MS messes
|
|
up the positioning, so I suspect no applications actually use
|
|
them. */
|
|
if (item_index == 0 && infoPtr->himlSmall)
|
|
nLabelWidth += infoPtr->iconSize.cx + IMAGE_PADDING;
|
|
cx = (nLabelWidth>cx)?nLabelWidth:cx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* call header to update the column change */
|
|
hdi.mask = HDI_WIDTH;
|
|
|
|
hdi.cxy = cx;
|
|
lret = Header_SetItemW(infoPtr->hwndHeader, (WPARAM)iCol, (LPARAM)&hdi);
|
|
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
|
|
return lret;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the extended listview style.
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] DWORD : mask
|
|
* [I] DWORD : style
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : previous style
|
|
* FAILURE : 0
|
|
*/
|
|
static LRESULT LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
|
|
{
|
|
DWORD dwOldStyle = infoPtr->dwLvExStyle;
|
|
|
|
/* set new style */
|
|
if (dwMask)
|
|
infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
|
|
else
|
|
infoPtr->dwLvExStyle = dwStyle;
|
|
|
|
return dwOldStyle;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the new hot cursor used during hot tracking and hover selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I} hCurosr : the new hot cursor handle
|
|
*
|
|
* RETURN:
|
|
* Returns the previous hot cursor
|
|
*/
|
|
static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
|
|
{
|
|
HCURSOR oldCursor = infoPtr->hHotCursor;
|
|
infoPtr->hHotCursor = hCursor;
|
|
return oldCursor;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the hot item index.
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : previous hot item index
|
|
* FAILURE : -1 (no hot item)
|
|
*/
|
|
static LRESULT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
|
|
{
|
|
INT iOldIndex = infoPtr->nHotItem;
|
|
infoPtr->nHotItem = iIndex;
|
|
return iOldIndex;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the amount of time the cursor must hover over an item before it is selected.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] DWORD : dwHoverTime, if -1 the hover time is set to the default
|
|
*
|
|
* RETURN:
|
|
* Returns the previous hover time
|
|
*/
|
|
static LRESULT LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
|
|
{
|
|
DWORD oldHoverTime = infoPtr->dwHoverTime;
|
|
infoPtr->dwHoverTime = dwHoverTime;
|
|
return oldHoverTime;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets spacing for icons of LVS_ICON style.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] DWORD : MAKELONG(cx, cy)
|
|
*
|
|
* RETURN:
|
|
* MAKELONG(oldcx, oldcy)
|
|
*/
|
|
static LRESULT LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, DWORD spacing)
|
|
{
|
|
INT cy = HIWORD(spacing), cx = LOWORD(spacing);
|
|
DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
|
|
TRACE("requested=(%d,%d)\n", cx, cy);
|
|
|
|
/* this is supported only for LVS_ICON style */
|
|
if (uView != LVS_ICON) return oldspacing;
|
|
|
|
/* set to defaults, if instructed to */
|
|
if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
|
|
if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
|
|
|
|
/* if 0 then compute width
|
|
* FIXME: Should scan each item and determine max width of
|
|
* icon or label, then make that the width */
|
|
if (cx == 0)
|
|
cx = infoPtr->iconSpacing.cx;
|
|
|
|
/* if 0 then compute height */
|
|
if (cy == 0)
|
|
cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
|
|
ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
|
|
|
|
|
|
infoPtr->iconSpacing.cx = cx;
|
|
infoPtr->iconSpacing.cy = cy;
|
|
|
|
TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
|
|
LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
|
|
infoPtr->iconSize.cx, infoPtr->iconSize.cy,
|
|
infoPtr->ntmHeight);
|
|
|
|
/* these depend on the iconSpacing */
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
|
|
return oldspacing;
|
|
}
|
|
|
|
inline void update_icon_size(HIMAGELIST himl, SIZE *size)
|
|
{
|
|
INT cx, cy;
|
|
|
|
if (himl && ImageList_GetIconSize(himl, &cx, &cy))
|
|
{
|
|
size->cx = cx;
|
|
size->cy = cy;
|
|
}
|
|
else
|
|
size->cx = size->cy = 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets image lists.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : image list type
|
|
* [I] HIMAGELIST : image list handle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : old image list
|
|
* FAILURE : NULL
|
|
*/
|
|
static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT oldHeight = infoPtr->nItemHeight;
|
|
HIMAGELIST himlOld = 0;
|
|
|
|
switch (nType)
|
|
{
|
|
case LVSIL_NORMAL:
|
|
himlOld = infoPtr->himlNormal;
|
|
infoPtr->himlNormal = himl;
|
|
if (uView == LVS_ICON) update_icon_size(himl, &infoPtr->iconSize);
|
|
LISTVIEW_SetIconSpacing(infoPtr, 0);
|
|
break;
|
|
|
|
case LVSIL_SMALL:
|
|
himlOld = infoPtr->himlSmall;
|
|
infoPtr->himlSmall = himl;
|
|
if (uView != LVS_ICON) update_icon_size(himl, &infoPtr->iconSize);
|
|
break;
|
|
|
|
case LVSIL_STATE:
|
|
himlOld = infoPtr->himlState;
|
|
infoPtr->himlState = himl;
|
|
update_icon_size(himl, &infoPtr->iconStateSize);
|
|
ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
|
|
break;
|
|
|
|
default:
|
|
ERR("Unknown icon type=%d\n", nType);
|
|
return NULL;
|
|
}
|
|
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
if (infoPtr->nItemHeight != oldHeight)
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
return himlOld;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Preallocates memory (does *not* set the actual count of items !)
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item count (projected number of items to allocate)
|
|
* [I] DWORD : update flags
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
|
|
{
|
|
TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
int precount,topvisible;
|
|
|
|
TRACE("LVS_OWNERDATA is set!\n");
|
|
if (dwFlags & (LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL))
|
|
FIXME("flags %s %s not implemented\n",
|
|
(dwFlags & LVSICF_NOINVALIDATEALL) ? "LVSICF_NOINVALIDATEALL"
|
|
: "",
|
|
(dwFlags & LVSICF_NOSCROLL) ? "LVSICF_NOSCROLL" : "");
|
|
|
|
LISTVIEW_RemoveAllSelections(infoPtr);
|
|
|
|
precount = infoPtr->nItemCount;
|
|
topvisible = LISTVIEW_GetTopIndex(infoPtr) +
|
|
LISTVIEW_GetCountPerColumn(infoPtr) + 1;
|
|
|
|
infoPtr->nItemCount = nItems;
|
|
infoPtr->nItemWidth = max(LISTVIEW_CalculateMaxWidth(infoPtr),
|
|
DEFAULT_COLUMN_WIDTH);
|
|
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
if (min(precount,infoPtr->nItemCount) < topvisible)
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
}
|
|
else
|
|
{
|
|
/* According to MSDN for non-LVS_OWNERDATA this is just
|
|
* a performance issue. The control allocates its internal
|
|
* data structures for the number of items specified. It
|
|
* cuts down on the number of memory allocations. Therefore
|
|
* we will just issue a WARN here
|
|
*/
|
|
WARN("for non-ownerdata performance option not implemented.\n");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the position of an item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] pt : coordinate
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
|
|
{
|
|
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
|
|
POINT old;
|
|
|
|
TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
|
|
|
|
if (nItem < 0 || nItem >= infoPtr->nItemCount ||
|
|
!(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
|
|
|
|
/* This point value seems to be an undocumented feature.
|
|
* The best guess is that it means either at the origin,
|
|
* or at true beginning of the list. I will assume the origin. */
|
|
if ((pt.x == -1) && (pt.y == -1))
|
|
LISTVIEW_GetOrigin(infoPtr, &pt);
|
|
else if (uView == LVS_ICON)
|
|
{
|
|
pt.x -= (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
|
|
pt.y -= ICON_TOP_PADDING;
|
|
}
|
|
|
|
/* save the old position */
|
|
old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
|
|
old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
|
|
|
|
/* Is the position changing? */
|
|
if (pt.x == old.x && pt.y == old.y) return TRUE;
|
|
|
|
/* FIXME: shouldn't we invalidate, as the item moved? */
|
|
|
|
/* Allocating a POINTER for every item is too resource intensive,
|
|
* so we'll keep the (x,y) in different arrays */
|
|
if (DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)pt.x) &&
|
|
DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)pt.y) )
|
|
return TRUE;
|
|
|
|
ERR("We should never fail here (nItem=%d, pt=%s), please report.\n",
|
|
nItem, debugpoint(&pt));
|
|
return FALSE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the state of one or many items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I]INT : item index
|
|
* [I] LPLVITEM : item or subitem info
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
LVITEMW lvItem;
|
|
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.mask = LVIF_STATE;
|
|
lvItem.state = lpLVItem->state;
|
|
lvItem.stateMask = lpLVItem->stateMask;
|
|
TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
|
|
|
|
if (nItem == -1)
|
|
{
|
|
/* apply to all items */
|
|
for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
|
|
if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
|
|
}
|
|
else
|
|
bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the text of an item or subitem.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nItem : item index
|
|
* [I] lpLVItem : item or subitem info
|
|
* [I] isW : TRUE if input is Unicode
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = lpLVItem->iSubItem;
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.pszText = lpLVItem->pszText;
|
|
lvItem.cchTextMax = lpLVItem->cchTextMax;
|
|
|
|
TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
|
|
|
|
return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Set item index that marks the start of a multiple selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : index
|
|
*
|
|
* RETURN:
|
|
* Index number or -1 if there is no selection mark.
|
|
*/
|
|
static LRESULT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
|
|
{
|
|
INT nOldIndex = infoPtr->nSelectionMark;
|
|
|
|
TRACE("(nIndex=%d)\n", nIndex);
|
|
|
|
infoPtr->nSelectionMark = nIndex;
|
|
|
|
return nOldIndex;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the text background color.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] COLORREF : text background color
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
|
|
{
|
|
TRACE("(clrTextBk=%lx)\n", clrTextBk);
|
|
|
|
if (infoPtr->clrTextBk != clrTextBk)
|
|
{
|
|
infoPtr->clrTextBk = clrTextBk;
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the text foreground color.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] COLORREF : text color
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
|
|
{
|
|
TRACE("(clrText=%lx)\n", clrText);
|
|
|
|
if (infoPtr->clrText != clrText)
|
|
{
|
|
infoPtr->clrText = clrText;
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* LISTVIEW_SetToolTips */
|
|
/* LISTVIEW_SetUnicodeFormat */
|
|
/* LISTVIEW_SetWorkAreas */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Callback internally used by LISTVIEW_SortItems()
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] LPVOID : first LISTVIEW_ITEM to compare
|
|
* [I] LPVOID : second LISTVIEW_ITEM to compare
|
|
* [I] LPARAM : HWND of control
|
|
*
|
|
* RETURN:
|
|
* if first comes before second : negative
|
|
* if first comes after second : positive
|
|
* if first and second are equivalent : zero
|
|
*/
|
|
static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
|
|
LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
|
|
LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
|
|
|
|
/* Forward the call to the client defined callback */
|
|
return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sorts the listview items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] WPARAM : application-defined value
|
|
* [I] LPARAM : pointer to comparision callback
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
|
|
{
|
|
UINT lStyle = infoPtr->dwStyle;
|
|
HDPA hdpaSubItems;
|
|
LISTVIEW_ITEM *lpItem;
|
|
LPVOID selectionMarkItem;
|
|
int i;
|
|
|
|
TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
|
|
|
|
if (lStyle & LVS_OWNERDATA) return FALSE;
|
|
|
|
if (!infoPtr->hdpaItems) return FALSE;
|
|
|
|
/* if there are 0 or 1 items, there is no need to sort */
|
|
if (infoPtr->nItemCount < 2) return TRUE;
|
|
|
|
if (infoPtr->nFocusedItem >= 0)
|
|
{
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
|
|
lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
|
|
if (lpItem) lpItem->state |= LVIS_FOCUSED;
|
|
}
|
|
|
|
infoPtr->pfnCompare = pfnCompare;
|
|
infoPtr->lParamSort = lParamSort;
|
|
DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
|
|
|
|
/* Adjust selections and indices so that they are the way they should
|
|
* be after the sort (otherwise, the list items move around, but
|
|
* whatever is at the item's previous original position will be
|
|
* selected instead)
|
|
* FIXME: can't this be made more efficient?
|
|
*/
|
|
selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
|
|
for (i=0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
|
|
lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
|
|
|
|
if (lpItem->state & LVIS_SELECTED)
|
|
LISTVIEW_AddSelectionRange(infoPtr, i, i);
|
|
else
|
|
LISTVIEW_RemoveSelectionRange(infoPtr, i, i);
|
|
if (lpItem->state & LVIS_FOCUSED)
|
|
{
|
|
infoPtr->nFocusedItem = i;
|
|
lpItem->state &= ~LVIS_FOCUSED;
|
|
}
|
|
}
|
|
if (selectionMarkItem != NULL)
|
|
infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
|
|
/* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
|
|
|
|
/* align the items */
|
|
LISTVIEW_AlignTop(infoPtr);
|
|
|
|
/* refresh the display */
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: display should not change for [SMALL]ICON view */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Updates an items or rearranges the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : item index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
|
|
TRACE("(nItem=%d)\n", nItem);
|
|
|
|
if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
/* rearrange with default alignment style */
|
|
if ((lStyle & LVS_AUTOARRANGE) && ((uView == LVS_ICON) ||(uView == LVS_SMALLICON)))
|
|
LISTVIEW_Arrange(infoPtr, 0);
|
|
else
|
|
LISTVIEW_InvalidateItem(infoPtr, nItem);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Creates the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] lpcs : the create parameters
|
|
*
|
|
* RETURN:
|
|
* Success: 0
|
|
* Failure: -1
|
|
*/
|
|
static LRESULT LISTVIEW_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
|
|
{
|
|
LISTVIEW_INFO *infoPtr;
|
|
UINT uView = lpcs->style & LVS_TYPEMASK;
|
|
LOGFONTW logFont;
|
|
|
|
TRACE("(lpcs=%p)\n", lpcs);
|
|
|
|
/* initialize info pointer */
|
|
infoPtr = (LISTVIEW_INFO *)COMCTL32_Alloc(sizeof(LISTVIEW_INFO));
|
|
if (!infoPtr) return -1;
|
|
|
|
SetWindowLongW(hwnd, 0, (LONG)infoPtr);
|
|
|
|
infoPtr->hwndSelf = hwnd;
|
|
infoPtr->dwStyle = lpcs->style;
|
|
/* determine the type of structures to use */
|
|
infoPtr->notifyFormat = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFYFORMAT,
|
|
(WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
|
|
|
|
/* initialize color information */
|
|
infoPtr->clrBk = CLR_NONE;
|
|
infoPtr->clrText = comctl32_color.clrWindowText;
|
|
infoPtr->clrTextBk = CLR_DEFAULT;
|
|
LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
|
|
|
|
/* set default values */
|
|
infoPtr->nFocusedItem = -1;
|
|
infoPtr->nSelectionMark = -1;
|
|
infoPtr->nHotItem = -1;
|
|
infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
|
|
infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
|
|
infoPtr->nEditLabelItem = -1;
|
|
|
|
/* get default font (icon title) */
|
|
SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
|
|
infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
|
|
infoPtr->hFont = infoPtr->hDefaultFont;
|
|
LISTVIEW_SaveTextMetrics(infoPtr);
|
|
|
|
/* create header */
|
|
infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, (LPCWSTR)NULL,
|
|
WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
|
|
0, 0, 0, 0, hwnd, (HMENU)0,
|
|
lpcs->hInstance, NULL);
|
|
|
|
/* set header unicode format */
|
|
SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT,(WPARAM)TRUE,(LPARAM)NULL);
|
|
|
|
/* set header font */
|
|
SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont,
|
|
(LPARAM)TRUE);
|
|
|
|
if (uView == LVS_ICON)
|
|
{
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYICON);
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
if (!(LVS_NOCOLUMNHEADER & lpcs->style))
|
|
{
|
|
ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
|
|
}
|
|
else
|
|
{
|
|
/* set HDS_HIDDEN flag to hide the header bar */
|
|
SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
|
|
GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
|
|
}
|
|
|
|
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
|
|
}
|
|
else
|
|
{
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
|
|
}
|
|
|
|
infoPtr->iconStateSize.cx = GetSystemMetrics(SM_CXSMICON);
|
|
infoPtr->iconStateSize.cy = GetSystemMetrics(SM_CYSMICON);
|
|
|
|
/* display unsupported listview window styles */
|
|
LISTVIEW_UnsupportedStyles(lpcs->style);
|
|
|
|
/* allocate memory for the data structure */
|
|
infoPtr->hdpaItems = DPA_Create(10);
|
|
infoPtr->hdpaPosX = DPA_Create(10);
|
|
infoPtr->hdpaPosY = DPA_Create(10);
|
|
|
|
/* allocate memory for the selection ranges */
|
|
infoPtr->hdpaSelectionRanges = DPA_Create(10);
|
|
|
|
/* initialize size of items */
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
|
|
/* initialize the hover time to -1(indicating the default system hover time) */
|
|
infoPtr->dwHoverTime = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Erases the background of the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
RECT rc;
|
|
|
|
TRACE("(hdc=%x)\n", hdc);
|
|
|
|
if (!GetClipBox(hdc, &rc)) return FALSE;
|
|
|
|
return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper function for LISTVIEW_[HV]Scroll *only*.
|
|
* Performs vertical/horizontal scrolling by a give amount.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] dx : amount of horizontal scroll
|
|
* [I] dy : amount of vertical scroll
|
|
*/
|
|
static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
|
|
{
|
|
/* now we can scroll the list */
|
|
ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
|
|
&infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
|
|
/* if we have focus, adjust rect */
|
|
OffsetRect(&infoPtr->rcFocus, dx, dy);
|
|
UpdateWindow(infoPtr->hwndSelf);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Performs vertical scrolling.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nScrollCode : scroll code
|
|
* [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
|
|
* [I] hScrollWnd : scrollbar control window handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*
|
|
* NOTES:
|
|
* SB_LINEUP/SB_LINEDOWN:
|
|
* for LVS_ICON, LVS_SMALLICON is 37 by experiment
|
|
* for LVS_REPORT is 1 line
|
|
* for LVS_LIST cannot occur
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
|
|
INT nScrollDiff, HWND hScrollWnd)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nOldScrollPos, nNewScrollPos;
|
|
SCROLLINFO scrollInfo;
|
|
BOOL is_an_icon;
|
|
|
|
TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
|
|
|
|
SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
|
|
|
|
is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
|
|
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
|
|
|
|
nOldScrollPos = scrollInfo.nPos;
|
|
switch (nScrollCode)
|
|
{
|
|
case SB_INTERNAL:
|
|
break;
|
|
|
|
case SB_LINEUP:
|
|
nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
|
|
break;
|
|
|
|
case SB_PAGEUP:
|
|
nScrollDiff = -scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
nScrollDiff = scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_THUMBPOSITION:
|
|
case SB_THUMBTRACK:
|
|
nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
|
|
break;
|
|
|
|
default:
|
|
nScrollDiff = 0;
|
|
}
|
|
|
|
/* quit right away if pos isn't changing */
|
|
if (nScrollDiff == 0) return 0;
|
|
|
|
/* calculate new position, and handle overflows */
|
|
nNewScrollPos = scrollInfo.nPos + nScrollDiff;
|
|
if (nScrollDiff > 0) {
|
|
if (nNewScrollPos < nOldScrollPos ||
|
|
nNewScrollPos > scrollInfo.nMax)
|
|
nNewScrollPos = scrollInfo.nMax;
|
|
} else {
|
|
if (nNewScrollPos > nOldScrollPos ||
|
|
nNewScrollPos < scrollInfo.nMin)
|
|
nNewScrollPos = scrollInfo.nMin;
|
|
}
|
|
|
|
/* set the new position, and reread in case it changed */
|
|
scrollInfo.fMask = SIF_POS;
|
|
scrollInfo.nPos = nNewScrollPos;
|
|
nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
|
|
|
|
/* carry on only if it really changed */
|
|
if (nNewScrollPos == nOldScrollPos) return 0;
|
|
|
|
/* now adjust to client coordinates */
|
|
nScrollDiff = nOldScrollPos - nNewScrollPos;
|
|
if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
|
|
|
|
/* and scroll the window */
|
|
scroll_list(infoPtr, 0, nScrollDiff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Performs horizontal scrolling.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nScrollCode : scroll code
|
|
* [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
|
|
* [I] hScrollWnd : scrollbar control window handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*
|
|
* NOTES:
|
|
* SB_LINELEFT/SB_LINERIGHT:
|
|
* for LVS_ICON, LVS_SMALLICON 1 pixel
|
|
* for LVS_REPORT is 1 pixel
|
|
* for LVS_LIST is 1 column --> which is a 1 because the
|
|
* scroll is based on columns not pixels
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
|
|
INT nScrollDiff, HWND hScrollWnd)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nOldScrollPos, nNewScrollPos;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
|
|
|
|
SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
|
|
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
|
|
|
|
nOldScrollPos = scrollInfo.nPos;
|
|
|
|
switch (nScrollCode)
|
|
{
|
|
case SB_INTERNAL:
|
|
break;
|
|
|
|
case SB_LINELEFT:
|
|
nScrollDiff = -1;
|
|
break;
|
|
|
|
case SB_LINERIGHT:
|
|
nScrollDiff = 1;
|
|
break;
|
|
|
|
case SB_PAGELEFT:
|
|
nScrollDiff = -scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_PAGERIGHT:
|
|
nScrollDiff = scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_THUMBPOSITION:
|
|
case SB_THUMBTRACK:
|
|
nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
|
|
break;
|
|
|
|
default:
|
|
nScrollDiff = 0;
|
|
}
|
|
|
|
/* quit right away if pos isn't changing */
|
|
if (nScrollDiff == 0) return 0;
|
|
|
|
/* calculate new position, and handle overflows */
|
|
nNewScrollPos = scrollInfo.nPos + nScrollDiff;
|
|
if (nScrollDiff > 0) {
|
|
if (nNewScrollPos < nOldScrollPos ||
|
|
nNewScrollPos > scrollInfo.nMax)
|
|
nNewScrollPos = scrollInfo.nMax;
|
|
} else {
|
|
if (nNewScrollPos > nOldScrollPos ||
|
|
nNewScrollPos < scrollInfo.nMin)
|
|
nNewScrollPos = scrollInfo.nMin;
|
|
}
|
|
|
|
/* set the new position, and reread in case it changed */
|
|
scrollInfo.fMask = SIF_POS;
|
|
scrollInfo.nPos = nNewScrollPos;
|
|
nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
|
|
|
|
/* carry on only if it really changed */
|
|
if (nNewScrollPos == nOldScrollPos) return 0;
|
|
|
|
if(uView == LVS_REPORT)
|
|
LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
|
|
|
|
/* now adjust to client coordinates */
|
|
nScrollDiff = nOldScrollPos - nNewScrollPos;
|
|
if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
|
|
|
|
/* and scroll the window */
|
|
scroll_list(infoPtr, nScrollDiff, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT gcWheelDelta = 0;
|
|
UINT pulScrollLines = 3;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
TRACE("(wheelDelta=%d)\n", wheelDelta);
|
|
|
|
SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
|
|
gcWheelDelta -= wheelDelta;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_POS;
|
|
|
|
switch(uView)
|
|
{
|
|
case LVS_ICON:
|
|
case LVS_SMALLICON:
|
|
/*
|
|
* listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
|
|
* should be fixed in the future.
|
|
*/
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION,
|
|
scrollInfo.nPos + (gcWheelDelta < 0) ?
|
|
LISTVIEW_SCROLL_ICON_LINE_SIZE :
|
|
-LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
|
|
break;
|
|
|
|
case LVS_REPORT:
|
|
if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
|
|
{
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
{
|
|
int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
|
|
cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
|
|
LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION, scrollInfo.nPos + cLineScroll, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LVS_LIST:
|
|
LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* ???
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : virtual key
|
|
* [I] LONG : key data
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
|
|
{
|
|
UINT uView = LISTVIEW_GetType(infoPtr);
|
|
INT nItem = -1;
|
|
NMLVKEYDOWN nmKeyDown;
|
|
|
|
TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
|
|
|
|
/* send LVN_KEYDOWN notification */
|
|
nmKeyDown.wVKey = nVirtualKey;
|
|
nmKeyDown.flags = 0;
|
|
notify(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
|
|
|
|
switch (nVirtualKey)
|
|
{
|
|
case VK_RETURN:
|
|
if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
|
|
{
|
|
notify_return(infoPtr);
|
|
notify_itemactivate(infoPtr);
|
|
}
|
|
break;
|
|
|
|
case VK_HOME:
|
|
if (infoPtr->nItemCount > 0)
|
|
nItem = 0;
|
|
break;
|
|
|
|
case VK_END:
|
|
if (infoPtr->nItemCount > 0)
|
|
nItem = infoPtr->nItemCount - 1;
|
|
break;
|
|
|
|
case VK_LEFT:
|
|
nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
|
|
break;
|
|
|
|
case VK_UP:
|
|
nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
|
|
break;
|
|
|
|
case VK_RIGHT:
|
|
nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
|
|
break;
|
|
|
|
case VK_DOWN:
|
|
nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
|
|
break;
|
|
|
|
case VK_PRIOR:
|
|
if (uView == LVS_REPORT)
|
|
nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
|
|
else
|
|
nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
|
|
* LISTVIEW_GetCountPerRow(infoPtr);
|
|
if(nItem < 0) nItem = 0;
|
|
break;
|
|
|
|
case VK_NEXT:
|
|
if (uView == LVS_REPORT)
|
|
nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
|
|
else
|
|
nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
|
|
* LISTVIEW_GetCountPerRow(infoPtr);
|
|
if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
|
|
break;
|
|
}
|
|
|
|
if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
|
|
LISTVIEW_KeySelection(infoPtr, nItem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Kills the focus.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
TRACE("()\n");
|
|
|
|
/* if we did not have the focus, there's nothing to do */
|
|
if (!infoPtr->bFocus) return 0;
|
|
|
|
/* send NM_KILLFOCUS notification */
|
|
notify_killfocus(infoPtr);
|
|
|
|
/* if we have a focus rectagle, get rid of it */
|
|
LISTVIEW_ShowFocusRect(infoPtr, FALSE);
|
|
|
|
/* set window focus flag */
|
|
infoPtr->bFocus = FALSE;
|
|
|
|
/* invalidate the selected items before reseting focus flag */
|
|
LISTVIEW_InvalidateSelectedItems(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes double click messages (left mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] pts : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
|
|
{
|
|
LVHITTESTINFO htInfo;
|
|
NMLISTVIEW nmlv;
|
|
|
|
TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
|
|
|
|
htInfo.pt.x = pts.x;
|
|
htInfo.pt.y = pts.y;
|
|
|
|
/* send NM_DBLCLK notification */
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
LISTVIEW_HitTest(infoPtr, &htInfo, TRUE);
|
|
nmlv.iItem = htInfo.iItem;
|
|
nmlv.iSubItem = htInfo.iSubItem;
|
|
nmlv.ptAction = htInfo.pt;
|
|
notify_listview(infoPtr, NM_DBLCLK, &nmlv);
|
|
|
|
/* To send the LVN_ITEMACTIVATE, it must be on an Item */
|
|
if(nmlv.iItem != -1)
|
|
notify_itemactivate(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes mouse down messages (left mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] pts : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
static BOOL bGroupSelect = TRUE;
|
|
POINT pt = { pts.x, pts.y };
|
|
INT nItem;
|
|
|
|
TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
|
|
|
|
/* FIXME: NM_CLICK */
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
notify_releasedcapture(infoPtr);
|
|
|
|
if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
|
|
|
|
/* set left button down flag */
|
|
infoPtr->bLButtonDown = TRUE;
|
|
|
|
nItem = LISTVIEW_GetItemAtPt(infoPtr, pt);
|
|
TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
|
|
if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
|
|
{
|
|
if (lStyle & LVS_SINGLESEL)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
|
|
&& infoPtr->nEditLabelItem == -1)
|
|
infoPtr->nEditLabelItem = nItem;
|
|
else
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
|
|
{
|
|
if (bGroupSelect)
|
|
LISTVIEW_AddGroupSelection(infoPtr, nItem);
|
|
else
|
|
{
|
|
LVITEMW item;
|
|
|
|
item.state = LVIS_SELECTED | LVIS_FOCUSED;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
|
|
LISTVIEW_SetItemState(infoPtr,nItem,&item);
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
}
|
|
else if (wKey & MK_CONTROL)
|
|
{
|
|
LVITEMW item;
|
|
|
|
bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
|
|
|
|
item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &item);
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
else if (wKey & MK_SHIFT)
|
|
{
|
|
LISTVIEW_SetGroupSelection(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
BOOL was_selected = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
|
|
|
|
/* set selection (clears other pre-existing selections) */
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
|
|
if (was_selected && infoPtr->nEditLabelItem == -1)
|
|
infoPtr->nEditLabelItem = nItem;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* remove all selections */
|
|
LISTVIEW_RemoveAllSelections(infoPtr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes mouse up messages (left mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] pts : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
NMLISTVIEW nmlv;
|
|
|
|
TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
|
|
|
|
if (!infoPtr->bLButtonDown) return 0;
|
|
|
|
lvHitTestInfo.pt.x = pts.x;
|
|
lvHitTestInfo.pt.y = pts.y;
|
|
|
|
/* send NM_CLICK notification */
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE);
|
|
nmlv.iItem = lvHitTestInfo.iItem;
|
|
nmlv.iSubItem = lvHitTestInfo.iSubItem;
|
|
nmlv.ptAction = lvHitTestInfo.pt;
|
|
notify_listview(infoPtr, NM_CLICK, &nmlv);
|
|
|
|
/* set left button flag */
|
|
infoPtr->bLButtonDown = FALSE;
|
|
|
|
if(infoPtr->nEditLabelItem != -1)
|
|
{
|
|
if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem &&
|
|
(lvHitTestInfo.flags & LVHT_ONITEMLABEL))
|
|
LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
|
|
infoPtr->nEditLabelItem = -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Destroys the listview control (called after WM_DESTROY).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* delete all items */
|
|
LISTVIEW_DeleteAllItems(infoPtr);
|
|
|
|
/* destroy data structure */
|
|
DPA_Destroy(infoPtr->hdpaItems);
|
|
DPA_Destroy(infoPtr->hdpaSelectionRanges);
|
|
|
|
/* destroy image lists */
|
|
if (!(lStyle & LVS_SHAREIMAGELISTS))
|
|
{
|
|
/* FIXME: If the caller does a ImageList_Destroy and then we
|
|
* do this code the area will be freed twice. Currently
|
|
* this generates an "err:heap:HEAP_ValidateInUseArena
|
|
* Heap xxxxxxxx: in-use arena yyyyyyyy next block
|
|
* has PREV_FREE flag" sometimes.
|
|
*
|
|
* We will leak the memory till we figure out how to fix
|
|
*/
|
|
if (infoPtr->himlNormal)
|
|
ImageList_Destroy(infoPtr->himlNormal);
|
|
if (infoPtr->himlSmall)
|
|
ImageList_Destroy(infoPtr->himlSmall);
|
|
if (infoPtr->himlState)
|
|
ImageList_Destroy(infoPtr->himlState);
|
|
}
|
|
|
|
/* destroy font, bkgnd brush */
|
|
infoPtr->hFont = 0;
|
|
if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
|
|
if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
|
|
|
|
/* free listview info pointer*/
|
|
COMCTL32_Free(infoPtr);
|
|
|
|
SetWindowLongW(infoPtr->hwndSelf, 0, 0);
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Handles notifications from children.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] INT : control identifier
|
|
* [I] LPNMHDR : notification information
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, INT nCtrlId, LPNMHDR lpnmh)
|
|
{
|
|
TRACE("(nCtrlId=%d, lpnmh=%p)\n", nCtrlId, lpnmh);
|
|
|
|
if (lpnmh->hwndFrom == infoPtr->hwndHeader)
|
|
{
|
|
/* handle notification from header control */
|
|
if (lpnmh->code == HDN_ENDTRACKW)
|
|
{
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
}
|
|
else if(lpnmh->code == HDN_ITEMCLICKW || lpnmh->code == HDN_ITEMCLICKA)
|
|
{
|
|
/* Handle sorting by Header Column */
|
|
NMLISTVIEW nmlv;
|
|
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = -1;
|
|
nmlv.iSubItem = ((LPNMHEADERW)lpnmh)->iItem;
|
|
notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
|
|
}
|
|
else if(lpnmh->code == NM_RELEASEDCAPTURE)
|
|
{
|
|
/* Idealy this should be done in HDN_ENDTRACKA
|
|
* but since SetItemBounds in Header.c is called after
|
|
* the notification is sent, it is neccessary to handle the
|
|
* update of the scroll bar here (Header.c works fine as it is,
|
|
* no need to disturb it)
|
|
*/
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Determines the type of structure to use.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structureof the sender
|
|
* [I] HWND : listview window handle
|
|
* [I] INT : command specifying the nature of the WM_NOTIFYFORMAT
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
|
|
{
|
|
TRACE("(hwndFrom=%x, nCommand=%d)\n", hwndFrom, nCommand);
|
|
|
|
if (nCommand == NF_REQUERY)
|
|
infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT,
|
|
(WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Paints/Repaints the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] HDC : device context handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
TRACE("(hdc=%x)\n", hdc);
|
|
|
|
if (hdc)
|
|
LISTVIEW_Refresh(infoPtr, hdc);
|
|
else
|
|
{
|
|
PAINTSTRUCT ps;
|
|
|
|
hdc = BeginPaint(infoPtr->hwndSelf, &ps);
|
|
if (!hdc) return 1;
|
|
if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
|
|
LISTVIEW_Refresh(infoPtr, hdc);
|
|
EndPaint(infoPtr->hwndSelf, &ps);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes double click messages (right mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] pts : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
|
|
{
|
|
TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
notify_releasedcapture(infoPtr);
|
|
|
|
/* send NM_RDBLCLK notification */
|
|
notify_rdblclk(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes mouse down messages (right mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] pts : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
NMLISTVIEW nmlv;
|
|
INT nItem;
|
|
|
|
TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
|
|
|
|
/* FIXME: NM_CLICK */
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
notify_releasedcapture(infoPtr);
|
|
|
|
/* make sure the listview control window has the focus */
|
|
if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
|
|
|
|
/* set right button down flag */
|
|
infoPtr->bRButtonDown = TRUE;
|
|
|
|
/* determine the index of the selected item */
|
|
lvHitTestInfo.pt.x = pts.x;
|
|
lvHitTestInfo.pt.y = pts.y;
|
|
nItem = LISTVIEW_GetItemAtPt(infoPtr, lvHitTestInfo.pt);
|
|
|
|
if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
|
|
{
|
|
LISTVIEW_SetItemFocus(infoPtr,nItem);
|
|
if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
|
|
!LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
LISTVIEW_RemoveAllSelections(infoPtr);
|
|
}
|
|
|
|
|
|
/* Send NM_RClICK notification */
|
|
ZeroMemory(&nmlv, sizeof(nmlv));
|
|
LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE);
|
|
nmlv.iItem = lvHitTestInfo.iItem;
|
|
nmlv.iSubItem = lvHitTestInfo.iSubItem;
|
|
nmlv.ptAction = lvHitTestInfo.pt;
|
|
notify_listview(infoPtr, NM_RCLICK, &nmlv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes mouse up messages (right mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] pts : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
|
|
{
|
|
POINT pt = { pts.x, pts.y };
|
|
|
|
TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
|
|
|
|
if (!infoPtr->bRButtonDown) return 0;
|
|
|
|
/* set button flag */
|
|
infoPtr->bRButtonDown = FALSE;
|
|
|
|
/* Change to screen coordinate for WM_CONTEXTMENU */
|
|
ClientToScreen(infoPtr->hwndSelf, &pt);
|
|
|
|
/* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
|
|
SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
|
|
(WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the cursor.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hwnd : window handle of window containing the cursor
|
|
* [I] nHittest : hit-test code
|
|
* [I] wMouseMsg : ideintifier of the mouse message
|
|
*
|
|
* RETURN:
|
|
* TRUE if cursor is set
|
|
* FALSE otherwise
|
|
*/
|
|
static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
|
|
{
|
|
POINT pt;
|
|
|
|
if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
|
|
|
|
if(!infoPtr->hHotCursor) return FALSE;
|
|
|
|
GetCursorPos(&pt);
|
|
if (LISTVIEW_GetItemAtPt(infoPtr, pt) < 0) return FALSE;
|
|
|
|
SetCursor(infoPtr->hHotCursor);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the focus.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] infoPtr : handle of previously focused window
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
|
|
{
|
|
TRACE("(hwndLoseFocus=%x)\n", hwndLoseFocus);
|
|
|
|
/* if we have the focus already, there's nothing to do */
|
|
if (infoPtr->bFocus) return 0;
|
|
|
|
/* send NM_SETFOCUS notification */
|
|
notify_setfocus(infoPtr);
|
|
|
|
/* set window focus flag */
|
|
infoPtr->bFocus = TRUE;
|
|
|
|
/* put the focus rect back on */
|
|
LISTVIEW_ShowFocusRect(infoPtr, TRUE);
|
|
|
|
/* redraw all visible selected items */
|
|
LISTVIEW_InvalidateSelectedItems(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the font.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] HFONT : font handle
|
|
* [I] WORD : redraw flag
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
|
|
{
|
|
HFONT oldFont = infoPtr->hFont;
|
|
|
|
TRACE("(hfont=%x,redraw=%hu)\n", hFont, fRedraw);
|
|
|
|
infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
|
|
if (infoPtr->hFont == oldFont) return 0;
|
|
|
|
LISTVIEW_SaveTextMetrics(infoPtr);
|
|
|
|
if (LISTVIEW_GetType(infoPtr) == LVS_REPORT)
|
|
SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
|
|
|
|
if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Message handling for WM_SETREDRAW.
|
|
* For the Listview, it invalidates the entire window (the doc specifies otherwise)
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] bRedraw: state of redraw flag
|
|
*
|
|
* RETURN:
|
|
* DefWinProc return value
|
|
*/
|
|
static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
|
|
{
|
|
/* FIXME: this is bogus */
|
|
LRESULT lResult = DefWindowProcW(infoPtr->hwndSelf, WM_SETREDRAW, bRedraw, 0);
|
|
if(bRedraw)
|
|
RedrawWindow(infoPtr->hwndSelf, NULL, 0,
|
|
RDW_INVALIDATE | RDW_FRAME | RDW_ERASE | RDW_ALLCHILDREN | RDW_ERASENOW);
|
|
return lResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Resizes the listview control. This function processes WM_SIZE
|
|
* messages. At this time, the width and height are not used.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] WORD : new width
|
|
* [I] WORD : new height
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
|
|
TRACE("(width=%d, height=%d)\n", Width, Height);
|
|
|
|
if (LISTVIEW_UpdateSize(infoPtr))
|
|
{
|
|
if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
|
|
{
|
|
if (lStyle & LVS_ALIGNLEFT)
|
|
LISTVIEW_AlignLeft(infoPtr);
|
|
else
|
|
LISTVIEW_AlignTop(infoPtr);
|
|
}
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the size information.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Zero if no size change
|
|
* 1 of size changed
|
|
*/
|
|
static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
LONG lStyle = infoPtr->dwStyle;
|
|
UINT uView = lStyle & LVS_TYPEMASK;
|
|
RECT rcList;
|
|
RECT rcOld;
|
|
|
|
GetClientRect(infoPtr->hwndSelf, &rcList);
|
|
CopyRect(&rcOld,&(infoPtr->rcList));
|
|
infoPtr->rcList.left = 0;
|
|
infoPtr->rcList.right = max(rcList.right - rcList.left, 1);
|
|
infoPtr->rcList.top = 0;
|
|
infoPtr->rcList.bottom = max(rcList.bottom - rcList.top, 1);
|
|
|
|
if (uView == LVS_LIST)
|
|
{
|
|
/* Apparently the "LIST" style is supposed to have the same
|
|
* number of items in a column even if there is no scroll bar.
|
|
* Since if a scroll bar already exists then the bottom is already
|
|
* reduced, only reduce if the scroll bar does not currently exist.
|
|
* The "2" is there to mimic the native control. I think it may be
|
|
* related to either padding or edges. (GLA 7/2002)
|
|
*/
|
|
if (!(lStyle & WS_HSCROLL))
|
|
{
|
|
INT nHScrollHeight = GetSystemMetrics(SM_CYHSCROLL);
|
|
if (infoPtr->rcList.bottom > nHScrollHeight)
|
|
infoPtr->rcList.bottom -= (nHScrollHeight + 2);
|
|
}
|
|
else
|
|
{
|
|
if (infoPtr->rcList.bottom > 2)
|
|
infoPtr->rcList.bottom -= 2;
|
|
}
|
|
}
|
|
else if (uView == LVS_REPORT)
|
|
{
|
|
HDLAYOUT hl;
|
|
WINDOWPOS wp;
|
|
|
|
hl.prc = &rcList;
|
|
hl.pwpos = ℘
|
|
Header_Layout(infoPtr->hwndHeader, &hl);
|
|
|
|
SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
|
|
|
|
if (!(LVS_NOCOLUMNHEADER & lStyle))
|
|
infoPtr->rcList.top = max(wp.cy, 0);
|
|
}
|
|
return (EqualRect(&rcOld,&(infoPtr->rcList)));
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes WM_STYLECHANGED messages.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] WPARAM : window style type (normal or extended)
|
|
* [I] LPSTYLESTRUCT : window style information
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
|
|
LPSTYLESTRUCT lpss)
|
|
{
|
|
UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
|
|
UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
|
|
RECT rcList = infoPtr->rcList;
|
|
|
|
TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
|
|
wStyleType, lpss->styleOld, lpss->styleNew);
|
|
|
|
/* FIXME: if LVS_NOSORTHEADER changed, update header */
|
|
|
|
if (wStyleType == GWL_STYLE)
|
|
{
|
|
infoPtr->dwStyle = lpss->styleNew;
|
|
|
|
if (((lpss->styleOld & WS_HSCROLL) != 0)&&
|
|
((lpss->styleNew & WS_HSCROLL) == 0))
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
|
|
|
|
if (((lpss->styleOld & WS_VSCROLL) != 0)&&
|
|
((lpss->styleNew & WS_VSCROLL) == 0))
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
|
|
|
|
/* If switching modes, then start with no scroll bars and then
|
|
* decide.
|
|
*/
|
|
if (uNewView != uOldView)
|
|
{
|
|
if (uOldView == LVS_REPORT)
|
|
ShowWindow(infoPtr->hwndHeader, SW_HIDE);
|
|
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
}
|
|
|
|
if (uNewView == LVS_ICON)
|
|
{
|
|
INT oldcx, oldcy;
|
|
|
|
/* First readjust the iconSize and if necessary the iconSpacing */
|
|
oldcx = infoPtr->iconSize.cx;
|
|
oldcy = infoPtr->iconSize.cy;
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYICON);
|
|
if (infoPtr->himlNormal != NULL)
|
|
{
|
|
INT cx, cy;
|
|
ImageList_GetIconSize(infoPtr->himlNormal, &cx, &cy);
|
|
infoPtr->iconSize.cx = cx;
|
|
infoPtr->iconSize.cy = cy;
|
|
}
|
|
if ((infoPtr->iconSize.cx != oldcx) || (infoPtr->iconSize.cy != oldcy))
|
|
{
|
|
TRACE("icon old size=(%d,%d), new size=(%ld,%ld)\n",
|
|
oldcx, oldcy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
|
|
LISTVIEW_SetIconSpacing(infoPtr,0);
|
|
}
|
|
|
|
/* Now update the full item width and height */
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
if (lpss->styleNew & LVS_ALIGNLEFT)
|
|
LISTVIEW_AlignLeft(infoPtr);
|
|
else
|
|
LISTVIEW_AlignTop(infoPtr);
|
|
}
|
|
else if (uNewView == LVS_REPORT)
|
|
{
|
|
HDLAYOUT hl;
|
|
WINDOWPOS wp;
|
|
|
|
hl.prc = &rcList;
|
|
hl.pwpos = ℘
|
|
Header_Layout(infoPtr->hwndHeader, &hl);
|
|
SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
|
|
wp.flags);
|
|
if (!(LVS_NOCOLUMNHEADER & lpss->styleNew))
|
|
ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
|
|
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
}
|
|
else if (uNewView == LVS_LIST)
|
|
{
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
}
|
|
else
|
|
{
|
|
infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
|
|
infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_GetItemHeight(infoPtr);
|
|
if (lpss->styleNew & LVS_ALIGNLEFT)
|
|
LISTVIEW_AlignLeft(infoPtr);
|
|
else
|
|
LISTVIEW_AlignTop(infoPtr);
|
|
}
|
|
|
|
/* update the size of the client area */
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
|
|
/* add scrollbars if needed */
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* invalidate client area + erase background */
|
|
LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
|
|
|
|
/* print the list of unsupported window styles */
|
|
LISTVIEW_UnsupportedStyles(lpss->styleNew);
|
|
}
|
|
|
|
/* If they change the view and we have an active edit control
|
|
we will need to kill the control since the redraw will
|
|
misplace the edit control.
|
|
*/
|
|
if (infoPtr->bEditing &&
|
|
((uNewView & (LVS_ICON|LVS_LIST|LVS_SMALLICON)) !=
|
|
((LVS_ICON|LVS_LIST|LVS_SMALLICON) & uOldView)))
|
|
{
|
|
SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Window procedure of the listview control.
|
|
*
|
|
*/
|
|
static LRESULT WINAPI
|
|
LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
|
|
|
|
TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
|
|
|
|
if (!infoPtr && (uMsg != WM_CREATE))
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
|
|
if (infoPtr)
|
|
{
|
|
infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
|
|
}
|
|
|
|
switch (uMsg)
|
|
{
|
|
case LVM_APPROXIMATEVIEWRECT:
|
|
return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
|
|
LOWORD(lParam), HIWORD(lParam));
|
|
case LVM_ARRANGE:
|
|
return LISTVIEW_Arrange(infoPtr, (INT)wParam);
|
|
|
|
/* case LVN_CANCELEDITLABEL */
|
|
|
|
/* case LVM_CREATEDRAGIMAGE: */
|
|
|
|
case LVM_DELETEALLITEMS:
|
|
return LISTVIEW_DeleteAllItems(infoPtr);
|
|
|
|
case LVM_DELETECOLUMN:
|
|
return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
|
|
|
|
case LVM_DELETEITEM:
|
|
return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
|
|
|
|
case LVM_EDITLABELW:
|
|
return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
|
|
|
|
case LVM_EDITLABELA:
|
|
return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
|
|
|
|
/* case LVN_ENABLEGROUPVIEW: */
|
|
|
|
case LVM_ENSUREVISIBLE:
|
|
return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
|
|
|
|
case LVM_FINDITEMW:
|
|
return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
|
|
|
|
case LVM_FINDITEMA:
|
|
return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
|
|
|
|
case LVM_GETBKCOLOR:
|
|
return infoPtr->clrBk;
|
|
|
|
/* case LVM_GETBKIMAGE: */
|
|
|
|
case LVM_GETCALLBACKMASK:
|
|
return infoPtr->uCallbackMask;
|
|
|
|
case LVM_GETCOLUMNA:
|
|
return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
|
|
|
|
case LVM_GETCOLUMNW:
|
|
return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
|
|
|
|
case LVM_GETCOLUMNORDERARRAY:
|
|
return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
|
|
|
|
case LVM_GETCOLUMNWIDTH:
|
|
return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
|
|
|
|
case LVM_GETCOUNTPERPAGE:
|
|
return LISTVIEW_GetCountPerPage(infoPtr);
|
|
|
|
case LVM_GETEDITCONTROL:
|
|
return (LRESULT)infoPtr->hwndEdit;
|
|
|
|
case LVM_GETEXTENDEDLISTVIEWSTYLE:
|
|
return infoPtr->dwLvExStyle;
|
|
|
|
case LVM_GETHEADER:
|
|
return (LRESULT)infoPtr->hwndHeader;
|
|
|
|
case LVM_GETHOTCURSOR:
|
|
return infoPtr->hHotCursor;
|
|
|
|
case LVM_GETHOTITEM:
|
|
return infoPtr->nHotItem;
|
|
|
|
case LVM_GETHOVERTIME:
|
|
return infoPtr->dwHoverTime;
|
|
|
|
case LVM_GETIMAGELIST:
|
|
return LISTVIEW_GetImageList(infoPtr, (INT)wParam);
|
|
|
|
/* case LVN_GETINSERTMARK: */
|
|
|
|
/* case LVN_GETINSERTMARKCOLOR: */
|
|
|
|
/* case LVN_GETINSERTMARKRECT: */
|
|
|
|
case LVM_GETISEARCHSTRINGA:
|
|
case LVM_GETISEARCHSTRINGW:
|
|
FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
|
|
return FALSE;
|
|
|
|
case LVM_GETITEMA:
|
|
return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
|
|
|
|
case LVM_GETITEMW:
|
|
return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
|
|
|
|
case LVM_GETITEMCOUNT:
|
|
return infoPtr->nItemCount;
|
|
|
|
case LVM_GETITEMPOSITION:
|
|
return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
|
|
|
|
case LVM_GETITEMRECT:
|
|
return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
|
|
|
|
case LVM_GETITEMSPACING:
|
|
return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
|
|
|
|
case LVM_GETITEMSTATE:
|
|
return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
|
|
|
|
case LVM_GETITEMTEXTA:
|
|
return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
|
|
|
|
case LVM_GETITEMTEXTW:
|
|
return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
|
|
|
|
case LVM_GETNEXTITEM:
|
|
return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
|
|
|
|
case LVM_GETNUMBEROFWORKAREAS:
|
|
FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
|
|
return 1;
|
|
|
|
case LVM_GETORIGIN:
|
|
return LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
|
|
|
|
/* case LVN_GETOUTLINECOLOR: */
|
|
|
|
/* case LVM_GETSELECTEDCOLUMN: */
|
|
|
|
case LVM_GETSELECTEDCOUNT:
|
|
return LISTVIEW_GetSelectedCount(infoPtr);
|
|
|
|
case LVM_GETSELECTIONMARK:
|
|
return infoPtr->nSelectionMark;
|
|
|
|
case LVM_GETSTRINGWIDTHA:
|
|
return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
|
|
|
|
case LVM_GETSTRINGWIDTHW:
|
|
return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
|
|
|
|
case LVM_GETSUBITEMRECT:
|
|
return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
|
|
|
|
case LVM_GETTEXTBKCOLOR:
|
|
return infoPtr->clrTextBk;
|
|
|
|
case LVM_GETTEXTCOLOR:
|
|
return infoPtr->clrText;
|
|
|
|
/* case LVN_GETTILEINFO: */
|
|
|
|
/* case LVN_GETTILEVIEWINFO: */
|
|
|
|
case LVM_GETTOOLTIPS:
|
|
FIXME("LVM_GETTOOLTIPS: unimplemented\n");
|
|
return FALSE;
|
|
|
|
case LVM_GETTOPINDEX:
|
|
return LISTVIEW_GetTopIndex(infoPtr);
|
|
|
|
/*case LVM_GETUNICODEFORMAT:
|
|
FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
|
|
return FALSE;*/
|
|
|
|
case LVM_GETVIEWRECT:
|
|
return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
|
|
|
|
case LVM_GETWORKAREAS:
|
|
FIXME("LVM_GETWORKAREAS: unimplemented\n");
|
|
return FALSE;
|
|
|
|
/* case LVN_HASGROUP: */
|
|
|
|
case LVM_HITTEST:
|
|
return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE);
|
|
|
|
case LVM_INSERTCOLUMNA:
|
|
return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
|
|
|
|
case LVM_INSERTCOLUMNW:
|
|
return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
|
|
|
|
/* case LVN_INSERTGROUP: */
|
|
|
|
/* case LVN_INSERTGROUPSORTED: */
|
|
|
|
case LVM_INSERTITEMA:
|
|
return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
|
|
|
|
case LVM_INSERTITEMW:
|
|
return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
|
|
|
|
/* case LVN_INSERTMARKHITTEST: */
|
|
|
|
/* case LVN_ISGROUPVIEWENABLED: */
|
|
|
|
/* case LVN_MAPIDTOINDEX: */
|
|
|
|
/* case LVN_INEDXTOID: */
|
|
|
|
/* case LVN_MOVEGROUP: */
|
|
|
|
/* case LVN_MOVEITEMTOGROUP: */
|
|
|
|
case LVM_REDRAWITEMS:
|
|
return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
|
|
|
|
/* case LVN_REMOVEALLGROUPS: */
|
|
|
|
/* case LVN_REMOVEGROUP: */
|
|
|
|
case LVM_SCROLL:
|
|
return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
|
|
|
|
case LVM_SETBKCOLOR:
|
|
return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
|
|
|
|
/* case LVM_SETBKIMAGE: */
|
|
|
|
case LVM_SETCALLBACKMASK:
|
|
infoPtr->uCallbackMask = (UINT)wParam;
|
|
return TRUE;
|
|
|
|
case LVM_SETCOLUMNA:
|
|
return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
|
|
|
|
case LVM_SETCOLUMNW:
|
|
return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
|
|
|
|
case LVM_SETCOLUMNORDERARRAY:
|
|
return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
|
|
|
|
case LVM_SETCOLUMNWIDTH:
|
|
return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, SLOWORD(lParam));
|
|
|
|
case LVM_SETEXTENDEDLISTVIEWSTYLE:
|
|
return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
|
|
|
|
/* case LVN_SETGROUPINFO: */
|
|
|
|
/* case LVN_SETGROUPMETRICS: */
|
|
|
|
case LVM_SETHOTCURSOR:
|
|
return LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
|
|
|
|
case LVM_SETHOTITEM:
|
|
return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
|
|
|
|
case LVM_SETHOVERTIME:
|
|
return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
|
|
|
|
case LVM_SETICONSPACING:
|
|
return LISTVIEW_SetIconSpacing(infoPtr, (DWORD)lParam);
|
|
|
|
case LVM_SETIMAGELIST:
|
|
return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
|
|
|
|
/* case LVN_SETINFOTIP: */
|
|
|
|
/* case LVN_SETINSERTMARK: */
|
|
|
|
/* case LVN_SETINSERTMARKCOLOR: */
|
|
|
|
case LVM_SETITEMA:
|
|
return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
|
|
|
|
case LVM_SETITEMW:
|
|
return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
|
|
|
|
case LVM_SETITEMCOUNT:
|
|
return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
|
|
|
|
case LVM_SETITEMPOSITION:
|
|
{
|
|
POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
|
|
return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
|
|
}
|
|
|
|
case LVM_SETITEMPOSITION32:
|
|
if (lParam == 0) return FALSE;
|
|
return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
|
|
|
|
case LVM_SETITEMSTATE:
|
|
return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
|
|
|
|
case LVM_SETITEMTEXTA:
|
|
return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
|
|
|
|
case LVM_SETITEMTEXTW:
|
|
return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
|
|
|
|
/* case LVN_SETOUTLINECOLOR: */
|
|
|
|
/* case LVN_SETSELECTEDCOLUMN: */
|
|
|
|
case LVM_SETSELECTIONMARK:
|
|
return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
|
|
|
|
case LVM_SETTEXTBKCOLOR:
|
|
return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
|
|
|
|
case LVM_SETTEXTCOLOR:
|
|
return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
|
|
|
|
/* case LVN_SETTILEINFO: */
|
|
|
|
/* case LVN_SETTILEVIEWINFO: */
|
|
|
|
/* case LVN_SETTILEWIDTH: */
|
|
|
|
/* case LVM_SETTOOLTIPS: */
|
|
|
|
/* case LVM_SETUNICODEFORMAT: */
|
|
|
|
/* case LVN_SETVIEW: */
|
|
|
|
/* case LVM_SETWORKAREAS: */
|
|
|
|
/* case LVN_SORTGROUPS: */
|
|
|
|
case LVM_SORTITEMS:
|
|
return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
|
|
|
|
case LVM_SUBITEMHITTEST:
|
|
return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE);
|
|
|
|
case LVM_UPDATE:
|
|
return LISTVIEW_Update(infoPtr, (INT)wParam);
|
|
|
|
case WM_CHAR:
|
|
return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
|
|
|
|
case WM_COMMAND:
|
|
return LISTVIEW_Command(infoPtr, wParam, lParam);
|
|
|
|
case WM_CREATE:
|
|
return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
|
|
|
|
case WM_ERASEBKGND:
|
|
return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
|
|
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTCHARS | DLGC_WANTARROWS;
|
|
|
|
case WM_GETFONT:
|
|
return infoPtr->hFont;
|
|
|
|
case WM_HSCROLL:
|
|
return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
|
|
|
|
case WM_KEYDOWN:
|
|
return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
|
|
|
|
case WM_KILLFOCUS:
|
|
return LISTVIEW_KillFocus(infoPtr);
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_LBUTTONDOWN:
|
|
return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_LBUTTONUP:
|
|
return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_MOUSEMOVE:
|
|
return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_MOUSEHOVER:
|
|
return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_NCDESTROY:
|
|
return LISTVIEW_NCDestroy(infoPtr);
|
|
|
|
case WM_NOTIFY:
|
|
return LISTVIEW_Notify(infoPtr, (INT)wParam, (LPNMHDR)lParam);
|
|
|
|
case WM_NOTIFYFORMAT:
|
|
return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
|
|
|
|
case WM_PAINT:
|
|
return LISTVIEW_Paint(infoPtr, (HDC)wParam);
|
|
|
|
case WM_RBUTTONDBLCLK:
|
|
return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_RBUTTONDOWN:
|
|
return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_RBUTTONUP:
|
|
return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
|
|
|
|
case WM_SETCURSOR:
|
|
if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
|
|
return TRUE;
|
|
goto fwd_msg;
|
|
|
|
case WM_SETFOCUS:
|
|
return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
|
|
|
|
case WM_SETFONT:
|
|
return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
|
|
|
|
case WM_SETREDRAW:
|
|
return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
|
|
|
|
case WM_SIZE:
|
|
return LISTVIEW_Size(infoPtr, (int)SLOWORD(lParam), (int)SHIWORD(lParam));
|
|
|
|
case WM_STYLECHANGED:
|
|
return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
|
|
|
|
case WM_SYSCOLORCHANGE:
|
|
COMCTL32_RefreshSysColors();
|
|
return 0;
|
|
|
|
/* case WM_TIMER: */
|
|
|
|
case WM_VSCROLL:
|
|
return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
|
|
|
|
case WM_MOUSEWHEEL:
|
|
if (wParam & (MK_SHIFT | MK_CONTROL))
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
|
|
|
|
case WM_WINDOWPOSCHANGED:
|
|
if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE)) {
|
|
SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
|
|
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
}
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
|
|
/* case WM_WININICHANGE: */
|
|
|
|
default:
|
|
if ((uMsg >= WM_USER) && (uMsg < WM_APP))
|
|
ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
|
|
|
|
fwd_msg:
|
|
/* call default window procedure */
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Registers the window class.
|
|
*
|
|
* PARAMETER(S):
|
|
* None
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
void LISTVIEW_Register(void)
|
|
{
|
|
WNDCLASSW wndClass;
|
|
|
|
ZeroMemory(&wndClass, sizeof(WNDCLASSW));
|
|
wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
|
|
wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
|
|
wndClass.cbClsExtra = 0;
|
|
wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
|
|
wndClass.hCursor = LoadCursorW(0, IDC_ARROWW);
|
|
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
wndClass.lpszClassName = WC_LISTVIEWW;
|
|
RegisterClassW(&wndClass);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Unregisters the window class.
|
|
*
|
|
* PARAMETER(S):
|
|
* None
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
void LISTVIEW_Unregister(void)
|
|
{
|
|
UnregisterClassW(WC_LISTVIEWW, (HINSTANCE)NULL);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Handle any WM_COMMAND messages
|
|
*
|
|
* PARAMETER(S):
|
|
*
|
|
* RETURN:
|
|
*/
|
|
static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case EN_UPDATE:
|
|
{
|
|
/*
|
|
* Adjust the edit window size
|
|
*/
|
|
WCHAR buffer[1024];
|
|
HDC hdc = GetDC(infoPtr->hwndEdit);
|
|
HFONT hFont, hOldFont = 0;
|
|
RECT rect;
|
|
SIZE sz;
|
|
int len;
|
|
|
|
len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
|
|
GetWindowRect(infoPtr->hwndEdit, &rect);
|
|
|
|
/* Select font to get the right dimension of the string */
|
|
hFont = SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
|
|
if(hFont != 0)
|
|
{
|
|
hOldFont = SelectObject(hdc, hFont);
|
|
}
|
|
|
|
if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
|
|
{
|
|
TEXTMETRICW textMetric;
|
|
|
|
/* Add Extra spacing for the next character */
|
|
GetTextMetricsW(hdc, &textMetric);
|
|
sz.cx += (textMetric.tmMaxCharWidth * 2);
|
|
|
|
SetWindowPos (
|
|
infoPtr->hwndEdit,
|
|
HWND_TOP,
|
|
0,
|
|
0,
|
|
sz.cx,
|
|
rect.bottom - rect.top,
|
|
SWP_DRAWFRAME|SWP_NOMOVE);
|
|
}
|
|
if(hFont != 0)
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return SendMessageW (GetParent (infoPtr->hwndSelf), WM_COMMAND, wParam, lParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Subclassed edit control windproc function
|
|
*
|
|
* PARAMETER(S):
|
|
*
|
|
* RETURN:
|
|
*/
|
|
static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg,
|
|
WPARAM wParam, LPARAM lParam, BOOL isW)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
|
|
static BOOL bIgnoreKillFocus = FALSE;
|
|
BOOL cancel = FALSE;
|
|
|
|
TRACE("(hwnd=%x, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
|
|
hwnd, uMsg, wParam, lParam, isW);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
|
|
|
|
case WM_KILLFOCUS:
|
|
if(bIgnoreKillFocus) return TRUE;
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
WNDPROC editProc = infoPtr->EditWndProc;
|
|
infoPtr->EditWndProc = 0;
|
|
SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
|
|
return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
|
|
}
|
|
|
|
case WM_KEYDOWN:
|
|
if (VK_ESCAPE == (INT)wParam)
|
|
{
|
|
cancel = TRUE;
|
|
break;
|
|
}
|
|
else if (VK_RETURN == (INT)wParam)
|
|
break;
|
|
|
|
default:
|
|
return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
|
|
}
|
|
|
|
if (infoPtr->bEditing)
|
|
{
|
|
LPWSTR buffer = NULL;
|
|
|
|
if (!cancel)
|
|
{
|
|
DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
|
|
|
|
if (len)
|
|
{
|
|
if ( (buffer = COMCTL32_Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
|
|
{
|
|
if (isW) GetWindowTextW(hwnd, buffer, len+1);
|
|
else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
|
|
}
|
|
}
|
|
}
|
|
/* Processing LVN_ENDLABELEDIT message could kill the focus */
|
|
/* eg. Using a messagebox */
|
|
bIgnoreKillFocus = TRUE;
|
|
LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
|
|
|
|
if (buffer) COMCTL32_Free(buffer);
|
|
|
|
bIgnoreKillFocus = FALSE;
|
|
}
|
|
|
|
SendMessageW(hwnd, WM_CLOSE, 0, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Subclassed edit control windproc function
|
|
*
|
|
* PARAMETER(S):
|
|
*
|
|
* RETURN:
|
|
*/
|
|
LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Subclassed edit control windproc function
|
|
*
|
|
* PARAMETER(S):
|
|
*
|
|
* RETURN:
|
|
*/
|
|
LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Creates a subclassed edit cotrol
|
|
*
|
|
* PARAMETER(S):
|
|
*
|
|
* RETURN:
|
|
*/
|
|
static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
|
|
INT x, INT y, INT width, INT height, BOOL isW)
|
|
{
|
|
WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
|
|
HWND hedit;
|
|
SIZE sz;
|
|
HDC hdc;
|
|
HDC hOldFont=0;
|
|
TEXTMETRICW textMetric;
|
|
HINSTANCE hinst = GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
|
|
|
|
TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
|
|
|
|
style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
|
|
hdc = GetDC(infoPtr->hwndSelf);
|
|
|
|
/* Select the font to get appropriate metric dimensions */
|
|
if(infoPtr->hFont != 0)
|
|
hOldFont = SelectObject(hdc, infoPtr->hFont);
|
|
|
|
/*Get String Lenght in pixels */
|
|
GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
|
|
|
|
/*Add Extra spacing for the next character */
|
|
GetTextMetricsW(hdc, &textMetric);
|
|
sz.cx += (textMetric.tmMaxCharWidth * 2);
|
|
|
|
if(infoPtr->hFont != 0)
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
if (isW)
|
|
hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
|
|
else
|
|
hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
|
|
|
|
if (!hedit) return 0;
|
|
|
|
infoPtr->EditWndProc = (WNDPROC)
|
|
(isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
|
|
SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
|
|
|
|
SendMessageW(hedit, WM_SETFONT, infoPtr->hFont, FALSE);
|
|
|
|
return hedit;
|
|
}
|