1 /*
2 * Listview control
3 *
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
9 * Copyright 2009 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 *
26 * NOTES
27 *
28 * This code was audited for completeness against the documented features
29 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
30 *
31 * Unless otherwise noted, we believe this code to be complete, as per
32 * the specification mentioned above.
33 * If you discover missing features, or bugs, please note them below.
34 *
35 * TODO:
36 *
37 * Default Message Processing
38 * -- WM_CREATE: create the icon and small icon image lists at this point only if
39 * the LVS_SHAREIMAGELISTS style is not specified.
40 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
41 * or small icon and the LVS_AUTOARRANGE style is specified.
42 * -- WM_TIMER
43 * -- WM_WININICHANGE
44 *
45 * Features
46 * -- Hot item handling, mouse hovering
47 * -- Workareas support
48 * -- Tilemode support
49 * -- Groups support
50 *
51 * Bugs
52 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
53 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
54 * -- LVA_SNAPTOGRID not implemented
55 * -- LISTVIEW_ApproximateViewRect partially implemented
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
59 *
60 * Speedups
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
70 *
71 * Flags
72 * -- LVIF_COLUMNS
73 * -- LVIF_GROUPID
74 *
75 * States
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
77 * -- LVIS_CUT
78 * -- LVIS_DROPHILITED
79 * -- LVIS_OVERLAYMASK
80 *
81 * Styles
82 * -- LVS_NOLABELWRAP
83 * -- LVS_NOSCROLL (see Q137520)
84 * -- LVS_ALIGNTOP
85 *
86 * Extended Styles
87 * -- LVS_EX_BORDERSELECT
88 * -- LVS_EX_FLATSB
89 * -- LVS_EX_INFOTIP
90 * -- LVS_EX_LABELTIP
91 * -- LVS_EX_MULTIWORKAREAS
92 * -- LVS_EX_REGIONAL
93 * -- LVS_EX_SIMPLESELECT
94 * -- LVS_EX_TWOCLICKACTIVATE
95 * -- LVS_EX_UNDERLINECOLD
96 * -- LVS_EX_UNDERLINEHOT
97 *
98 * Notifications:
99 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
100 * -- LVN_GETINFOTIP
101 * -- LVN_HOTTRACK
102 * -- LVN_SETDISPINFO
103 * -- NM_HOVER
104 * -- LVN_BEGINRDRAG
105 *
106 * Messages:
107 * -- LVM_ENABLEGROUPVIEW
108 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
109 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
110 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
111 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
112 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
113 * -- LVM_GETINSERTMARKRECT
114 * -- LVM_GETNUMBEROFWORKAREAS
115 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
116 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
117 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
118 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
119 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
120 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
121 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
122 * -- LVM_INSERTGROUPSORTED
123 * -- LVM_INSERTMARKHITTEST
124 * -- LVM_ISGROUPVIEWENABLED
125 * -- LVM_MOVEGROUP
126 * -- LVM_MOVEITEMTOGROUP
127 * -- LVM_SETINFOTIP
128 * -- LVM_SETTILEWIDTH
129 * -- LVM_SORTGROUPS
130 *
131 * Macros:
132 * -- ListView_GetHoverTime, ListView_SetHoverTime
133 * -- ListView_GetISearchString
134 * -- ListView_GetNumberOfWorkAreas
135 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
136 *
137 * Functions:
138 * -- LVGroupComparE
139 *
140 * Known differences in message stream from native control (not known if
141 * these differences cause problems):
142 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
143 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
144 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
145 * processing for "USEDOUBLECLICKTIME".
146 */
147
148 #include "config.h"
149 #include "wine/port.h"
150
151 #include <assert.h>
152 #include <ctype.h>
153 #include <string.h>
154 #include <stdlib.h>
155 #include <stdarg.h>
156 #include <stdio.h>
157
158 #include "windef.h"
159 #include "winbase.h"
160 #include "winnt.h"
161 #include "wingdi.h"
162 #include "winuser.h"
163 #include "winnls.h"
164 #include "commctrl.h"
165 #include "comctl32.h"
166 #include "uxtheme.h"
167
168 #include "wine/debug.h"
169 #include "wine/unicode.h"
170
171 WINE_DEFAULT_DEBUG_CHANNEL(listview);
172
173 /* make sure you set this to 0 for production use! */
174 #define DEBUG_RANGES 1
175
176 typedef struct tagCOLUMN_INFO
177 {
178 RECT rcHeader; /* tracks the header's rectangle */
179 INT fmt; /* same as LVCOLUMN.fmt */
180 INT cxMin;
181 } COLUMN_INFO;
182
183 typedef struct tagITEMHDR
184 {
185 LPWSTR pszText;
186 INT iImage;
187 } ITEMHDR, *LPITEMHDR;
188
189 typedef struct tagSUBITEM_INFO
190 {
191 ITEMHDR hdr;
192 INT iSubItem;
193 } SUBITEM_INFO;
194
195 typedef struct tagITEM_ID ITEM_ID;
196
197 typedef struct tagITEM_INFO
198 {
199 ITEMHDR hdr;
200 UINT state;
201 LPARAM lParam;
202 INT iIndent;
203 ITEM_ID *id;
204 } ITEM_INFO;
205
206 struct tagITEM_ID
207 {
208 UINT id; /* item id */
209 HDPA item; /* link to item data */
210 };
211
212 typedef struct tagRANGE
213 {
214 INT lower;
215 INT upper;
216 } RANGE;
217
218 typedef struct tagRANGES
219 {
220 HDPA hdpa;
221 } *RANGES;
222
223 typedef struct tagITERATOR
224 {
225 INT nItem;
226 INT nSpecial;
227 RANGE range;
228 RANGES ranges;
229 INT index;
230 } ITERATOR;
231
232 typedef struct tagDELAYED_ITEM_EDIT
233 {
234 BOOL fEnabled;
235 INT iItem;
236 } DELAYED_ITEM_EDIT;
237
238 typedef struct tagLISTVIEW_INFO
239 {
240 HWND hwndSelf;
241 HBRUSH hBkBrush;
242 COLORREF clrBk;
243 COLORREF clrText;
244 COLORREF clrTextBk;
245 HIMAGELIST himlNormal;
246 HIMAGELIST himlSmall;
247 HIMAGELIST himlState;
248 BOOL bLButtonDown;
249 BOOL bRButtonDown;
250 BOOL bDragging;
251 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
252 RECT marqueeRect;
253 POINT ptClickPos; /* point where the user clicked */
254 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
255 INT nItemHeight;
256 INT nItemWidth;
257 RANGES selectionRanges;
258 INT nSelectionMark;
259 INT nHotItem;
260 SHORT notifyFormat;
261 HWND hwndNotify;
262 RECT rcList; /* This rectangle is really the window
263 * client rectangle possibly reduced by the
264 * horizontal scroll bar and/or header - see
265 * LISTVIEW_UpdateSize. This rectangle offset
266 * by the LISTVIEW_GetOrigin value is in
267 * client coordinates */
268 SIZE iconSize;
269 SIZE iconSpacing;
270 SIZE iconStateSize;
271 UINT uCallbackMask;
272 HWND hwndHeader;
273 HCURSOR hHotCursor;
274 HFONT hDefaultFont;
275 HFONT hFont;
276 INT ntmHeight; /* Some cached metrics of the font used */
277 INT ntmMaxCharWidth; /* by the listview to draw items */
278 INT nEllipsisWidth;
279 BOOL bRedraw; /* Turns on/off repaints & invalidations */
280 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
281 BOOL bFocus;
282 BOOL bDoChangeNotify; /* send change notification messages? */
283 INT nFocusedItem;
284 RECT rcFocus;
285 DWORD dwStyle; /* the cached window GWL_STYLE */
286 DWORD dwLvExStyle; /* extended listview style */
287 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
288 INT nItemCount; /* the number of items in the list */
289 HDPA hdpaItems; /* array ITEM_INFO pointers */
290 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
291 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
292 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
293 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
294 BOOL colRectsDirty; /* trigger column rectangles requery from header */
295 POINT currIconPos; /* this is the position next icon will be placed */
296 PFNLVCOMPARE pfnCompare;
297 LPARAM lParamSort;
298 HWND hwndEdit;
299 WNDPROC EditWndProc;
300 INT nEditLabelItem;
301 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
302 DWORD dwHoverTime;
303 HWND hwndToolTip;
304
305 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
306
307 DWORD lastKeyPressTimestamp;
308 WPARAM charCode;
309 INT nSearchParamLength;
310 WCHAR szSearchParam[ MAX_PATH ];
311 BOOL bIsDrawing;
312 INT nMeasureItemHeight;
313 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
314 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
315
316 DWORD iVersion; /* CCM_[G,S]ETVERSION */
317 } LISTVIEW_INFO;
318
319 /*
320 * constants
321 */
322 /* How many we debug buffer to allocate */
323 #define DEBUG_BUFFERS 20
324 /* The size of a single debug buffer */
325 #define DEBUG_BUFFER_SIZE 256
326
327 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
328 #define SB_INTERNAL -1
329
330 /* maximum size of a label */
331 #define DISP_TEXT_SIZE 512
332
333 /* padding for items in list and small icon display modes */
334 #define WIDTH_PADDING 12
335
336 /* padding for items in list, report and small icon display modes */
337 #define HEIGHT_PADDING 1
338
339 /* offset of items in report display mode */
340 #define REPORT_MARGINX 2
341
342 /* padding for icon in large icon display mode
343 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
344 * that HITTEST will see.
345 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
346 * ICON_TOP_PADDING - sum of the two above.
347 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
348 * LABEL_HOR_PADDING - between text and sides of box
349 * LABEL_VERT_PADDING - between bottom of text and end of box
350 *
351 * ICON_LR_PADDING - additional width above icon size.
352 * ICON_LR_HALF - half of the above value
353 */
354 #define ICON_TOP_PADDING_NOTHITABLE 2
355 #define ICON_TOP_PADDING_HITABLE 2
356 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
357 #define ICON_BOTTOM_PADDING 4
358 #define LABEL_HOR_PADDING 5
359 #define LABEL_VERT_PADDING 7
360 #define ICON_LR_PADDING 16
361 #define ICON_LR_HALF (ICON_LR_PADDING/2)
362
363 /* default label width for items in list and small icon display modes */
364 #define DEFAULT_LABEL_WIDTH 40
365 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
366 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
367
368 /* default column width for items in list display mode */
369 #define DEFAULT_COLUMN_WIDTH 128
370
371 /* Size of "line" scroll for V & H scrolls */
372 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
373
374 /* Padding between image and label */
375 #define IMAGE_PADDING 2
376
377 /* Padding behind the label */
378 #define TRAILING_LABEL_PADDING 12
379 #define TRAILING_HEADER_PADDING 11
380
381 /* Border for the icon caption */
382 #define CAPTION_BORDER 2
383
384 /* Standard DrawText flags */
385 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
386 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
387 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
388
389 /* Image index from state */
390 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
391
392 /* The time in milliseconds to reset the search in the list */
393 #define KEY_DELAY 450
394
395 /* Dump the LISTVIEW_INFO structure to the debug channel */
396 #define LISTVIEW_DUMP(iP) do { \
397 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
398 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
399 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
400 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
401 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
402 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
403 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
404 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
405 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
406 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
407 } while(0)
408
409 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
410
411 /*
412 * forward declarations
413 */
414 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
415 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
416 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
417 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
418 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
419 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
420 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
421 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
422 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
423 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
424 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
425 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
426 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
427 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
428 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
429 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
430 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
431 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, BOOL);
432 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
433 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
434 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
435
436 /******** Text handling functions *************************************/
437
438 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
439 * text string. The string may be ANSI or Unicode, in which case
440 * the boolean isW tells us the type of the string.
441 *
442 * The name of the function tell what type of strings it expects:
443 * W: Unicode, T: ANSI/Unicode - function of isW
444 */
445
446 static inline BOOL is_textW(LPCWSTR text)
447 {
448 return text != NULL && text != LPSTR_TEXTCALLBACKW;
449 }
450
451 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
452 {
453 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
454 return is_textW(text);
455 }
456
457 static inline int textlenT(LPCWSTR text, BOOL isW)
458 {
459 return !is_textT(text, isW) ? 0 :
460 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
461 }
462
463 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
464 {
465 if (isDestW)
466 if (isSrcW) lstrcpynW(dest, src, max);
467 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
468 else
469 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
470 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
471 }
472
473 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
474 {
475 LPWSTR wstr = (LPWSTR)text;
476
477 if (!isW && is_textT(text, isW))
478 {
479 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
480 wstr = Alloc(len * sizeof(WCHAR));
481 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
482 }
483 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
484 return wstr;
485 }
486
487 static inline void textfreeT(LPWSTR wstr, BOOL isW)
488 {
489 if (!isW && is_textT(wstr, isW)) Free (wstr);
490 }
491
492 /*
493 * dest is a pointer to a Unicode string
494 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
495 */
496 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
497 {
498 BOOL bResult = TRUE;
499
500 if (src == LPSTR_TEXTCALLBACKW)
501 {
502 if (is_textW(*dest)) Free(*dest);
503 *dest = LPSTR_TEXTCALLBACKW;
504 }
505 else
506 {
507 LPWSTR pszText = textdupTtoW(src, isW);
508 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
509 bResult = Str_SetPtrW(dest, pszText);
510 textfreeT(pszText, isW);
511 }
512 return bResult;
513 }
514
515 /*
516 * compares a Unicode to a Unicode/ANSI text string
517 */
518 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
519 {
520 if (!aw) return bt ? -1 : 0;
521 if (!bt) return aw ? 1 : 0;
522 if (aw == LPSTR_TEXTCALLBACKW)
523 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
524 if (bt != LPSTR_TEXTCALLBACKW)
525 {
526 LPWSTR bw = textdupTtoW(bt, isW);
527 int r = bw ? lstrcmpW(aw, bw) : 1;
528 textfreeT(bw, isW);
529 return r;
530 }
531
532 return 1;
533 }
534
535 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
536 {
537 int res;
538
539 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
540 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
541 return res ? res - sizeof(WCHAR) : res;
542 }
543
544 /******** Debugging functions *****************************************/
545
546 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
547 {
548 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
549 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
550 }
551
552 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
553 {
554 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
555 n = min(textlenT(text, isW), n);
556 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
557 }
558
559 static char* debug_getbuf(void)
560 {
561 static int index = 0;
562 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
563 return buffers[index++ % DEBUG_BUFFERS];
564 }
565
566 static inline const char* debugrange(const RANGE *lprng)
567 {
568 if (!lprng) return "(null)";
569 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
570 }
571
572 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
573 {
574 char* buf = debug_getbuf(), *text = buf;
575 int len, size = DEBUG_BUFFER_SIZE;
576
577 if (pScrollInfo == NULL) return "(null)";
578 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
579 if (len == -1) goto end; buf += len; size -= len;
580 if (pScrollInfo->fMask & SIF_RANGE)
581 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
582 else len = 0;
583 if (len == -1) goto end; buf += len; size -= len;
584 if (pScrollInfo->fMask & SIF_PAGE)
585 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
586 else len = 0;
587 if (len == -1) goto end; buf += len; size -= len;
588 if (pScrollInfo->fMask & SIF_POS)
589 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
590 else len = 0;
591 if (len == -1) goto end; buf += len; size -= len;
592 if (pScrollInfo->fMask & SIF_TRACKPOS)
593 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
594 else len = 0;
595 if (len == -1) goto end; buf += len; size -= len;
596 goto undo;
597 end:
598 buf = text + strlen(text);
599 undo:
600 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
601 return text;
602 }
603
604 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
605 {
606 if (!plvnm) return "(null)";
607 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
608 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
609 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
610 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
611 }
612
613 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
614 {
615 char* buf = debug_getbuf(), *text = buf;
616 int len, size = DEBUG_BUFFER_SIZE;
617
618 if (lpLVItem == NULL) return "(null)";
619 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
620 if (len == -1) goto end; buf += len; size -= len;
621 if (lpLVItem->mask & LVIF_STATE)
622 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
623 else len = 0;
624 if (len == -1) goto end; buf += len; size -= len;
625 if (lpLVItem->mask & LVIF_TEXT)
626 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
627 else len = 0;
628 if (len == -1) goto end; buf += len; size -= len;
629 if (lpLVItem->mask & LVIF_IMAGE)
630 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
631 else len = 0;
632 if (len == -1) goto end; buf += len; size -= len;
633 if (lpLVItem->mask & LVIF_PARAM)
634 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
635 else len = 0;
636 if (len == -1) goto end; buf += len; size -= len;
637 if (lpLVItem->mask & LVIF_INDENT)
638 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
639 else len = 0;
640 if (len == -1) goto end; buf += len; size -= len;
641 goto undo;
642 end:
643 buf = text + strlen(text);
644 undo:
645 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
646 return text;
647 }
648
649 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
650 {
651 char* buf = debug_getbuf(), *text = buf;
652 int len, size = DEBUG_BUFFER_SIZE;
653
654 if (lpColumn == NULL) return "(null)";
655 len = snprintf(buf, size, "{");
656 if (len == -1) goto end; buf += len; size -= len;
657 if (lpColumn->mask & LVCF_SUBITEM)
658 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
659 else len = 0;
660 if (len == -1) goto end; buf += len; size -= len;
661 if (lpColumn->mask & LVCF_FMT)
662 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
663 else len = 0;
664 if (len == -1) goto end; buf += len; size -= len;
665 if (lpColumn->mask & LVCF_WIDTH)
666 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
667 else len = 0;
668 if (len == -1) goto end; buf += len; size -= len;
669 if (lpColumn->mask & LVCF_TEXT)
670 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
671 else len = 0;
672 if (len == -1) goto end; buf += len; size -= len;
673 if (lpColumn->mask & LVCF_IMAGE)
674 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
675 else len = 0;
676 if (len == -1) goto end; buf += len; size -= len;
677 if (lpColumn->mask & LVCF_ORDER)
678 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
679 else len = 0;
680 if (len == -1) goto end; buf += len; size -= len;
681 goto undo;
682 end:
683 buf = text + strlen(text);
684 undo:
685 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
686 return text;
687 }
688
689 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
690 {
691 if (!lpht) return "(null)";
692
693 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
694 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
695 }
696
697 /* Return the corresponding text for a given scroll value */
698 static inline LPCSTR debugscrollcode(int nScrollCode)
699 {
700 switch(nScrollCode)
701 {
702 case SB_LINELEFT: return "SB_LINELEFT";
703 case SB_LINERIGHT: return "SB_LINERIGHT";
704 case SB_PAGELEFT: return "SB_PAGELEFT";
705 case SB_PAGERIGHT: return "SB_PAGERIGHT";
706 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
707 case SB_THUMBTRACK: return "SB_THUMBTRACK";
708 case SB_ENDSCROLL: return "SB_ENDSCROLL";
709 case SB_INTERNAL: return "SB_INTERNAL";
710 default: return "unknown";
711 }
712 }
713
714
715 /******** Notification functions ************************************/
716
717 static int get_ansi_notification(UINT unicodeNotificationCode)
718 {
719 switch (unicodeNotificationCode)
720 {
721 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
722 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
723 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
724 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
725 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
726 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
727 /* header forwards */
728 case HDN_TRACKW: return HDN_TRACKA;
729 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
730 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
731 case HDN_ENDDRAG: return HDN_ENDDRAG;
732 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
733 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
734 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
735 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
736 }
737 ERR("unknown notification %x\n", unicodeNotificationCode);
738 assert(FALSE);
739 return 0;
740 }
741
742 /* forwards header notifications to listview parent */
743 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
744 {
745 NMHEADERA nmhA;
746 HDITEMA hditema;
747 HD_TEXTFILTERA textfilter;
748 LPSTR text = NULL, filter = NULL;
749 LRESULT ret;
750
751 /* on unicode format exit earlier */
752 if (infoPtr->notifyFormat == NFR_UNICODE)
753 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
754 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
755
756 /* header always supplies unicode notifications,
757 all we have to do is to convert strings to ANSI */
758 nmhA = *(NMHEADERA*)lpnmh;
759 if (lpnmh->pitem)
760 {
761 hditema = *(HDITEMA*)lpnmh->pitem;
762 nmhA.pitem = &hditema;
763 /* convert item text */
764 if (lpnmh->pitem->mask & HDI_TEXT)
765 {
766 hditema.pszText = NULL;
767 Str_SetPtrWtoA(&hditema.pszText, lpnmh->pitem->pszText);
768 text = hditema.pszText;
769 }
770 /* convert filter text */
771 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
772 lpnmh->pitem->pvFilter)
773 {
774 hditema.pvFilter = &textfilter;
775 textfilter = *(HD_TEXTFILTERA*)(lpnmh->pitem->pvFilter);
776 textfilter.pszText = NULL;
777 Str_SetPtrWtoA(&textfilter.pszText, ((HD_TEXTFILTERW*)lpnmh->pitem->pvFilter)->pszText);
778 filter = textfilter.pszText;
779 }
780 }
781 nmhA.hdr.code = get_ansi_notification(lpnmh->hdr.code);
782
783 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
784 (WPARAM)nmhA.hdr.idFrom, (LPARAM)&nmhA);
785
786 /* cleanup */
787 Free(text);
788 Free(filter);
789
790 return ret;
791 }
792
793 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
794 {
795 LRESULT result;
796
797 TRACE("(code=%d)\n", code);
798
799 pnmh->hwndFrom = infoPtr->hwndSelf;
800 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
801 pnmh->code = code;
802 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
803
804 TRACE(" <= %ld\n", result);
805
806 return result;
807 }
808
809 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
810 {
811 NMHDR nmh;
812 HWND hwnd = infoPtr->hwndSelf;
813 notify_hdr(infoPtr, code, &nmh);
814 return IsWindow(hwnd);
815 }
816
817 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
818 {
819 NMITEMACTIVATE nmia;
820 LVITEMW item;
821
822 if (htInfo) {
823 nmia.uNewState = 0;
824 nmia.uOldState = 0;
825 nmia.uChanged = 0;
826 nmia.uKeyFlags = 0;
827
828 item.mask = LVIF_PARAM|LVIF_STATE;
829 item.iItem = htInfo->iItem;
830 item.iSubItem = 0;
831 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
832 nmia.lParam = item.lParam;
833 nmia.uOldState = item.state;
834 nmia.uNewState = item.state | LVIS_ACTIVATING;
835 nmia.uChanged = LVIF_STATE;
836 }
837
838 nmia.iItem = htInfo->iItem;
839 nmia.iSubItem = htInfo->iSubItem;
840 nmia.ptAction = htInfo->pt;
841
842 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
843 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
844 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
845 }
846 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
847 }
848
849 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
850 {
851 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
852 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
853 }
854
855 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
856 {
857 NMITEMACTIVATE nmia;
858 LVITEMW item;
859 HWND hwnd = infoPtr->hwndSelf;
860
861 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
862 ZeroMemory(&nmia, sizeof(nmia));
863 nmia.iItem = lvht->iItem;
864 nmia.iSubItem = lvht->iSubItem;
865 nmia.ptAction = lvht->pt;
866 item.mask = LVIF_PARAM;
867 item.iItem = lvht->iItem;
868 item.iSubItem = 0;
869 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
870 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
871 return IsWindow(hwnd);
872 }
873
874 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
875 {
876 NMLISTVIEW nmlv;
877 LVITEMW item;
878 HWND hwnd = infoPtr->hwndSelf;
879
880 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
881 nmlv.iItem = nItem;
882 item.mask = LVIF_PARAM;
883 item.iItem = nItem;
884 item.iSubItem = 0;
885 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
886 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
887 return IsWindow(hwnd);
888 }
889
890 /*
891 Send notification. depends on dispinfoW having same
892 structure as dispinfoA.
893 infoPtr : listview struct
894 notificationCode : *Unicode* notification code
895 pdi : dispinfo structure (can be unicode or ansi)
896 isW : TRUE if dispinfo is Unicode
897 */
898 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
899 {
900 BOOL bResult = FALSE;
901 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
902 INT cchTempBufMax = 0, savCchTextMax = 0;
903 UINT realNotifCode;
904 LPWSTR pszTempBuf = NULL, savPszText = NULL;
905
906 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
907 {
908 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
909 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
910 }
911
912 if (convertToAnsi || convertToUnicode)
913 {
914 if (notificationCode != LVN_GETDISPINFOW)
915 {
916 cchTempBufMax = convertToUnicode ?
917 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
918 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
919 }
920 else
921 {
922 cchTempBufMax = pdi->item.cchTextMax;
923 *pdi->item.pszText = 0; /* make sure we don't process garbage */
924 }
925
926 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
927 if (!pszTempBuf) return FALSE;
928
929 if (convertToUnicode)
930 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
931 pszTempBuf, cchTempBufMax);
932 else
933 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
934 cchTempBufMax, NULL, NULL);
935
936 savCchTextMax = pdi->item.cchTextMax;
937 savPszText = pdi->item.pszText;
938 pdi->item.pszText = pszTempBuf;
939 pdi->item.cchTextMax = cchTempBufMax;
940 }
941
942 if (infoPtr->notifyFormat == NFR_ANSI)
943 realNotifCode = get_ansi_notification(notificationCode);
944 else
945 realNotifCode = notificationCode;
946 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
947 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
948
949 if (convertToUnicode || convertToAnsi)
950 {
951 if (convertToUnicode) /* note : pointer can be changed by app ! */
952 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
953 savCchTextMax, NULL, NULL);
954 else
955 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
956 savPszText, savCchTextMax);
957 pdi->item.pszText = savPszText; /* restores our buffer */
958 pdi->item.cchTextMax = savCchTextMax;
959 Free (pszTempBuf);
960 }
961 return bResult;
962 }
963
964 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
965 const RECT *rcBounds, const LVITEMW *lplvItem)
966 {
967 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
968 lpnmlvcd->nmcd.hdc = hdc;
969 lpnmlvcd->nmcd.rc = *rcBounds;
970 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
971 lpnmlvcd->clrText = infoPtr->clrText;
972 if (!lplvItem) return;
973 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
974 lpnmlvcd->iSubItem = lplvItem->iSubItem;
975 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
976 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
977 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
978 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
979 }
980
981 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
982 {
983 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
984 DWORD result;
985
986 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
987 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
988 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
989 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
990 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
991 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
992 return result;
993 }
994
995 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
996 {
997 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
998 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
999 if (lpnmlvcd->clrText == CLR_DEFAULT)
1000 lpnmlvcd->clrText = comctl32_color.clrWindowText;
1001
1002 /* apparently, for selected items, we have to override the returned values */
1003 if (!SubItem)
1004 {
1005 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1006 {
1007 if (infoPtr->bFocus)
1008 {
1009 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1010 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1011 }
1012 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1013 {
1014 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1015 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1016 }
1017 }
1018 }
1019
1020 /* Set the text attributes */
1021 if (lpnmlvcd->clrTextBk != CLR_NONE)
1022 {
1023 SetBkMode(hdc, OPAQUE);
1024 SetBkColor(hdc,lpnmlvcd->clrTextBk);
1025 }
1026 else
1027 SetBkMode(hdc, TRANSPARENT);
1028 SetTextColor(hdc, lpnmlvcd->clrText);
1029 }
1030
1031 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1032 {
1033 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1034 }
1035
1036 /* returns TRUE when repaint needed, FALSE otherwise */
1037 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1038 {
1039 MEASUREITEMSTRUCT mis;
1040 mis.CtlType = ODT_LISTVIEW;
1041 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1042 mis.itemID = -1;
1043 mis.itemWidth = 0;
1044 mis.itemData = 0;
1045 mis.itemHeight= infoPtr->nItemHeight;
1046 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1047 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1048 {
1049 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1050 return TRUE;
1051 }
1052 return FALSE;
1053 }
1054
1055 /******** Item iterator functions **********************************/
1056
1057 static RANGES ranges_create(int count);
1058 static void ranges_destroy(RANGES ranges);
1059 static BOOL ranges_add(RANGES ranges, RANGE range);
1060 static BOOL ranges_del(RANGES ranges, RANGE range);
1061 static void ranges_dump(RANGES ranges);
1062
1063 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1064 {
1065 RANGE range = { nItem, nItem + 1 };
1066
1067 return ranges_add(ranges, range);
1068 }
1069
1070 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1071 {
1072 RANGE range = { nItem, nItem + 1 };
1073
1074 return ranges_del(ranges, range);
1075 }
1076
1077 /***
1078 * ITERATOR DOCUMENTATION
1079 *
1080 * The iterator functions allow for easy, and convenient iteration
1081 * over items of interest in the list. Typically, you create a
1082 * iterator, use it, and destroy it, as such:
1083 * ITERATOR i;
1084 *
1085 * iterator_xxxitems(&i, ...);
1086 * while (iterator_{prev,next}(&i)
1087 * {
1088 * //code which uses i.nItem
1089 * }
1090 * iterator_destroy(&i);
1091 *
1092 * where xxx is either: framed, or visible.
1093 * Note that it is important that the code destroys the iterator
1094 * after it's done with it, as the creation of the iterator may
1095 * allocate memory, which thus needs to be freed.
1096 *
1097 * You can iterate both forwards, and backwards through the list,
1098 * by using iterator_next or iterator_prev respectively.
1099 *
1100 * Lower numbered items are draw on top of higher number items in
1101 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1102 * items may overlap). So, to test items, you should use
1103 * iterator_next
1104 * which lists the items top to bottom (in Z-order).
1105 * For drawing items, you should use
1106 * iterator_prev
1107 * which lists the items bottom to top (in Z-order).
1108 * If you keep iterating over the items after the end-of-items
1109 * marker (-1) is returned, the iterator will start from the
1110 * beginning. Typically, you don't need to test for -1,
1111 * because iterator_{next,prev} will return TRUE if more items
1112 * are to be iterated over, or FALSE otherwise.
1113 *
1114 * Note: the iterator is defined to be bidirectional. That is,
1115 * any number of prev followed by any number of next, or
1116 * five versa, should leave the iterator at the same item:
1117 * prev * n, next * n = next * n, prev * n
1118 *
1119 * The iterator has a notion of an out-of-order, special item,
1120 * which sits at the start of the list. This is used in
1121 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1122 * which needs to be first, as it may overlap other items.
1123 *
1124 * The code is a bit messy because we have:
1125 * - a special item to deal with
1126 * - simple range, or composite range
1127 * - empty range.
1128 * If you find bugs, or want to add features, please make sure you
1129 * always check/modify *both* iterator_prev, and iterator_next.
1130 */
1131
1132 /****
1133 * This function iterates through the items in increasing order,
1134 * but prefixed by the special item, then -1. That is:
1135 * special, 1, 2, 3, ..., n, -1.
1136 * Each item is listed only once.
1137 */
1138 static inline BOOL iterator_next(ITERATOR* i)
1139 {
1140 if (i->nItem == -1)
1141 {
1142 i->nItem = i->nSpecial;
1143 if (i->nItem != -1) return TRUE;
1144 }
1145 if (i->nItem == i->nSpecial)
1146 {
1147 if (i->ranges) i->index = 0;
1148 goto pickarange;
1149 }
1150
1151 i->nItem++;
1152 testitem:
1153 if (i->nItem == i->nSpecial) i->nItem++;
1154 if (i->nItem < i->range.upper) return TRUE;
1155
1156 pickarange:
1157 if (i->ranges)
1158 {
1159 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1160 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1161 else goto end;
1162 }
1163 else if (i->nItem >= i->range.upper) goto end;
1164
1165 i->nItem = i->range.lower;
1166 if (i->nItem >= 0) goto testitem;
1167 end:
1168 i->nItem = -1;
1169 return FALSE;
1170 }
1171
1172 /****
1173 * This function iterates through the items in decreasing order,
1174 * followed by the special item, then -1. That is:
1175 * n, n-1, ..., 3, 2, 1, special, -1.
1176 * Each item is listed only once.
1177 */
1178 static inline BOOL iterator_prev(ITERATOR* i)
1179 {
1180 BOOL start = FALSE;
1181
1182 if (i->nItem == -1)
1183 {
1184 start = TRUE;
1185 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1186 goto pickarange;
1187 }
1188 if (i->nItem == i->nSpecial)
1189 {
1190 i->nItem = -1;
1191 return FALSE;
1192 }
1193
1194 testitem:
1195 i->nItem--;
1196 if (i->nItem == i->nSpecial) i->nItem--;
1197 if (i->nItem >= i->range.lower) return TRUE;
1198
1199 pickarange:
1200 if (i->ranges)
1201 {
1202 if (i->index > 0)
1203 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1204 else goto end;
1205 }
1206 else if (!start && i->nItem < i->range.lower) goto end;
1207
1208 i->nItem = i->range.upper;
1209 if (i->nItem > 0) goto testitem;
1210 end:
1211 return (i->nItem = i->nSpecial) != -1;
1212 }
1213
1214 static RANGE iterator_range(const ITERATOR *i)
1215 {
1216 RANGE range;
1217
1218 if (!i->ranges) return i->range;
1219
1220 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1221 {
1222 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1223 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1224 }
1225 else range.lower = range.upper = 0;
1226
1227 return range;
1228 }
1229
1230 /***
1231 * Releases resources associated with this ierator.
1232 */
1233 static inline void iterator_destroy(const ITERATOR *i)
1234 {
1235 ranges_destroy(i->ranges);
1236 }
1237
1238 /***
1239 * Create an empty iterator.
1240 */
1241 static inline BOOL iterator_empty(ITERATOR* i)
1242 {
1243 ZeroMemory(i, sizeof(*i));
1244 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1245 return TRUE;
1246 }
1247
1248 /***
1249 * Create an iterator over a range.
1250 */
1251 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1252 {
1253 iterator_empty(i);
1254 i->range = range;
1255 return TRUE;
1256 }
1257
1258 /***
1259 * Create an iterator over a bunch of ranges.
1260 * Please note that the iterator will take ownership of the ranges,
1261 * and will free them upon destruction.
1262 */
1263 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1264 {
1265 iterator_empty(i);
1266 i->ranges = ranges;
1267 return TRUE;
1268 }
1269
1270 /***
1271 * Creates an iterator over the items which intersect lprc.
1272 */
1273 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1274 {
1275 RECT frame = *lprc, rcItem, rcTemp;
1276 POINT Origin;
1277
1278 /* in case we fail, we want to return an empty iterator */
1279 if (!iterator_empty(i)) return FALSE;
1280
1281 LISTVIEW_GetOrigin(infoPtr, &Origin);
1282
1283 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1284 OffsetRect(&frame, -Origin.x, -Origin.y);
1285
1286 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1287 {
1288 INT nItem;
1289
1290 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1291 {
1292 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1293 if (IntersectRect(&rcTemp, &rcItem, lprc))
1294 i->nSpecial = infoPtr->nFocusedItem;
1295 }
1296 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1297 /* to do better here, we need to have PosX, and PosY sorted */
1298 TRACE("building icon ranges:\n");
1299 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1300 {
1301 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1302 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1303 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1304 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1305 if (IntersectRect(&rcTemp, &rcItem, &frame))
1306 ranges_additem(i->ranges, nItem);
1307 }
1308 return TRUE;
1309 }
1310 else if (infoPtr->uView == LV_VIEW_DETAILS)
1311 {
1312 RANGE range;
1313
1314 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1315 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1316
1317 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1318 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1319 if (range.upper <= range.lower) return TRUE;
1320 if (!iterator_rangeitems(i, range)) return FALSE;
1321 TRACE(" report=%s\n", debugrange(&i->range));
1322 }
1323 else
1324 {
1325 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1326 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1327 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1328 INT nFirstCol;
1329 INT nLastCol;
1330 INT lower;
1331 RANGE item_range;
1332 INT nCol;
1333
1334 if (infoPtr->nItemWidth)
1335 {
1336 nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1337 nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1338 }
1339 else
1340 {
1341 nFirstCol = max(frame.left, 0);
1342 nLastCol = min(frame.right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1343 }
1344
1345 lower = nFirstCol * nPerCol + nFirstRow;
1346
1347 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1348 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1349
1350 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1351
1352 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1353 TRACE("building list ranges:\n");
1354 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1355 {
1356 item_range.lower = nCol * nPerCol + nFirstRow;
1357 if(item_range.lower >= infoPtr->nItemCount) break;
1358 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1359 TRACE(" list=%s\n", debugrange(&item_range));
1360 ranges_add(i->ranges, item_range);
1361 }
1362 }
1363
1364 return TRUE;
1365 }
1366
1367 /***
1368 * Creates an iterator over the items which intersect the visible region of hdc.
1369 */
1370 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1371 {
1372 POINT Origin, Position;
1373 RECT rcItem, rcClip;
1374 INT rgntype;
1375
1376 rgntype = GetClipBox(hdc, &rcClip);
1377 if (rgntype == NULLREGION) return iterator_empty(i);
1378 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1379 if (rgntype == SIMPLEREGION) return TRUE;
1380
1381 /* first deal with the special item */
1382 if (i->nSpecial != -1)
1383 {
1384 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1385 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1386 }
1387
1388 /* if we can't deal with the region, we'll just go with the simple range */
1389 LISTVIEW_GetOrigin(infoPtr, &Origin);
1390 TRACE("building visible range:\n");
1391 if (!i->ranges && i->range.lower < i->range.upper)
1392 {
1393 if (!(i->ranges = ranges_create(50))) return TRUE;
1394 if (!ranges_add(i->ranges, i->range))
1395 {
1396 ranges_destroy(i->ranges);
1397 i->ranges = 0;
1398 return TRUE;
1399 }
1400 }
1401
1402 /* now delete the invisible items from the list */
1403 while(iterator_next(i))
1404 {
1405 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1406 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1407 rcItem.top = Position.y + Origin.y;
1408 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1409 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1410 if (!RectVisible(hdc, &rcItem))
1411 ranges_delitem(i->ranges, i->nItem);
1412 }
1413 /* the iterator should restart on the next iterator_next */
1414 TRACE("done\n");
1415
1416 return TRUE;
1417 }
1418
1419 /******** Misc helper functions ************************************/
1420
1421 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1422 WPARAM wParam, LPARAM lParam, BOOL isW)
1423 {
1424 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1425 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1426 }
1427
1428 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1429 {
1430 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1431 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1432 }
1433
1434 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1435 {
1436 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1437 if(state == 1 || state == 2)
1438 {
1439 LVITEMW lvitem;
1440 state ^= 3;
1441 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1442 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1443 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1444 }
1445 }
1446
1447 /* this should be called after window style got updated,
1448 it used to reset view state to match current window style */
1449 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1450 {
1451 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1452 {
1453 case LVS_ICON:
1454 infoPtr->uView = LV_VIEW_ICON;
1455 break;
1456 case LVS_REPORT:
1457 infoPtr->uView = LV_VIEW_DETAILS;
1458 break;
1459 case LVS_SMALLICON:
1460 infoPtr->uView = LV_VIEW_SMALLICON;
1461 break;
1462 case LVS_LIST:
1463 infoPtr->uView = LV_VIEW_LIST;
1464 }
1465 }
1466
1467 /* computes next item id value */
1468 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1469 {
1470 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1471
1472 if (count > 0)
1473 {
1474 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1475 return lpID->id + 1;
1476 }
1477 return 0;
1478 }
1479
1480 /******** Internal API functions ************************************/
1481
1482 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1483 {
1484 static COLUMN_INFO mainItem;
1485
1486 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1487 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1488
1489 /* update cached column rectangles */
1490 if (infoPtr->colRectsDirty)
1491 {
1492 COLUMN_INFO *info;
1493 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1494 INT i;
1495
1496 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1497 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1498 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1499 }
1500 Ptr->colRectsDirty = FALSE;
1501 }
1502
1503 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1504 }
1505
1506 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1507 {
1508 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1509 HINSTANCE hInst;
1510
1511 if (infoPtr->hwndHeader) return 0;
1512
1513 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1514
1515 /* setup creation flags */
1516 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1517 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1518
1519 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1520
1521 /* create header */
1522 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1523 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1524 if (!infoPtr->hwndHeader) return -1;
1525
1526 /* set header unicode format */
1527 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1528
1529 /* set header font */
1530 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1531
1532 LISTVIEW_UpdateSize(infoPtr);
1533
1534 return 0;
1535 }
1536
1537 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1538 {
1539 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1540 }
1541
1542 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1543 {
1544 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1545 }
1546
1547 /* used to handle collapse main item column case */
1548 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1549 {
1550 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1551 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1552 }
1553
1554 /* Listview invalidation functions: use _only_ these functions to invalidate */
1555
1556 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1557 {
1558 return infoPtr->bRedraw;
1559 }
1560
1561 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1562 {
1563 if(!is_redrawing(infoPtr)) return;
1564 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1565 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1566 }
1567
1568 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1569 {
1570 RECT rcBox;
1571
1572 if(!is_redrawing(infoPtr)) return;
1573 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1574 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1575 }
1576
1577 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1578 {
1579 POINT Origin, Position;
1580 RECT rcBox;
1581
1582 if(!is_redrawing(infoPtr)) return;
1583 assert (infoPtr->uView == LV_VIEW_DETAILS);
1584 LISTVIEW_GetOrigin(infoPtr, &Origin);
1585 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1586 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1587 rcBox.top = 0;
1588 rcBox.bottom = infoPtr->nItemHeight;
1589 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1590 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1591 }
1592
1593 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1594 {
1595 LISTVIEW_InvalidateRect(infoPtr, NULL);
1596 }
1597
1598 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1599 {
1600 RECT rcCol;
1601
1602 if(!is_redrawing(infoPtr)) return;
1603 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1604 rcCol.top = infoPtr->rcList.top;
1605 rcCol.bottom = infoPtr->rcList.bottom;
1606 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1607 }
1608
1609 /***
1610 * DESCRIPTION:
1611 * Retrieves the number of items that can fit vertically in the client area.
1612 *
1613 * PARAMETER(S):
1614 * [I] infoPtr : valid pointer to the listview structure
1615 *
1616 * RETURN:
1617 * Number of items per row.
1618 */
1619 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1620 {
1621 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1622
1623 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1624 }
1625
1626 /***
1627 * DESCRIPTION:
1628 * Retrieves the number of items that can fit horizontally in the client
1629 * area.
1630 *
1631 * PARAMETER(S):
1632 * [I] infoPtr : valid pointer to the listview structure
1633 *
1634 * RETURN:
1635 * Number of items per column.
1636 */
1637 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1638 {
1639 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1640
1641 return max(nListHeight / infoPtr->nItemHeight, 1);
1642 }
1643
1644
1645 /*************************************************************************
1646 * LISTVIEW_ProcessLetterKeys
1647 *
1648 * Processes keyboard messages generated by pressing the letter keys
1649 * on the keyboard.
1650 * What this does is perform a case insensitive search from the
1651 * current position with the following quirks:
1652 * - If two chars or more are pressed in quick succession we search
1653 * for the corresponding string (e.g. 'abc').
1654 * - If there is a delay we wipe away the current search string and
1655 * restart with just that char.
1656 * - If the user keeps pressing the same character, whether slowly or
1657 * fast, so that the search string is entirely composed of this
1658 * character ('aaaaa' for instance), then we search for first item
1659 * that starting with that character.
1660 * - If the user types the above character in quick succession, then
1661 * we must also search for the corresponding string ('aaaaa'), and
1662 * go to that string if there is a match.
1663 *
1664 * PARAMETERS
1665 * [I] hwnd : handle to the window
1666 * [I] charCode : the character code, the actual character
1667 * [I] keyData : key data
1668 *
1669 * RETURNS
1670 *
1671 * Zero.
1672 *
1673 * BUGS
1674 *
1675 * - The current implementation has a list of characters it will
1676 * accept and it ignores everything else. In particular it will
1677 * ignore accentuated characters which seems to match what
1678 * Windows does. But I'm not sure it makes sense to follow
1679 * Windows there.
1680 * - We don't sound a beep when the search fails.
1681 *
1682 * SEE ALSO
1683 *
1684 * TREEVIEW_ProcessLetterKeys
1685 */
1686 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1687 {
1688 INT nItem;
1689 INT endidx,idx;
1690 LVITEMW item;
1691 WCHAR buffer[MAX_PATH];
1692 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1693
1694 /* simple parameter checking */
1695 if (!charCode || !keyData) return 0;
1696
1697 /* only allow the valid WM_CHARs through */
1698 if (!isalnumW(charCode) &&
1699 charCode != '.' && charCode != '`' && charCode != '!' &&
1700 charCode != '@' && charCode != '#' && charCode != '$' &&
1701 charCode != '%' && charCode != '^' && charCode != '&' &&
1702 charCode != '*' && charCode != '(' && charCode != ')' &&
1703 charCode != '-' && charCode != '_' && charCode != '+' &&
1704 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1705 charCode != '}' && charCode != '[' && charCode != '{' &&
1706 charCode != '/' && charCode != '?' && charCode != '>' &&
1707 charCode != '<' && charCode != ',' && charCode != '~')
1708 return 0;
1709
1710 /* if there's one item or less, there is no where to go */
1711 if (infoPtr->nItemCount <= 1) return 0;
1712
1713 /* update the search parameters */
1714 infoPtr->lastKeyPressTimestamp = GetTickCount();
1715 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1716 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1717 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1718 if (infoPtr->charCode != charCode)
1719 infoPtr->charCode = charCode = 0;
1720 } else {
1721 infoPtr->charCode=charCode;
1722 infoPtr->szSearchParam[0]=charCode;
1723 infoPtr->nSearchParamLength=1;
1724 /* Redundant with the 1 char string */
1725 charCode=0;
1726 }
1727
1728 /* and search from the current position */
1729 nItem=-1;
1730 if (infoPtr->nFocusedItem >= 0) {
1731 endidx=infoPtr->nFocusedItem;
1732 idx=endidx;
1733 /* if looking for single character match,
1734 * then we must always move forward
1735 */
1736 if (infoPtr->nSearchParamLength == 1)
1737 idx++;
1738 } else {
1739 endidx=infoPtr->nItemCount;
1740 idx=0;
1741 }
1742
1743 /* Let application handle this for virtual listview */
1744 if (infoPtr->dwStyle & LVS_OWNERDATA)
1745 {
1746 NMLVFINDITEMW nmlv;
1747 LVFINDINFOW lvfi;
1748
1749 ZeroMemory(&lvfi, sizeof(lvfi));
1750 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1751 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1752 lvfi.psz = infoPtr->szSearchParam;
1753 nmlv.iStart = idx;
1754 nmlv.lvfi = lvfi;
1755
1756 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1757
1758 if (nItem != -1)
1759 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1760
1761 return 0;
1762 }
1763
1764 do {
1765 if (idx == infoPtr->nItemCount) {
1766 if (endidx == infoPtr->nItemCount || endidx == 0)
1767 break;
1768 idx=0;
1769 }
1770
1771 /* get item */
1772 item.mask = LVIF_TEXT;
1773 item.iItem = idx;
1774 item.iSubItem = 0;
1775 item.pszText = buffer;
1776 item.cchTextMax = MAX_PATH;
1777 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1778
1779 /* check for a match */
1780 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1781 nItem=idx;
1782 break;
1783 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1784 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1785 /* This would work but we must keep looking for a longer match */
1786 nItem=idx;
1787 }
1788 idx++;
1789 } while (idx != endidx);
1790
1791 if (nItem != -1)
1792 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1793
1794 return 0;
1795 }
1796
1797 /*************************************************************************
1798 * LISTVIEW_UpdateHeaderSize [Internal]
1799 *
1800 * Function to resize the header control
1801 *
1802 * PARAMS
1803 * [I] hwnd : handle to a window
1804 * [I] nNewScrollPos : scroll pos to set
1805 *
1806 * RETURNS
1807 * None.
1808 */
1809 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1810 {
1811 RECT winRect;
1812 POINT point[2];
1813
1814 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1815
1816 if (!infoPtr->hwndHeader) return;
1817
1818 GetWindowRect(infoPtr->hwndHeader, &winRect);
1819 point[0].x = winRect.left;
1820 point[0].y = winRect.top;
1821 point[1].x = winRect.right;
1822 point[1].y = winRect.bottom;
1823
1824 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1825 point[0].x = -nNewScrollPos;
1826 point[1].x += nNewScrollPos;
1827
1828 SetWindowPos(infoPtr->hwndHeader,0,
1829 point[0].x,point[0].y,point[1].x,point[1].y,
1830 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1831 SWP_NOZORDER | SWP_NOACTIVATE);
1832 }
1833
1834 /***
1835 * DESCRIPTION:
1836 * Update the scrollbars. This functions should be called whenever
1837 * the content, size or view changes.
1838 *
1839 * PARAMETER(S):
1840 * [I] infoPtr : valid pointer to the listview structure
1841 *
1842 * RETURN:
1843 * None
1844 */
1845 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1846 {
1847 SCROLLINFO horzInfo, vertInfo;
1848 INT dx, dy;
1849
1850 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1851
1852 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1853 horzInfo.cbSize = sizeof(SCROLLINFO);
1854 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1855
1856 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1857 if (infoPtr->uView == LV_VIEW_LIST)
1858 {
1859 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1860 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1861
1862 /* scroll by at least one column per page */
1863 if(horzInfo.nPage < infoPtr->nItemWidth)
1864 horzInfo.nPage = infoPtr->nItemWidth;
1865
1866 if (infoPtr->nItemWidth)
1867 horzInfo.nPage /= infoPtr->nItemWidth;
1868 }
1869 else if (infoPtr->uView == LV_VIEW_DETAILS)
1870 {
1871 horzInfo.nMax = infoPtr->nItemWidth;
1872 }
1873 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1874 {
1875 RECT rcView;
1876
1877 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1878 }
1879
1880 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1881 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1882 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1883 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1884 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1885
1886 /* Setting the horizontal scroll can change the listview size
1887 * (and potentially everything else) so we need to recompute
1888 * everything again for the vertical scroll
1889 */
1890
1891 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1892 vertInfo.cbSize = sizeof(SCROLLINFO);
1893 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1894
1895 if (infoPtr->uView == LV_VIEW_DETAILS)
1896 {
1897 vertInfo.nMax = infoPtr->nItemCount;
1898
1899 /* scroll by at least one page */
1900 if(vertInfo.nPage < infoPtr->nItemHeight)
1901 vertInfo.nPage = infoPtr->nItemHeight;
1902
1903 if (infoPtr->nItemHeight > 0)
1904 vertInfo.nPage /= infoPtr->nItemHeight;
1905 }
1906 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1907 {
1908 RECT rcView;
1909
1910 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1911 }
1912
1913 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1914 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1915 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1916 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1917 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1918
1919 /* Change of the range may have changed the scroll pos. If so move the content */
1920 if (dx != 0 || dy != 0)
1921 {
1922 RECT listRect;
1923 listRect = infoPtr->rcList;
1924 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1925 SW_ERASE | SW_INVALIDATE);
1926 }
1927
1928 /* Update the Header Control */
1929 if (infoPtr->uView == LV_VIEW_DETAILS)
1930 {
1931 horzInfo.fMask = SIF_POS;
1932 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1933 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1934 }
1935 }
1936
1937
1938 /***
1939 * DESCRIPTION:
1940 * Shows/hides the focus rectangle.
1941 *
1942 * PARAMETER(S):
1943 * [I] infoPtr : valid pointer to the listview structure
1944 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1945 *
1946 * RETURN:
1947 * None
1948 */
1949 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1950 {
1951 HDC hdc;
1952
1953 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1954
1955 if (infoPtr->nFocusedItem < 0) return;
1956
1957 /* we need some gymnastics in ICON mode to handle large items */
1958 if (infoPtr->uView == LV_VIEW_ICON)
1959 {
1960 RECT rcBox;
1961
1962 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1963 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1964 {
1965 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1966 return;
1967 }
1968 }
1969
1970 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1971
1972 /* for some reason, owner draw should work only in report mode */
1973 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
1974 {
1975 DRAWITEMSTRUCT dis;
1976 LVITEMW item;
1977
1978 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1979 HFONT hOldFont = SelectObject(hdc, hFont);
1980
1981 item.iItem = infoPtr->nFocusedItem;
1982 item.iSubItem = 0;
1983 item.mask = LVIF_PARAM;
1984 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1985
1986 ZeroMemory(&dis, sizeof(dis));
1987 dis.CtlType = ODT_LISTVIEW;
1988 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1989 dis.itemID = item.iItem;
1990 dis.itemAction = ODA_FOCUS;
1991 if (fShow) dis.itemState |= ODS_FOCUS;
1992 dis.hwndItem = infoPtr->hwndSelf;
1993 dis.hDC = hdc;
1994 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1995 dis.itemData = item.lParam;
1996
1997 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1998
1999 SelectObject(hdc, hOldFont);
2000 }
2001 else
2002 {
2003 LISTVIEW_DrawFocusRect(infoPtr, hdc);
2004 }
2005 done:
2006 ReleaseDC(infoPtr->hwndSelf, hdc);
2007 }
2008
2009 /***
2010 * Invalidates all visible selected items.
2011 */
2012 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2013 {
2014 ITERATOR i;
2015
2016 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2017 while(iterator_next(&i))
2018 {
2019 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2020 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2021 }
2022 iterator_destroy(&i);
2023 }
2024
2025
2026 /***
2027 * DESCRIPTION: [INTERNAL]
2028 * Computes an item's (left,top) corner, relative to rcView.
2029 * That is, the position has NOT been made relative to the Origin.
2030 * This is deliberate, to avoid computing the Origin over, and
2031 * over again, when this function is called in a loop. Instead,
2032 * one can factor the computation of the Origin before the loop,
2033 * and offset the value returned by this function, on every iteration.
2034 *
2035 * PARAMETER(S):
2036 * [I] infoPtr : valid pointer to the listview structure
2037 * [I] nItem : item number
2038 * [O] lpptOrig : item top, left corner
2039 *
2040 * RETURN:
2041 * None.
2042 */
2043 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2044 {
2045 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2046
2047 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2048 {
2049 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2050 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2051 }
2052 else if (infoPtr->uView == LV_VIEW_LIST)
2053 {
2054 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2055 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2056 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2057 }
2058 else /* LV_VIEW_DETAILS */
2059 {
2060 lpptPosition->x = REPORT_MARGINX;
2061 /* item is always at zero indexed column */
2062 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2063 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2064 lpptPosition->y = nItem * infoPtr->nItemHeight;
2065 }
2066 }
2067
2068 /***
2069 * DESCRIPTION: [INTERNAL]
2070 * Compute the rectangles of an item. This is to localize all
2071 * the computations in one place. If you are not interested in some
2072 * of these values, simply pass in a NULL -- the function is smart
2073 * enough to compute only what's necessary. The function computes
2074 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2075 * one, the BOX rectangle. This rectangle is very cheap to compute,
2076 * and is guaranteed to contain all the other rectangles. Computing
2077 * the ICON rect is also cheap, but all the others are potentially
2078 * expensive. This gives an easy and effective optimization when
2079 * searching (like point inclusion, or rectangle intersection):
2080 * first test against the BOX, and if TRUE, test against the desired
2081 * rectangle.
2082 * If the function does not have all the necessary information
2083 * to computed the requested rectangles, will crash with a
2084 * failed assertion. This is done so we catch all programming
2085 * errors, given that the function is called only from our code.
2086 *
2087 * We have the following 'special' meanings for a few fields:
2088 * * If LVIS_FOCUSED is set, we assume the item has the focus
2089 * This is important in ICON mode, where it might get a larger
2090 * then usual rectangle
2091 *
2092 * Please note that subitem support works only in REPORT mode.
2093 *
2094 * PARAMETER(S):
2095 * [I] infoPtr : valid pointer to the listview structure
2096 * [I] lpLVItem : item to compute the measures for
2097 * [O] lprcBox : ptr to Box rectangle
2098 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2099 * [0] lprcSelectBox : ptr to select box rectangle
2100 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2101 * [O] lprcIcon : ptr to Icon rectangle
2102 * Same as LVM_GETITEMRECT with LVIR_ICON
2103 * [O] lprcStateIcon: ptr to State Icon rectangle
2104 * [O] lprcLabel : ptr to Label rectangle
2105 * Same as LVM_GETITEMRECT with LVIR_LABEL
2106 *
2107 * RETURN:
2108 * None.
2109 */
2110 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2111 LPRECT lprcBox, LPRECT lprcSelectBox,
2112 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2113 {
2114 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2115 RECT Box, SelectBox, Icon, Label;
2116 COLUMN_INFO *lpColumnInfo = NULL;
2117 SIZE labelSize = { 0, 0 };
2118
2119 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2120
2121 /* Be smart and try to figure out the minimum we have to do */
2122 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2123 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2124 {
2125 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2126 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2127 }
2128 if (lprcSelectBox) doSelectBox = TRUE;
2129 if (lprcLabel) doLabel = TRUE;
2130 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2131 if (doSelectBox)
2132 {
2133 doIcon = TRUE;
2134 doLabel = TRUE;
2135 }
2136
2137 /************************************************************/
2138 /* compute the box rectangle (it should be cheap to do) */
2139 /************************************************************/
2140 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2141 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2142
2143 if (lpLVItem->iSubItem)
2144 {
2145 Box = lpColumnInfo->rcHeader;
2146 }
2147 else
2148 {
2149 Box.left = 0;
2150 Box.right = infoPtr->nItemWidth;
2151 }
2152 Box.top = 0;
2153 Box.bottom = infoPtr->nItemHeight;
2154
2155 /******************************************************************/
2156 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2157 /******************************************************************/
2158 if (doIcon)
2159 {
2160 LONG state_width = 0;
2161
2162 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2163 state_width = infoPtr->iconStateSize.cx;
2164
2165 if (infoPtr->uView == LV_VIEW_ICON)
2166 {
2167 Icon.left = Box.left + state_width;
2168 if (infoPtr->himlNormal)
2169 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2170 Icon.top = Box.top + ICON_TOP_PADDING;
2171 Icon.right = Icon.left;
2172 Icon.bottom = Icon.top;
2173 if (infoPtr->himlNormal)
2174 {
2175 Icon.right += infoPtr->iconSize.cx;
2176 Icon.bottom += infoPtr->iconSize.cy;
2177 }
2178 }
2179 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2180 {
2181 Icon.left = Box.left + state_width;
2182
2183 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2184 {
2185 /* we need the indent in report mode */
2186 assert(lpLVItem->mask & LVIF_INDENT);
2187 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2188 }
2189
2190 Icon.top = Box.top;
2191 Icon.right = Icon.left;
2192 if (infoPtr->himlSmall &&
2193 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2194 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2195 Icon.right += infoPtr->iconSize.cx;
2196 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2197 }
2198 if(lprcIcon) *lprcIcon = Icon;
2199 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2200
2201 /* TODO: is this correct? */
2202 if (lprcStateIcon)
2203 {
2204 lprcStateIcon->left = Icon.left - state_width;
2205 lprcStateIcon->right = Icon.left;
2206 lprcStateIcon->top = Icon.top;
2207 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2208 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2209 }
2210 }
2211 else Icon.right = 0;
2212
2213 /************************************************************/
2214 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2215 /************************************************************/
2216 if (doLabel)
2217 {
2218 /* calculate how far to the right can the label stretch */
2219 Label.right = Box.right;
2220 if (infoPtr->uView == LV_VIEW_DETAILS)
2221 {
2222 if (lpLVItem->iSubItem == 0)
2223 {
2224 /* we need a zero based rect here */
2225 Label = lpColumnInfo->rcHeader;
2226 OffsetRect(&Label, -Label.left, 0);
2227 }
2228 }
2229
2230 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2231 {
2232 labelSize.cx = infoPtr->nItemWidth;
2233 labelSize.cy = infoPtr->nItemHeight;
2234 goto calc_label;
2235 }
2236
2237 /* we need the text in non owner draw mode */
2238 assert(lpLVItem->mask & LVIF_TEXT);
2239 if (is_textT(lpLVItem->pszText, TRUE))
2240 {
2241 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2242 HDC hdc = GetDC(infoPtr->hwndSelf);
2243 HFONT hOldFont = SelectObject(hdc, hFont);
2244 UINT uFormat;
2245 RECT rcText;
2246
2247 /* compute rough rectangle where the label will go */
2248 SetRectEmpty(&rcText);
2249 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2250 rcText.bottom = infoPtr->nItemHeight;
2251 if (infoPtr->uView == LV_VIEW_ICON)
2252 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2253
2254 /* now figure out the flags */
2255 if (infoPtr->uView == LV_VIEW_ICON)
2256 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2257 else
2258 uFormat = LV_SL_DT_FLAGS;
2259
2260 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2261
2262 if (rcText.right != rcText.left)
2263 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2264
2265 labelSize.cy = rcText.bottom - rcText.top;
2266
2267 SelectObject(hdc, hOldFont);
2268 ReleaseDC(infoPtr->hwndSelf, hdc);
2269 }
2270
2271 calc_label:
2272 if (infoPtr->uView == LV_VIEW_ICON)
2273 {
2274 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2275 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2276 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2277 Label.right = Label.left + labelSize.cx;
2278 Label.bottom = Label.top + infoPtr->nItemHeight;
2279 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2280 {
2281 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2282 labelSize.cy /= infoPtr->ntmHeight;
2283 labelSize.cy = max(labelSize.cy, 1);
2284 labelSize.cy *= infoPtr->ntmHeight;
2285 }
2286 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2287 }
2288 else if (infoPtr->uView == LV_VIEW_DETAILS)
2289 {
2290 Label.left = Icon.right;
2291 Label.top = Box.top;
2292 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2293 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2294 Label.bottom = Label.top + infoPtr->nItemHeight;
2295 }
2296 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2297 {
2298 Label.left = Icon.right;
2299 Label.top = Box.top;
2300 Label.right = min(Label.left + labelSize.cx, Label.right);
2301 Label.bottom = Label.top + infoPtr->nItemHeight;
2302 }
2303
2304 if (lprcLabel) *lprcLabel = Label;
2305 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2306 }
2307
2308 /************************************************************/
2309 /* compute SELECT bounding box */
2310 /************************************************************/
2311 if (doSelectBox)
2312 {
2313 if (infoPtr->uView == LV_VIEW_DETAILS)
2314 {
2315 SelectBox.left = Icon.left;
2316 SelectBox.top = Box.top;
2317 SelectBox.bottom = Box.bottom;
2318
2319 if (labelSize.cx)
2320 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2321 else
2322 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2323 }
2324 else
2325 {
2326 UnionRect(&SelectBox, &Icon, &Label);
2327 }
2328 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2329 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2330 }
2331
2332 /* Fix the Box if necessary */
2333 if (lprcBox)
2334 {
2335 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2336 else *lprcBox = Box;
2337 }
2338 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2339 }
2340
2341 /***
2342 * DESCRIPTION: [INTERNAL]
2343 *
2344 * PARAMETER(S):
2345 * [I] infoPtr : valid pointer to the listview structure
2346 * [I] nItem : item number
2347 * [O] lprcBox : ptr to Box rectangle
2348 *
2349 * RETURN:
2350 * None.
2351 */
2352 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2353 {
2354 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2355 POINT Position, Origin;
2356 LVITEMW lvItem;
2357
2358 LISTVIEW_GetOrigin(infoPtr, &Origin);
2359 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2360
2361 /* Be smart and try to figure out the minimum we have to do */
2362 lvItem.mask = 0;
2363 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2364 lvItem.mask |= LVIF_TEXT;
2365 lvItem.iItem = nItem;
2366 lvItem.iSubItem = 0;
2367 lvItem.pszText = szDispText;
2368 lvItem.cchTextMax = DISP_TEXT_SIZE;
2369 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2370 if (infoPtr->uView == LV_VIEW_ICON)
2371 {
2372 lvItem.mask |= LVIF_STATE;
2373 lvItem.stateMask = LVIS_FOCUSED;
2374 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2375 }
2376 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2377
2378 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2379 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2380 {
2381 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2382 }
2383 else
2384 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2385 }
2386
2387 /* LISTVIEW_MapIdToIndex helper */
2388 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2389 {
2390 ITEM_ID *id1 = (ITEM_ID*)p1;
2391 ITEM_ID *id2 = (ITEM_ID*)p2;
2392
2393 if (id1->id == id2->id) return 0;
2394
2395 return (id1->id < id2->id) ? -1 : 1;
2396 }
2397
2398 /***
2399 * DESCRIPTION:
2400 * Returns the item index for id specified.
2401 *
2402 * PARAMETER(S):
2403 * [I] infoPtr : valid pointer to the listview structure
2404 * [I] iID : item id to get index for
2405 *
2406 * RETURN:
2407 * Item index, or -1 on failure.
2408 */
2409 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2410 {
2411 ITEM_ID ID;
2412 INT index;
2413
2414 TRACE("iID=%d\n", iID);
2415
2416 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2417 if (infoPtr->nItemCount == 0) return -1;
2418
2419 ID.id = iID;
2420 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, &MapIdSearchCompare, 0, DPAS_SORTED);
2421
2422 if (index != -1)
2423 {
2424 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2425 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2426 }
2427
2428 return -1;
2429 }
2430
2431 /***
2432 * DESCRIPTION:
2433 * Returns the item id for index given.
2434 *
2435 * PARAMETER(S):
2436 * [I] infoPtr : valid pointer to the listview structure
2437 * [I] iItem : item index to get id for
2438 *
2439 * RETURN:
2440 * Item id.
2441 */
2442 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2443 {
2444 ITEM_INFO *lpItem;
2445 HDPA hdpaSubItems;
2446
2447 TRACE("iItem=%d\n", iItem);
2448
2449 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2450 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2451
2452 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2453 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2454
2455 return lpItem->id->id;
2456 }
2457
2458 /***
2459 * DESCRIPTION:
2460 * Returns the current icon position, and advances it along the top.
2461 * The returned position is not offset by Origin.
2462 *
2463 * PARAMETER(S):
2464 * [I] infoPtr : valid pointer to the listview structure
2465 * [O] lpPos : will get the current icon position
2466 *
2467 * RETURN:
2468 * None
2469 */
2470 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2471 {
2472 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2473
2474 *lpPos = infoPtr->currIconPos;
2475
2476 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2477 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2478
2479 infoPtr->currIconPos.x = 0;
2480 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2481 }
2482
2483
2484 /***
2485 * DESCRIPTION:
2486 * Returns the current icon position, and advances it down the left edge.
2487 * The returned position is not offset by Origin.
2488 *
2489 * PARAMETER(S):
2490 * [I] infoPtr : valid pointer to the listview structure
2491 * [O] lpPos : will get the current icon position
2492 *
2493 * RETURN:
2494 * None
2495 */
2496 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2497 {
2498 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2499
2500 *lpPos = infoPtr->currIconPos;
2501
2502 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2503 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2504
2505 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2506 infoPtr->currIconPos.y = 0;
2507 }
2508
2509
2510 /***
2511 * DESCRIPTION:
2512 * Moves an icon to the specified position.
2513 * It takes care of invalidating the item, etc.
2514 *
2515 * PARAMETER(S):
2516 * [I] infoPtr : valid pointer to the listview structure
2517 * [I] nItem : the item to move
2518 * [I] lpPos : the new icon position
2519 * [I] isNew : flags the item as being new
2520 *
2521 * RETURN:
2522 * Success: TRUE
2523 * Failure: FALSE
2524 */
2525 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2526 {
2527 POINT old;
2528
2529 if (!isNew)
2530 {
2531 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2532 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2533
2534 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2535 LISTVIEW_InvalidateItem(infoPtr, nItem);
2536 }
2537
2538 /* Allocating a POINTER for every item is too resource intensive,
2539 * so we'll keep the (x,y) in different arrays */
2540 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2541 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2542
2543 LISTVIEW_InvalidateItem(infoPtr, nItem);
2544
2545 return TRUE;
2546 }
2547
2548 /***
2549 * DESCRIPTION:
2550 * Arranges listview items in icon display mode.
2551 *
2552 * PARAMETER(S):
2553 * [I] infoPtr : valid pointer to the listview structure
2554 * [I] nAlignCode : alignment code
2555 *
2556 * RETURN:
2557 * SUCCESS : TRUE
2558 * FAILURE : FALSE
2559 */
2560 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2561 {
2562 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2563 POINT pos;
2564 INT i;
2565
2566 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2567
2568 TRACE("nAlignCode=%d\n", nAlignCode);
2569
2570 if (nAlignCode == LVA_DEFAULT)
2571 {
2572 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2573 else nAlignCode = LVA_ALIGNTOP;
2574 }
2575
2576 switch (nAlignCode)
2577 {
2578 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2579 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2580 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2581 default: return FALSE;
2582 }
2583
2584 infoPtr->bAutoarrange = TRUE;
2585 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2586 for (i = 0; i < infoPtr->nItemCount; i++)
2587 {
2588 next_pos(infoPtr, &pos);
2589 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2590 }
2591
2592 return TRUE;
2593 }
2594
2595 /***
2596 * DESCRIPTION:
2597 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2598 * For LVS_REPORT always returns empty rectangle.
2599 *
2600 * PARAMETER(S):
2601 * [I] infoPtr : valid pointer to the listview structure
2602 * [O] lprcView : bounding rectangle
2603 *
2604 * RETURN:
2605 * SUCCESS : TRUE
2606 * FAILURE : FALSE
2607 */
2608 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2609 {
2610 INT i, x, y;
2611
2612 SetRectEmpty(lprcView);
2613
2614 switch (infoPtr->uView)
2615 {
2616 case LV_VIEW_ICON:
2617 case LV_VIEW_SMALLICON:
2618 for (i = 0; i < infoPtr->nItemCount; i++)
2619 {
2620 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2621 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2622 lprcView->right = max(lprcView->right, x);
2623 lprcView->bottom = max(lprcView->bottom, y);
2624 }
2625 if (infoPtr->nItemCount > 0)
2626 {
2627 lprcView->right += infoPtr->nItemWidth;
2628 lprcView->bottom += infoPtr->nItemHeight;
2629 }
2630 break;
2631
2632 case LV_VIEW_LIST:
2633 y = LISTVIEW_GetCountPerColumn(infoPtr);
2634 x = infoPtr->nItemCount / y;
2635 if (infoPtr->nItemCount % y) x++;
2636 lprcView->right = x * infoPtr->nItemWidth;
2637 lprcView->bottom = y * infoPtr->nItemHeight;
2638 break;
2639 }
2640 }
2641
2642 /***
2643 * DESCRIPTION:
2644 * Retrieves the bounding rectangle of all the items.
2645 *
2646 * PARAMETER(S):
2647 * [I] infoPtr : valid pointer to the listview structure
2648 * [O] lprcView : bounding rectangle
2649 *
2650 * RETURN:
2651 * SUCCESS : TRUE
2652 * FAILURE : FALSE
2653 */
2654 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2655 {
2656 POINT ptOrigin;
2657
2658 TRACE("(lprcView=%p)\n", lprcView);
2659
2660 if (!lprcView) return FALSE;
2661
2662 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2663
2664 if (infoPtr->uView != LV_VIEW_DETAILS)
2665 {
2666 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2667 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2668 }
2669
2670 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2671
2672 return TRUE;
2673 }
2674
2675 /***
2676 * DESCRIPTION:
2677 * Retrieves the subitem pointer associated with the subitem index.
2678 *
2679 * PARAMETER(S):
2680 * [I] hdpaSubItems : DPA handle for a specific item
2681 * [I] nSubItem : index of subitem
2682 *
2683 * RETURN:
2684 * SUCCESS : subitem pointer
2685 * FAILURE : NULL
2686 */
2687 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2688 {
2689 SUBITEM_INFO *lpSubItem;
2690 INT i;
2691
2692 /* we should binary search here if need be */
2693 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2694 {
2695 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2696 if (lpSubItem->iSubItem == nSubItem)
2697 return lpSubItem;
2698 }
2699
2700 return NULL;
2701 }
2702
2703
2704 /***
2705 * DESCRIPTION:
2706 * Calculates the desired item width.
2707 *
2708 * PARAMETER(S):
2709 * [I] infoPtr : valid pointer to the listview structure
2710 *
2711 * RETURN:
2712 * The desired item width.
2713 */
2714 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2715 {
2716 INT nItemWidth = 0;
2717
2718 TRACE("uView=%d\n", infoPtr->uView);
2719
2720 if (infoPtr->uView == LV_VIEW_ICON)
2721 nItemWidth = infoPtr->iconSpacing.cx;
2722 else if (infoPtr->uView == LV_VIEW_DETAILS)
2723 {
2724 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2725 {
2726 RECT rcHeader;
2727 INT index;
2728
2729 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2730 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2731
2732 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2733 nItemWidth = rcHeader.right;
2734 }
2735 }
2736 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2737 {
2738 INT i;
2739
2740 for (i = 0; i < infoPtr->nItemCount; i++)
2741 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2742
2743 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2744 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2745
2746 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2747 }
2748
2749 return nItemWidth;
2750 }
2751
2752 /***
2753 * DESCRIPTION:
2754 * Calculates the desired item height.
2755 *
2756 * PARAMETER(S):
2757 * [I] infoPtr : valid pointer to the listview structure
2758 *
2759 * RETURN:
2760 * The desired item height.
2761 */
2762 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2763 {
2764 INT nItemHeight;
2765
2766 TRACE("uView=%d\n", infoPtr->uView);
2767
2768 if (infoPtr->uView == LV_VIEW_ICON)
2769 nItemHeight = infoPtr->iconSpacing.cy;
2770 else
2771 {
2772 nItemHeight = infoPtr->ntmHeight;
2773 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2774 nItemHeight++;
2775 if (infoPtr->himlState)
2776 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2777 if (infoPtr->himlSmall)
2778 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2779 if (infoPtr->himlState || infoPtr->himlSmall)
2780 nItemHeight += HEIGHT_PADDING;
2781 if (infoPtr->nMeasureItemHeight > 0)
2782 nItemHeight = infoPtr->nMeasureItemHeight;
2783 }
2784
2785 return max(nItemHeight, 1);
2786 }
2787
2788 /***
2789 * DESCRIPTION:
2790 * Updates the width, and height of an item.
2791 *
2792 * PARAMETER(S):
2793 * [I] infoPtr : valid pointer to the listview structure
2794 *
2795 * RETURN:
2796 * None.
2797 */
2798 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2799 {
2800 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2801 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2802 }
2803
2804
2805 /***
2806 * DESCRIPTION:
2807 * Retrieves and saves important text metrics info for the current
2808 * Listview font.
2809 *
2810 * PARAMETER(S):
2811 * [I] infoPtr : valid pointer to the listview structure
2812 *
2813 */
2814 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2815 {
2816 HDC hdc = GetDC(infoPtr->hwndSelf);
2817 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2818 HFONT hOldFont = SelectObject(hdc, hFont);
2819 TEXTMETRICW tm;
2820 SIZE sz;
2821
2822 if (GetTextMetricsW(hdc, &tm))
2823 {
2824 infoPtr->ntmHeight = tm.tmHeight;
2825 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2826 }
2827
2828 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2829 infoPtr->nEllipsisWidth = sz.cx;
2830
2831 SelectObject(hdc, hOldFont);
2832 ReleaseDC(infoPtr->hwndSelf, hdc);
2833
2834 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2835 }
2836
2837 /***
2838 * DESCRIPTION:
2839 * A compare function for ranges
2840 *
2841 * PARAMETER(S)
2842 * [I] range1 : pointer to range 1;
2843 * [I] range2 : pointer to range 2;
2844 * [I] flags : flags
2845 *
2846 * RETURNS:
2847 * > 0 : if range 1 > range 2
2848 * < 0 : if range 2 > range 1
2849 * = 0 : if range intersects range 2
2850 */
2851 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2852 {
2853 INT cmp;
2854
2855 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2856 cmp = -1;
2857 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2858 cmp = 1;
2859 else
2860 cmp = 0;
2861
2862 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2863
2864 return cmp;
2865 }
2866
2867 #if DEBUG_RANGES
2868 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2869 #else
2870 #define ranges_check(ranges, desc) do { } while(0)
2871 #endif
2872
2873 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2874 {
2875 INT i;
2876 RANGE *prev, *curr;
2877
2878 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2879 assert (ranges);
2880 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2881 ranges_dump(ranges);
2882 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2883 {
2884 prev = DPA_GetPtr(ranges->hdpa, 0);
2885 assert (prev->lower >= 0 && prev->lower < prev->upper);
2886 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2887 {
2888 curr = DPA_GetPtr(ranges->hdpa, i);
2889 assert (prev->upper <= curr->lower);
2890 assert (curr->lower < curr->upper);
2891 prev = curr;
2892 }
2893 }
2894 TRACE("--- Done checking---\n");
2895 }
2896
2897 static RANGES ranges_create(int count)
2898 {
2899 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2900 if (!ranges) return NULL;
2901 ranges->hdpa = DPA_Create(count);
2902 if (ranges->hdpa) return ranges;
2903 Free(ranges);
2904 return NULL;
2905 }
2906
2907 static void ranges_clear(RANGES ranges)
2908 {
2909 INT i;
2910
2911 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2912 Free(DPA_GetPtr(ranges->hdpa, i));
2913 DPA_DeleteAllPtrs(ranges->hdpa);
2914 }
2915
2916
2917 static void ranges_destroy(RANGES ranges)
2918 {
2919 if (!ranges) return;
2920 ranges_clear(ranges);
2921 DPA_Destroy(ranges->hdpa);
2922 Free(ranges);
2923 }
2924
2925 static RANGES ranges_clone(RANGES ranges)
2926 {
2927 RANGES clone;
2928 INT i;
2929
2930 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2931
2932 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2933 {
2934 RANGE *newrng = Alloc(sizeof(RANGE));
2935 if (!newrng) goto fail;
2936 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2937 DPA_SetPtr(clone->hdpa, i, newrng);
2938 }
2939 return clone;
2940
2941 fail:
2942 TRACE ("clone failed\n");
2943 ranges_destroy(clone);
2944 return NULL;
2945 }
2946
2947 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2948 {
2949 INT i;
2950
2951 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2952 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2953
2954 return ranges;
2955 }
2956
2957 static void ranges_dump(RANGES ranges)
2958 {
2959 INT i;
2960
2961 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2962 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2963 }
2964
2965 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2966 {
2967 RANGE srchrng = { nItem, nItem + 1 };
2968
2969 TRACE("(nItem=%d)\n", nItem);
2970 ranges_check(ranges, "before contain");
2971 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2972 }
2973
2974 static INT ranges_itemcount(RANGES ranges)
2975 {
2976 INT i, count = 0;
2977
2978 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2979 {
2980 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2981 count += sel->upper - sel->lower;
2982 }
2983
2984 return count;
2985 }
2986
2987 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2988 {
2989 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2990 INT index;
2991
2992 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2993 if (index == -1) return TRUE;
2994
2995 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2996 {
2997 chkrng = DPA_GetPtr(ranges->hdpa, index);
2998 if (chkrng->lower >= nItem)
2999 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3000 if (chkrng->upper > nItem)
3001 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3002 }
3003 return TRUE;
3004 }
3005
3006 static BOOL ranges_add(RANGES ranges, RANGE range)
3007 {
3008 RANGE srchrgn;
3009 INT index;
3010
3011 TRACE("(%s)\n", debugrange(&range));
3012 ranges_check(ranges, "before add");
3013
3014 /* try find overlapping regions first */
3015 srchrgn.lower = range.lower - 1;
3016 srchrgn.upper = range.upper + 1;
3017 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3018
3019 if (index == -1)
3020 {
3021 RANGE *newrgn;
3022
3023 TRACE("Adding new range\n");
3024
3025 /* create the brand new range to insert */
3026 newrgn = Alloc(sizeof(RANGE));
3027 if(!newrgn) goto fail;
3028 *newrgn = range;
3029
3030 /* figure out where to insert it */
3031 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3032 TRACE("index=%d\n", index);
3033 if (index == -1) index = 0;
3034
3035 /* and get it over with */
3036 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3037 {
3038 Free(newrgn);
3039 goto fail;
3040 }
3041 }
3042 else
3043 {
3044 RANGE *chkrgn, *mrgrgn;
3045 INT fromindex, mergeindex;
3046
3047 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3048 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3049
3050 chkrgn->lower = min(range.lower, chkrgn->lower);
3051 chkrgn->upper = max(range.upper, chkrgn->upper);
3052
3053 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3054
3055 /* merge now common ranges */
3056 fromindex = 0;
3057 srchrgn.lower = chkrgn->lower - 1;
3058 srchrgn.upper = chkrgn->upper + 1;
3059
3060 do
3061 {
3062 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3063 if (mergeindex == -1) break;
3064 if (mergeindex == index)
3065 {
3066 fromindex = index + 1;
3067 continue;
3068 }
3069
3070 TRACE("Merge with index %i\n", mergeindex);
3071
3072 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3073 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3074 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3075 Free(mrgrgn);
3076 DPA_DeletePtr(ranges->hdpa, mergeindex);
3077 if (mergeindex < index) index --;
3078 } while(1);
3079 }
3080
3081 ranges_check(ranges, "after add");
3082 return TRUE;
3083
3084 fail:
3085 ranges_check(ranges, "failed add");
3086 return FALSE;
3087 }
3088
3089 static BOOL ranges_del(RANGES ranges, RANGE range)
3090 {
3091 RANGE *chkrgn;
3092 INT index;
3093
3094 TRACE("(%s)\n", debugrange(&range));
3095 ranges_check(ranges, "before del");
3096
3097 /* we don't use DPAS_SORTED here, since we need *
3098 * to find the first overlapping range */
3099 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3100 while(index != -1)
3101 {
3102 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3103
3104 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3105
3106 /* case 1: Same range */
3107 if ( (chkrgn->upper == range.upper) &&
3108 (chkrgn->lower == range.lower) )
3109 {
3110 DPA_DeletePtr(ranges->hdpa, index);
3111 break;
3112 }
3113 /* case 2: engulf */
3114 else if ( (chkrgn->upper <= range.upper) &&
3115 (chkrgn->lower >= range.lower) )
3116 {
3117 DPA_DeletePtr(ranges->hdpa, index);
3118 }
3119 /* case 3: overlap upper */
3120 else if ( (chkrgn->upper <= range.upper) &&
3121 (chkrgn->lower < range.lower) )
3122 {
3123 chkrgn->upper = range.lower;
3124 }
3125 /* case 4: overlap lower */
3126 else if ( (chkrgn->upper > range.upper) &&
3127 (chkrgn->lower >= range.lower) )
3128 {
3129 chkrgn->lower = range.upper;
3130 break;
3131 }
3132 /* case 5: fully internal */
3133 else
3134 {
3135 RANGE tmprgn = *chkrgn, *newrgn;
3136
3137 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3138 newrgn->lower = chkrgn->lower;
3139 newrgn->upper = range.lower;
3140 chkrgn->lower = range.upper;
3141 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3142 {
3143 Free(newrgn);
3144 goto fail;
3145 }
3146 chkrgn = &tmprgn;
3147 break;
3148 }
3149
3150 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3151 }
3152
3153 ranges_check(ranges, "after del");
3154 return TRUE;
3155
3156 fail:
3157 ranges_check(ranges, "failed del");
3158 return FALSE;
3159 }
3160
3161 /***
3162 * DESCRIPTION:
3163 * Removes all selection ranges
3164 *
3165 * Parameters(s):
3166 * [I] infoPtr : valid pointer to the listview structure
3167 * [I] toSkip : item range to skip removing the selection
3168 *
3169 * RETURNS:
3170 * SUCCESS : TRUE
3171 * FAILURE : FALSE
3172 */
3173 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3174 {
3175 LVITEMW lvItem;
3176 ITERATOR i;
3177 RANGES clone;
3178
3179 TRACE("()\n");
3180
3181 lvItem.state = 0;
3182 lvItem.stateMask = LVIS_SELECTED;
3183
3184 /* need to clone the DPA because callbacks can change it */
3185 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3186 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3187 while(iterator_next(&i))
3188 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3189 /* note that the iterator destructor will free the cloned range */
3190 iterator_destroy(&i);
3191
3192 return TRUE;
3193 }
3194
3195 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3196 {
3197 RANGES toSkip;
3198
3199 if (!(toSkip = ranges_create(1))) return FALSE;
3200 if (nItem != -1) ranges_additem(toSkip, nItem);
3201 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3202 ranges_destroy(toSkip);
3203 return TRUE;
3204 }
3205
3206 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3207 {
3208 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3209 }
3210
3211 /***
3212 * DESCRIPTION:
3213 * Retrieves the number of items that are marked as selected.
3214 *
3215 * PARAMETER(S):
3216 * [I] infoPtr : valid pointer to the listview structure
3217 *
3218 * RETURN:
3219 * Number of items selected.
3220 */
3221 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3222 {
3223 INT nSelectedCount = 0;
3224
3225 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3226 {
3227 INT i;
3228 for (i = 0; i < infoPtr->nItemCount; i++)
3229 {
3230 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3231 nSelectedCount++;
3232 }
3233 }
3234 else
3235 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3236
3237 TRACE("nSelectedCount=%d\n", nSelectedCount);
3238 return nSelectedCount;
3239 }
3240
3241 /***
3242 * DESCRIPTION:
3243 * Manages the item focus.
3244 *
3245 * PARAMETER(S):
3246 * [I] infoPtr : valid pointer to the listview structure
3247 * [I] nItem : item index
3248 *
3249 * RETURN:
3250 * TRUE : focused item changed
3251 * FALSE : focused item has NOT changed
3252 */
3253 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3254 {
3255 INT oldFocus = infoPtr->nFocusedItem;
3256 LVITEMW lvItem;
3257
3258 if (nItem == infoPtr->nFocusedItem) return FALSE;
3259
3260 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3261 lvItem.stateMask = LVIS_FOCUSED;
3262 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3263
3264 return oldFocus != infoPtr->nFocusedItem;
3265 }
3266
3267 /* Helper function for LISTVIEW_ShiftIndices *only* */
3268 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3269 {
3270 if (nShiftItem < nItem) return nShiftItem;
3271
3272 if (nShiftItem > nItem) return nShiftItem + direction;
3273
3274 if (direction > 0) return nShiftItem + direction;
3275
3276 return min(nShiftItem, infoPtr->nItemCount - 1);
3277 }
3278
3279 /**
3280 * DESCRIPTION:
3281 * Updates the various indices after an item has been inserted or deleted.
3282 *
3283 * PARAMETER(S):
3284 * [I] infoPtr : valid pointer to the listview structure
3285 * [I] nItem : item index
3286 * [I] direction : Direction of shift, +1 or -1.
3287 *
3288 * RETURN:
3289 * None
3290 */
3291 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3292 {
3293 INT nNewFocus;
3294 BOOL bOldChange;
3295
3296 /* temporarily disable change notification while shifting items */
3297 bOldChange = infoPtr->bDoChangeNotify;
3298 infoPtr->bDoChangeNotify = FALSE;
3299
3300 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3301
3302 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3303
3304 assert(abs(direction) == 1);
3305
3306 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3307
3308 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3309 if (nNewFocus != infoPtr->nFocusedItem)
3310 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3311
3312 /* But we are not supposed to modify nHotItem! */
3313
3314 infoPtr->bDoChangeNotify = bOldChange;
3315 }
3316
3317
3318 /**
3319 * DESCRIPTION:
3320 * Adds a block of selections.
3321 *
3322 * PARAMETER(S):
3323 * [I] infoPtr : valid pointer to the listview structure
3324 * [I] nItem : item index
3325 *
3326 * RETURN:
3327 * Whether the window is still valid.
3328 */
3329 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3330 {
3331 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3332 INT nLast = max(infoPtr->nSelectionMark, nItem);
3333 HWND hwndSelf = infoPtr->hwndSelf;
3334 NMLVODSTATECHANGE nmlv;
3335 LVITEMW item;
3336 BOOL bOldChange;
3337 INT i;
3338
3339 /* Temporarily disable change notification
3340 * If the control is LVS_OWNERDATA, we need to send
3341 * only one LVN_ODSTATECHANGED notification.
3342 * See MSDN documentation for LVN_ITEMCHANGED.
3343 */
3344 bOldChange = infoPtr->bDoChangeNotify;
3345 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3346
3347 if (nFirst == -1) nFirst = nItem;
3348
3349 item.state = LVIS_SELECTED;
3350 item.stateMask = LVIS_SELECTED;
3351
3352 for (i = nFirst; i <= nLast; i++)
3353 LISTVIEW_SetItemState(infoPtr,i,&item);
3354
3355 ZeroMemory(&nmlv, sizeof(nmlv));
3356 nmlv.iFrom = nFirst;
3357 nmlv.iTo = nLast;
3358 nmlv.uNewState = 0;
3359 nmlv.uOldState = item.state;
3360
3361 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3362 if (!IsWindow(hwndSelf))
3363 return FALSE;
3364 infoPtr->bDoChangeNotify = bOldChange;
3365 return TRUE;
3366 }
3367
3368
3369 /***
3370 * DESCRIPTION:
3371 * Sets a single group selection.
3372 *
3373 * PARAMETER(S):
3374 * [I] infoPtr : valid pointer to the listview structure
3375 * [I] nItem : item index
3376 *
3377 * RETURN:
3378 * None
3379 */
3380 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3381 {
3382 RANGES selection;
3383 LVITEMW item;
3384 ITERATOR i;
3385 BOOL bOldChange;
3386
3387 if (!(selection = ranges_create(100))) return;
3388
3389 item.state = LVIS_SELECTED;
3390 item.stateMask = LVIS_SELECTED;
3391
3392 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3393 {
3394 if (infoPtr->nSelectionMark == -1)
3395 {
3396 infoPtr->nSelectionMark = nItem;
3397 ranges_additem(selection, nItem);
3398 }