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