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