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