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