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