📄 listview.c
字号:
/***
* Create an iterator over a bunch of ranges.
* Please note that the iterator will take ownership of the ranges,
* and will free them upon destruction.
*/
static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
{
iterator_empty(i);
i->ranges = ranges;
return TRUE;
}
/***
* Creates an iterator over the items which intersect lprc.
*/
static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
{
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
RECT frame = *lprc, rcItem, rcTemp;
POINT Origin;
/* in case we fail, we want to return an empty iterator */
if (!iterator_empty(i)) return FALSE;
LISTVIEW_GetOrigin(infoPtr, &Origin);
TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
OffsetRect(&frame, -Origin.x, -Origin.y);
if (uView == LVS_ICON || uView == LVS_SMALLICON)
{
INT nItem;
if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
{
LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
if (IntersectRect(&rcTemp, &rcItem, lprc))
i->nSpecial = infoPtr->nFocusedItem;
}
if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
/* to do better here, we need to have PosX, and PosY sorted */
TRACE("building icon ranges:\n");
for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
{
rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
rcItem.right = rcItem.left + infoPtr->nItemWidth;
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
if (IntersectRect(&rcTemp, &rcItem, &frame))
ranges_additem(i->ranges, nItem);
}
return TRUE;
}
else if (uView == LVS_REPORT)
{
RANGE range;
if (frame.left >= infoPtr->nItemWidth) return TRUE;
if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
range.lower = max(frame.top / infoPtr->nItemHeight, 0);
range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
if (range.upper <= range.lower) return TRUE;
if (!iterator_rangeitems(i, range)) return FALSE;
TRACE(" report=%s\n", debugrange(&i->range));
}
else
{
INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
INT lower = nFirstCol * nPerCol + nFirstRow;
RANGE item_range;
INT nCol;
TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
TRACE("building list ranges:\n");
for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
{
item_range.lower = nCol * nPerCol + nFirstRow;
if(item_range.lower >= infoPtr->nItemCount) break;
item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
TRACE(" list=%s\n", debugrange(&item_range));
ranges_add(i->ranges, item_range);
}
}
return TRUE;
}
/***
* Creates an iterator over the items which intersect the visible region of hdc.
*/
static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
{
POINT Origin, Position;
RECT rcItem, rcClip;
INT 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;
/* first deal with the special item */
if (i->nSpecial != -1)
{
LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
}
/* if we can't deal with the region, we'll just go with the simple range */
LISTVIEW_GetOrigin(infoPtr, &Origin);
TRACE("building visible range:\n");
if (!i->ranges && i->range.lower < i->range.upper)
{
if (!(i->ranges = ranges_create(50))) return TRUE;
if (!ranges_add(i->ranges, i->range))
{
ranges_destroy(i->ranges);
i->ranges = 0;
return TRUE;
}
}
/* now delete the invisible items from the list */
while(iterator_next(i))
{
LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
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))
ranges_delitem(i->ranges, i->nItem);
}
/* the iterator should restart on the next iterator_next */
TRACE("done\n");
return TRUE;
}
/******** 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);
}
static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
{
UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
(uView == LVS_ICON || uView == LVS_SMALLICON);
}
/******** Internal API functions ************************************/
static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
{
static COLUMN_INFO mainItem;
if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
}
static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
{
*lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
}
static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
{
return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
}
/* Listview invalidation functions: use _only_ these functions to invalidate */
static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
{
return infoPtr->bRedraw;
}
static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
{
if(!is_redrawing(infoPtr)) return;
TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
}
static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
{
RECT rcBox;
if(!is_redrawing(infoPtr)) return;
LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
LISTVIEW_InvalidateRect(infoPtr, &rcBox);
}
static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
{
POINT Origin, Position;
RECT rcBox;
if(!is_redrawing(infoPtr)) return;
assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
LISTVIEW_GetOrigin(infoPtr, &Origin);
LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
rcBox.top = 0;
rcBox.bottom = infoPtr->nItemHeight;
OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
LISTVIEW_InvalidateRect(infoPtr, &rcBox);
}
static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
{
LISTVIEW_InvalidateRect(infoPtr, NULL);
}
static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
{
RECT rcCol;
if(!is_redrawing(infoPtr)) return;
LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
rcCol.top = infoPtr->rcList.top;
rcCol.bottom = infoPtr->rcList.bottom;
LISTVIEW_InvalidateRect(infoPtr, &rcCol);
}
/***
* 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 lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
/* 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;
/* update the search parameters */
infoPtr->lastKeyPressTimestamp = GetTickCount();
if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < 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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -