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