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