efc6ea49ce1e28a2870b34f77bb75c8b3d7067a3
[reactos.git] / reactos / dll / win32 / comctl32 / listview.c
1 /*
2 * Listview control
3 *
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
9 * Copyright 2009-2015 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
11 * Copyright 2012-2013 Daniel Jelinski
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 *
27 * NOTES
28 *
29 * This code was audited for completeness against the documented features
30 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
31 *
32 * Unless otherwise noted, we believe this code to be complete, as per
33 * the specification mentioned above.
34 * If you discover missing features, or bugs, please note them below.
35 *
36 * TODO:
37 *
38 * Default Message Processing
39 * -- WM_CREATE: create the icon and small icon image lists at this point only if
40 * the LVS_SHAREIMAGELISTS style is not specified.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
45 *
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
51 *
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
58 *
59 * Speedups
60 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61 * linear in the number of items in the list, and this is
62 * unacceptable for large lists.
63 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64 * binary search to calculate item index (e.g. DPA_Search()).
65 * This requires sorted state to be reliably tracked in item modifiers.
66 * -- we should keep an ordered array of coordinates in iconic mode.
67 * This would allow framing items (iterator_frameditems),
68 * and finding the nearest item (LVFI_NEARESTXY) a lot more efficiently.
69 *
70 * Flags
71 * -- LVIF_COLUMNS
72 * -- LVIF_GROUPID
73 *
74 * States
75 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVIS_DROPHILITED
77 *
78 * Styles
79 * -- LVS_NOLABELWRAP
80 * -- LVS_NOSCROLL (see Q137520)
81 * -- LVS_ALIGNTOP
82 *
83 * Extended Styles
84 * -- LVS_EX_BORDERSELECT
85 * -- LVS_EX_FLATSB
86 * -- LVS_EX_INFOTIP
87 * -- LVS_EX_LABELTIP
88 * -- LVS_EX_MULTIWORKAREAS
89 * -- LVS_EX_REGIONAL
90 * -- LVS_EX_SIMPLESELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
94 *
95 * Notifications:
96 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
97 * -- LVN_GETINFOTIP
98 * -- LVN_HOTTRACK
99 * -- LVN_SETDISPINFO
100 *
101 * Messages:
102 * -- LVM_ENABLEGROUPVIEW
103 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
104 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
105 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
106 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
107 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
108 * -- LVM_GETINSERTMARKRECT
109 * -- LVM_GETNUMBEROFWORKAREAS
110 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
111 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
112 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
113 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
114 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
115 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
116 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
117 * -- LVM_INSERTGROUPSORTED
118 * -- LVM_INSERTMARKHITTEST
119 * -- LVM_ISGROUPVIEWENABLED
120 * -- LVM_MOVEGROUP
121 * -- LVM_MOVEITEMTOGROUP
122 * -- LVM_SETINFOTIP
123 * -- LVM_SETTILEWIDTH
124 * -- LVM_SORTGROUPS
125 *
126 * Macros:
127 * -- ListView_GetHoverTime, ListView_SetHoverTime
128 * -- ListView_GetISearchString
129 * -- ListView_GetNumberOfWorkAreas
130 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
131 *
132 * Functions:
133 * -- LVGroupComparE
134 */
135
136 #include "comctl32.h"
137
138 #include <stdio.h>
139
140 WINE_DEFAULT_DEBUG_CHANNEL(listview);
141
142 typedef struct tagCOLUMN_INFO
143 {
144 RECT rcHeader; /* tracks the header's rectangle */
145 INT fmt; /* same as LVCOLUMN.fmt */
146 INT cxMin;
147 } COLUMN_INFO;
148
149 typedef struct tagITEMHDR
150 {
151 LPWSTR pszText;
152 INT iImage;
153 } ITEMHDR, *LPITEMHDR;
154
155 typedef struct tagSUBITEM_INFO
156 {
157 ITEMHDR hdr;
158 INT iSubItem;
159 } SUBITEM_INFO;
160
161 typedef struct tagITEM_ID ITEM_ID;
162
163 typedef struct tagITEM_INFO
164 {
165 ITEMHDR hdr;
166 UINT state;
167 LPARAM lParam;
168 INT iIndent;
169 ITEM_ID *id;
170 } ITEM_INFO;
171
172 struct tagITEM_ID
173 {
174 UINT id; /* item id */
175 HDPA item; /* link to item data */
176 };
177
178 typedef struct tagRANGE
179 {
180 INT lower;
181 INT upper;
182 } RANGE;
183
184 typedef struct tagRANGES
185 {
186 HDPA hdpa;
187 } *RANGES;
188
189 typedef struct tagITERATOR
190 {
191 INT nItem;
192 INT nSpecial;
193 RANGE range;
194 RANGES ranges;
195 INT index;
196 } ITERATOR;
197
198 typedef struct tagDELAYED_ITEM_EDIT
199 {
200 BOOL fEnabled;
201 INT iItem;
202 } DELAYED_ITEM_EDIT;
203
204 typedef struct tagLISTVIEW_INFO
205 {
206 /* control window */
207 HWND hwndSelf;
208 RECT rcList; /* This rectangle is really the window
209 * client rectangle possibly reduced by the
210 * horizontal scroll bar and/or header - see
211 * LISTVIEW_UpdateSize. This rectangle offset
212 * by the LISTVIEW_GetOrigin value is in
213 * client coordinates */
214
215 /* notification window */
216 SHORT notifyFormat;
217 HWND hwndNotify;
218 BOOL bDoChangeNotify; /* send change notification messages? */
219 UINT uCallbackMask;
220
221 /* tooltips */
222 HWND hwndToolTip;
223
224 /* items */
225 INT nItemCount; /* the number of items in the list */
226 HDPA hdpaItems; /* array ITEM_INFO pointers */
227 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
228 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
229 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
230 RANGES selectionRanges;
231 INT nSelectionMark; /* item to start next multiselection from */
232 INT nHotItem;
233 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
234
235 /* columns */
236 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
237 BOOL colRectsDirty; /* trigger column rectangles requery from header */
238
239 /* item metrics */
240 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
241 INT nItemHeight;
242 INT nItemWidth;
243
244 /* sorting */
245 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
246 LPARAM lParamSort;
247
248 /* style */
249 DWORD dwStyle; /* the cached window GWL_STYLE */
250 DWORD dwLvExStyle; /* extended listview style */
251 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
252
253 /* edit item */
254 HWND hwndEdit;
255 WNDPROC EditWndProc;
256 INT nEditLabelItem;
257 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
258
259 /* icons */
260 HIMAGELIST himlNormal;
261 HIMAGELIST himlSmall;
262 HIMAGELIST himlState;
263 SIZE iconSize;
264 BOOL autoSpacing;
265 SIZE iconSpacing;
266 SIZE iconStateSize;
267 POINT currIconPos; /* this is the position next icon will be placed */
268
269 /* header */
270 HWND hwndHeader;
271 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
272
273 /* marquee selection */
274 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
275 BOOL bScrolling;
276 RECT marqueeRect; /* absolute coordinates of marquee selection */
277 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
278 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
279
280 /* focus drawing */
281 BOOL bFocus; /* control has focus */
282 INT nFocusedItem;
283 RECT rcFocus; /* focus bounds */
284
285 /* colors */
286 HBRUSH hBkBrush;
287 COLORREF clrBk;
288 COLORREF clrText;
289 COLORREF clrTextBk;
290 #ifdef __REACTOS__
291 BOOL bDefaultBkColor;
292 #endif
293
294 /* font */
295 HFONT hDefaultFont;
296 HFONT hFont;
297 INT ntmHeight; /* Some cached metrics of the font used */
298 INT ntmMaxCharWidth; /* by the listview to draw items */
299 INT nEllipsisWidth;
300
301 /* mouse operation */
302 BOOL bLButtonDown;
303 BOOL bDragging;
304 POINT ptClickPos; /* point where the user clicked */
305 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
306 DWORD dwHoverTime;
307 HCURSOR hHotCursor;
308 INT cWheelRemainder;
309
310 /* keyboard operation */
311 DWORD lastKeyPressTimestamp;
312 WPARAM charCode;
313 INT nSearchParamLength;
314 WCHAR szSearchParam[ MAX_PATH ];
315
316 /* painting */
317 BOOL bIsDrawing; /* Drawing in progress */
318 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
319 BOOL bRedraw; /* WM_SETREDRAW switch */
320
321 /* misc */
322 DWORD iVersion; /* CCM_[G,S]ETVERSION */
323 } LISTVIEW_INFO;
324
325 /*
326 * constants
327 */
328 /* How many we debug buffer to allocate */
329 #define DEBUG_BUFFERS 20
330 /* The size of a single debug buffer */
331 #define DEBUG_BUFFER_SIZE 256
332
333 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
334 #define SB_INTERNAL -1
335
336 /* maximum size of a label */
337 #define DISP_TEXT_SIZE 260
338
339 /* padding for items in list and small icon display modes */
340 #define WIDTH_PADDING 12
341
342 /* padding for items in list, report and small icon display modes */
343 #define HEIGHT_PADDING 1
344
345 /* offset of items in report display mode */
346 #define REPORT_MARGINX 2
347
348 /* padding for icon in large icon display mode
349 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
350 * that HITTEST will see.
351 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
352 * ICON_TOP_PADDING - sum of the two above.
353 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
354 * LABEL_HOR_PADDING - between text and sides of box
355 * LABEL_VERT_PADDING - between bottom of text and end of box
356 *
357 * ICON_LR_PADDING - additional width above icon size.
358 * ICON_LR_HALF - half of the above value
359 */
360 #define ICON_TOP_PADDING_NOTHITABLE 2
361 #define ICON_TOP_PADDING_HITABLE 2
362 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
363 #define ICON_BOTTOM_PADDING 4
364 #define LABEL_HOR_PADDING 5
365 #define LABEL_VERT_PADDING 7
366 #define ICON_LR_PADDING 16
367 #define ICON_LR_HALF (ICON_LR_PADDING/2)
368
369 /* default label width for items in list and small icon display modes */
370 #define DEFAULT_LABEL_WIDTH 40
371 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
372 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
373
374 /* default column width for items in list display mode */
375 #define DEFAULT_COLUMN_WIDTH 128
376
377 /* Size of "line" scroll for V & H scrolls */
378 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
379
380 /* Padding between image and label */
381 #define IMAGE_PADDING 2
382
383 /* Padding behind the label */
384 #define TRAILING_LABEL_PADDING 12
385 #define TRAILING_HEADER_PADDING 11
386
387 /* Border for the icon caption */
388 #define CAPTION_BORDER 2
389
390 /* Standard DrawText flags */
391 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
392 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
393 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
394
395 /* Image index from state */
396 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
397
398 /* The time in milliseconds to reset the search in the list */
399 #define KEY_DELAY 450
400
401 /* Dump the LISTVIEW_INFO structure to the debug channel */
402 #define LISTVIEW_DUMP(iP) do { \
403 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
404 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
405 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
406 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
407 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
408 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
409 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
410 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
411 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
412 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
413 } while(0)
414
415 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
416
417 /*
418 * forward declarations
419 */
420 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
421 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
422 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
423 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
424 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
425 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
426 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
427 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
428 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
429 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
430 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
431 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
432 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
433 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
434 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
435 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
436 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
437 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
438 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
439 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
440
441 /******** Text handling functions *************************************/
442
443 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
444 * text string. The string may be ANSI or Unicode, in which case
445 * the boolean isW tells us the type of the string.
446 *
447 * The name of the function tell what type of strings it expects:
448 * W: Unicode, T: ANSI/Unicode - function of isW
449 */
450
451 static inline BOOL is_text(LPCWSTR text)
452 {
453 return text != NULL && text != LPSTR_TEXTCALLBACKW;
454 }
455
456 static inline int textlenT(LPCWSTR text, BOOL isW)
457 {
458 return !is_text(text) ? 0 :
459 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
460 }
461
462 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
463 {
464 if (isDestW)
465 if (isSrcW) lstrcpynW(dest, src, max);
466 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
467 else
468 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
469 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
470 }
471
472 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
473 {
474 LPWSTR wstr = (LPWSTR)text;
475
476 if (!isW && is_text(text))
477 {
478 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
479 wstr = Alloc(len * sizeof(WCHAR));
480 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
481 }
482 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
483 return wstr;
484 }
485
486 static inline void textfreeT(LPWSTR wstr, BOOL isW)
487 {
488 if (!isW && is_text(wstr)) Free (wstr);
489 }
490
491 /*
492 * dest is a pointer to a Unicode string
493 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
494 */
495 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
496 {
497 BOOL bResult = TRUE;
498
499 if (src == LPSTR_TEXTCALLBACKW)
500 {
501 if (is_text(*dest)) Free(*dest);
502 *dest = LPSTR_TEXTCALLBACKW;
503 }
504 else
505 {
506 LPWSTR pszText = textdupTtoW(src, isW);
507 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
508 bResult = Str_SetPtrW(dest, pszText);
509 textfreeT(pszText, isW);
510 }
511 return bResult;
512 }
513
514 /*
515 * compares a Unicode to a Unicode/ANSI text string
516 */
517 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
518 {
519 if (!aw) return bt ? -1 : 0;
520 if (!bt) return 1;
521 if (aw == LPSTR_TEXTCALLBACKW)
522 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
523 if (bt != LPSTR_TEXTCALLBACKW)
524 {
525 LPWSTR bw = textdupTtoW(bt, isW);
526 int r = bw ? lstrcmpW(aw, bw) : 1;
527 textfreeT(bw, isW);
528 return r;
529 }
530
531 return 1;
532 }
533
534 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
535 {
536 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
537 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
538 }
539
540 /******** Debugging functions *****************************************/
541
542 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
543 {
544 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
545 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
546 }
547
548 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
549 {
550 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
551 n = min(textlenT(text, isW), n);
552 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
553 }
554
555 static char* debug_getbuf(void)
556 {
557 static int index = 0;
558 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
559 return buffers[index++ % DEBUG_BUFFERS];
560 }
561
562 static inline const char* debugrange(const RANGE *lprng)
563 {
564 if (!lprng) return "(null)";
565 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
566 }
567
568 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
569 {
570 char* buf = debug_getbuf(), *text = buf;
571 int len, size = DEBUG_BUFFER_SIZE;
572
573 if (pScrollInfo == NULL) return "(null)";
574 len = snprintf(buf, size, "{cbSize=%u, ", pScrollInfo->cbSize);
575 if (len == -1) goto end; buf += len; size -= len;
576 if (pScrollInfo->fMask & SIF_RANGE)
577 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
578 else len = 0;
579 if (len == -1) goto end; buf += len; size -= len;
580 if (pScrollInfo->fMask & SIF_PAGE)
581 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
582 else len = 0;
583 if (len == -1) goto end; buf += len; size -= len;
584 if (pScrollInfo->fMask & SIF_POS)
585 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
586 else len = 0;
587 if (len == -1) goto end; buf += len; size -= len;
588 if (pScrollInfo->fMask & SIF_TRACKPOS)
589 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
590 else len = 0;
591 if (len == -1) goto end; buf += len;
592 goto undo;
593 end:
594 buf = text + strlen(text);
595 undo:
596 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
597 return text;
598 }
599
600 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
601 {
602 if (!plvnm) return "(null)";
603 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
604 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
605 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
606 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
607 }
608
609 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
610 {
611 char* buf = debug_getbuf(), *text = buf;
612 int len, size = DEBUG_BUFFER_SIZE;
613
614 if (lpLVItem == NULL) return "(null)";
615 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
616 if (len == -1) goto end; buf += len; size -= len;
617 if (lpLVItem->mask & LVIF_STATE)
618 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
619 else len = 0;
620 if (len == -1) goto end; buf += len; size -= len;
621 if (lpLVItem->mask & LVIF_TEXT)
622 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
623 else len = 0;
624 if (len == -1) goto end; buf += len; size -= len;
625 if (lpLVItem->mask & LVIF_IMAGE)
626 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
627 else len = 0;
628 if (len == -1) goto end; buf += len; size -= len;
629 if (lpLVItem->mask & LVIF_PARAM)
630 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
631 else len = 0;
632 if (len == -1) goto end; buf += len; size -= len;
633 if (lpLVItem->mask & LVIF_INDENT)
634 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
635 else len = 0;
636 if (len == -1) goto end; buf += len;
637 goto undo;
638 end:
639 buf = text + strlen(text);
640 undo:
641 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
642 return text;
643 }
644
645 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
646 {
647 char* buf = debug_getbuf(), *text = buf;
648 int len, size = DEBUG_BUFFER_SIZE;
649
650 if (lpColumn == NULL) return "(null)";
651 len = snprintf(buf, size, "{");
652 if (len == -1) goto end; buf += len; size -= len;
653 if (lpColumn->mask & LVCF_SUBITEM)
654 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
655 else len = 0;
656 if (len == -1) goto end; buf += len; size -= len;
657 if (lpColumn->mask & LVCF_FMT)
658 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
659 else len = 0;
660 if (len == -1) goto end; buf += len; size -= len;
661 if (lpColumn->mask & LVCF_WIDTH)
662 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
663 else len = 0;
664 if (len == -1) goto end; buf += len; size -= len;
665 if (lpColumn->mask & LVCF_TEXT)
666 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
667 else len = 0;
668 if (len == -1) goto end; buf += len; size -= len;
669 if (lpColumn->mask & LVCF_IMAGE)
670 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
671 else len = 0;
672 if (len == -1) goto end; buf += len; size -= len;
673 if (lpColumn->mask & LVCF_ORDER)
674 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
675 else len = 0;
676 if (len == -1) goto end; buf += len;
677 goto undo;
678 end:
679 buf = text + strlen(text);
680 undo:
681 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
682 return text;
683 }
684
685 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
686 {
687 if (!lpht) return "(null)";
688
689 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
690 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
691 }
692
693 /* Return the corresponding text for a given scroll value */
694 static inline LPCSTR debugscrollcode(int nScrollCode)
695 {
696 switch(nScrollCode)
697 {
698 case SB_LINELEFT: return "SB_LINELEFT";
699 case SB_LINERIGHT: return "SB_LINERIGHT";
700 case SB_PAGELEFT: return "SB_PAGELEFT";
701 case SB_PAGERIGHT: return "SB_PAGERIGHT";
702 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
703 case SB_THUMBTRACK: return "SB_THUMBTRACK";
704 case SB_ENDSCROLL: return "SB_ENDSCROLL";
705 case SB_INTERNAL: return "SB_INTERNAL";
706 default: return "unknown";
707 }
708 }
709
710
711 /******** Notification functions ************************************/
712
713 static int get_ansi_notification(UINT unicodeNotificationCode)
714 {
715 switch (unicodeNotificationCode)
716 {
717 case LVN_BEGINLABELEDITA:
718 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
719 case LVN_ENDLABELEDITA:
720 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
721 case LVN_GETDISPINFOA:
722 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
723 case LVN_SETDISPINFOA:
724 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
725 case LVN_ODFINDITEMA:
726 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
727 case LVN_GETINFOTIPA:
728 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
729 /* header forwards */
730 case HDN_TRACKA:
731 case HDN_TRACKW: return HDN_TRACKA;
732 case HDN_ENDTRACKA:
733 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
734 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
735 case HDN_ENDDRAG: return HDN_ENDDRAG;
736 case HDN_ITEMCHANGINGA:
737 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
738 case HDN_ITEMCHANGEDA:
739 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
740 case HDN_ITEMCLICKA:
741 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
742 case HDN_DIVIDERDBLCLICKA:
743 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
744 default: break;
745 }
746 FIXME("unknown notification %x\n", unicodeNotificationCode);
747 return unicodeNotificationCode;
748 }
749
750 /* forwards header notifications to listview parent */
751 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
752 {
753 LPCWSTR text = NULL, filter = NULL;
754 LRESULT ret;
755 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
756
757 /* on unicode format exit earlier */
758 if (infoPtr->notifyFormat == NFR_UNICODE)
759 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
760 (LPARAM)lpnmh);
761
762 /* header always supplies unicode notifications,
763 all we have to do is to convert strings to ANSI */
764 if (lpnmh->pitem)
765 {
766 /* convert item text */
767 if (lpnmh->pitem->mask & HDI_TEXT)
768 {
769 text = (LPCWSTR)lpnmh->pitem->pszText;
770 lpnmh->pitem->pszText = NULL;
771 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
772 }
773 /* convert filter text */
774 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
775 lpnmh->pitem->pvFilter)
776 {
777 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
778 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = NULL;
779 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
780 }
781 }
782 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
783
784 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
785 (LPARAM)lpnmh);
786
787 /* cleanup */
788 if(text)
789 {
790 Free(lpnmh->pitem->pszText);
791 lpnmh->pitem->pszText = (LPSTR)text;
792 }
793 if(filter)
794 {
795 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
796 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
797 }
798
799 return ret;
800 }
801
802 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
803 {
804 LRESULT result;
805
806 TRACE("(code=%d)\n", code);
807
808 pnmh->hwndFrom = infoPtr->hwndSelf;
809 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
810 pnmh->code = code;
811 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
812
813 TRACE(" <= %ld\n", result);
814
815 return result;
816 }
817
818 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
819 {
820 NMHDR nmh;
821 HWND hwnd = infoPtr->hwndSelf;
822 notify_hdr(infoPtr, code, &nmh);
823 return IsWindow(hwnd);
824 }
825
826 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
827 {
828 NMITEMACTIVATE nmia;
829 LVITEMW item;
830
831 if (htInfo) {
832 nmia.uNewState = 0;
833 nmia.uOldState = 0;
834 nmia.uChanged = 0;
835 nmia.uKeyFlags = 0;
836
837 item.mask = LVIF_PARAM|LVIF_STATE;
838 item.iItem = htInfo->iItem;
839 item.iSubItem = 0;
840 item.stateMask = (UINT)-1;
841 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
842 nmia.lParam = item.lParam;
843 nmia.uOldState = item.state;
844 nmia.uNewState = item.state | LVIS_ACTIVATING;
845 nmia.uChanged = LVIF_STATE;
846 }
847
848 nmia.iItem = htInfo->iItem;
849 nmia.iSubItem = htInfo->iSubItem;
850 nmia.ptAction = htInfo->pt;
851
852 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
853 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
854 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
855 }
856 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
857 }
858
859 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
860 {
861 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
862 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
863 }
864
865 /* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
866 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
867 {
868 NMITEMACTIVATE nmia;
869 LVITEMW item;
870 HWND hwnd = infoPtr->hwndSelf;
871 LRESULT ret;
872
873 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
874 ZeroMemory(&nmia, sizeof(nmia));
875 nmia.iItem = lvht->iItem;
876 nmia.iSubItem = lvht->iSubItem;
877 nmia.ptAction = lvht->pt;
878 item.mask = LVIF_PARAM;
879 item.iItem = lvht->iItem;
880 item.iSubItem = 0;
881 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
882 ret = notify_hdr(infoPtr, code, (NMHDR*)&nmia);
883 return IsWindow(hwnd) && (code == NM_RCLICK ? !ret : TRUE);
884 }
885
886 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
887 {
888 NMLISTVIEW nmlv;
889 LVITEMW item;
890 HWND hwnd = infoPtr->hwndSelf;
891
892 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
893 nmlv.iItem = nItem;
894 item.mask = LVIF_PARAM;
895 item.iItem = nItem;
896 item.iSubItem = 0;
897 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
898 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
899 return IsWindow(hwnd);
900 }
901
902 /*
903 Send notification. depends on dispinfoW having same
904 structure as dispinfoA.
905 infoPtr : listview struct
906 code : *Unicode* notification code
907 pdi : dispinfo structure (can be unicode or ansi)
908 isW : TRUE if dispinfo is Unicode
909 */
910 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
911 {
912 INT length = 0, ret_length;
913 LPWSTR buffer = NULL, ret_text;
914 BOOL return_ansi = FALSE;
915 BOOL return_unicode = FALSE;
916 BOOL ret;
917
918 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
919 {
920 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
921 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
922 }
923
924 ret_length = pdi->item.cchTextMax;
925 ret_text = pdi->item.pszText;
926
927 if (return_unicode || return_ansi)
928 {
929 if (code != LVN_GETDISPINFOW)
930 {
931 length = return_ansi ?
932 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
933 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
934 }
935 else
936 {
937 length = pdi->item.cchTextMax;
938 *pdi->item.pszText = 0; /* make sure we don't process garbage */
939 }
940
941 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
942 if (!buffer) return FALSE;
943
944 if (return_ansi)
945 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
946 buffer, length);
947 else
948 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
949 length, NULL, NULL);
950
951 pdi->item.pszText = buffer;
952 pdi->item.cchTextMax = length;
953 }
954
955 if (infoPtr->notifyFormat == NFR_ANSI)
956 code = get_ansi_notification(code);
957
958 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
959 ret = notify_hdr(infoPtr, code, &pdi->hdr);
960 TRACE(" resulting code=%d\n", pdi->hdr.code);
961
962 if (return_ansi || return_unicode)
963 {
964 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
965 {
966 strcpy((char*)ret_text, (char*)pdi->item.pszText);
967 }
968 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
969 {
970 strcpyW(ret_text, pdi->item.pszText);
971 }
972 else if (return_ansi) /* note : pointer can be changed by app ! */
973 {
974 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
975 ret_length, NULL, NULL);
976 }
977 else
978 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
979 ret_text, ret_length);
980
981 pdi->item.pszText = ret_text; /* restores our buffer */
982 pdi->item.cchTextMax = ret_length;
983
984 Free(buffer);
985 return ret;
986 }
987
988 /* if dipsinfo holder changed notification code then convert */
989 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
990 {
991 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
992
993 buffer = Alloc(length * sizeof(CHAR));
994 if (!buffer) return FALSE;
995
996 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
997 ret_length, NULL, NULL);
998
999 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1000 Free(buffer);
1001 }
1002
1003 return ret;
1004 }
1005
1006 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1007 const RECT *rcBounds, const LVITEMW *lplvItem)
1008 {
1009 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1010 lpnmlvcd->nmcd.hdc = hdc;
1011 lpnmlvcd->nmcd.rc = *rcBounds;
1012 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1013 lpnmlvcd->clrText = infoPtr->clrText;
1014 if (!lplvItem) return;
1015 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1016 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1017 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1018 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1019 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1020 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1021 }
1022
1023 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1024 {
1025 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1026 DWORD result;
1027
1028 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1029 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1030 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1031 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1032 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1033 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1034 return result;
1035 }
1036
1037 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1038 {
1039 COLORREF backcolor, textcolor;
1040
1041 /* apparently, for selected items, we have to override the returned values */
1042 if (!SubItem)
1043 {
1044 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1045 {
1046 if (infoPtr->bFocus)
1047 {
1048 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1049 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1050 }
1051 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1052 {
1053 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1054 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1055 }
1056 }
1057 }
1058
1059 backcolor = lpnmlvcd->clrTextBk;
1060 textcolor = lpnmlvcd->clrText;
1061
1062 if (backcolor == CLR_DEFAULT)
1063 backcolor = comctl32_color.clrWindow;
1064 if (textcolor == CLR_DEFAULT)
1065 textcolor = comctl32_color.clrWindowText;
1066
1067 /* Set the text attributes */
1068 if (backcolor != CLR_NONE)
1069 {
1070 SetBkMode(hdc, OPAQUE);
1071 SetBkColor(hdc, backcolor);
1072 }
1073 else
1074 SetBkMode(hdc, TRANSPARENT);
1075 SetTextColor(hdc, textcolor);
1076 }
1077
1078 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1079 {
1080 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1081 }
1082
1083 /* returns TRUE when repaint needed, FALSE otherwise */
1084 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1085 {
1086 MEASUREITEMSTRUCT mis;
1087 mis.CtlType = ODT_LISTVIEW;
1088 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1089 mis.itemID = -1;
1090 mis.itemWidth = 0;
1091 mis.itemData = 0;
1092 mis.itemHeight= infoPtr->nItemHeight;
1093 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1094 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1095 {
1096 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1097 return TRUE;
1098 }
1099 return FALSE;
1100 }
1101
1102 /******** Item iterator functions **********************************/
1103
1104 static RANGES ranges_create(int count);
1105 static void ranges_destroy(RANGES ranges);
1106 static BOOL ranges_add(RANGES ranges, RANGE range);
1107 static BOOL ranges_del(RANGES ranges, RANGE range);
1108 static void ranges_dump(RANGES ranges);
1109
1110 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1111 {
1112 RANGE range = { nItem, nItem + 1 };
1113
1114 return ranges_add(ranges, range);
1115 }
1116
1117 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1118 {
1119 RANGE range = { nItem, nItem + 1 };
1120
1121 return ranges_del(ranges, range);
1122 }
1123
1124 /***
1125 * ITERATOR DOCUMENTATION
1126 *
1127 * The iterator functions allow for easy, and convenient iteration
1128 * over items of interest in the list. Typically, you create an
1129 * iterator, use it, and destroy it, as such:
1130 * ITERATOR i;
1131 *
1132 * iterator_xxxitems(&i, ...);
1133 * while (iterator_{prev,next}(&i)
1134 * {
1135 * //code which uses i.nItem
1136 * }
1137 * iterator_destroy(&i);
1138 *
1139 * where xxx is either: framed, or visible.
1140 * Note that it is important that the code destroys the iterator
1141 * after it's done with it, as the creation of the iterator may
1142 * allocate memory, which thus needs to be freed.
1143 *
1144 * You can iterate both forwards, and backwards through the list,
1145 * by using iterator_next or iterator_prev respectively.
1146 *
1147 * Lower numbered items are draw on top of higher number items in
1148 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1149 * items may overlap). So, to test items, you should use
1150 * iterator_next
1151 * which lists the items top to bottom (in Z-order).
1152 * For drawing items, you should use
1153 * iterator_prev
1154 * which lists the items bottom to top (in Z-order).
1155 * If you keep iterating over the items after the end-of-items
1156 * marker (-1) is returned, the iterator will start from the
1157 * beginning. Typically, you don't need to test for -1,
1158 * because iterator_{next,prev} will return TRUE if more items
1159 * are to be iterated over, or FALSE otherwise.
1160 *
1161 * Note: the iterator is defined to be bidirectional. That is,
1162 * any number of prev followed by any number of next, or
1163 * five versa, should leave the iterator at the same item:
1164 * prev * n, next * n = next * n, prev * n
1165 *
1166 * The iterator has a notion of an out-of-order, special item,
1167 * which sits at the start of the list. This is used in
1168 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1169 * which needs to be first, as it may overlap other items.
1170 *
1171 * The code is a bit messy because we have:
1172 * - a special item to deal with
1173 * - simple range, or composite range
1174 * - empty range.
1175 * If you find bugs, or want to add features, please make sure you
1176 * always check/modify *both* iterator_prev, and iterator_next.
1177 */
1178
1179 /****
1180 * This function iterates through the items in increasing order,
1181 * but prefixed by the special item, then -1. That is:
1182 * special, 1, 2, 3, ..., n, -1.
1183 * Each item is listed only once.
1184 */
1185 static inline BOOL iterator_next(ITERATOR* i)
1186 {
1187 if (i->nItem == -1)
1188 {
1189 i->nItem = i->nSpecial;
1190 if (i->nItem != -1) return TRUE;
1191 }
1192 if (i->nItem == i->nSpecial)
1193 {
1194 if (i->ranges) i->index = 0;
1195 goto pickarange;
1196 }
1197
1198 i->nItem++;
1199 testitem:
1200 if (i->nItem == i->nSpecial) i->nItem++;
1201 if (i->nItem < i->range.upper) return TRUE;
1202
1203 pickarange:
1204 if (i->ranges)
1205 {
1206 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1207 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1208 else goto end;
1209 }
1210 else if (i->nItem >= i->range.upper) goto end;
1211
1212 i->nItem = i->range.lower;
1213 if (i->nItem >= 0) goto testitem;
1214 end:
1215 i->nItem = -1;
1216 return FALSE;
1217 }
1218
1219 /****
1220 * This function iterates through the items in decreasing order,
1221 * followed by the special item, then -1. That is:
1222 * n, n-1, ..., 3, 2, 1, special, -1.
1223 * Each item is listed only once.
1224 */
1225 static inline BOOL iterator_prev(ITERATOR* i)
1226 {
1227 BOOL start = FALSE;
1228
1229 if (i->nItem == -1)
1230 {
1231 start = TRUE;
1232 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1233 goto pickarange;
1234 }
1235 if (i->nItem == i->nSpecial)
1236 {
1237 i->nItem = -1;
1238 return FALSE;
1239 }
1240
1241 testitem:
1242 i->nItem--;
1243 if (i->nItem == i->nSpecial) i->nItem--;
1244 if (i->nItem >= i->range.lower) return TRUE;
1245
1246 pickarange:
1247 if (i->ranges)
1248 {
1249 if (i->index > 0)
1250 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1251 else goto end;
1252 }
1253 else if (!start && i->nItem < i->range.lower) goto end;
1254
1255 i->nItem = i->range.upper;
1256 if (i->nItem > 0) goto testitem;
1257 end:
1258 return (i->nItem = i->nSpecial) != -1;
1259 }
1260
1261 static RANGE iterator_range(const ITERATOR *i)
1262 {
1263 RANGE range;
1264
1265 if (!i->ranges) return i->range;
1266
1267 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1268 {
1269 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1270 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1271 }
1272 else range.lower = range.upper = 0;
1273
1274 return range;
1275 }
1276
1277 /***
1278 * Releases resources associated with this iterator.
1279 */
1280 static inline void iterator_destroy(const ITERATOR *i)
1281 {
1282 ranges_destroy(i->ranges);
1283 }
1284
1285 /***
1286 * Create an empty iterator.
1287 */
1288 static inline BOOL iterator_empty(ITERATOR* i)
1289 {
1290 ZeroMemory(i, sizeof(*i));
1291 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1292 return TRUE;
1293 }
1294
1295 /***
1296 * Create an iterator over a range.
1297 */
1298 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1299 {
1300 iterator_empty(i);
1301 i->range = range;
1302 return TRUE;
1303 }
1304
1305 /***
1306 * Create an iterator over a bunch of ranges.
1307 * Please note that the iterator will take ownership of the ranges,
1308 * and will free them upon destruction.
1309 */
1310 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1311 {
1312 iterator_empty(i);
1313 i->ranges = ranges;
1314 return TRUE;
1315 }
1316
1317 /***
1318 * Creates an iterator over the items which intersect frame.
1319 * Uses absolute coordinates rather than compensating for the current offset.
1320 */
1321 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1322 {
1323 RECT rcItem, rcTemp;
1324
1325 /* in case we fail, we want to return an empty iterator */
1326 if (!iterator_empty(i)) return FALSE;
1327
1328 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1329
1330 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1331 {
1332 INT nItem;
1333
1334 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1335 {
1336 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1337 if (IntersectRect(&rcTemp, &rcItem, frame))
1338 i->nSpecial = infoPtr->nFocusedItem;
1339 }
1340 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1341 /* to do better here, we need to have PosX, and PosY sorted */
1342 TRACE("building icon ranges:\n");
1343 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1344 {
1345 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1346 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1347 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1348 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1349 if (IntersectRect(&rcTemp, &rcItem, frame))
1350 ranges_additem(i->ranges, nItem);
1351 }
1352 return TRUE;
1353 }
1354 else if (infoPtr->uView == LV_VIEW_DETAILS)
1355 {
1356 RANGE range;
1357
1358 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1359 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1360
1361 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1362 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1363 if (range.upper <= range.lower) return TRUE;
1364 if (!iterator_rangeitems(i, range)) return FALSE;
1365 TRACE(" report=%s\n", debugrange(&i->range));
1366 }
1367 else
1368 {
1369 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1370 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1371 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1372 INT nFirstCol;
1373 INT nLastCol;
1374 INT lower;
1375 RANGE item_range;
1376 INT nCol;
1377
1378 if (infoPtr->nItemWidth)
1379 {
1380 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1381 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1382 }
1383 else
1384 {
1385 nFirstCol = max(frame->left, 0);
1386 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1387 }
1388
1389 lower = nFirstCol * nPerCol + nFirstRow;
1390
1391 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1392 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1393
1394 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1395
1396 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1397 TRACE("building list ranges:\n");
1398 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1399 {
1400 item_range.lower = nCol * nPerCol + nFirstRow;
1401 if(item_range.lower >= infoPtr->nItemCount) break;
1402 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1403 TRACE(" list=%s\n", debugrange(&item_range));
1404 ranges_add(i->ranges, item_range);
1405 }
1406 }
1407
1408 return TRUE;
1409 }
1410
1411 /***
1412 * Creates an iterator over the items which intersect lprc.
1413 */
1414 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1415 {
1416 RECT frame = *lprc;
1417 POINT Origin;
1418
1419 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1420
1421 LISTVIEW_GetOrigin(infoPtr, &Origin);
1422 OffsetRect(&frame, -Origin.x, -Origin.y);
1423
1424 return iterator_frameditems_absolute(i, infoPtr, &frame);
1425 }
1426
1427 /***
1428 * Creates an iterator over the items which intersect the visible region of hdc.
1429 */
1430 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1431 {
1432 POINT Origin, Position;
1433 RECT rcItem, rcClip;
1434 INT rgntype;
1435
1436 rgntype = GetClipBox(hdc, &rcClip);
1437 if (rgntype == NULLREGION) return iterator_empty(i);
1438 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1439 if (rgntype == SIMPLEREGION) return TRUE;
1440
1441 /* first deal with the special item */
1442 if (i->nSpecial != -1)
1443 {
1444 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1445 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1446 }
1447
1448 /* if we can't deal with the region, we'll just go with the simple range */
1449 LISTVIEW_GetOrigin(infoPtr, &Origin);
1450 TRACE("building visible range:\n");
1451 if (!i->ranges && i->range.lower < i->range.upper)
1452 {
1453 if (!(i->ranges = ranges_create(50))) return TRUE;
1454 if (!ranges_add(i->ranges, i->range))
1455 {
1456 ranges_destroy(i->ranges);
1457 i->ranges = 0;
1458 return TRUE;
1459 }
1460 }
1461
1462 /* now delete the invisible items from the list */
1463 while(iterator_next(i))
1464 {
1465 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1466 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1467 rcItem.top = Position.y + Origin.y;
1468 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1469 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1470 if (!RectVisible(hdc, &rcItem))
1471 ranges_delitem(i->ranges, i->nItem);
1472 }
1473 /* the iterator should restart on the next iterator_next */
1474 TRACE("done\n");
1475
1476 return TRUE;
1477 }
1478
1479 /* Remove common elements from two iterators */
1480 /* Passed iterators have to point on the first elements */
1481 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1482 {
1483 if(!iter1->ranges || !iter2->ranges) {
1484 int lower, upper;
1485
1486 if(iter1->ranges || iter2->ranges ||
1487 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1488 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1489 ERR("result is not a one range iterator\n");
1490 return FALSE;
1491 }
1492
1493 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1494 return TRUE;
1495
1496 lower = iter1->range.lower;
1497 upper = iter1->range.upper;
1498
1499 if(lower < iter2->range.lower)
1500 iter1->range.upper = iter2->range.lower;
1501 else if(upper > iter2->range.upper)
1502 iter1->range.lower = iter2->range.upper;
1503 else
1504 iter1->range.lower = iter1->range.upper = -1;
1505
1506 if(iter2->range.lower < lower)
1507 iter2->range.upper = lower;
1508 else if(iter2->range.upper > upper)
1509 iter2->range.lower = upper;
1510 else
1511 iter2->range.lower = iter2->range.upper = -1;
1512
1513 return TRUE;
1514 }
1515
1516 iterator_next(iter1);
1517 iterator_next(iter2);
1518
1519 while(1) {
1520 if(iter1->nItem==-1 || iter2->nItem==-1)
1521 break;
1522
1523 if(iter1->nItem == iter2->nItem) {
1524 int delete = iter1->nItem;
1525
1526 iterator_prev(iter1);
1527 iterator_prev(iter2);
1528 ranges_delitem(iter1->ranges, delete);
1529 ranges_delitem(iter2->ranges, delete);
1530 iterator_next(iter1);
1531 iterator_next(iter2);
1532 } else if(iter1->nItem > iter2->nItem)
1533 iterator_next(iter2);
1534 else
1535 iterator_next(iter1);
1536 }
1537
1538 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1539 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1540 return TRUE;
1541 }
1542
1543 /******** Misc helper functions ************************************/
1544
1545 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1546 WPARAM wParam, LPARAM lParam, BOOL isW)
1547 {
1548 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1549 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1550 }
1551
1552 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1553 {
1554 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1555 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1556 }
1557
1558 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1559 {
1560 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1561 if(state == 1 || state == 2)
1562 {
1563 LVITEMW lvitem;
1564 state ^= 3;
1565 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1566 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1567 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1568 }
1569 }
1570
1571 /* this should be called after window style got updated,
1572 it used to reset view state to match current window style */
1573 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1574 {
1575 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1576 {
1577 case LVS_ICON:
1578 infoPtr->uView = LV_VIEW_ICON;
1579 break;
1580 case LVS_REPORT:
1581 infoPtr->uView = LV_VIEW_DETAILS;
1582 break;
1583 case LVS_SMALLICON:
1584 infoPtr->uView = LV_VIEW_SMALLICON;
1585 break;
1586 case LVS_LIST:
1587 infoPtr->uView = LV_VIEW_LIST;
1588 }
1589 }
1590
1591 /* computes next item id value */
1592 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1593 {
1594 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1595
1596 if (count > 0)
1597 {
1598 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1599 return lpID->id + 1;
1600 }
1601 return 0;
1602 }
1603
1604 /******** Internal API functions ************************************/
1605
1606 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1607 {
1608 static COLUMN_INFO mainItem;
1609
1610 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1611 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1612
1613 /* update cached column rectangles */
1614 if (infoPtr->colRectsDirty)
1615 {
1616 COLUMN_INFO *info;
1617 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1618 INT i;
1619
1620 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1621 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1622 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1623 }
1624 Ptr->colRectsDirty = FALSE;
1625 }
1626
1627 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1628 }
1629
1630 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1631 {
1632 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1633 HINSTANCE hInst;
1634
1635 if (infoPtr->hwndHeader) return 0;
1636
1637 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1638
1639 /* setup creation flags */
1640 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1641 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1642
1643 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1644
1645 /* create header */
1646 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1647 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1648 if (!infoPtr->hwndHeader) return -1;
1649
1650 /* set header unicode format */
1651 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1652
1653 /* set header font */
1654 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1655
1656 /* set header image list */
1657 if (infoPtr->himlSmall)
1658 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
1659
1660 LISTVIEW_UpdateSize(infoPtr);
1661
1662 return 0;
1663 }
1664
1665 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1666 {
1667 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1668 }
1669
1670 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1671 {
1672 return (infoPtr->uView == LV_VIEW_DETAILS ||
1673 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1674 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1675 }
1676
1677 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1678 {
1679 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1680 }
1681
1682 /* used to handle collapse main item column case */
1683 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1684 {
1685 #ifdef __REACTOS__
1686 BOOL Ret = FALSE;
1687
1688 if (infoPtr->rcFocus.left < infoPtr->rcFocus.right)
1689 {
1690 DWORD dwOldBkColor, dwOldTextColor;
1691
1692 dwOldBkColor = SetBkColor(hdc, RGB(255, 255, 255));
1693 dwOldTextColor = SetBkColor(hdc, RGB(0, 0, 0));
1694 Ret = DrawFocusRect(hdc, &infoPtr->rcFocus);
1695 SetBkColor(hdc, dwOldBkColor);
1696 SetBkColor(hdc, dwOldTextColor);
1697 }
1698 return Ret;
1699 #else
1700 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1701 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1702 #endif
1703 }
1704
1705 /* Listview invalidation functions: use _only_ these functions to invalidate */
1706
1707 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1708 {
1709 return infoPtr->bRedraw;
1710 }
1711
1712 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1713 {
1714 if(!is_redrawing(infoPtr)) return;
1715 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1716 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1717 }
1718
1719 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1720 {
1721 RECT rcBox;
1722
1723 if(!is_redrawing(infoPtr)) return;
1724 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1725 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1726 }
1727
1728 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1729 {
1730 POINT Origin, Position;
1731 RECT rcBox;
1732
1733 if(!is_redrawing(infoPtr)) return;
1734 assert (infoPtr->uView == LV_VIEW_DETAILS);
1735 LISTVIEW_GetOrigin(infoPtr, &Origin);
1736 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1737 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1738 rcBox.top = 0;
1739 rcBox.bottom = infoPtr->nItemHeight;
1740 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1741 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1742 }
1743
1744 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1745 {
1746 LISTVIEW_InvalidateRect(infoPtr, NULL);
1747 }
1748
1749 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1750 {
1751 RECT rcCol;
1752
1753 if(!is_redrawing(infoPtr)) return;
1754 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1755 rcCol.top = infoPtr->rcList.top;
1756 rcCol.bottom = infoPtr->rcList.bottom;
1757 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1758 }
1759
1760 /***
1761 * DESCRIPTION:
1762 * Retrieves the number of items that can fit vertically in the client area.
1763 *
1764 * PARAMETER(S):
1765 * [I] infoPtr : valid pointer to the listview structure
1766 *
1767 * RETURN:
1768 * Number of items per row.
1769 */
1770 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1771 {
1772 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1773
1774 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1775 }
1776
1777 /***
1778 * DESCRIPTION:
1779 * Retrieves the number of items that can fit horizontally in the client
1780 * area.
1781 *
1782 * PARAMETER(S):
1783 * [I] infoPtr : valid pointer to the listview structure
1784 *
1785 * RETURN:
1786 * Number of items per column.
1787 */
1788 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1789 {
1790 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1791
1792 return max(nListHeight / infoPtr->nItemHeight, 1);
1793 }
1794
1795
1796 /*************************************************************************
1797 * LISTVIEW_ProcessLetterKeys
1798 *
1799 * Processes keyboard messages generated by pressing the letter keys
1800 * on the keyboard.
1801 * What this does is perform a case insensitive search from the
1802 * current position with the following quirks:
1803 * - If two chars or more are pressed in quick succession we search
1804 * for the corresponding string (e.g. 'abc').
1805 * - If there is a delay we wipe away the current search string and
1806 * restart with just that char.
1807 * - If the user keeps pressing the same character, whether slowly or
1808 * fast, so that the search string is entirely composed of this
1809 * character ('aaaaa' for instance), then we search for first item
1810 * that starting with that character.
1811 * - If the user types the above character in quick succession, then
1812 * we must also search for the corresponding string ('aaaaa'), and
1813 * go to that string if there is a match.
1814 *
1815 * PARAMETERS
1816 * [I] hwnd : handle to the window
1817 * [I] charCode : the character code, the actual character
1818 * [I] keyData : key data
1819 *
1820 * RETURNS
1821 *
1822 * Zero.
1823 *
1824 * BUGS
1825 *
1826 * - The current implementation has a list of characters it will
1827 * accept and it ignores everything else. In particular it will
1828 * ignore accentuated characters which seems to match what
1829 * Windows does. But I'm not sure it makes sense to follow
1830 * Windows there.
1831 * - We don't sound a beep when the search fails.
1832 *
1833 * SEE ALSO
1834 *
1835 * TREEVIEW_ProcessLetterKeys
1836 */
1837 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1838 {
1839 WCHAR buffer[MAX_PATH];
1840 DWORD prevTime;
1841 LVITEMW item;
1842 int startidx;
1843 INT nItem;
1844 INT diff;
1845
1846 /* simple parameter checking */
1847 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1848
1849 /* only allow the valid WM_CHARs through */
1850 if (!isalnumW(charCode) &&
1851 charCode != '.' && charCode != '`' && charCode != '!' &&
1852 charCode != '@' && charCode != '#' && charCode != '$' &&
1853 charCode != '%' && charCode != '^' && charCode != '&' &&
1854 charCode != '*' && charCode != '(' && charCode != ')' &&
1855 charCode != '-' && charCode != '_' && charCode != '+' &&
1856 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1857 charCode != '}' && charCode != '[' && charCode != '{' &&
1858 charCode != '/' && charCode != '?' && charCode != '>' &&
1859 charCode != '<' && charCode != ',' && charCode != '~')
1860 return 0;
1861
1862 /* update the search parameters */
1863 prevTime = infoPtr->lastKeyPressTimestamp;
1864 infoPtr->lastKeyPressTimestamp = GetTickCount();
1865 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1866
1867 if (diff >= 0 && diff < KEY_DELAY)
1868 {
1869 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1870 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1871
1872 if (infoPtr->charCode != charCode)
1873 infoPtr->charCode = charCode = 0;
1874 }
1875 else
1876 {
1877 infoPtr->charCode = charCode;
1878 infoPtr->szSearchParam[0] = charCode;
1879 infoPtr->nSearchParamLength = 1;
1880 }
1881
1882 /* should start from next after focused item, so next item that matches
1883 will be selected, if there isn't any and focused matches it will be selected
1884 on second search stage from beginning of the list */
1885 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1886 {
1887 /* with some accumulated search data available start with current focus, otherwise
1888 it's excluded from search */
1889 startidx = infoPtr->nSearchParamLength > 1 ? infoPtr->nFocusedItem : infoPtr->nFocusedItem + 1;
1890 if (startidx == infoPtr->nItemCount) startidx = 0;
1891 }
1892 else
1893 startidx = 0;
1894
1895 /* let application handle this for virtual listview */
1896 if (infoPtr->dwStyle & LVS_OWNERDATA)
1897 {
1898 NMLVFINDITEMW nmlv;
1899
1900 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1901 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1902 nmlv.lvfi.psz = infoPtr->szSearchParam;
1903 nmlv.iStart = startidx;
1904
1905 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1906
1907 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1908 }
1909 else
1910 {
1911 int i = startidx, endidx;
1912
1913 /* and search from the current position */
1914 nItem = -1;
1915 endidx = infoPtr->nItemCount;
1916
1917 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1918 while (1)
1919 {
1920 /* start from first item if not found with >= startidx */
1921 if (i == infoPtr->nItemCount && startidx > 0)
1922 {
1923 endidx = startidx;
1924 startidx = 0;
1925 }
1926
1927 for (i = startidx; i < endidx; i++)
1928 {
1929 /* retrieve text */
1930 item.mask = LVIF_TEXT;
1931 item.iItem = i;
1932 item.iSubItem = 0;
1933 item.pszText = buffer;
1934 item.cchTextMax = MAX_PATH;
1935 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1936
1937 if (!lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength))
1938 {
1939 nItem = i;
1940 break;
1941 }
1942 /* this is used to find first char match when search string is not available yet,
1943 otherwise every WM_CHAR will search to next item by first char, ignoring that we're
1944 already waiting for user to complete a string */
1945 else if (nItem == -1 && infoPtr->nSearchParamLength == 1 && !lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1))
1946 {
1947 /* this would work but we must keep looking for a longer match */
1948 nItem = i;
1949 }
1950 }
1951
1952 if ( nItem != -1 || /* found something */
1953 endidx != infoPtr->nItemCount || /* second search done */
1954 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1955 break;
1956 };
1957 }
1958
1959 if (nItem != -1)
1960 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1961
1962 return 0;
1963 }
1964
1965 /*************************************************************************
1966 * LISTVIEW_UpdateHeaderSize [Internal]
1967 *
1968 * Function to resize the header control
1969 *
1970 * PARAMS
1971 * [I] hwnd : handle to a window
1972 * [I] nNewScrollPos : scroll pos to set
1973 *
1974 * RETURNS
1975 * None.
1976 */
1977 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1978 {
1979 RECT winRect;
1980 POINT point[2];
1981
1982 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1983
1984 if (!infoPtr->hwndHeader) return;
1985
1986 GetWindowRect(infoPtr->hwndHeader, &winRect);
1987 point[0].x = winRect.left;
1988 point[0].y = winRect.top;
1989 point[1].x = winRect.right;
1990 point[1].y = winRect.bottom;
1991
1992 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1993 point[0].x = -nNewScrollPos;
1994 point[1].x += nNewScrollPos;
1995
1996 SetWindowPos(infoPtr->hwndHeader,0,
1997 point[0].x,point[0].y,point[1].x,point[1].y,
1998 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1999 SWP_NOZORDER | SWP_NOACTIVATE);
2000 }
2001
2002 /***
2003 * DESCRIPTION:
2004 * Update the scrollbars. This functions should be called whenever
2005 * the content, size or view changes.
2006 *
2007 * PARAMETER(S):
2008 * [I] infoPtr : valid pointer to the listview structure
2009 *
2010 * RETURN:
2011 * None
2012 */
2013 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
2014 {
2015 SCROLLINFO horzInfo, vertInfo;
2016 INT dx, dy;
2017
2018 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2019
2020 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2021 horzInfo.cbSize = sizeof(SCROLLINFO);
2022 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2023
2024 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2025 if (infoPtr->uView == LV_VIEW_LIST)
2026 {
2027 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2028 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2029
2030 /* scroll by at least one column per page */
2031 if(horzInfo.nPage < infoPtr->nItemWidth)
2032 horzInfo.nPage = infoPtr->nItemWidth;
2033
2034 if (infoPtr->nItemWidth)
2035 horzInfo.nPage /= infoPtr->nItemWidth;
2036 }
2037 else if (infoPtr->uView == LV_VIEW_DETAILS)
2038 {
2039 horzInfo.nMax = infoPtr->nItemWidth;
2040 }
2041 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2042 {
2043 RECT rcView;
2044
2045 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2046 }
2047
2048 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2049 {
2050 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2051 {
2052 RECT rcHeader;
2053 INT index;
2054
2055 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2056 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2057
2058 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2059 horzInfo.nMax = rcHeader.right;
2060 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2061 }
2062 }
2063
2064 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2065 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2066 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2067 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2068 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2069
2070 /* Setting the horizontal scroll can change the listview size
2071 * (and potentially everything else) so we need to recompute
2072 * everything again for the vertical scroll
2073 */
2074
2075 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2076 vertInfo.cbSize = sizeof(SCROLLINFO);
2077 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2078
2079 if (infoPtr->uView == LV_VIEW_DETAILS)
2080 {
2081 vertInfo.nMax = infoPtr->nItemCount;
2082
2083 /* scroll by at least one page */
2084 if(vertInfo.nPage < infoPtr->nItemHeight)
2085 vertInfo.nPage = infoPtr->nItemHeight;
2086
2087 if (infoPtr->nItemHeight > 0)
2088 vertInfo.nPage /= infoPtr->nItemHeight;
2089 }
2090 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2091 {
2092 RECT rcView;
2093
2094 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2095 }
2096
2097 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2098 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2099 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2100 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2101 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2102
2103 /* Change of the range may have changed the scroll pos. If so move the content */
2104 if (dx != 0 || dy != 0)
2105 {
2106 RECT listRect;
2107 listRect = infoPtr->rcList;
2108 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2109 SW_ERASE | SW_INVALIDATE);
2110 }
2111
2112 /* Update the Header Control */
2113 if (infoPtr->hwndHeader)
2114 {
2115 horzInfo.fMask = SIF_POS;
2116 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2117 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2118 }
2119 }
2120
2121
2122 /***
2123 * DESCRIPTION:
2124 * Shows/hides the focus rectangle.
2125 *
2126 * PARAMETER(S):
2127 * [I] infoPtr : valid pointer to the listview structure
2128 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2129 *
2130 * RETURN:
2131 * None
2132 */
2133 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2134 {
2135 HDC hdc;
2136
2137 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2138
2139 if (infoPtr->nFocusedItem < 0) return;
2140
2141 /* we need some gymnastics in ICON mode to handle large items */
2142 if (infoPtr->uView == LV_VIEW_ICON)
2143 {
2144 RECT rcBox;
2145
2146 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2147 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2148 {
2149 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2150 return;
2151 }
2152 }
2153
2154 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2155
2156 /* for some reason, owner draw should work only in report mode */
2157 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2158 {
2159 DRAWITEMSTRUCT dis;
2160 LVITEMW item;
2161
2162 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2163 HFONT hOldFont = SelectObject(hdc, hFont);
2164
2165 item.iItem = infoPtr->nFocusedItem;
2166 item.iSubItem = 0;
2167 item.mask = LVIF_PARAM;
2168 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2169
2170 ZeroMemory(&dis, sizeof(dis));
2171 dis.CtlType = ODT_LISTVIEW;
2172 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2173 dis.itemID = item.iItem;
2174 dis.itemAction = ODA_FOCUS;
2175 if (fShow) dis.itemState |= ODS_FOCUS;
2176 dis.hwndItem = infoPtr->hwndSelf;
2177 dis.hDC = hdc;
2178 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2179 dis.itemData = item.lParam;
2180
2181 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2182
2183 SelectObject(hdc, hOldFont);
2184 }
2185 else
2186 {
2187 LISTVIEW_DrawFocusRect(infoPtr, hdc);
2188 }
2189 done:
2190 ReleaseDC(infoPtr->hwndSelf, hdc);
2191 }
2192
2193 /***
2194 * Invalidates all visible selected items.
2195 */
2196 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2197 {
2198 ITERATOR i;
2199
2200 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2201 while(iterator_next(&i))
2202 {
2203 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2204 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2205 }
2206 iterator_destroy(&i);
2207 }
2208
2209
2210 /***
2211 * DESCRIPTION: [INTERNAL]
2212 * Computes an item's (left,top) corner, relative to rcView.
2213 * That is, the position has NOT been made relative to the Origin.
2214 * This is deliberate, to avoid computing the Origin over, and
2215 * over again, when this function is called in a loop. Instead,
2216 * one can factor the computation of the Origin before the loop,
2217 * and offset the value returned by this function, on every iteration.
2218 *
2219 * PARAMETER(S):
2220 * [I] infoPtr : valid pointer to the listview structure
2221 * [I] nItem : item number
2222 * [O] lpptOrig : item top, left corner
2223 *
2224 * RETURN:
2225 * None.
2226 */
2227 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2228 {
2229 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2230
2231 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2232 {
2233 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2234 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2235 }
2236 else if (infoPtr->uView == LV_VIEW_LIST)
2237 {
2238 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2239 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2240 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2241 }
2242 else /* LV_VIEW_DETAILS */
2243 {
2244 lpptPosition->x = REPORT_MARGINX;
2245 /* item is always at zero indexed column */
2246 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2247 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2248 lpptPosition->y = nItem * infoPtr->nItemHeight;
2249 }
2250 }
2251
2252 /***
2253 * DESCRIPTION: [INTERNAL]
2254 * Compute the rectangles of an item. This is to localize all
2255 * the computations in one place. If you are not interested in some
2256 * of these values, simply pass in a NULL -- the function is smart
2257 * enough to compute only what's necessary. The function computes
2258 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2259 * one, the BOX rectangle. This rectangle is very cheap to compute,
2260 * and is guaranteed to contain all the other rectangles. Computing
2261 * the ICON rect is also cheap, but all the others are potentially
2262 * expensive. This gives an easy and effective optimization when
2263 * searching (like point inclusion, or rectangle intersection):
2264 * first test against the BOX, and if TRUE, test against the desired
2265 * rectangle.
2266 * If the function does not have all the necessary information
2267 * to computed the requested rectangles, will crash with a
2268 * failed assertion. This is done so we catch all programming
2269 * errors, given that the function is called only from our code.
2270 *
2271 * We have the following 'special' meanings for a few fields:
2272 * * If LVIS_FOCUSED is set, we assume the item has the focus
2273 * This is important in ICON mode, where it might get a larger
2274 * then usual rectangle
2275 *
2276 * Please note that subitem support works only in REPORT mode.
2277 *
2278 * PARAMETER(S):
2279 * [I] infoPtr : valid pointer to the listview structure
2280 * [I] lpLVItem : item to compute the measures for
2281 * [O] lprcBox : ptr to Box rectangle
2282 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2283 * [0] lprcSelectBox : ptr to select box rectangle
2284 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2285 * [O] lprcIcon : ptr to Icon rectangle
2286 * Same as LVM_GETITEMRECT with LVIR_ICON
2287 * [O] lprcStateIcon: ptr to State Icon rectangle
2288 * [O] lprcLabel : ptr to Label rectangle
2289 * Same as LVM_GETITEMRECT with LVIR_LABEL
2290 *
2291 * RETURN:
2292 * None.
2293 */
2294 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2295 LPRECT lprcBox, LPRECT lprcSelectBox,
2296 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2297 {
2298 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2299 RECT Box, SelectBox, Icon, Label;
2300 COLUMN_INFO *lpColumnInfo = NULL;
2301 SIZE labelSize = { 0, 0 };
2302
2303 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2304
2305 /* Be smart and try to figure out the minimum we have to do */
2306 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2307 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2308 {
2309 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2310 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2311 }
2312 if (lprcSelectBox) doSelectBox = TRUE;
2313 if (lprcLabel) doLabel = TRUE;
2314 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2315 if (doSelectBox)
2316 {
2317 doIcon = TRUE;
2318 doLabel = TRUE;
2319 }
2320
2321 /************************************************************/
2322 /* compute the box rectangle (it should be cheap to do) */
2323 /************************************************************/
2324 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2325 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2326
2327 if (lpLVItem->iSubItem)
2328 {
2329 Box = lpColumnInfo->rcHeader;
2330 }
2331 else
2332 {
2333 Box.left = 0;
2334 Box.right = infoPtr->nItemWidth;
2335 }
2336 Box.top = 0;
2337 Box.bottom = infoPtr->nItemHeight;
2338
2339 /******************************************************************/
2340 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2341 /******************************************************************/
2342 if (doIcon)
2343 {
2344 LONG state_width = 0;
2345
2346 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2347 state_width = infoPtr->iconStateSize.cx;
2348
2349 if (infoPtr->uView == LV_VIEW_ICON)
2350 {
2351 Icon.left = Box.left + state_width;
2352 if (infoPtr->himlNormal)
2353 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2354 Icon.top = Box.top + ICON_TOP_PADDING;
2355 Icon.right = Icon.left;
2356 Icon.bottom = Icon.top;
2357 if (infoPtr->himlNormal)
2358 {
2359 Icon.right += infoPtr->iconSize.cx;
2360 Icon.bottom += infoPtr->iconSize.cy;
2361 }
2362 }
2363 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2364 {
2365 Icon.left = Box.left + state_width;
2366
2367 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2368 {
2369 /* we need the indent in report mode */
2370 assert(lpLVItem->mask & LVIF_INDENT);
2371 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2372 }
2373
2374 Icon.top = Box.top;
2375 Icon.right = Icon.left;
2376 if (infoPtr->himlSmall &&
2377 (!lpColumnInfo || lpLVItem->iSubItem == 0 ||
2378 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2379 Icon.right += infoPtr->iconSize.cx;
2380 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2381 }
2382 if(lprcIcon) *lprcIcon = Icon;
2383 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2384
2385 /* TODO: is this correct? */
2386 if (lprcStateIcon)
2387 {
2388 lprcStateIcon->left = Icon.left - state_width;
2389 lprcStateIcon->right = Icon.left;
2390 lprcStateIcon->top = Icon.top;
2391 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2392 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2393 }
2394 }
2395 else Icon.right = 0;
2396
2397 /************************************************************/
2398 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2399 /************************************************************/
2400 if (doLabel)
2401 {
2402 /* calculate how far to the right can the label stretch */
2403 Label.right = Box.right;
2404 if (infoPtr->uView == LV_VIEW_DETAILS)
2405 {
2406 if (lpLVItem->iSubItem == 0)
2407 {
2408 /* we need a zero based rect here */
2409 Label = lpColumnInfo->rcHeader;
2410 OffsetRect(&Label, -Label.left, 0);
2411 }
2412 }
2413
2414 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2415 {
2416 labelSize.cx = infoPtr->nItemWidth;
2417 labelSize.cy = infoPtr->nItemHeight;
2418 goto calc_label;
2419 }
2420
2421 /* we need the text in non owner draw mode */
2422 assert(lpLVItem->mask & LVIF_TEXT);
2423 if (is_text(lpLVItem->pszText))
2424 {
2425 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2426 HDC hdc = GetDC(infoPtr->hwndSelf);
2427 HFONT hOldFont = SelectObject(hdc, hFont);
2428 UINT uFormat;
2429 RECT rcText;
2430
2431 /* compute rough rectangle where the label will go */
2432 SetRectEmpty(&rcText);
2433 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2434 rcText.bottom = infoPtr->nItemHeight;
2435 if (infoPtr->uView == LV_VIEW_ICON)
2436 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2437
2438 /* now figure out the flags */
2439 if (infoPtr->uView == LV_VIEW_ICON)
2440 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2441 else
2442 uFormat = LV_SL_DT_FLAGS;
2443
2444 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2445
2446 if (rcText.right != rcText.left)
2447 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2448
2449 labelSize.cy = rcText.bottom - rcText.top;
2450
2451 SelectObject(hdc, hOldFont);
2452 ReleaseDC(infoPtr->hwndSelf, hdc);
2453 }
2454
2455 calc_label:
2456 if (infoPtr->uView == LV_VIEW_ICON)
2457 {
2458 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2459 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2460 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2461 Label.right = Label.left + labelSize.cx;
2462 Label.bottom = Label.top + infoPtr->nItemHeight;
2463 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2464 {
2465 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2466 labelSize.cy /= infoPtr->ntmHeight;
2467 labelSize.cy = max(labelSize.cy, 1);
2468 labelSize.cy *= infoPtr->ntmHeight;
2469 }
2470 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2471 }
2472 else if (infoPtr->uView == LV_VIEW_DETAILS)
2473 {
2474 Label.left = Icon.right;
2475 Label.top = Box.top;
2476 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2477 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2478 Label.bottom = Label.top + infoPtr->nItemHeight;
2479 }
2480 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2481 {
2482 Label.left = Icon.right;
2483 Label.top = Box.top;
2484 Label.right = min(Label.left + labelSize.cx, Label.right);
2485 Label.bottom = Label.top + infoPtr->nItemHeight;
2486 }
2487
2488 if (lprcLabel) *lprcLabel = Label;
2489 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2490 }
2491
2492 /************************************************************/
2493 /* compute SELECT bounding box */
2494 /************************************************************/
2495 if (doSelectBox)
2496 {
2497 if (infoPtr->uView == LV_VIEW_DETAILS)
2498 {
2499 SelectBox.left = Icon.left;
2500 SelectBox.top = Box.top;
2501 SelectBox.bottom = Box.bottom;
2502
2503 if (labelSize.cx)
2504 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2505 else
2506 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2507 }
2508 else
2509 {
2510 UnionRect(&SelectBox, &Icon, &Label);
2511 }
2512 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2513 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2514 }
2515
2516 /* Fix the Box if necessary */
2517 if (lprcBox)
2518 {
2519 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2520 else *lprcBox = Box;
2521 }
2522 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2523 }
2524
2525 /***
2526 * DESCRIPTION: [INTERNAL]
2527 *
2528 * PARAMETER(S):
2529 * [I] infoPtr : valid pointer to the listview structure
2530 * [I] nItem : item number
2531 * [O] lprcBox : ptr to Box rectangle
2532 *
2533 * RETURN:
2534 * None.
2535 */
2536 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2537 {
2538 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2539 POINT Position, Origin;
2540 LVITEMW lvItem;
2541
2542 LISTVIEW_GetOrigin(infoPtr, &Origin);
2543 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2544
2545 /* Be smart and try to figure out the minimum we have to do */
2546 lvItem.mask = 0;
2547 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2548 lvItem.mask |= LVIF_TEXT;
2549 lvItem.iItem = nItem;
2550 lvItem.iSubItem = 0;
2551 lvItem.pszText = szDispText;
2552 lvItem.cchTextMax = DISP_TEXT_SIZE;
2553 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2554 if (infoPtr->uView == LV_VIEW_ICON)
2555 {
2556 lvItem.mask |= LVIF_STATE;
2557 lvItem.stateMask = LVIS_FOCUSED;
2558 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2559 }
2560 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2561
2562 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2563 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2564 {
2565 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2566 }
2567 else
2568 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2569 }
2570
2571 /* LISTVIEW_MapIdToIndex helper */
2572 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2573 {
2574 ITEM_ID *id1 = (ITEM_ID*)p1;
2575 ITEM_ID *id2 = (ITEM_ID*)p2;
2576
2577 if (id1->id == id2->id) return 0;
2578
2579 return (id1->id < id2->id) ? -1 : 1;
2580 }
2581
2582 /***
2583 * DESCRIPTION:
2584 * Returns the item index for id specified.
2585 *
2586 * PARAMETER(S):
2587 * [I] infoPtr : valid pointer to the listview structure
2588 * [I] iID : item id to get index for
2589 *
2590 * RETURN:
2591 * Item index, or -1 on failure.
2592 */
2593 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2594 {
2595 ITEM_ID ID;
2596 INT index;
2597
2598 TRACE("iID=%d\n", iID);
2599
2600 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2601 if (infoPtr->nItemCount == 0) return -1;
2602
2603 ID.id = iID;
2604 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2605
2606 if (index != -1)
2607 {
2608 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2609 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2610 }
2611
2612 return -1;
2613 }
2614
2615 /***
2616 * DESCRIPTION:
2617 * Returns the item id for index given.
2618 *
2619 * PARAMETER(S):
2620 * [I] infoPtr : valid pointer to the listview structure
2621 * [I] iItem : item index to get id for
2622 *
2623 * RETURN:
2624 * Item id.
2625 */
2626 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2627 {
2628 ITEM_INFO *lpItem;
2629 HDPA hdpaSubItems;
2630
2631 TRACE("iItem=%d\n", iItem);
2632
2633 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2634 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2635
2636 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2637 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2638
2639 return lpItem->id->id;
2640 }
2641
2642 /***
2643 * DESCRIPTION:
2644 * Returns the current icon position, and advances it along the top.
2645 * The returned position is not offset by Origin.
2646 *
2647 * PARAMETER(S):
2648 * [I] infoPtr : valid pointer to the listview structure
2649 * [O] lpPos : will get the current icon position
2650 *
2651 * RETURN:
2652 * None
2653 */
2654 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2655 {
2656 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2657
2658 *lpPos = infoPtr->currIconPos;
2659
2660 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2661 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2662
2663 infoPtr->currIconPos.x = 0;
2664 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2665 }
2666
2667
2668 /***
2669 * DESCRIPTION:
2670 * Returns the current icon position, and advances it down the left edge.
2671 * The returned position is not offset by Origin.
2672 *
2673 * PARAMETER(S):
2674 * [I] infoPtr : valid pointer to the listview structure
2675 * [O] lpPos : will get the current icon position
2676 *
2677 * RETURN:
2678 * None
2679 */
2680 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2681 {
2682 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2683
2684 *lpPos = infoPtr->currIconPos;
2685
2686 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2687 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2688
2689 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2690 infoPtr->currIconPos.y = 0;
2691 }
2692
2693
2694 /***
2695 * DESCRIPTION:
2696 * Moves an icon to the specified position.
2697 * It takes care of invalidating the item, etc.
2698 *
2699 * PARAMETER(S):
2700 * [I] infoPtr : valid pointer to the listview structure
2701 * [I] nItem : the item to move
2702 * [I] lpPos : the new icon position
2703 * [I] isNew : flags the item as being new
2704 *
2705 * RETURN:
2706 * Success: TRUE
2707 * Failure: FALSE
2708 */
2709 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2710 {
2711 POINT old;
2712
2713 if (!isNew)
2714 {
2715 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2716 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2717
2718 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2719 LISTVIEW_InvalidateItem(infoPtr, nItem);
2720 }
2721
2722 /* Allocating a POINTER for every item is too resource intensive,
2723 * so we'll keep the (x,y) in different arrays */
2724 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2725 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2726
2727 LISTVIEW_InvalidateItem(infoPtr, nItem);
2728
2729 return TRUE;
2730 }
2731
2732 /***
2733 * DESCRIPTION:
2734 * Arranges listview items in icon display mode.
2735 *
2736 * PARAMETER(S):
2737 * [I] infoPtr : valid pointer to the listview structure
2738 * [I] nAlignCode : alignment code
2739 *
2740 * RETURN:
2741 * SUCCESS : TRUE
2742 * FAILURE : FALSE
2743 */
2744 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2745 {
2746 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2747 POINT pos;
2748 INT i;
2749
2750 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2751
2752 TRACE("nAlignCode=%d\n", nAlignCode);
2753
2754 if (nAlignCode == LVA_DEFAULT)
2755 {
2756 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2757 else nAlignCode = LVA_ALIGNTOP;
2758 }
2759
2760 switch (nAlignCode)
2761 {
2762 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2763 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2764 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2765 default: return FALSE;
2766 }
2767
2768 infoPtr->bAutoarrange = TRUE;
2769 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2770 for (i = 0; i < infoPtr->nItemCount; i++)
2771 {
2772 next_pos(infoPtr, &pos);
2773 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2774 }
2775
2776 return TRUE;
2777 }
2778
2779 /***
2780 * DESCRIPTION:
2781 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2782 * For LVS_REPORT always returns empty rectangle.
2783 *
2784 * PARAMETER(S):
2785 * [I] infoPtr : valid pointer to the listview structure
2786 * [O] lprcView : bounding rectangle
2787 *
2788 * RETURN:
2789 * SUCCESS : TRUE
2790 * FAILURE : FALSE
2791 */
2792 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2793 {
2794 INT i, x, y;
2795
2796 SetRectEmpty(lprcView);
2797
2798 switch (infoPtr->uView)
2799 {
2800 case LV_VIEW_ICON:
2801 case LV_VIEW_SMALLICON:
2802 for (i = 0; i < infoPtr->nItemCount; i++)
2803 {
2804 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2805 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2806 lprcView->right = max(lprcView->right, x);
2807 lprcView->bottom = max(lprcView->bottom, y);
2808 }
2809 if (infoPtr->nItemCount > 0)
2810 {
2811 lprcView->right += infoPtr->nItemWidth;
2812 lprcView->bottom += infoPtr->nItemHeight;
2813 }
2814 break;
2815
2816 case LV_VIEW_LIST:
2817 y = LISTVIEW_GetCountPerColumn(infoPtr);
2818 x = infoPtr->nItemCount / y;
2819 if (infoPtr->nItemCount % y) x++;
2820 lprcView->right = x * infoPtr->nItemWidth;
2821 lprcView->bottom = y * infoPtr->nItemHeight;
2822 break;
2823 }
2824 }
2825
2826 /***
2827 * DESCRIPTION:
2828 * Retrieves the bounding rectangle of all the items.
2829 *
2830 * PARAMETER(S):
2831 * [I] infoPtr : valid pointer to the listview structure
2832 * [O] lprcView : bounding rectangle
2833 *
2834 * RETURN:
2835 * SUCCESS : TRUE
2836 * FAILURE : FALSE
2837 */
2838 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2839 {
2840 POINT ptOrigin;
2841
2842 TRACE("(lprcView=%p)\n", lprcView);
2843
2844 if (!lprcView) return FALSE;
2845
2846 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2847
2848 if (infoPtr->uView != LV_VIEW_DETAILS)
2849 {
2850 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2851 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2852 }
2853
2854 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2855
2856 return TRUE;
2857 }
2858
2859 /***
2860 * DESCRIPTION:
2861 * Retrieves the subitem pointer associated with the subitem index.
2862 *
2863 * PARAMETER(S):
2864 * [I] hdpaSubItems : DPA handle for a specific item
2865 * [I] nSubItem : index of subitem
2866 *
2867 * RETURN:
2868 * SUCCESS : subitem pointer
2869 * FAILURE : NULL
2870 */
2871 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2872 {
2873 SUBITEM_INFO *lpSubItem;
2874 INT i;
2875
2876 /* we should binary search here if need be */
2877 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2878 {
2879 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2880 if (lpSubItem->iSubItem == nSubItem)
2881 return lpSubItem;
2882 }
2883
2884 return NULL;
2885 }
2886
2887
2888 /***
2889 * DESCRIPTION:
2890 * Calculates the desired item width.
2891 *
2892 * PARAMETER(S):
2893 * [I] infoPtr : valid pointer to the listview structure
2894 *
2895 * RETURN:
2896 * The desired item width.
2897 */
2898 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2899 {
2900 INT nItemWidth = 0;
2901
2902 TRACE("uView=%d\n", infoPtr->uView);
2903
2904 if (infoPtr->uView == LV_VIEW_ICON)
2905 nItemWidth = infoPtr->iconSpacing.cx;
2906 else if (infoPtr->uView == LV_VIEW_DETAILS)
2907 {
2908 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2909 {
2910 RECT rcHeader;
2911 INT index;
2912
2913 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2914 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2915
2916 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2917 nItemWidth = rcHeader.right;
2918 }
2919 }
2920 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2921 {
2922 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2923 LVITEMW lvItem;
2924 INT i;
2925
2926 lvItem.mask = LVIF_TEXT;
2927 lvItem.iSubItem = 0;
2928
2929 for (i = 0; i < infoPtr->nItemCount; i++)
2930 {
2931 lvItem.iItem = i;
2932 lvItem.pszText = szDispText;
2933 lvItem.cchTextMax = DISP_TEXT_SIZE;
2934 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2935 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2936 nItemWidth);
2937 }
2938
2939 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2940 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2941
2942 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2943 }
2944
2945 return nItemWidth;
2946 }
2947
2948 /***
2949 * DESCRIPTION:
2950 * Calculates the desired item height.
2951 *
2952 * PARAMETER(S):
2953 * [I] infoPtr : valid pointer to the listview structure
2954 *
2955 * RETURN:
2956 * The desired item height.
2957 */
2958 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2959 {
2960 INT nItemHeight;
2961
2962 TRACE("uView=%d\n", infoPtr->uView);
2963
2964 if (infoPtr->uView == LV_VIEW_ICON)
2965 nItemHeight = infoPtr->iconSpacing.cy;
2966 else
2967 {
2968 nItemHeight = infoPtr->ntmHeight;
2969 if (infoPtr->himlState)
2970 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2971 if (infoPtr->himlSmall)
2972 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2973 nItemHeight += HEIGHT_PADDING;
2974 if (infoPtr->nMeasureItemHeight > 0)
2975 nItemHeight = infoPtr->nMeasureItemHeight;
2976 }
2977
2978 return max(nItemHeight, 1);
2979 }
2980
2981 /***
2982 * DESCRIPTION:
2983 * Updates the width, and height of an item.
2984 *
2985 * PARAMETER(S):
2986 * [I] infoPtr : valid pointer to the listview structure
2987 *
2988 * RETURN:
2989 * None.
2990 */
2991 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2992 {
2993 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2994 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2995 }
2996
2997
2998 /***
2999 * DESCRIPTION:
3000 * Retrieves and saves important text metrics info for the current
3001 * Listview font.
3002 *
3003 * PARAMETER(S):
3004 * [I] infoPtr : valid pointer to the listview structure
3005 *
3006 */
3007 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
3008 {
3009 HDC hdc = GetDC(infoPtr->hwndSelf);
3010 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3011 HFONT hOldFont = SelectObject(hdc, hFont);
3012 TEXTMETRICW tm;
3013 SIZE sz;
3014
3015 if (GetTextMetricsW(hdc, &tm))
3016 {
3017 infoPtr->ntmHeight = tm.tmHeight;
3018 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3019 }
3020
3021 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3022 infoPtr->nEllipsisWidth = sz.cx;
3023
3024 SelectObject(hdc, hOldFont);
3025 ReleaseDC(infoPtr->hwndSelf, hdc);
3026
3027 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3028 }
3029
3030 /***
3031 * DESCRIPTION:
3032 * A compare function for ranges
3033 *
3034 * PARAMETER(S)
3035 * [I] range1 : pointer to range 1;
3036 * [I] range2 : pointer to range 2;
3037 * [I] flags : flags
3038 *
3039 * RETURNS:
3040 * > 0 : if range 1 > range 2
3041 * < 0 : if range 2 > range 1
3042 * = 0 : if range intersects range 2
3043 */
3044 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3045 {
3046 INT cmp;
3047
3048 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3049 cmp = -1;
3050 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3051 cmp = 1;
3052 else
3053 cmp = 0;
3054
3055 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3056
3057 return cmp;
3058 }
3059
3060 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3061
3062 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3063 {
3064 INT i;
3065 RANGE *prev, *curr;
3066
3067 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3068 assert (ranges);
3069 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3070 ranges_dump(ranges);
3071 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3072 {
3073 prev = DPA_GetPtr(ranges->hdpa, 0);
3074 assert (prev->lower >= 0 && prev->lower < prev->upper);
3075 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3076 {
3077 curr = DPA_GetPtr(ranges->hdpa, i);
3078 assert (prev->upper <= curr->lower);
3079 assert (curr->lower < curr->upper);
3080 prev = curr;
3081 }
3082 }
3083 TRACE("--- Done checking---\n");
3084 }
3085
3086 static RANGES ranges_create(int count)
3087 {
3088 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3089 if (!ranges) return NULL;
3090 ranges->hdpa = DPA_Create(count);
3091 if (ranges->hdpa) return ranges;
3092 Free(ranges);
3093 return NULL;
3094 }
3095
3096 static void ranges_clear(RANGES ranges)
3097 {
3098 INT i;
3099
3100 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3101 Free(DPA_GetPtr(ranges->hdpa, i));
3102 DPA_DeleteAllPtrs(ranges->hdpa);
3103 }
3104
3105
3106 static void ranges_destroy(RANGES ranges)
3107 {
3108 if (!ranges) return;
3109 ranges_clear(ranges);
3110 DPA_Destroy(ranges->hdpa);
3111 Free(ranges);
3112 }
3113
3114 static RANGES ranges_clone(RANGES ranges)
3115 {
3116 RANGES clone;
3117 INT i;
3118
3119 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3120
3121 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3122 {
3123 RANGE *newrng = Alloc(sizeof(RANGE));
3124 if (!newrng) goto fail;
3125 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3126 DPA_SetPtr(clone->hdpa, i, newrng);
3127 }
3128 return clone;
3129
3130 fail:
3131 TRACE ("clone failed\n");
3132 ranges_destroy(clone);
3133 return NULL;
3134 }
3135
3136 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3137 {
3138 INT i;
3139
3140 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3141 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3142
3143 return ranges;
3144 }
3145
3146 static void ranges_dump(RANGES ranges)
3147 {
3148 INT i;
3149
3150 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3151 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3152 }
3153
3154 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3155 {
3156 RANGE srchrng = { nItem, nItem + 1 };
3157
3158 TRACE("(nItem=%d)\n", nItem);
3159 ranges_check(ranges, "before contain");
3160 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3161 }
3162
3163 static INT ranges_itemcount(RANGES ranges)
3164 {
3165 INT i, count = 0;
3166
3167 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3168 {
3169 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3170 count += sel->upper - sel->lower;
3171 }
3172
3173 return count;
3174 }
3175
3176 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3177 {
3178 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3179 INT index;
3180
3181 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3182 if (index == -1) return TRUE;
3183
3184 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3185 {
3186 chkrng = DPA_GetPtr(ranges->hdpa, index);
3187 if (chkrng->lower >= nItem)
3188 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3189 if (chkrng->upper > nItem)
3190 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3191 }
3192 return TRUE;
3193 }
3194
3195 static BOOL ranges_add(RANGES ranges, RANGE range)
3196 {
3197 RANGE srchrgn;
3198 INT index;
3199
3200 TRACE("(%s)\n", debugrange(&range));
3201 ranges_check(ranges, "before add");
3202
3203 /* try find overlapping regions first */
3204 srchrgn.lower = range.lower - 1;
3205 srchrgn.upper = range.upper + 1;
3206 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3207
3208 if (index == -1)
3209 {
3210 RANGE *newrgn;
3211
3212 TRACE("Adding new range\n");
3213
3214 /* create the brand new range to insert */
3215 newrgn = Alloc(sizeof(RANGE));
3216 if(!newrgn) goto fail;
3217 *newrgn = range;
3218
3219 /* figure out where to insert it */
3220 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3221 TRACE("index=%d\n", index);
3222 if (index == -1) index = 0;
3223
3224 /* and get it over with */
3225 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3226 {
3227 Free(newrgn);
3228 goto fail;
3229 }
3230 }
3231 else
3232 {
3233 RANGE *chkrgn, *mrgrgn;
3234 INT fromindex, mergeindex;
3235
3236 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3237 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3238
3239 chkrgn->lower = min(range.lower, chkrgn->lower);
3240 chkrgn->upper = max(range.upper, chkrgn->upper);
3241
3242 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3243
3244 /* merge now common ranges */
3245 fromindex = 0;
3246 srchrgn.lower = chkrgn->lower - 1;
3247 srchrgn.upper = chkrgn->upper + 1;
3248
3249 do
3250 {
3251 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3252 if (mergeindex == -1) break;
3253 if (mergeindex == index)
3254 {
3255 fromindex = index + 1;
3256 continue;
3257 }
3258
3259 TRACE("Merge with index %i\n", mergeindex);
3260
3261 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3262 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3263 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3264 Free(mrgrgn);
3265 DPA_DeletePtr(ranges->hdpa, mergeindex);
3266 if (mergeindex < index) index --;
3267 } while(1);
3268 }
3269
3270 ranges_check(ranges, "after add");
3271 return TRUE;
3272
3273 fail:
3274 ranges_check(ranges, "failed add");
3275 return FALSE;
3276 }
3277
3278 static BOOL ranges_del(RANGES ranges, RANGE range)
3279 {
3280 RANGE *chkrgn;
3281 INT index;
3282
3283 TRACE("(%s)\n", debugrange(&range));
3284 ranges_check(ranges, "before del");
3285
3286 /* we don't use DPAS_SORTED here, since we need *
3287 * to find the first overlapping range */
3288 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3289 while(index != -1)
3290 {
3291 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3292
3293 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3294
3295 /* case 1: Same range */
3296 if ( (chkrgn->upper == range.upper) &&
3297 (chkrgn->lower == range.lower) )
3298 {
3299 DPA_DeletePtr(ranges->hdpa, index);
3300 Free(chkrgn);
3301 break;
3302 }
3303 /* case 2: engulf */
3304 else if ( (chkrgn->upper <= range.upper) &&
3305 (chkrgn->lower >= range.lower) )
3306 {
3307 DPA_DeletePtr(ranges->hdpa, index);
3308 Free(chkrgn);
3309 }
3310 /* case 3: overlap upper */
3311 else if ( (chkrgn->upper <= range.upper) &&
3312 (chkrgn->lower < range.lower) )
3313 {
3314 chkrgn->upper = range.lower;
3315 }
3316 /* case 4: overlap lower */
3317 else if ( (chkrgn->upper > range.upper) &&
3318 (chkrgn->lower >= range.lower) )
3319 {
3320 chkrgn->lower = range.upper;
3321 break;
3322 }
3323 /* case 5: fully internal */
3324 else
3325 {
3326 RANGE *newrgn;
3327
3328 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3329 newrgn->lower = chkrgn->lower;
3330 newrgn->upper = range.lower;
3331 chkrgn->lower = range.upper;
3332 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3333 {
3334 Free(newrgn);
3335 goto fail;
3336 }
3337 break;
3338 }
3339
3340 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3341 }
3342
3343 ranges_check(ranges, "after del");
3344 return TRUE;
3345
3346 fail:
3347 ranges_check(ranges, "failed del");
3348 return FALSE;
3349 }
3350
3351 /***
3352 * DESCRIPTION:
3353 * Removes all selection ranges
3354 *
3355 * Parameters(s):
3356 * [I] infoPtr : valid pointer to the listview structure
3357 * [I] toSkip : item range to skip removing the selection
3358 *
3359 * RETURNS:
3360 * SUCCESS : TRUE
3361 * FAILURE : FALSE
3362 */
3363 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3364 {
3365 LVITEMW lvItem;
3366 ITERATOR i;
3367 RANGES clone;
3368
3369 TRACE("()\n");
3370
3371 lvItem.state = 0;
3372 lvItem.stateMask = LVIS_SELECTED;
3373
3374 /* need to clone the DPA because callbacks can change it */
3375 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3376 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3377 while(iterator_next(&i))
3378 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3379 /* note that the iterator destructor will free the cloned range */
3380 iterator_destroy(&i);
3381
3382 return TRUE;
3383 }
3384
3385 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3386 {
3387 RANGES toSkip;
3388
3389 if (!(toSkip = ranges_create(1))) return FALSE;
3390 if (nItem != -1) ranges_additem(toSkip, nItem);
3391 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3392 ranges_destroy(toSkip);
3393 return TRUE;
3394 }
3395
3396 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3397 {
3398 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3399 }
3400
3401 /***
3402 * DESCRIPTION:
3403 * Retrieves the number of items that are marked as selected.
3404 *
3405 * PARAMETER(S):
3406 * [I] infoPtr : valid pointer to the listview structure
3407 *
3408 * RETURN:
3409 * Number of items selected.
3410 */
3411 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3412 {
3413 INT nSelectedCount = 0;
3414
3415 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3416 {
3417 INT i;
3418 for (i = 0; i < infoPtr->nItemCount; i++)
3419 {
3420 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3421 nSelectedCount++;
3422 }
3423 }
3424 else
3425 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3426
3427 TRACE("nSelectedCount=%d\n", nSelectedCount);
3428 return nSelectedCount;
3429 }
3430
3431 /***
3432 * DESCRIPTION:
3433 * Manages the item focus.
3434 *
3435 * PARAMETER(S):
3436 * [I] infoPtr : valid pointer to the listview structure
3437 * [I] nItem : item index
3438 *
3439 * RETURN:
3440 * TRUE : focused item changed
3441 * FALSE : focused item has NOT changed
3442 */
3443 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3444 {
3445 INT oldFocus = infoPtr->nFocusedItem;
3446 LVITEMW lvItem;
3447
3448 if (nItem == infoPtr->nFocusedItem) return FALSE;
3449
3450 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3451 lvItem.stateMask = LVIS_FOCUSED;
3452 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3453
3454 return oldFocus != infoPtr->nFocusedItem;
3455 }
3456
3457 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3458 {
3459 if (nShiftItem < nItem) return nShiftItem;
3460
3461 if (nShiftItem > nItem) return nShiftItem + direction;
3462
3463 if (direction > 0) return nShiftItem + direction;
3464
3465 return min(nShiftItem, infoPtr->nItemCount - 1);
3466 }
3467
3468 /* This function updates focus index.
3469
3470 Parameters:
3471 focus : current focus index
3472 item : index of item to be added/removed
3473 direction : add/remove flag
3474 */
3475 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3476 {
3477 BOOL old_change = infoPtr->bDoChangeNotify;
3478
3479 infoPtr->bDoChangeNotify = FALSE;
3480 focus = shift_item(infoPtr, focus, item, direction);
3481 if (focus != infoPtr->nFocusedItem)
3482 LISTVIEW_SetItemFocus(infoPtr, focus);
3483 infoPtr->bDoChangeNotify = old_change;
3484 }
3485
3486 /**
3487 * DESCRIPTION:
3488 * Updates the various indices after an item has been inserted or deleted.
3489 *
3490 * PARAMETER(S):
3491 * [I] infoPtr : valid pointer to the listview structure
3492 * [I] nItem : item index
3493 * [I] direction : Direction of shift, +1 or -1.
3494 *
3495 * RETURN:
3496 * None
3497 */
3498 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3499 {
3500 TRACE("Shifting %i, %i steps\n", nItem, direction);
3501
3502 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3503 assert(abs(direction) == 1);
3504 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3505
3506 /* But we are not supposed to modify nHotItem! */
3507 }
3508
3509 /**
3510 * DESCRIPTION:
3511 * Adds a block of selections.
3512 *
3513 * PARAMETER(S):
3514 * [I] infoPtr : valid pointer to the listview structure
3515 * [I] nItem : item index
3516 *
3517 * RETURN:
3518 * Whether the window is still valid.
3519 */
3520 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3521 {
3522 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3523 INT nLast = max(infoPtr->nSelectionMark, nItem);
3524 HWND hwndSelf = infoPtr->hwndSelf;
3525 NMLVODSTATECHANGE nmlv;
3526 LVITEMW item;
3527 BOOL bOldChange;
3528 INT i;
3529
3530 /* Temporarily disable change notification
3531 * If the control is LVS_OWNERDATA, we need to send
3532 * only one LVN_ODSTATECHANGED notification.
3533 * See MSDN documentation for LVN_ITEMCHANGED.
3534 */
3535 bOldChange = infoPtr->bDoChangeNotify;
3536 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3537
3538 if (nFirst == -1) nFirst = nItem;
3539
3540 item.state = LVIS_SELECTED;
3541 item.stateMask = LVIS_SELECTED;
3542
3543 for (i = nFirst; i <= nLast; i++)
3544 LISTVIEW_SetItemState(infoPtr,i,&item);
3545
3546 ZeroMemory(&nmlv, sizeof(nmlv));
3547 nmlv.iFrom = nFirst;
3548 nmlv.iTo = nLast;
3549 nmlv.uOldState = 0;
3550 nmlv.uNewState = item.state;
3551
3552 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3553 if (!IsWindow(hwndSelf))
3554 return FALSE;
3555 infoPtr->bDoChangeNotify = bOldChange;
3556 return TRUE;
3557 }
3558
3559
3560 /***
3561 * DESCRIPTION:
3562 * Sets a single group selection.
3563 *
3564 * PARAMETER(S):
3565 * [I] infoPtr : valid pointer to the listview structure
3566 * [I] nItem : item index
3567 *
3568 * RETURN:
3569 * None
3570 */
3571 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3572 {
3573 RANGES selection;
3574 LVITEMW item;
3575 ITERATOR i;
3576 BOOL bOldChange;
3577
3578 if (!(selection = ranges_create(100))) return;
3579
3580 item.state = LVIS_SELECTED;
3581 item.stateMask = LVIS_SELECTED;
3582
3583 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3584 {
3585 if (infoPtr->nSelectionMark == -1)
3586 {
3587 infoPtr->nSelectionMark = nItem;
3588 ranges_additem(selection, nItem);
3589 }
3590 else
3591 {
3592 RANGE sel;
3593
3594 sel.lower = min(infoPtr->nSelectionMark, nItem);
3595 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3596 ranges_add(selection, sel);
3597 }
3598 }
3599 else
3600 {
3601 RECT rcItem, rcSel, rcSelMark;
3602 POINT ptItem;
3603
3604 rcItem.left = LVIR_BOUNDS;
3605 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
3606 ranges_destroy (selection);
3607 return;
3608 }
3609 rcSelMark.left = LVIR_BOUNDS;
3610 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3611 ranges_destroy (selection);
3612 return;
3613 }
3614 UnionRect(&rcSel, &rcItem, &rcSelMark);
3615 iterator_frameditems(&i, infoPtr, &rcSel);
3616 while(iterator_next(&i))
3617 {
3618 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3619 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3620 }
3621 iterator_destroy(&i);
3622 }
3623
3624 /* disable per item notifications on LVS_OWNERDATA style
3625 FIXME: single LVN_ODSTATECHANGED should be used */
3626 bOldChange = infoPtr->bDoChangeNotify;
3627 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3628
3629 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3630
3631
3632 iterator_rangesitems(&i, selection);
3633 while(iterator_next(&i))
3634 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3635 /* this will also destroy the selection */
3636 iterator_destroy(&i);
3637
3638 infoPtr->bDoChangeNotify = bOldChange;
3639
3640 LISTVIEW_SetItemFocus(infoPtr, nItem);
3641 }
3642
3643 /***
3644 * DESCRIPTION:
3645 * Sets a single selection.
3646 *
3647 * PARAMETER(S):
3648 * [I] infoPtr : valid pointer to the listview structure
3649 * [I] nItem : item index
3650 *
3651 * RETURN:
3652 * None
3653 */
3654 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3655 {
3656 LVITEMW lvItem;
3657
3658 TRACE("nItem=%d\n", nItem);
3659
3660 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3661
3662 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3663 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3664 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3665
3666 infoPtr->nSelectionMark = nItem;
3667 }
3668
3669 /***
3670 * DESCRIPTION:
3671 * Set selection(s) with keyboard.
3672 *
3673 * PARAMETER(S):
3674 * [I] infoPtr : valid pointer to the listview structure
3675 * [I] nItem : item index
3676 * [I] space : VK_SPACE code sent
3677 *
3678 * RETURN:
3679 * SUCCESS : TRUE (needs to be repainted)
3680 * FAILURE : FALSE (nothing has changed)
3681 */
3682 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3683 {
3684 /* FIXME: pass in the state */
3685 WORD wShift = GetKeyState(VK_SHIFT) & 0x8000;
3686 WORD wCtrl = GetKeyState(VK_CONTROL) & 0x8000;
3687 BOOL bResult = FALSE;
3688
3689 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3690 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3691 {
3692 bResult = TRUE;
3693
3694 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3695 LISTVIEW_SetSelection(infoPtr, nItem);
3696 else
3697 {
3698 if (wShift)
3699 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3700 else if (wCtrl)
3701 {
3702 LVITEMW lvItem;
3703 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3704 lvItem.stateMask = LVIS_SELECTED;
3705 if (space)
3706 {
3707 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3708 if (lvItem.state & LVIS_SELECTED)
3709 infoPtr->nSelectionMark = nItem;
3710 }
3711 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3712 }
3713 }
3714 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3715 }
3716
3717 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3718 return bResult;
3719 }
3720
3721 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3722 {
3723 LVHITTESTINFO lvHitTestInfo;
3724
3725 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3726 lvHitTestInfo.pt.x = pt.x;
3727 lvHitTestInfo.pt.y = pt.y;
3728
3729 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3730
3731 lpLVItem->mask = LVIF_PARAM;
3732 lpLVItem->iItem = lvHitTestInfo.iItem;
3733 lpLVItem->iSubItem = 0;
3734
3735 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3736 }
3737
3738 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3739 {
3740 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3741 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3742 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3743 }
3744
3745 /***
3746 * DESCRIPTION:
3747 * Called when the mouse is being actively tracked and has hovered for a specified
3748 * amount of time
3749 *
3750 * PARAMETER(S):
3751 * [I] infoPtr : valid pointer to the listview structure
3752 * [I] fwKeys : key indicator
3753 * [I] x,y : mouse position
3754 *
3755 * RETURN:
3756 * 0 if the message was processed, non-zero if there was an error
3757 *
3758 * INFO:
3759 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3760 * over the item for a certain period of time.
3761 *
3762 */
3763 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3764 {
3765 NMHDR hdr;
3766
3767 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3768
3769 if (LISTVIEW_IsHotTracking(infoPtr))
3770 {
3771 LVITEMW item;
3772 POINT pt;
3773
3774 pt.x = x;
3775 pt.y = y;
3776
3777 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3778 LISTVIEW_SetSelection(infoPtr, item.iItem);
3779
3780 SetFocus(infoPtr->hwndSelf);
3781 }
3782
3783 return 0;
3784 }
3785
3786 #define SCROLL_LEFT 0x1
3787 #define SCROLL_RIGHT 0x2
3788 #define SCROLL_UP 0x4
3789 #define SCROLL_DOWN 0x8
3790
3791 /***
3792 * DESCRIPTION:
3793 * Utility routine to draw and highlight items within a marquee selection rectangle.
3794 *
3795 * PARAMETER(S):
3796 * [I] infoPtr : valid pointer to the listview structure
3797 * [I] coords_orig : original co-ordinates of the cursor
3798 * [I] coords_offs : offsetted coordinates of the cursor
3799 * [I] offset : offset amount
3800 * [I] scroll : Bitmask of which directions we should scroll, if at all
3801 *
3802 * RETURN:
3803 * None.
3804 */
3805 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3806 const POINT *coords_offs, const POINT *offset,
3807 INT scroll)
3808 {
3809 BOOL controlDown = FALSE;
3810 LVITEMW item;
3811 ITERATOR old_elems, new_elems;
3812 RECT rect;
3813
3814 if (coords_offs->x > infoPtr->marqueeOrigin.x)
3815 {
3816 rect.left = infoPtr->marqueeOrigin.x;
3817 rect.right = coords_offs->x;
3818 }
3819 else
3820 {
3821 rect.left = coords_offs->x;
3822 rect.right = infoPtr->marqueeOrigin.x;
3823 }
3824
3825 if (coords_offs->y > infoPtr->marqueeOrigin.y)
3826 {
3827 rect.top = infoPtr->marqueeOrigin.y;
3828 rect.bottom = coords_offs->y;
3829 }
3830 else
3831 {
3832 rect.top = coords_offs->y;
3833 rect.bottom = infoPtr->marqueeOrigin.y;
3834 }
3835
3836 /* Cancel out the old marquee rectangle and draw the new one */
3837 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3838
3839 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3840 the cursor is further away */
3841
3842 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3843 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3844
3845 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3846 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3847
3848 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3849 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3850
3851 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3852 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3853
3854 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3855
3856 CopyRect(&infoPtr->marqueeRect, &rect);
3857
3858 CopyRect(&infoPtr->marqueeDrawRect, &rect);
3859 OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3860
3861 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3862 iterator_remove_common_items(&old_elems, &new_elems);
3863
3864 /* Iterate over no longer selected items */
3865 while (iterator_next(&old_elems))
3866 {
3867 if (old_elems.nItem > -1)
3868 {
3869 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3870 item.state = 0;
3871 else
3872 item.state = LVIS_SELECTED;
3873
3874 item.stateMask = LVIS_SELECTED;
3875
3876 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3877 }
3878 }
3879 iterator_destroy(&old_elems);
3880
3881
3882 /* Iterate over newly selected items */
3883 if (GetKeyState(VK_CONTROL) & 0x8000)
3884 controlDown = TRUE;
3885
3886 while (iterator_next(&new_elems))
3887 {
3888 if (new_elems.nItem > -1)
3889 {
3890 /* If CTRL is pressed, invert. If not, always select the item. */
3891 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3892 item.state = 0;
3893 else
3894 item.state = LVIS_SELECTED;
3895
3896 item.stateMask = LVIS_SELECTED;
3897
3898 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3899 }
3900 }
3901 iterator_destroy(&new_elems);
3902
3903 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3904 }
3905
3906 /***
3907 * DESCRIPTION:
3908 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3909 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3910 *
3911 * PARAMETER(S):
3912 * [I] hwnd : Handle to the listview
3913 * [I] uMsg : WM_TIMER (ignored)
3914 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3915 * [I] dwTimer : The elapsed time (ignored)
3916 *
3917 * RETURN:
3918 * None.
3919 */
3920 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3921 {
3922 LISTVIEW_INFO *infoPtr;
3923 SCROLLINFO scrollInfo;
3924 POINT coords_orig;
3925 POINT coords_offs;
3926 POINT offset;
3927 INT scroll = 0;
3928
3929 infoPtr = (LISTVIEW_INFO *) idEvent;
3930
3931 if (!infoPtr)
3932 return;
3933
3934 /* Get the current cursor position and convert to client coordinates */
3935 GetCursorPos(&coords_orig);
3936 ScreenToClient(hWnd, &coords_orig);
3937
3938 /* Ensure coordinates are within client bounds */
3939 coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3940 coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3941
3942 /* Get offset */
3943 LISTVIEW_GetOrigin(infoPtr, &offset);
3944
3945 /* Offset coordinates by the appropriate amount */
3946 coords_offs.x -= offset.x;
3947 coords_offs.y -= offset.y;
3948
3949 scrollInfo.cbSize = sizeof(SCROLLINFO);
3950 scrollInfo.fMask = SIF_ALL;
3951
3952 /* Work out in which directions we can scroll */
3953 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3954 {
3955 if (scrollInfo.nPos != scrollInfo.nMin)
3956 scroll |= SCROLL_UP;
3957
3958 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3959 scroll |= SCROLL_DOWN;
3960 }
3961
3962 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3963 {
3964 if (scrollInfo.nPos != scrollInfo.nMin)
3965 scroll |= SCROLL_LEFT;
3966
3967 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3968 scroll |= SCROLL_RIGHT;
3969 }
3970
3971 if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
3972 ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
3973 ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
3974 ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
3975 {
3976 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
3977 }
3978 }
3979
3980 /***
3981 * DESCRIPTION:
3982 * Called whenever WM_MOUSEMOVE is received.
3983 *
3984 * PARAMETER(S):
3985 * [I] infoPtr : valid pointer to the listview structure
3986 * [I] fwKeys : key indicator
3987 * [I] x,y : mouse position
3988 *
3989 * RETURN:
3990 * 0 if the message is processed, non-zero if there was an error
3991 */
3992 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3993 {
3994 LVHITTESTINFO ht;
3995 RECT rect;
3996 POINT pt;
3997
3998 if (!(fwKeys & MK_LBUTTON))
3999 infoPtr->bLButtonDown = FALSE;
4000
4001 if (infoPtr->bLButtonDown)
4002 {
4003 rect.left = rect.right = infoPtr->ptClickPos.x;
4004 rect.top = rect.bottom = infoPtr->ptClickPos.y;
4005
4006 InflateRect(&rect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
4007
4008 if (infoPtr->bMarqueeSelect)
4009 {
4010 POINT coords_orig;
4011 POINT coords_offs;
4012 POINT offset;
4013
4014 coords_orig.x = x;
4015 coords_orig.y = y;
4016
4017 /* Get offset */
4018 LISTVIEW_GetOrigin(infoPtr, &offset);
4019
4020 /* Ensure coordinates are within client bounds */
4021 coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4022 coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4023
4024 /* Offset coordinates by the appropriate amount */
4025 coords_offs.x -= offset.x;
4026 coords_offs.y -= offset.y;
4027
4028 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4029 move the mouse again */
4030
4031 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4032 (y >= infoPtr->rcList.bottom))
4033 {
4034 if (!infoPtr->bScrolling)
4035 {
4036 infoPtr->bScrolling = TRUE;
4037 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4038 }
4039 }
4040 else
4041 {
4042 infoPtr->bScrolling = FALSE;
4043 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4044 }
4045
4046 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4047 return 0;
4048 }
4049
4050 pt.x = x;
4051 pt.y = y;
4052
4053 ht.pt = pt;
4054 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4055
4056 /* reset item marker */
4057 if (infoPtr->nLButtonDownItem != ht.iItem)
4058 infoPtr->nLButtonDownItem = -1;
4059
4060 if (!PtInRect(&rect, pt))
4061 {
4062 /* this path covers the following:
4063 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4064 2. change focus with keys
4065 3. move mouse over item from step 1 selects it and moves focus on it */
4066 if (infoPtr->nLButtonDownItem != -1 &&
4067 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4068 {
4069 LVITEMW lvItem;
4070
4071 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4072 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4073
4074 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4075 infoPtr->nLButtonDownItem = -1;
4076 }
4077
4078 if (!infoPtr->bDragging)
4079 {
4080 ht.pt = infoPtr->ptClickPos;
4081 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4082
4083 /* If the click is outside the range of an item, begin a
4084 highlight. If not, begin an item drag. */
4085 if (ht.iItem == -1)
4086 {
4087 NMHDR hdr;
4088
4089 /* If we're allowing multiple selections, send notification.
4090 If return value is non-zero, cancel. */
4091 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4092 {
4093 /* Store the absolute coordinates of the click */
4094 POINT offset;
4095 LISTVIEW_GetOrigin(infoPtr, &offset);
4096
4097 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4098 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4099
4100 /* Begin selection and capture mouse */
4101 infoPtr->bMarqueeSelect = TRUE;
4102 SetCapture(infoPtr->hwndSelf);
4103 }
4104 }
4105 else
4106 {
4107 NMLISTVIEW nmlv;
4108
4109 ZeroMemory(&nmlv, sizeof(nmlv));
4110 nmlv.iItem = ht.iItem;
4111 nmlv.ptAction = infoPtr->ptClickPos;
4112
4113 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4114 infoPtr->bDragging = TRUE;
4115 }
4116 }
4117
4118 return 0;
4119 }
4120 }
4121
4122 /* see if we are supposed to be tracking mouse hovering */
4123 if (LISTVIEW_IsHotTracking(infoPtr)) {
4124 TRACKMOUSEEVENT trackinfo;
4125 DWORD flags;
4126
4127 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4128 trackinfo.dwFlags = TME_QUERY;
4129
4130 /* see if we are already tracking this hwnd */
4131 _TrackMouseEvent(&trackinfo);
4132
4133 flags = TME_LEAVE;
4134 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
4135 flags |= TME_HOVER;
4136
4137 if((trackinfo.dwFlags & flags) != flags || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4138 trackinfo.dwFlags = flags;
4139 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4140 trackinfo.hwndTrack = infoPtr->hwndSelf;
4141
4142 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4143 _TrackMouseEvent(&trackinfo);
4144 }
4145 }
4146
4147 return 0;
4148 }
4149
4150
4151 /***
4152 * Tests whether the item is assignable to a list with style lStyle
4153 */
4154 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4155 {
4156 if ( (lpLVItem->mask & LVIF_TEXT) &&
4157 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4158 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4159
4160 return TRUE;
4161 }
4162
4163
4164 /***
4165 * DESCRIPTION:
4166 * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4167 *
4168 * PARAMETER(S):
4169 * [I] infoPtr : valid pointer to the listview structure
4170 * [I] lpLVItem : valid pointer to new item attributes
4171 * [I] isNew : the item being set is being inserted
4172 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4173 * [O] bChanged : will be set to TRUE if the item really changed
4174 *
4175 * RETURN:
4176 * SUCCESS : TRUE
4177 * FAILURE : FALSE
4178 */
4179 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4180 {
4181 ITEM_INFO *lpItem;
4182 NMLISTVIEW nmlv;
4183 UINT uChanged = 0;
4184 LVITEMW item;
4185 /* stateMask is ignored for LVM_INSERTITEM */
4186 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4187
4188 TRACE("()\n");
4189
4190 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4191
4192 if (lpLVItem->mask == 0) return TRUE;
4193
4194 if (infoPtr->dwStyle & LVS_OWNERDATA)
4195 {
4196 /* a virtual listview only stores selection and focus */
4197 if (lpLVItem->mask & ~LVIF_STATE)
4198 return FALSE;
4199 lpItem = NULL;
4200 }
4201 else
4202 {
4203 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4204 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4205 assert (lpItem);
4206 }
4207
4208 /* we need to get the lParam and state of the item */
4209 item.iItem = lpLVItem->iItem;
4210 item.iSubItem = lpLVItem->iSubItem;
4211 item.mask = LVIF_STATE | LVIF_PARAM;
4212 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4213
4214 item.state = 0;
4215 item.lParam = 0;
4216 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4217
4218 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4219 /* determine what fields will change */
4220 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4221 uChanged |= LVIF_STATE;
4222
4223 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4224 uChanged |= LVIF_IMAGE;
4225
4226 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4227 uChanged |= LVIF_PARAM;
4228
4229 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4230 uChanged |= LVIF_INDENT;
4231
4232 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4233 uChanged |= LVIF_TEXT;
4234
4235 TRACE("change mask=0x%x\n", uChanged);
4236
4237 memset(&nmlv, 0, sizeof(NMLISTVIEW));
4238 nmlv.iItem = lpLVItem->iItem;
4239 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4240 nmlv.uOldState = item.state;
4241 nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4242 nmlv.lParam = item.lParam;
4243
4244 /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4245 and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4246 are enabled. Even nothing really changed we still need to send this,
4247 in this case uChanged mask is just set to passed item mask. */
4248 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4249 {
4250 HWND hwndSelf = infoPtr->hwndSelf;
4251
4252 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4253 return FALSE;
4254 if (!IsWindow(hwndSelf))
4255 return FALSE;
4256 }
4257
4258 /* When item is inserted we need to shift existing focus index if new item has lower index. */
4259 if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4260 /* this means we won't hit a focus change path later */
4261 ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4262 {
4263 if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4264 infoPtr->nFocusedItem++;
4265 }
4266
4267 if (!uChanged) return TRUE;
4268 *bChanged = TRUE;
4269
4270 /* copy information */
4271 if (lpLVItem->mask & LVIF_TEXT)
4272 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4273
4274 if (lpLVItem->mask & LVIF_IMAGE)
4275 lpItem->hdr.iImage = lpLVItem->iImage;
4276
4277 if (lpLVItem->mask & LVIF_PARAM)
4278 lpItem->lParam = lpLVItem->lParam;
4279
4280 if (lpLVItem->mask & LVIF_INDENT)
4281 lpItem->iIndent = lpLVItem->iIndent;
4282
4283 if (uChanged & LVIF_STATE)
4284 {
4285 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4286 {
4287 lpItem->state &= ~stateMask;
4288 lpItem->state |= (lpLVItem->state & stateMask);
4289 }
4290 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4291 {
4292 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4293 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4294 }
4295 else if (stateMask & LVIS_SELECTED)
4296 {
4297 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4298 }
4299 /* If we are asked to change focus, and we manage it, do it.
4300 It's important to have all new item data stored at this point,
4301 because changing existing focus could result in a redrawing operation,
4302 which in turn could ask for disp data, application should see all data
4303 for inserted item when processing LVN_GETDISPINFO.
4304
4305 The way this works application will see nested item change notifications -
4306 changed item notifications interrupted by ones from item losing focus. */
4307 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4308 {
4309 if (lpLVItem->state & LVIS_FOCUSED)
4310 {
4311 /* update selection mark */
4312 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4313 infoPtr->nSelectionMark = lpLVItem->iItem;
4314
4315 if (infoPtr->nFocusedItem != -1)
4316 {
4317 /* remove current focus */
4318 item.mask = LVIF_STATE;
4319 item.state = 0;
4320 item.stateMask = LVIS_FOCUSED;
4321
4322 /* recurse with redrawing an item */
4323 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4324 }
4325
4326 infoPtr->nFocusedItem = lpLVItem->iItem;
4327 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4328 }
4329 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4330 {
4331 infoPtr->nFocusedItem = -1;
4332 }
4333 }
4334 }
4335
4336 /* if we're inserting the item, we're done */
4337 if (isNew) return TRUE;
4338
4339 /* send LVN_ITEMCHANGED notification */
4340 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4341 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4342
4343 return TRUE;
4344 }
4345
4346 /***
4347 * DESCRIPTION:
4348 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4349 *
4350 * PARAMETER(S):
4351 * [I] infoPtr : valid pointer to the listview structure
4352 * [I] lpLVItem : valid pointer to new subitem attributes
4353 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4354 * [O] bChanged : will be set to TRUE if the item really changed
4355 *
4356 * RETURN:
4357 * SUCCESS : TRUE
4358 * FAILURE : FALSE
4359 */
4360 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4361 {
4362 HDPA hdpaSubItems;
4363 SUBITEM_INFO *lpSubItem;
4364
4365 /* we do not support subitems for virtual listviews */
4366 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4367
4368 /* set subitem only if column is present */
4369 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4370
4371 /* First do some sanity checks */
4372 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4373 particularly useful. We currently do not actually do anything with
4374 the flag on subitems.
4375 */
4376 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4377 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4378
4379 /* get the subitem structure, and create it if not there */
4380 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4381 assert (hdpaSubItems);
4382
4383 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4384 if (!lpSubItem)
4385 {
4386 SUBITEM_INFO *tmpSubItem;
4387 INT i;
4388
4389 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4390 if (!lpSubItem) return FALSE;
4391 /* we could binary search here, if need be...*/
4392 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4393 {
4394 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4395 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4396 }
4397 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4398 {
4399 Free(lpSubItem);
4400 return FALSE;
4401 }
4402 lpSubItem->iSubItem = lpLVItem->iSubItem;
4403 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4404 *bChanged = TRUE;
4405 }
4406
4407 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4408 {
4409 lpSubItem->hdr.iImage = lpLVItem->iImage;
4410 *bChanged = TRUE;
4411 }
4412
4413 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4414 {
4415 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4416 *bChanged = TRUE;
4417 }
4418
4419 return TRUE;
4420 }
4421
4422 /***
4423 * DESCRIPTION:
4424 * Sets item attributes.
4425 *
4426 * PARAMETER(S):
4427 * [I] infoPtr : valid pointer to the listview structure
4428 * [I] lpLVItem : new item attributes
4429 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4430 *
4431 * RETURN:
4432 * SUCCESS : TRUE
4433 * FAILURE : FALSE
4434 */
4435 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4436 {
4437 HWND hwndSelf = infoPtr->hwndSelf;
4438 LPWSTR pszText = NULL;
4439 BOOL bResult, bChanged = FALSE;
4440 RECT oldItemArea;
4441
4442 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4443
4444 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4445 return FALSE;
4446
4447 /* Store old item area */
4448 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4449
4450 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4451 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4452 {
4453 pszText = lpLVItem->pszText;
4454 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4455 }
4456
4457 /* actually set the fields */
4458 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4459
4460 if (lpLVItem->iSubItem)
4461 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4462 else
4463 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4464 if (!IsWindow(hwndSelf))
4465 return FALSE;
4466
4467 /* redraw item, if necessary */
4468 if (bChanged && !infoPtr->bIsDrawing)
4469 {
4470 /* this little optimization eliminates some nasty flicker */
4471 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4472 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4473 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4474 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4475 else
4476 {
4477 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4478 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4479 }
4480 }
4481 /* restore text */
4482 if (pszText)
4483 {
4484 textfreeT(lpLVItem->pszText, isW);
4485 lpLVItem->pszText = pszText;
4486 }
4487
4488 return bResult;
4489 }
4490
4491 /***
4492 * DESCRIPTION:
4493 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4494 *
4495 * PARAMETER(S):
4496 * [I] infoPtr : valid pointer to the listview structure
4497 *
4498 * RETURN:
4499 * item index
4500 */
4501 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4502 {
4503 INT nItem = 0;
4504 SCROLLINFO scrollInfo;
4505
4506 scrollInfo.cbSize = sizeof(SCROLLINFO);
4507 scrollInfo.fMask = SIF_POS;
4508
4509 if (infoPtr->uView == LV_VIEW_LIST)
4510 {
4511 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4512 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4513 }
4514 else if (infoPtr->uView == LV_VIEW_DETAILS)
4515 {
4516 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4517 nItem = scrollInfo.nPos;
4518 }
4519 else
4520 {
4521 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4522 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4523 }
4524
4525 TRACE("nItem=%d\n", nItem);
4526
4527 return nItem;
4528 }
4529
4530
4531 /***
4532 * DESCRIPTION:
4533 * Erases the background of the given rectangle
4534 *
4535 * PARAMETER(S):
4536 * [I] infoPtr : valid pointer to the listview structure
4537 * [I] hdc : device context handle
4538 * [I] lprcBox : clipping rectangle
4539 *
4540 * RETURN:
4541 * Success: TRUE
4542 * Failure: FALSE
4543 */
4544 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4545 {
4546 if (!infoPtr->hBkBrush) return FALSE;
4547
4548 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4549
4550 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4551 }
4552
4553 /* Draw main item or subitem */
4554 static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const NMLVCUSTOMDRAW *nmlvcd, const POINT *pos)
4555 {
4556 RECT rcSelect, rcLabel, rcBox, rcStateIcon, rcIcon;
4557 const RECT *background;
4558 HIMAGELIST himl;
4559 UINT format;
4560 RECT *focus;
4561
4562 /* now check if we need to update the focus rectangle */
4563 focus = infoPtr->bFocus && (item->state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4564 if (!focus) item->state &= ~LVIS_FOCUSED;
4565
4566 LISTVIEW_GetItemMetrics(infoPtr, item, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4567 OffsetRect(&rcBox, pos->x, pos->y);
4568 OffsetRect(&rcSelect, pos->x, pos->y);
4569 OffsetRect(&rcIcon, pos->x, pos->y);
4570 OffsetRect(&rcStateIcon, pos->x, pos->y);
4571 OffsetRect(&rcLabel, pos->x, pos->y);
4572 TRACE("%d: box=%s, select=%s, icon=%s. label=%s\n", item->iSubItem,
4573 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4574 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4575
4576 /* FIXME: temporary hack */
4577 rcSelect.left = rcLabel.left;
4578
4579 if (infoPtr->uView == LV_VIEW_DETAILS && item->iSubItem == 0)
4580 {
4581 if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4582 OffsetRect(&rcSelect, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4583 OffsetRect(&rcIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4584 OffsetRect(&rcStateIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4585 OffsetRect(&rcLabel, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4586 }
4587
4588 /* in icon mode, the label rect is really what we want to draw the
4589 * background for */
4590 /* in detail mode, we want to paint background for label rect when
4591 * item is not selected or listview has full row select; otherwise paint
4592 * background for text only */
4593 if ( infoPtr->uView == LV_VIEW_ICON ||
4594 (infoPtr->uView == LV_VIEW_DETAILS && (!(item->state & LVIS_SELECTED) ||
4595 (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
4596 background = &rcLabel;
4597 else
4598 background = &rcSelect;
4599
4600 if (nmlvcd->clrTextBk != CLR_NONE)
4601 ExtTextOutW(nmlvcd->nmcd.hdc, background->left, background->top, ETO_OPAQUE, background, NULL, 0, NULL);
4602
4603 if (item->state & LVIS_FOCUSED)
4604 {
4605 if (infoPtr->uView == LV_VIEW_DETAILS)
4606 {
4607 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4608 {
4609 /* we have to update left focus bound too if item isn't in leftmost column
4610 and reduce right box bound */
4611 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4612 {
4613 INT leftmost;
4614
4615 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4616 {
4617 INT Originx = pos->x - LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left;
4618 INT rightmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4619 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4620
4621 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, rightmost)->rcHeader.right + Originx;
4622 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4623 }
4624 }
4625 rcSelect.right = rcBox.right;
4626 }
4627 infoPtr->rcFocus = rcSelect;
4628 }
4629 else
4630 infoPtr->rcFocus = rcLabel;
4631 }
4632
4633 /* state icons */
4634 if (infoPtr->himlState && STATEIMAGEINDEX(item->state) && (item->iSubItem == 0))
4635 {
4636 UINT stateimage = STATEIMAGEINDEX(item->state);
4637 if (stateimage)
4638 {
4639 TRACE("stateimage=%d\n", stateimage);
4640 ImageList_Draw(infoPtr->himlState, stateimage-1, nmlvcd->nmcd.hdc, rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4641 }
4642 }
4643
4644 /* item icons */
4645 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4646 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon))
4647 {
4648 UINT style;
4649
4650 TRACE("iImage=%d\n", item->iImage);
4651
4652 if (item->state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4653 style = ILD_SELECTED;
4654 else
4655 style = ILD_NORMAL;
4656
4657 ImageList_DrawEx(himl, item->iImage, nmlvcd->nmcd.hdc, rcIcon.left, rcIcon.top,
4658 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4659 item->state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4660 style | (item->state & LVIS_OVERLAYMASK));
4661 }
4662
4663 /* Don't bother painting item being edited */
4664 if (infoPtr->hwndEdit && item->iItem == infoPtr->nEditLabelItem && item->iSubItem == 0) return;
4665
4666 /* figure out the text drawing flags */
4667 format = (infoPtr->uView == LV_VIEW_ICON ? (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4668 if (infoPtr->uView == LV_VIEW_ICON)
4669 format = (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4670 else if (item->iSubItem)
4671 {
4672 switch (LISTVIEW_GetColumnInfo(infoPtr, item->iSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4673 {
4674 case LVCFMT_RIGHT: format |= DT_RIGHT; break;
4675 case LVCFMT_CENTER: format |= DT_CENTER; break;
4676 default: format |= DT_LEFT;
4677 }
4678 }
4679 if (!(format & (DT_RIGHT | DT_CENTER)))
4680 {
4681 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4682 else rcLabel.left += LABEL_HOR_PADDING;
4683 }
4684 else if (format & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4685
4686 /* for GRIDLINES reduce the bottom so the text formats correctly */
4687 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4688 rcLabel.bottom--;
4689
4690 #ifdef __REACTOS__
4691 if ((!(item->state & LVIS_SELECTED) || !infoPtr->bFocus) && (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTSHADOWTEXT))
4692 DrawShadowText(nmlvcd->nmcd.hdc, item->pszText, -1, &rcLabel, format, RGB(255, 255, 255), RGB(0, 0, 0), 2, 2);
4693 else
4694 #endif
4695 DrawTextW(nmlvcd->nmcd.hdc, item->pszText, -1, &rcLabel, format);
4696 }
4697
4698 /***
4699 * DESCRIPTION:
4700 * Draws an item.
4701 *
4702 * PARAMETER(S):
4703 * [I] infoPtr : valid pointer to the listview structure
4704 * [I] hdc : device context handle
4705 * [I] nItem : item index
4706 * [I] nSubItem : subitem index
4707 * [I] pos : item position in client coordinates
4708 * [I] cdmode : custom draw mode
4709 *
4710 * RETURN:
4711 * Success: TRUE
4712 * Failure: FALSE
4713 */
4714 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERATOR *subitems, POINT pos, DWORD cdmode)
4715 {
4716 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4717 static WCHAR callbackW[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4718 DWORD cdsubitemmode = CDRF_DODEFAULT;
4719 RECT *focus, rcBox;
4720 NMLVCUSTOMDRAW nmlvcd;
4721 LVITEMW lvItem;
4722
4723 TRACE("(hdc=%p, nItem=%d, subitems=%p, pos=%s)\n", hdc, nItem, subitems, wine_dbgstr_point(&pos));
4724
4725 /* get information needed for drawing the item */
4726 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
4727 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4728 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4729 lvItem.iItem = nItem;
4730 lvItem.iSubItem = 0;
4731 lvItem.state = 0;
4732 lvItem.lParam = 0;
4733 lvItem.cchTextMax = DISP_TEXT_SIZE;
4734 lvItem.pszText = szDispText;
4735 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4736 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4737 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4738
4739 /* now check if we need to update the focus rectangle */
4740 focus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4741 if (!focus) lvItem.state &= ~LVIS_FOCUSED;
4742
4743 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, NULL, NULL, NULL);
4744 OffsetRect(&rcBox, pos.x, pos.y);
4745
4746 /* Full custom draw stage sequence looks like this:
4747
4748 LV_VIEW_DETAILS:
4749
4750 - CDDS_ITEMPREPAINT
4751 - CDDS_ITEMPREPAINT|CDDS_SUBITEM | => sent n times, where n is number of subitems,
4752 CDDS_ITEMPOSTPAINT|CDDS_SUBITEM | including item iself
4753 - CDDS_ITEMPOSTPAINT
4754
4755 other styles:
4756
4757 - CDDS_ITEMPREPAINT
4758 - CDDS_ITEMPOSTPAINT
4759 */
4760
4761 /* fill in the custom draw structure */
4762 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4763 if (cdmode & CDRF_NOTIFYITEMDRAW)
4764 cdsubitemmode = notify_customdraw(infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
4765 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4766
4767 if (subitems)
4768 {
4769 while (iterator_next(subitems))
4770 {
4771 DWORD subitemstage = CDRF_DODEFAULT;
4772
4773 /* We need to query for each subitem, item's data (subitem == 0) is already here at this point */
4774 if (subitems->nItem)
4775 {
4776 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_INDENT;
4777 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4778 lvItem.iItem = nItem;
4779 lvItem.iSubItem = subitems->nItem;
4780 lvItem.state = 0;
4781 lvItem.lParam = 0;
4782 lvItem.cchTextMax = DISP_TEXT_SIZE;
4783 lvItem.pszText = szDispText;
4784 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4785 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4786 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4787 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4788 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4789
4790 /* update custom draw data */
4791 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &nmlvcd.nmcd.rc, NULL, NULL, NULL, NULL);
4792 OffsetRect(&nmlvcd.nmcd.rc, pos.x, pos.y);
4793 nmlvcd.iSubItem = subitems->nItem;
4794 }
4795
4796 if (cdsubitemmode & CDRF_NOTIFYSUBITEMDRAW)
4797 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4798 else
4799 {
4800 nmlvcd.clrTextBk = infoPtr->clrTextBk;
4801 nmlvcd.clrText = infoPtr->clrText;
4802 }
4803
4804 if (subitems->nItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4805 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4806 else if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4807 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4808
4809 if (!(subitemstage & CDRF_SKIPDEFAULT))
4810 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4811
4812 if (subitemstage & CDRF_NOTIFYPOSTPAINT)
4813 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPOSTPAINT, &nmlvcd);
4814 }
4815 }
4816 else
4817 {
4818 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4819 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4820 }
4821
4822 postpaint:
4823 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4824 {
4825 nmlvcd.iSubItem = 0;
4826 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
4827 }
4828
4829 return TRUE;
4830 }
4831
4832 /***
4833 * DESCRIPTION:
4834 * Draws listview items when in owner draw mode.
4835 *
4836 * PARAMETER(S):
4837 * [I] infoPtr : valid pointer to the listview structure
4838 * [I] hdc : device context handle
4839 *
4840 * RETURN:
4841 * None
4842 */
4843 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4844 {
4845 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4846 DWORD cditemmode = CDRF_DODEFAULT;
4847 NMLVCUSTOMDRAW nmlvcd;
4848 POINT Origin, Position;
4849 DRAWITEMSTRUCT dis;
4850 LVITEMW item;
4851
4852 TRACE("()\n");
4853
4854 ZeroMemory(&dis, sizeof(dis));
4855
4856 /* Get scroll info once before loop */
4857 LISTVIEW_GetOrigin(infoPtr, &Origin);
4858
4859 /* iterate through the invalidated rows */
4860 while(iterator_next(i))
4861 {
4862 item.iItem = i->nItem;
4863 item.iSubItem = 0;
4864 item.mask = LVIF_PARAM | LVIF_STATE;
4865 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4866 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4867
4868 dis.CtlType = ODT_LISTVIEW;
4869 dis.CtlID = uID;
4870 dis.itemID = item.iItem;
4871 dis.itemAction = ODA_DRAWENTIRE;
4872 dis.itemState = 0;
4873 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4874 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4875 dis.hwndItem = infoPtr->hwndSelf;
4876 dis.hDC = hdc;
4877 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4878 dis.rcItem.left = Position.x + Origin.x;
4879 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4880 dis.rcItem.top = Position.y + Origin.y;
4881 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4882 dis.itemData = item.lParam;
4883
4884 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4885
4886 /*
4887 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4888 * structure for the rest. of the paint cycle
4889 */
4890 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4891 if (cdmode & CDRF_NOTIFYITEMDRAW)
4892 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4893
4894 if (!(cditemmode & CDRF_SKIPDEFAULT))
4895 {
4896 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4897 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4898 }
4899
4900 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4901 notify_postpaint(infoPtr, &nmlvcd);
4902 }
4903 }
4904
4905 /***
4906 * DESCRIPTION:
4907 * Draws listview items when in report display mode.
4908 *
4909 * PARAMETER(S):
4910 * [I] infoPtr : valid pointer to the listview structure
4911 * [I] hdc : device context handle
4912 * [I] cdmode : custom draw mode
4913 *
4914 * RETURN:
4915 * None
4916 */
4917 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4918 {
4919 INT rgntype;
4920 RECT rcClip, rcItem;
4921 POINT Origin;
4922 RANGES colRanges;
4923 INT col;
4924 ITERATOR j;
4925
4926 TRACE("()\n");
4927
4928 /* figure out what to draw */
4929 rgntype = GetClipBox(hdc, &rcClip);
4930 if (rgntype == NULLREGION) return;
4931
4932 /* Get scroll info once before loop */
4933 LISTVIEW_GetOrigin(infoPtr, &Origin);
4934
4935 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4936
4937 /* narrow down the columns we need to paint */
4938 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4939 {
4940 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4941
4942 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4943 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4944 ranges_additem(colRanges, index);
4945 }
4946 iterator_rangesitems(&j, colRanges);
4947
4948 /* in full row select, we _have_ to draw the main item */
4949 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4950 j.nSpecial = 0;
4951
4952 /* iterate through the invalidated rows */
4953 while(iterator_next(i))
4954 {
4955 RANGES subitems;
4956 POINT Position;
4957 ITERATOR k;
4958
4959 SelectObject(hdc, infoPtr->hFont);
4960 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4961 Position.x = Origin.x;
4962 Position.y += Origin.y;
4963
4964 subitems = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4965
4966 /* iterate through the invalidated columns */
4967 while(iterator_next(&j))
4968 {
4969 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4970
4971 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4972 {
4973 rcItem.top = 0;
4974 rcItem.bottom = infoPtr->nItemHeight;
4975 OffsetRect(&rcItem, Origin.x, Position.y);
4976 if (!RectVisible(hdc, &rcItem)) continue;
4977 }
4978
4979 ranges_additem(subitems, j.nItem);
4980 }
4981
4982 iterator_rangesitems(&k, subitems);
4983 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, &k, Position, cdmode);
4984 iterator_destroy(&k);
4985 }
4986 iterator_destroy(&j);
4987 }
4988
4989 /***
4990 * DESCRIPTION:
4991 * Draws the gridlines if necessary when in report display mode.
4992 *
4993 * PARAMETER(S):
4994 * [I] infoPtr : valid pointer to the listview structure
4995 * [I] hdc : device context handle
4996 *
4997 * RETURN:
4998 * None
4999 */
5000 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
5001 {
5002 INT rgntype;
5003 INT y, itemheight;
5004 INT col, index;
5005 HPEN hPen, hOldPen;
5006 RECT rcClip, rcItem = {0};
5007 POINT Origin;
5008 RANGES colRanges;
5009 ITERATOR j;
5010 BOOL rmost = FALSE;
5011
5012 TRACE("()\n");
5013
5014 /* figure out what to draw */
5015 rgntype = GetClipBox(hdc, &rcClip);
5016 if (rgntype == NULLREGION) return;
5017
5018 /* Get scroll info once before loop */
5019 LISTVIEW_GetOrigin(infoPtr, &Origin);
5020
5021 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
5022
5023 /* narrow down the columns we need to paint */
5024 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
5025 {
5026 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
5027
5028 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5029 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
5030 ranges_additem(colRanges, index);
5031 }
5032
5033 /* is right most vertical line visible? */
5034 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
5035 {
5036 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5037 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5038 rmost = (rcItem.right + Origin.x < rcClip.right);
5039 }
5040
5041 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
5042 {
5043 hOldPen = SelectObject ( hdc, hPen );
5044
5045 /* draw the vertical lines for the columns */
5046 iterator_rangesitems(&j, colRanges);
5047 while(iterator_next(&j))
5048 {
5049 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
5050 if (rcItem.left == 0) continue; /* skip leftmost column */
5051 rcItem.left += Origin.x;
5052 rcItem.right += Origin.x;
5053 rcItem.top = infoPtr->rcList.top;
5054 rcItem.bottom = infoPtr->rcList.bottom;
5055 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
5056 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5057 LineTo (hdc, rcItem.left, rcItem.bottom);
5058 }
5059 iterator_destroy(&j);
5060 /* draw rightmost grid line if visible */
5061 if (rmost)
5062 {
5063 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
5064 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5065 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5066
5067 rcItem.right += Origin.x;
5068
5069 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
5070 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
5071 }
5072
5073 /* draw the horizontal lines for the rows */
5074 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
5075 rcItem.left = infoPtr->rcList.left;
5076 rcItem.right = infoPtr->rcList.right;
5077 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
5078 {
5079 rcItem.bottom = rcItem.top = y;
5080 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
5081 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5082 LineTo (hdc, rcItem.right, rcItem.top);
5083 }
5084
5085 SelectObject( hdc, hOldPen );
5086 DeleteObject( hPen );
5087 }
5088 else
5089 ranges_destroy(colRanges);
5090 }
5091
5092 /***
5093 * DESCRIPTION:
5094 * Draws listview items when in list display mode.
5095 *
5096 * PARAMETER(S):
5097 * [I] infoPtr : valid pointer to the listview structure
5098 * [I] hdc : device context handle
5099 * [I] cdmode : custom draw mode
5100 *
5101 * RETURN:
5102 * None
5103 */
5104 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
5105 {
5106 POINT Origin, Position;
5107
5108 /* Get scroll info once before loop */
5109 LISTVIEW_GetOrigin(infoPtr, &Origin);
5110
5111 while(iterator_prev(i))
5112 {
5113 SelectObject(hdc, infoPtr->hFont);
5114 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
5115 Position.x += Origin.x;
5116 Position.y += Origin.y;
5117
5118 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, NULL, Position, cdmode);
5119 }
5120 }
5121
5122
5123 /***
5124 * DESCRIPTION:
5125 * Draws listview items.
5126 *
5127 * PARAMETER(S):
5128 * [I] infoPtr : valid pointer to the listview structure
5129 * [I] hdc : device context handle
5130 * [I] prcErase : rect to be erased before refresh (may be NULL)
5131 *
5132 * RETURN:
5133 * NoneX
5134 */
5135 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5136 {
5137 COLORREF oldTextColor = 0, oldBkColor = 0;
5138 NMLVCUSTOMDRAW nmlvcd;
5139 HFONT hOldFont = 0;
5140 DWORD cdmode;
5141 INT oldBkMode = 0;
5142 RECT rcClient;
5143 ITERATOR i;
5144 HDC hdcOrig = hdc;
5145 HBITMAP hbmp = NULL;
5146 RANGE range;
5147
5148 LISTVIEW_DUMP(infoPtr);
5149
5150 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5151 TRACE("double buffering\n");
5152
5153 hdc = CreateCompatibleDC(hdcOrig);
5154 if (!hdc) {
5155 ERR("Failed to create DC for backbuffer\n");
5156 return;
5157 }
5158 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5159 infoPtr->rcList.bottom);
5160 if (!hbmp) {
5161 ERR("Failed to create bitmap for backbuffer\n");
5162 DeleteDC(hdc);
5163 return;
5164 }
5165
5166 SelectObject(hdc, hbmp);
5167 SelectObject(hdc, infoPtr->hFont);
5168
5169 if(GetClipBox(hdcOrig, &rcClient))
5170 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5171 } else {
5172 /* Save dc values we're gonna trash while drawing
5173 * FIXME: Should be done in LISTVIEW_DrawItem() */
5174 hOldFont = SelectObject(hdc, infoPtr->hFont);
5175 oldBkMode = GetBkMode(hdc);
5176 oldBkColor = GetBkColor(hdc);
5177 oldTextColor = GetTextColor(hdc);
5178 }
5179
5180 infoPtr->bIsDrawing = TRUE;
5181
5182 if (prcErase) {
5183 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5184 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5185 /* If no erasing was done (usually because RedrawWindow was called
5186 * with RDW_INVALIDATE only) we need to copy the old contents into
5187 * the backbuffer before continuing. */
5188 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5189 infoPtr->rcList.right - infoPtr->rcList.left,
5190 infoPtr->rcList.bottom - infoPtr->rcList.top,
5191 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5192 }
5193
5194 GetClientRect(infoPtr->hwndSelf, &rcClient);
5195 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5196 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5197 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5198
5199 /* nothing to draw */
5200 if(infoPtr->nItemCount == 0) goto enddraw;
5201
5202 /* figure out what we need to draw */
5203 iterator_visibleitems(&i, infoPtr, hdc);
5204 range = iterator_range(&i);
5205
5206 /* send cache hint notification */
5207 if (infoPtr->dwStyle & LVS_OWNERDATA)
5208 {
5209 NMLVCACHEHINT nmlv;
5210
5211 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5212 nmlv.iFrom = range.lower;
5213 nmlv.iTo = range.upper - 1;
5214 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5215 }
5216
5217 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5218 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5219 else
5220 {
5221 if (infoPtr->uView == LV_VIEW_DETAILS)
5222 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5223 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5224 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5225
5226 /* if we have a focus rect and it's visible, draw it */
5227 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5228 (range.upper - 1) >= infoPtr->nFocusedItem)
5229 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5230 }
5231 iterator_destroy(&i);
5232
5233 enddraw:
5234 /* For LVS_EX_GRIDLINES go and draw lines */
5235 /* This includes the case where there were *no* items */
5236 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5237 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5238
5239 /* Draw marquee rectangle if appropriate */
5240 if (infoPtr->bMarqueeSelect)
5241 {
5242 SetBkColor(hdc, RGB(255, 255, 255));
5243 SetTextColor(hdc, RGB(0, 0, 0));
5244 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5245 }
5246
5247 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5248 notify_postpaint(infoPtr, &nmlvcd);
5249
5250 if(hbmp) {
5251 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5252 infoPtr->rcList.right - infoPtr->rcList.left,
5253 infoPtr->rcList.bottom - infoPtr->rcList.top,
5254 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5255
5256 DeleteObject(hbmp);
5257 DeleteDC(hdc);
5258 } else {
5259 SelectObject(hdc, hOldFont);
5260 SetBkMode(hdc, oldBkMode);
5261 SetBkColor(hdc, oldBkColor);
5262 SetTextColor(hdc, oldTextColor);
5263 }
5264
5265 infoPtr->bIsDrawing = FALSE;
5266 }
5267
5268
5269 /***
5270 * DESCRIPTION:
5271 * Calculates the approximate width and height of a given number of items.
5272 *
5273 * PARAMETER(S):
5274 * [I] infoPtr : valid pointer to the listview structure
5275 * [I] nItemCount : number of items
5276 * [I] wWidth : width
5277 * [I] wHeight : height
5278 *
5279 * RETURN:
5280 * Returns a DWORD. The width in the low word and the height in high word.
5281 */
5282 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5283 WORD wWidth, WORD wHeight)
5284 {
5285 DWORD dwViewRect = 0;
5286
5287 if (nItemCount == -1)
5288 nItemCount = infoPtr->nItemCount;
5289
5290 if (infoPtr->uView == LV_VIEW_LIST)
5291 {
5292 INT nItemCountPerColumn = 1;
5293 INT nColumnCount = 0;
5294
5295 if (wHeight == 0xFFFF)
5296 {
5297 /* use current height */
5298 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5299 }
5300
5301 if (wHeight < infoPtr->nItemHeight)
5302 wHeight = infoPtr->nItemHeight;
5303
5304 if (nItemCount > 0)
5305 {
5306 if (infoPtr->nItemHeight > 0)
5307 {
5308 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5309 if (nItemCountPerColumn == 0)
5310 nItemCountPerColumn = 1;
5311
5312 if (nItemCount % nItemCountPerColumn != 0)
5313 nColumnCount = nItemCount / nItemCountPerColumn;
5314 else
5315 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5316 }
5317 }
5318
5319 /* Microsoft padding magic */
5320 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5321 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5322
5323 dwViewRect = MAKELONG(wWidth, wHeight);
5324 }
5325 else if (infoPtr->uView == LV_VIEW_DETAILS)
5326 {
5327 RECT rcBox;
5328
5329 if (infoPtr->nItemCount > 0)
5330 {
5331 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5332 wWidth = rcBox.right - rcBox.left;
5333 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5334 }
5335 else
5336 {
5337 /* use current height and width */
5338 if (wHeight == 0xffff)
5339 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5340 if (wWidth == 0xffff)
5341 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5342 }
5343
5344 dwViewRect = MAKELONG(wWidth, wHeight);
5345 }
5346 else if (infoPtr->uView == LV_VIEW_ICON)
5347 {
5348 UINT rows,cols;
5349 UINT nItemWidth;
5350 UINT nItemHeight;
5351
5352 nItemWidth = infoPtr->iconSpacing.cx;
5353 nItemHeight = infoPtr->iconSpacing.cy;
5354
5355 if (wWidth == 0xffff)
5356 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5357
5358 if (wWidth < nItemWidth)
5359 wWidth = nItemWidth;
5360
5361 cols = wWidth / nItemWidth;
5362 if (cols > nItemCount)
5363 cols = nItemCount;
5364 if (cols < 1)
5365 cols = 1;
5366
5367 if (nItemCount)
5368 {
5369 rows = nItemCount / cols;
5370 if (nItemCount % cols)
5371 rows++;
5372 }
5373 else
5374 rows = 0;
5375
5376 wHeight = (nItemHeight * rows)+2;
5377 wWidth = (nItemWidth * cols)+2;
5378
5379 dwViewRect = MAKELONG(wWidth, wHeight);
5380 }
5381 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5382 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5383
5384 return dwViewRect;
5385 }
5386
5387 /***
5388 * DESCRIPTION:
5389 * Cancel edit label with saving item text.
5390 *
5391 * PARAMETER(S):
5392 * [I] infoPtr : valid pointer to the listview structure
5393 *
5394 * RETURN:
5395 * Always returns TRUE.
5396 */
5397 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5398 {
5399 if (infoPtr->hwndEdit)
5400 {
5401 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5402 HWND edit = infoPtr->hwndEdit;
5403
5404 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5405 SendMessageW(edit, WM_CLOSE, 0, 0);
5406 }
5407
5408 return TRUE;
5409 }
5410
5411 /***
5412 * DESCRIPTION:
5413 * Create a drag image list for the specified item.
5414 *
5415 * PARAMETER(S):
5416 * [I] infoPtr : valid pointer to the listview structure
5417 * [I] iItem : index of item
5418 * [O] lppt : Upper-left corner of the image
5419 *
5420 * RETURN:
5421 * Returns a handle to the image list if successful, NULL otherwise.
5422 */
5423 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5424 {
5425 RECT rcItem;
5426 SIZE size;
5427 POINT pos;
5428 HDC hdc, hdcOrig;
5429 HBITMAP hbmp, hOldbmp;
5430 HFONT hOldFont;
5431 HIMAGELIST dragList = 0;
5432 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5433
5434 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5435 return 0;
5436
5437 rcItem.left = LVIR_BOUNDS;
5438 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5439 return 0;
5440
5441 lppt->x = rcItem.left;
5442 lppt->y = rcItem.top;
5443
5444 size.cx = rcItem.right - rcItem.left;
5445 size.cy = rcItem.bottom - rcItem.top;
5446
5447 hdcOrig = GetDC(infoPtr->hwndSelf);
5448 hdc = CreateCompatibleDC(hdcOrig);
5449 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5450 hOldbmp = SelectObject(hdc, hbmp);
5451 hOldFont = SelectObject(hdc, infoPtr->hFont);
5452
5453 rcItem.left = rcItem.top = 0;
5454 rcItem.right = size.cx;
5455 rcItem.bottom = size.cy;
5456 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5457
5458 pos.x = pos.y = 0;
5459 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, NULL, pos, CDRF_DODEFAULT))
5460 {
5461 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5462 SelectObject(hdc, hOldbmp);
5463 ImageList_Add(dragList, hbmp, 0);
5464 }
5465 else
5466 SelectObject(hdc, hOldbmp);
5467
5468 SelectObject(hdc, hOldFont);
5469 DeleteObject(hbmp);
5470 DeleteDC(hdc);
5471 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5472
5473 TRACE("ret=%p\n", dragList);
5474
5475 return dragList;
5476 }
5477
5478
5479 /***
5480 * DESCRIPTION:
5481 * Removes all listview items and subitems.
5482 *
5483 * PARAMETER(S):
5484 * [I] infoPtr : valid pointer to the listview structure
5485 *
5486 * RETURN:
5487 * SUCCESS : TRUE
5488 * FAILURE : FALSE
5489 */
5490 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5491 {
5492 HDPA hdpaSubItems = NULL;
5493 BOOL suppress = FALSE;
5494 ITEMHDR *hdrItem;
5495 ITEM_INFO *lpItem;
5496 ITEM_ID *lpID;
5497 INT i, j;
5498
5499 TRACE("()\n");
5500
5501 /* we do it directly, to avoid notifications */
5502 ranges_clear(infoPtr->selectionRanges);
5503 infoPtr->nSelectionMark = -1;
5504 infoPtr->nFocusedItem = -1;
5505 SetRectEmpty(&infoPtr->rcFocus);
5506 /* But we are supposed to leave nHotItem as is! */
5507
5508 /* send LVN_DELETEALLITEMS notification */
5509 if (!(infoPtr->dwStyle & LVS_OWNERDATA) || !destroy)
5510 {
5511 NMLISTVIEW nmlv;
5512
5513 memset(&nmlv, 0, sizeof(NMLISTVIEW));
5514 nmlv.iItem = -1;
5515 suppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5516 }
5517
5518 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5519 {
5520 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5521 {
5522 /* send LVN_DELETEITEM notification, if not suppressed
5523 and if it is not a virtual listview */
5524 if (!suppress) notify_deleteitem(infoPtr, i);
5525 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5526 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5527 /* free id struct */
5528 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5529 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5530 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5531 Free(lpID);
5532 /* both item and subitem start with ITEMHDR header */
5533 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5534 {
5535 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5536 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5537 Free(hdrItem);
5538 }
5539 DPA_Destroy(hdpaSubItems);
5540 DPA_DeletePtr(infoPtr->hdpaItems, i);
5541 }
5542 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5543 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5544 infoPtr->nItemCount --;
5545 }
5546
5547 if (!destroy)
5548 {
5549 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5550 LISTVIEW_UpdateScroll(infoPtr);
5551 }
5552 LISTVIEW_InvalidateList(infoPtr);
5553
5554 return TRUE;
5555 }
5556
5557 /***
5558 * DESCRIPTION:
5559 * Scrolls, and updates the columns, when a column is changing width.
5560 *
5561 * PARAMETER(S):
5562 * [I] infoPtr : valid pointer to the listview structure
5563 * [I] nColumn : column to scroll
5564 * [I] dx : amount of scroll, in pixels
5565 *
5566 * RETURN:
5567 * None.
5568 */
5569 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5570 {
5571 COLUMN_INFO *lpColumnInfo;
5572 RECT rcOld, rcCol;
5573 POINT ptOrigin;
5574 INT nCol;
5575 HDITEMW hdi;
5576
5577 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5578 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5579 rcCol = lpColumnInfo->rcHeader;
5580 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5581 rcCol.left = rcCol.right;
5582
5583 /* adjust the other columns */
5584 hdi.mask = HDI_ORDER;
5585 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5586 {
5587 INT nOrder = hdi.iOrder;
5588 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5589 {
5590 hdi.mask = HDI_ORDER;
5591 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5592 if (hdi.iOrder >= nOrder) {
5593 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5594 lpColumnInfo->rcHeader.left += dx;
5595 lpColumnInfo->rcHeader.right += dx;
5596 }
5597 }
5598 }
5599
5600 /* do not update screen if not in report mode */
5601 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5602
5603 /* Need to reset the item width when inserting a new column */
5604 infoPtr->nItemWidth += dx;
5605
5606 LISTVIEW_UpdateScroll(infoPtr);
5607 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5608
5609 /* scroll to cover the deleted column, and invalidate for redraw */
5610 rcOld = infoPtr->rcList;
5611 rcOld.left = ptOrigin.x + rcCol.left + dx;
5612 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5613 }
5614
5615 /***
5616 * DESCRIPTION:
5617 * Removes a column from the listview control.
5618 *
5619 * PARAMETER(S):
5620 * [I] infoPtr : valid pointer to the listview structure
5621 * [I] nColumn : column index
5622 *
5623 * RETURN:
5624 * SUCCESS : TRUE
5625 * FAILURE : FALSE
5626 */
5627 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5628 {
5629 RECT rcCol;
5630
5631 TRACE("nColumn=%d\n", nColumn);
5632
5633 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
5634 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5635
5636 /* While the MSDN specifically says that column zero should not be deleted,
5637 what actually happens is that the column itself is deleted but no items or subitems
5638 are removed.
5639 */
5640
5641 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5642
5643 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5644 return FALSE;
5645
5646 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5647 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5648
5649 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5650 {
5651 SUBITEM_INFO *lpSubItem, *lpDelItem;
5652 HDPA hdpaSubItems;
5653 INT nItem, nSubItem, i;
5654
5655 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5656 {
5657 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5658 nSubItem = 0;
5659 lpDelItem = 0;
5660 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5661 {
5662 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5663 if (lpSubItem->iSubItem == nColumn)
5664 {
5665 nSubItem = i;
5666 lpDelItem = lpSubItem;
5667 }
5668 else if (lpSubItem->iSubItem > nColumn)
5669 {
5670 lpSubItem->iSubItem--;
5671 }
5672 }
5673
5674 /* if we found our subitem, zap it */
5675 if (nSubItem > 0)
5676 {
5677 /* free string */
5678 if (is_text(lpDelItem->hdr.pszText))
5679 Free(lpDelItem->hdr.pszText);
5680
5681 /* free item */
5682 Free(lpDelItem);
5683
5684 /* free dpa memory */
5685 DPA_DeletePtr(hdpaSubItems, nSubItem);
5686 }
5687 }
5688 }
5689
5690 /* update the other column info */
5691 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5692 LISTVIEW_InvalidateList(infoPtr);
5693 else
5694 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5695 LISTVIEW_UpdateItemSize(infoPtr);
5696
5697 return TRUE;
5698 }
5699
5700 /***
5701 * DESCRIPTION:
5702 * Invalidates the listview after an item's insertion or deletion.
5703 *
5704 * PARAMETER(S):
5705 * [I] infoPtr : valid pointer to the listview structure
5706 * [I] nItem : item index
5707 * [I] dir : -1 if deleting, 1 if inserting
5708 *
5709 * RETURN:
5710 * None
5711 */
5712 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5713 {
5714 INT nPerCol, nItemCol, nItemRow;
5715 RECT rcScroll;
5716 POINT Origin;
5717
5718 /* if we don't refresh, what's the point of scrolling? */
5719 if (!is_redrawing(infoPtr)) return;
5720
5721 assert (abs(dir) == 1);
5722
5723 /* arrange icons if autoarrange is on */
5724 if (is_autoarrange(infoPtr))
5725 {
5726 BOOL arrange = TRUE;
5727 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5728 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5729 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5730 }
5731
5732 /* scrollbars need updating */
5733 LISTVIEW_UpdateScroll(infoPtr);
5734
5735 /* figure out the item's position */
5736 if (infoPtr->uView == LV_VIEW_DETAILS)
5737 nPerCol = infoPtr->nItemCount + 1;
5738 else if (infoPtr->uView == LV_VIEW_LIST)
5739 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5740 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5741 return;
5742
5743 nItemCol = nItem / nPerCol;
5744 nItemRow = nItem % nPerCol;
5745 LISTVIEW_GetOrigin(infoPtr, &Origin);
5746
5747 /* move the items below up a slot */
5748 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5749 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5750 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5751 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5752 OffsetRect(&rcScroll, Origin.x, Origin.y);
5753 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5754 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5755 {
5756 TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5757 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5758 }
5759
5760 /* report has only that column, so we're done */
5761 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5762
5763 /* now for LISTs, we have to deal with the columns to the right */
5764 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
5765 rcScroll.top = 0;
5766 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
5767 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5768 OffsetRect(&rcScroll, Origin.x, Origin.y);
5769 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5770 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5771 }
5772
5773 /***
5774 * DESCRIPTION:
5775 * Removes an item from the listview control.
5776 *
5777 * PARAMETER(S):
5778 * [I] infoPtr : valid pointer to the listview structure
5779 * [I] nItem : item index
5780 *
5781 * RETURN:
5782 * SUCCESS : TRUE
5783 * FAILURE : FALSE
5784 */
5785 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5786 {
5787 LVITEMW item;
5788 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5789 INT focus = infoPtr->nFocusedItem;
5790
5791 TRACE("(nItem=%d)\n", nItem);
5792
5793 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5794
5795 /* remove selection, and focus */
5796 item.state = 0;
5797 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5798 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5799
5800 /* send LVN_DELETEITEM notification. */
5801 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5802
5803 /* we need to do this here, because we'll be deleting stuff */
5804 if (is_icon)
5805 LISTVIEW_InvalidateItem(infoPtr, nItem);
5806
5807 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5808 {
5809 HDPA hdpaSubItems;
5810 ITEMHDR *hdrItem;
5811 ITEM_INFO *lpItem;
5812 ITEM_ID *lpID;
5813 INT i;
5814
5815 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5816 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5817
5818 /* free id struct */
5819 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5820 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5821 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5822 Free(lpID);
5823 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5824 {
5825 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5826 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5827 Free(hdrItem);
5828 }
5829 DPA_Destroy(hdpaSubItems);
5830 }
5831
5832 if (is_icon)
5833 {
5834 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5835 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5836 }
5837
5838 infoPtr->nItemCount--;
5839 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5840 LISTVIEW_ShiftFocus(infoPtr, focus, nItem, -1);
5841
5842 /* now is the invalidation fun */
5843 if (!is_icon)
5844 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5845 return TRUE;
5846 }
5847
5848
5849 /***
5850 * DESCRIPTION:
5851 * Callback implementation for editlabel control
5852 *
5853 * PARAMETER(S):
5854 * [I] infoPtr : valid pointer to the listview structure
5855 * [I] storeText : store edit box text as item text
5856 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5857 *
5858 * RETURN:
5859 * SUCCESS : TRUE
5860 * FAILURE : FALSE
5861 */
5862 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5863 {
5864 HWND hwndSelf = infoPtr->hwndSelf;
5865 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5866 NMLVDISPINFOW dispInfo;
5867 INT editedItem = infoPtr->nEditLabelItem;
5868 BOOL same;
5869 WCHAR *pszText = NULL;
5870 BOOL res;
5871
5872 if (storeText)
5873 {
5874 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5875
5876 if (len)
5877 {
5878 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5879 {
5880 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
5881 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
5882 }
5883 }
5884 }
5885
5886 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5887
5888 ZeroMemory(&dispInfo, sizeof(dispInfo));
5889 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5890 dispInfo.item.iItem = editedItem;
5891 dispInfo.item.iSubItem = 0;
5892 dispInfo.item.stateMask = ~0;
5893 dispInfo.item.pszText = szDispText;
5894 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5895 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5896 {
5897 res = FALSE;
5898 goto cleanup;
5899 }
5900
5901 if (isW)
5902 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5903 else
5904 {
5905 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5906 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5907 textfreeT(tmp, FALSE);
5908 }
5909
5910 /* add the text from the edit in */
5911 dispInfo.item.mask |= LVIF_TEXT;
5912 dispInfo.item.pszText = same ? NULL : pszText;
5913 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5914
5915 /* Do we need to update the Item Text */
5916 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5917
5918 infoPtr->nEditLabelItem = -1;
5919 infoPtr->hwndEdit = 0;
5920
5921 if (!res) goto cleanup;
5922
5923 if (!IsWindow(hwndSelf))
5924 {
5925 res = FALSE;
5926 goto cleanup;
5927 }
5928 if (!pszText) return TRUE;
5929 if (same)
5930 {
5931 res = TRUE;
5932 goto cleanup;
5933 }
5934
5935 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5936 {
5937 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5938 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5939 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5940 {
5941 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5942 res = TRUE;
5943 goto cleanup;
5944 }
5945 }
5946
5947 ZeroMemory(&dispInfo, sizeof(dispInfo));
5948 dispInfo.item.mask = LVIF_TEXT;
5949 dispInfo.item.iItem = editedItem;
5950 dispInfo.item.iSubItem = 0;
5951 dispInfo.item.pszText = pszText;
5952 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5953 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5954
5955 cleanup:
5956 Free(pszText);
5957
5958 return res;
5959 }
5960
5961 /***
5962 * DESCRIPTION:
5963 * Subclassed edit control windproc function
5964 *
5965 * PARAMETER(S):
5966 * [I] hwnd : the edit window handle
5967 * [I] uMsg : the message that is to be processed
5968 * [I] wParam : first message parameter
5969 * [I] lParam : second message parameter
5970 * [I] isW : TRUE if input is Unicode
5971 *
5972 * RETURN:
5973 * Zero.
5974 */
5975 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
5976 {
5977 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
5978 BOOL save = TRUE;
5979
5980 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
5981 hwnd, uMsg, wParam, lParam, isW);
5982
5983 switch (uMsg)
5984 {
5985 case WM_GETDLGCODE:
5986 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
5987
5988 case WM_DESTROY:
5989 {
5990 WNDPROC editProc = infoPtr->EditWndProc;
5991 infoPtr->EditWndProc = 0;
5992 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
5993 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
5994 }
5995
5996 case WM_KEYDOWN:
5997 if (VK_ESCAPE == (INT)wParam)
5998 {
5999 save = FALSE;
6000 break;
6001 }
6002 else if (VK_RETURN == (INT)wParam)
6003 break;
6004
6005 default:
6006 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
6007 }
6008
6009 /* kill the edit */
6010 if (infoPtr->hwndEdit)
6011 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
6012
6013 SendMessageW(hwnd, WM_CLOSE, 0, 0);
6014 return 0;
6015 }
6016
6017 /***
6018 * DESCRIPTION:
6019 * Subclassed edit control Unicode windproc function
6020 *
6021 * PARAMETER(S):
6022 * [I] hwnd : the edit window handle
6023 * [I] uMsg : the message that is to be processed
6024 * [I] wParam : first message parameter
6025 * [I] lParam : second message parameter
6026 *
6027 * RETURN:
6028 */
6029 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6030 {
6031 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
6032 }
6033
6034 /***
6035 * DESCRIPTION:
6036 * Subclassed edit control ANSI windproc function
6037 *
6038 * PARAMETER(S):
6039 * [I] hwnd : the edit window handle
6040 * [I] uMsg : the message that is to be processed
6041 * [I] wParam : first message parameter
6042 * [I] lParam : second message parameter
6043 *
6044 * RETURN:
6045 */
6046 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6047 {
6048 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
6049 }
6050
6051 /***
6052 * DESCRIPTION:
6053 * Creates a subclassed edit control
6054 *
6055 * PARAMETER(S):
6056 * [I] infoPtr : valid pointer to the listview structure
6057 * [I] text : initial text for the edit
6058 * [I] style : the window style
6059 * [I] isW : TRUE if input is Unicode
6060 *
6061 * RETURN:
6062 */
6063 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
6064 {
6065 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
6066 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
6067 HWND hedit;
6068
6069 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
6070
6071 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
6072 if (isW)
6073 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6074 else
6075 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6076
6077 if (!hedit) return 0;
6078
6079 infoPtr->EditWndProc = (WNDPROC)
6080 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
6081 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
6082
6083 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
6084 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
6085
6086 return hedit;
6087 }
6088
6089 /***
6090 * DESCRIPTION:
6091 * Begin in place editing of specified list view item
6092 *
6093 * PARAMETER(S):
6094 * [I] infoPtr : valid pointer to the listview structure
6095 * [I] nItem : item index
6096 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
6097 *
6098 * RETURN:
6099 * SUCCESS : TRUE
6100 * FAILURE : FALSE
6101 */
6102 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
6103 {
6104 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
6105 HWND hwndSelf = infoPtr->hwndSelf;
6106 NMLVDISPINFOW dispInfo;
6107 HFONT hOldFont = NULL;
6108 TEXTMETRICW tm;
6109 RECT rect;
6110 SIZE sz;
6111 HDC hdc;
6112
6113 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
6114
6115 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
6116
6117 /* remove existing edit box */
6118 if (infoPtr->hwndEdit)
6119 {
6120 SetFocus(infoPtr->hwndSelf);
6121 infoPtr->hwndEdit = 0;
6122 }
6123
6124 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6125
6126 infoPtr->nEditLabelItem = nItem;
6127
6128 LISTVIEW_SetSelection(infoPtr, nItem);
6129 LISTVIEW_SetItemFocus(infoPtr, nItem);
6130 LISTVIEW_InvalidateItem(infoPtr, nItem);
6131
6132 rect.left = LVIR_LABEL;
6133 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6134
6135 ZeroMemory(&dispInfo, sizeof(dispInfo));
6136 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6137 dispInfo.item.iItem = nItem;
6138 dispInfo.item.iSubItem = 0;
6139 dispInfo.item.stateMask = ~0;
6140 dispInfo.item.pszText = disptextW;
6141 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6142 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6143
6144 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6145 if (!infoPtr->hwndEdit) return 0;
6146
6147 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6148 {
6149 if (!IsWindow(hwndSelf))
6150 return 0;
6151 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6152 infoPtr->hwndEdit = 0;
6153 return 0;
6154 }
6155
6156 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6157
6158 /* position and display edit box */
6159 hdc = GetDC(infoPtr->hwndSelf);
6160
6161 /* select the font to get appropriate metric dimensions */
6162 if (infoPtr->hFont)
6163 hOldFont = SelectObject(hdc, infoPtr->hFont);
6164
6165 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6166 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6167 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6168
6169 /* get string length in pixels */
6170 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6171
6172 /* add extra spacing for the next character */
6173 GetTextMetricsW(hdc, &tm);
6174 sz.cx += tm.tmMaxCharWidth * 2;
6175
6176 if (infoPtr->hFont)
6177 SelectObject(hdc, hOldFont);
6178
6179 ReleaseDC(infoPtr->hwndSelf, hdc);
6180
6181 sz.cy = rect.bottom - rect.top + 2;
6182 rect.left -= 2;
6183 rect.top -= 1;
6184 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6185 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6186 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6187 SetFocus(infoPtr->hwndEdit);
6188 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6189 return infoPtr->hwndEdit;
6190 }
6191
6192
6193 /***
6194 * DESCRIPTION:
6195 * Ensures the specified item is visible, scrolling into view if necessary.
6196 *
6197 * PARAMETER(S):
6198 * [I] infoPtr : valid pointer to the listview structure
6199 * [I] nItem : item index
6200 * [I] bPartial : partially or entirely visible
6201 *
6202 * RETURN:
6203 * SUCCESS : TRUE
6204 * FAILURE : FALSE
6205 */
6206 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6207 {
6208 INT nScrollPosHeight = 0;
6209 INT nScrollPosWidth = 0;
6210 INT nHorzAdjust = 0;
6211 INT nVertAdjust = 0;
6212 INT nHorzDiff = 0;
6213 INT nVertDiff = 0;
6214 RECT rcItem, rcTemp;
6215
6216 rcItem.left = LVIR_BOUNDS;
6217 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6218
6219 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6220
6221 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6222 {
6223 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6224 if (infoPtr->uView == LV_VIEW_LIST)
6225 nScrollPosWidth = infoPtr->nItemWidth;
6226 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6227 nScrollPosWidth = 1;
6228
6229 if (rcItem.left < infoPtr->rcList.left)
6230 {
6231 nHorzAdjust = -1;
6232 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6233 }
6234 else
6235 {
6236 nHorzAdjust = 1;
6237 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6238 }
6239 }
6240
6241 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6242 {
6243 /* scroll up/down, but not in LVS_LIST mode */
6244 if (infoPtr->uView == LV_VIEW_DETAILS)
6245 nScrollPosHeight = infoPtr->nItemHeight;
6246 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6247 nScrollPosHeight = 1;
6248
6249 if (rcItem.top < infoPtr->rcList.top)
6250 {
6251 nVertAdjust = -1;
6252 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6253 }
6254 else
6255 {
6256 nVertAdjust = 1;
6257 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6258 }
6259 }
6260
6261 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6262
6263 if (nScrollPosWidth)
6264 {
6265 INT diff = nHorzDiff / nScrollPosWidth;
6266 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6267 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6268 }
6269
6270 if (nScrollPosHeight)
6271 {
6272 INT diff = nVertDiff / nScrollPosHeight;
6273 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6274 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6275 }
6276
6277 return TRUE;
6278 }
6279
6280 /***
6281 * DESCRIPTION:
6282 * Searches for an item with specific characteristics.
6283 *
6284 * PARAMETER(S):
6285 * [I] hwnd : window handle
6286 * [I] nStart : base item index
6287 * [I] lpFindInfo : item information to look for
6288 *
6289 * RETURN:
6290 * SUCCESS : index of item
6291 * FAILURE : -1
6292 */
6293 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6294 const LVFINDINFOW *lpFindInfo)
6295 {
6296 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6297 BOOL bWrap = FALSE, bNearest = FALSE;
6298 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6299 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6300 POINT Position, Destination;
6301 LVITEMW lvItem;
6302
6303 /* Search in virtual listviews should be done by application, not by
6304 listview control, so we just send LVN_ODFINDITEMW and return the result */
6305 if (infoPtr->dwStyle & LVS_OWNERDATA)
6306 {
6307 NMLVFINDITEMW nmlv;
6308
6309 nmlv.iStart = nStart;
6310 nmlv.lvfi = *lpFindInfo;
6311 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6312 }
6313
6314 if (!lpFindInfo || nItem < 0) return -1;
6315
6316 lvItem.mask = 0;
6317 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6318 lpFindInfo->flags & LVFI_SUBSTRING)
6319 {
6320 lvItem.mask |= LVIF_TEXT;
6321 lvItem.pszText = szDispText;
6322 lvItem.cchTextMax = DISP_TEXT_SIZE;
6323 }
6324
6325 if (lpFindInfo->flags & LVFI_WRAP)
6326 bWrap = TRUE;
6327
6328 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6329 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6330 {
6331 POINT Origin;
6332 RECT rcArea;
6333
6334 LISTVIEW_GetOrigin(infoPtr, &Origin);
6335 Destination.x = lpFindInfo->pt.x - Origin.x;
6336 Destination.y = lpFindInfo->pt.y - Origin.y;
6337 switch(lpFindInfo->vkDirection)
6338 {
6339 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6340 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6341 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6342 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6343 case VK_HOME: Destination.x = Destination.y = 0; break;
6344 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6345 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6346 case VK_END:
6347 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6348 Destination.x = rcArea.right;
6349 Destination.y = rcArea.bottom;
6350 break;
6351 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6352 }
6353 bNearest = TRUE;
6354 }
6355 else Destination.x = Destination.y = 0;
6356
6357 /* if LVFI_PARAM is specified, all other flags are ignored */
6358 if (lpFindInfo->flags & LVFI_PARAM)
6359 {
6360 lvItem.mask |= LVIF_PARAM;
6361 bNearest = FALSE;
6362 lvItem.mask &= ~LVIF_TEXT;
6363 }
6364
6365 again:
6366 for (; nItem < nLast; nItem++)
6367 {
6368 lvItem.iItem = nItem;
6369 lvItem.iSubItem = 0;
6370 lvItem.pszText = szDispText;
6371 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6372
6373 if (lvItem.mask & LVIF_PARAM)
6374 {
6375 if (lpFindInfo->lParam == lvItem.lParam)
6376 return nItem;
6377 else
6378 continue;
6379 }
6380
6381 if (lvItem.mask & LVIF_TEXT)
6382 {
6383 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6384 {
6385 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6386 if (!p || p != lvItem.pszText) continue;
6387 }
6388 else
6389 {
6390 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6391 }
6392 }
6393
6394 if (!bNearest) return nItem;
6395
6396 /* This is very inefficient. To do a good job here,
6397 * we need a sorted array of (x,y) item positions */
6398 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6399
6400 /* compute the distance^2 to the destination */
6401 xdist = Destination.x - Position.x;
6402 ydist = Destination.y - Position.y;
6403 dist = xdist * xdist + ydist * ydist;
6404
6405 /* remember the distance, and item if it's closer */
6406 if (dist < mindist)
6407 {
6408 mindist = dist;
6409 nNearestItem = nItem;
6410 }
6411 }
6412
6413 if (bWrap)
6414 {
6415 nItem = 0;
6416 nLast = min(nStart + 1, infoPtr->nItemCount);
6417 bWrap = FALSE;
6418 goto again;
6419 }
6420
6421 return nNearestItem;
6422 }
6423
6424 /***
6425 * DESCRIPTION:
6426 * Searches for an item with specific characteristics.
6427 *
6428 * PARAMETER(S):
6429 * [I] hwnd : window handle
6430 * [I] nStart : base item index
6431 * [I] lpFindInfo : item information to look for
6432 *
6433 * RETURN:
6434 * SUCCESS : index of item
6435 * FAILURE : -1
6436 */
6437 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6438 const LVFINDINFOA *lpFindInfo)
6439 {
6440 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6441 lpFindInfo->flags & LVFI_SUBSTRING;
6442 LVFINDINFOW fiw;
6443 INT res;
6444 LPWSTR strW = NULL;
6445
6446 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6447 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6448 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6449 textfreeT(strW, FALSE);
6450 return res;
6451 }
6452
6453 /***
6454 * DESCRIPTION:
6455 * Retrieves column attributes.
6456 *
6457 * PARAMETER(S):
6458 * [I] infoPtr : valid pointer to the listview structure
6459 * [I] nColumn : column index
6460 * [IO] lpColumn : column information
6461 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6462 * otherwise it is in fact a LPLVCOLUMNA
6463 *
6464 * RETURN:
6465 * SUCCESS : TRUE
6466 * FAILURE : FALSE
6467 */
6468 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6469 {
6470 COLUMN_INFO *lpColumnInfo;
6471 HDITEMW hdi;
6472
6473 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6474 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6475
6476 /* initialize memory */
6477 ZeroMemory(&hdi, sizeof(hdi));
6478
6479 if (lpColumn->mask & LVCF_TEXT)
6480 {
6481 hdi.mask |= HDI_TEXT;
6482 hdi.pszText = lpColumn->pszText;
6483 hdi.cchTextMax = lpColumn->cchTextMax;
6484 }
6485
6486 if (lpColumn->mask & LVCF_IMAGE)
6487 hdi.mask |= HDI_IMAGE;
6488
6489 if (lpColumn->mask & LVCF_ORDER)
6490 hdi.mask |= HDI_ORDER;
6491
6492 if (lpColumn->mask & LVCF_SUBITEM)
6493 hdi.mask |= HDI_LPARAM;
6494
6495 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6496
6497 if (lpColumn->mask & LVCF_FMT)
6498 lpColumn->fmt = lpColumnInfo->fmt;
6499
6500 if (lpColumn->mask & LVCF_WIDTH)
6501 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6502
6503 if (lpColumn->mask & LVCF_IMAGE)
6504 lpColumn->iImage = hdi.iImage;
6505
6506 if (lpColumn->mask & LVCF_ORDER)
6507 lpColumn->iOrder = hdi.iOrder;
6508
6509 if (lpColumn->mask & LVCF_SUBITEM)
6510 lpColumn->iSubItem = hdi.lParam;
6511
6512 if (lpColumn->mask & LVCF_MINWIDTH)
6513 lpColumn->cxMin = lpColumnInfo->cxMin;
6514
6515 return TRUE;
6516 }
6517
6518 static inline BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6519 {
6520 if (!infoPtr->hwndHeader) return FALSE;
6521 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6522 }
6523
6524 /***
6525 * DESCRIPTION:
6526 * Retrieves the column width.
6527 *
6528 * PARAMETER(S):
6529 * [I] infoPtr : valid pointer to the listview structure
6530 * [I] int : column index
6531 *
6532 * RETURN:
6533 * SUCCESS : column width
6534 * FAILURE : zero
6535 */
6536 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6537 {
6538 INT nColumnWidth = 0;
6539 HDITEMW hdItem;
6540
6541 TRACE("nColumn=%d\n", nColumn);
6542
6543 /* we have a 'column' in LIST and REPORT mode only */
6544 switch(infoPtr->uView)
6545 {
6546 case LV_VIEW_LIST:
6547 nColumnWidth = infoPtr->nItemWidth;
6548 break;
6549 case LV_VIEW_DETAILS:
6550 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6551 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6552 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6553 */
6554 hdItem.mask = HDI_WIDTH;
6555 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6556 {
6557 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6558 return 0;
6559 }
6560 nColumnWidth = hdItem.cxy;
6561 break;
6562 }
6563
6564 TRACE("nColumnWidth=%d\n", nColumnWidth);
6565 return nColumnWidth;
6566 }
6567
6568 /***
6569 * DESCRIPTION:
6570 * In list or report display mode, retrieves the number of items that can fit
6571 * vertically in the visible area. In icon or small icon display mode,
6572 * retrieves the total number of visible items.
6573 *
6574 * PARAMETER(S):
6575 * [I] infoPtr : valid pointer to the listview structure
6576 *
6577 * RETURN:
6578 * Number of fully visible items.
6579 */
6580 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6581 {
6582 switch (infoPtr->uView)
6583 {
6584 case LV_VIEW_ICON:
6585 case LV_VIEW_SMALLICON:
6586 return infoPtr->nItemCount;
6587 case LV_VIEW_DETAILS:
6588 return LISTVIEW_GetCountPerColumn(infoPtr);
6589 case LV_VIEW_LIST:
6590 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6591 }
6592 assert(FALSE);
6593 return 0;
6594 }
6595
6596 /***
6597 * DESCRIPTION:
6598 * Retrieves an image list handle.
6599 *
6600 * PARAMETER(S):
6601 * [I] infoPtr : valid pointer to the listview structure
6602 * [I] nImageList : image list identifier
6603 *
6604 * RETURN:
6605 * SUCCESS : image list handle
6606 * FAILURE : NULL
6607 */
6608 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6609 {
6610 switch (nImageList)
6611 {
6612 case LVSIL_NORMAL: return infoPtr->himlNormal;
6613 case LVSIL_SMALL: return infoPtr->himlSmall;
6614 case LVSIL_STATE: return infoPtr->himlState;
6615 case LVSIL_GROUPHEADER:
6616 FIXME("LVSIL_GROUPHEADER not supported\n");
6617 break;
6618 default:
6619 WARN("got unknown imagelist index - %d\n", nImageList);
6620 }
6621 return NULL;
6622 }
6623
6624 /* LISTVIEW_GetISearchString */
6625
6626 /***
6627 * DESCRIPTION:
6628 * Retrieves item attributes.
6629 *
6630 * PARAMETER(S):
6631 * [I] hwnd : window handle
6632 * [IO] lpLVItem : item info
6633 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6634 * if FALSE, then lpLVItem is a LPLVITEMA.
6635 *
6636 * NOTE:
6637 * This is the internal 'GetItem' interface -- it tries to
6638 * be smart and avoid text copies, if possible, by modifying
6639 * lpLVItem->pszText to point to the text string. Please note
6640 * that this is not always possible (e.g. OWNERDATA), so on
6641 * entry you *must* supply valid values for pszText, and cchTextMax.
6642 * The only difference to the documented interface is that upon
6643 * return, you should use *only* the lpLVItem->pszText, rather than
6644 * the buffer pointer you provided on input. Most code already does
6645 * that, so it's not a problem.
6646 * For the two cases when the text must be copied (that is,
6647 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6648 *
6649 * RETURN:
6650 * SUCCESS : TRUE
6651 * FAILURE : FALSE
6652 */
6653 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6654 {
6655 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6656 NMLVDISPINFOW dispInfo;
6657 ITEM_INFO *lpItem;
6658 ITEMHDR* pItemHdr;
6659 HDPA hdpaSubItems;
6660 INT isubitem;
6661
6662 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6663
6664 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6665 return FALSE;
6666
6667 if (lpLVItem->mask == 0) return TRUE;
6668 TRACE("mask=%x\n", lpLVItem->mask);
6669
6670 /* make a local copy */
6671 isubitem = lpLVItem->iSubItem;
6672
6673 /* a quick optimization if all we're asked is the focus state
6674 * these queries are worth optimising since they are common,
6675 * and can be answered in constant time, without the heavy accesses */
6676 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6677 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6678 {
6679 lpLVItem->state = 0;
6680 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6681 lpLVItem->state |= LVIS_FOCUSED;
6682 return TRUE;
6683 }
6684
6685 ZeroMemory(&dispInfo, sizeof(dispInfo));
6686
6687 /* if the app stores all the data, handle it separately */
6688 if (infoPtr->dwStyle & LVS_OWNERDATA)
6689 {
6690 dispInfo.item.state = 0;
6691
6692 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6693 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6694 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6695 {
6696 UINT mask = lpLVItem->mask;
6697
6698 /* NOTE: copy only fields which we _know_ are initialized, some apps
6699 * depend on the uninitialized fields being 0 */
6700 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6701 dispInfo.item.iItem = lpLVItem->iItem;
6702 dispInfo.item.iSubItem = isubitem;
6703 if (lpLVItem->mask & LVIF_TEXT)
6704 {
6705 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6706 /* reset mask */
6707 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6708 else
6709 {
6710 dispInfo.item.pszText = lpLVItem->pszText;
6711 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6712 }
6713 }
6714 if (lpLVItem->mask & LVIF_STATE)
6715 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6716 /* could be zeroed on LVIF_NORECOMPUTE case */
6717 if (dispInfo.item.mask)
6718 {
6719 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6720 dispInfo.item.stateMask = lpLVItem->stateMask;
6721 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6722 {
6723 /* full size structure expected - _WIN32IE >= 0x560 */
6724 *lpLVItem = dispInfo.item;
6725 }
6726 else if (lpLVItem->mask & LVIF_INDENT)
6727 {
6728 /* indent member expected - _WIN32IE >= 0x300 */
6729 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6730 }
6731 else
6732 {
6733 /* minimal structure expected */
6734 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6735 }
6736 lpLVItem->mask = mask;
6737 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6738 }
6739 }
6740
6741 /* make sure lParam is zeroed out */
6742 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6743
6744 /* callback marked pointer required here */
6745 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6746 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6747
6748 /* we store only a little state, so if we're not asked, we're done */
6749 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6750
6751 /* if focus is handled by us, report it */
6752 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6753 {
6754 lpLVItem->state &= ~LVIS_FOCUSED;
6755 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6756 lpLVItem->state |= LVIS_FOCUSED;
6757 }
6758
6759 /* and do the same for selection, if we handle it */
6760 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6761 {
6762 lpLVItem->state &= ~LVIS_SELECTED;
6763 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6764 lpLVItem->state |= LVIS_SELECTED;
6765 }
6766
6767 return TRUE;
6768 }
6769
6770 /* find the item and subitem structures before we proceed */
6771 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6772 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6773 assert (lpItem);
6774
6775 if (isubitem)
6776 {
6777 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6778 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6779 if (!lpSubItem)
6780 {
6781 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6782 isubitem = 0;
6783 }
6784 }
6785 else
6786 pItemHdr = &lpItem->hdr;
6787
6788 /* Do we need to query the state from the app? */
6789 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6790 {
6791 dispInfo.item.mask |= LVIF_STATE;
6792 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6793 }
6794
6795 /* Do we need to enquire about the image? */
6796 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6797 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6798 {
6799 dispInfo.item.mask |= LVIF_IMAGE;
6800 dispInfo.item.iImage = I_IMAGECALLBACK;
6801 }
6802
6803 /* Only items support indentation */
6804 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6805 (isubitem == 0))
6806 {
6807 dispInfo.item.mask |= LVIF_INDENT;
6808 dispInfo.item.iIndent = I_INDENTCALLBACK;
6809 }
6810
6811 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6812 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6813 !is_text(pItemHdr->pszText))
6814 {
6815 dispInfo.item.mask |= LVIF_TEXT;
6816 dispInfo.item.pszText = lpLVItem->pszText;
6817 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6818 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6819 *dispInfo.item.pszText = '\0';
6820 }
6821
6822 /* If we don't have all the requested info, query the application */
6823 if (dispInfo.item.mask)
6824 {
6825 dispInfo.item.iItem = lpLVItem->iItem;
6826 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6827 dispInfo.item.lParam = lpItem->lParam;
6828 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6829 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6830 }
6831
6832 /* we should not store values for subitems */
6833 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6834
6835 /* Now, handle the iImage field */
6836 if (dispInfo.item.mask & LVIF_IMAGE)
6837 {
6838 lpLVItem->iImage = dispInfo.item.iImage;
6839 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6840 pItemHdr->iImage = dispInfo.item.iImage;
6841 }
6842 else if (lpLVItem->mask & LVIF_IMAGE)
6843 {
6844 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6845 lpLVItem->iImage = pItemHdr->iImage;
6846 else
6847 lpLVItem->iImage = 0;
6848 }
6849
6850 /* The pszText field */
6851 if (dispInfo.item.mask & LVIF_TEXT)
6852 {
6853 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6854 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6855
6856 lpLVItem->pszText = dispInfo.item.pszText;
6857 }
6858 else if (lpLVItem->mask & LVIF_TEXT)
6859 {
6860 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6861 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6862 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6863 }
6864
6865 /* Next is the lParam field */
6866 if (dispInfo.item.mask & LVIF_PARAM)
6867 {
6868 lpLVItem->lParam = dispInfo.item.lParam;
6869 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6870 lpItem->lParam = dispInfo.item.lParam;
6871 }
6872 else if (lpLVItem->mask & LVIF_PARAM)
6873 lpLVItem->lParam = lpItem->lParam;
6874
6875 /* if this is a subitem, we're done */
6876 if (isubitem) return TRUE;
6877
6878 /* ... the state field (this one is different due to uCallbackmask) */
6879 if (lpLVItem->mask & LVIF_STATE)
6880 {
6881 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6882 if (dispInfo.item.mask & LVIF_STATE)
6883 {
6884 lpLVItem->state &= ~dispInfo.item.stateMask;
6885 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6886 }
6887 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6888 {
6889 lpLVItem->state &= ~LVIS_FOCUSED;
6890 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6891 lpLVItem->state |= LVIS_FOCUSED;
6892 }
6893 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6894 {
6895 lpLVItem->state &= ~LVIS_SELECTED;
6896 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6897 lpLVItem->state |= LVIS_SELECTED;
6898 }
6899 }
6900
6901 /* and last, but not least, the indent field */
6902 if (dispInfo.item.mask & LVIF_INDENT)
6903 {
6904 lpLVItem->iIndent = dispInfo.item.iIndent;
6905 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6906 lpItem->iIndent = dispInfo.item.iIndent;
6907 }
6908 else if (lpLVItem->mask & LVIF_INDENT)
6909 {
6910 lpLVItem->iIndent = lpItem->iIndent;
6911 }
6912
6913 return TRUE;
6914 }
6915
6916 /***
6917 * DESCRIPTION:
6918 * Retrieves item attributes.
6919 *
6920 * PARAMETER(S):
6921 * [I] hwnd : window handle
6922 * [IO] lpLVItem : item info
6923 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6924 * if FALSE, then lpLVItem is a LPLVITEMA.
6925 *
6926 * NOTE:
6927 * This is the external 'GetItem' interface -- it properly copies
6928 * the text in the provided buffer.
6929 *
6930 * RETURN:
6931 * SUCCESS : TRUE
6932 * FAILURE : FALSE
6933 */
6934 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6935 {
6936 LPWSTR pszText;
6937 BOOL bResult;
6938
6939 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6940 return FALSE;
6941
6942 pszText = lpLVItem->pszText;
6943 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6944 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6945 {
6946 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6947 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6948 else
6949 pszText = LPSTR_TEXTCALLBACKW;
6950 }
6951 lpLVItem->pszText = pszText;
6952
6953 return bResult;
6954 }
6955
6956
6957 /***
6958 * DESCRIPTION:
6959 * Retrieves the position (upper-left) of the listview control item.
6960 * Note that for LVS_ICON style, the upper-left is that of the icon
6961 * and not the bounding box.
6962 *
6963 * PARAMETER(S):
6964 * [I] infoPtr : valid pointer to the listview structure
6965 * [I] nItem : item index
6966 * [O] lpptPosition : coordinate information
6967 *
6968 * RETURN:
6969 * SUCCESS : TRUE
6970 * FAILURE : FALSE
6971 */
6972 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
6973 {
6974 POINT Origin;
6975
6976 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
6977
6978 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6979
6980 LISTVIEW_GetOrigin(infoPtr, &Origin);
6981 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
6982
6983 if (infoPtr->uView == LV_VIEW_ICON)
6984 {
6985 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6986 lpptPosition->y += ICON_TOP_PADDING;
6987 }
6988 lpptPosition->x += Origin.x;
6989 lpptPosition->y += Origin.y;
6990
6991 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
6992 return TRUE;
6993 }
6994
6995
6996 /***
6997 * DESCRIPTION:
6998 * Retrieves the bounding rectangle for a listview control item.
6999 *
7000 * PARAMETER(S):
7001 * [I] infoPtr : valid pointer to the listview structure
7002 * [I] nItem : item index
7003 * [IO] lprc : bounding rectangle coordinates
7004 * lprc->left specifies the portion of the item for which the bounding
7005 * rectangle will be retrieved.
7006 *
7007 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
7008 * including the icon and label.
7009 * *
7010 * * For LVS_ICON
7011 * * Experiment shows that native control returns:
7012 * * width = min (48, length of text line)
7013 * * .left = position.x - (width - iconsize.cx)/2
7014 * * .right = .left + width
7015 * * height = #lines of text * ntmHeight + icon height + 8
7016 * * .top = position.y - 2
7017 * * .bottom = .top + height
7018 * * separation between items .y = itemSpacing.cy - height
7019 * * .x = itemSpacing.cx - width
7020 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
7021 * *
7022 * * For LVS_ICON
7023 * * Experiment shows that native control returns:
7024 * * width = iconSize.cx + 16
7025 * * .left = position.x - (width - iconsize.cx)/2
7026 * * .right = .left + width
7027 * * height = iconSize.cy + 4
7028 * * .top = position.y - 2
7029 * * .bottom = .top + height
7030 * * separation between items .y = itemSpacing.cy - height
7031 * * .x = itemSpacing.cx - width
7032 * LVIR_LABEL Returns the bounding rectangle of the item text.
7033 * *
7034 * * For LVS_ICON
7035 * * Experiment shows that native control returns:
7036 * * width = text length
7037 * * .left = position.x - width/2
7038 * * .right = .left + width
7039 * * height = ntmH * linecount + 2
7040 * * .top = position.y + iconSize.cy + 6
7041 * * .bottom = .top + height
7042 * * separation between items .y = itemSpacing.cy - height
7043 * * .x = itemSpacing.cx - width
7044 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
7045 * rectangles, but excludes columns in report view.
7046 *
7047 * RETURN:
7048 * SUCCESS : TRUE
7049 * FAILURE : FALSE
7050 *
7051 * NOTES
7052 * Note that the bounding rectangle of the label in the LVS_ICON view depends
7053 * upon whether the window has the focus currently and on whether the item
7054 * is the one with the focus. Ensure that the control's record of which
7055 * item has the focus agrees with the items' records.
7056 */
7057 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
7058 {
7059 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7060 BOOL doLabel = TRUE, oversizedBox = FALSE;
7061 POINT Position, Origin;
7062 LVITEMW lvItem;
7063 LONG mode;
7064
7065 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
7066
7067 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7068
7069 LISTVIEW_GetOrigin(infoPtr, &Origin);
7070 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7071
7072 /* Be smart and try to figure out the minimum we have to do */
7073 if (lprc->left == LVIR_ICON) doLabel = FALSE;
7074 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
7075 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
7076 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
7077 oversizedBox = TRUE;
7078
7079 /* get what we need from the item before hand, so we make
7080 * only one request. This can speed up things, if data
7081 * is stored on the app side */
7082 lvItem.mask = 0;
7083 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7084 if (doLabel) lvItem.mask |= LVIF_TEXT;
7085 lvItem.iItem = nItem;
7086 lvItem.iSubItem = 0;
7087 lvItem.pszText = szDispText;
7088 lvItem.cchTextMax = DISP_TEXT_SIZE;
7089 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
7090 /* we got the state already up, simulate it here, to avoid a reget */
7091 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
7092 {
7093 lvItem.mask |= LVIF_STATE;
7094 lvItem.stateMask = LVIS_FOCUSED;
7095 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
7096 }
7097
7098 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
7099 lprc->left = LVIR_BOUNDS;
7100
7101 mode = lprc->left;
7102 switch(lprc->left)
7103 {
7104 case LVIR_ICON:
7105 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
7106 break;
7107
7108 case LVIR_LABEL:
7109 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
7110 break;
7111
7112 case LVIR_BOUNDS:
7113 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
7114 break;
7115
7116 case LVIR_SELECTBOUNDS:
7117 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
7118 break;
7119
7120 default:
7121 WARN("Unknown value: %d\n", lprc->left);
7122 return FALSE;
7123 }
7124
7125 if (infoPtr->uView == LV_VIEW_DETAILS)
7126 {
7127 if (mode != LVIR_BOUNDS)
7128 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7129 Position.y + Origin.y);
7130 else
7131 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7132 }
7133 else
7134 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7135
7136 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7137
7138 return TRUE;
7139 }
7140
7141 /***
7142 * DESCRIPTION:
7143 * Retrieves the spacing between listview control items.
7144 *
7145 * PARAMETER(S):
7146 * [I] infoPtr : valid pointer to the listview structure
7147 * [IO] lprc : rectangle to receive the output
7148 * on input, lprc->top = nSubItem
7149 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7150 *
7151 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7152 * not only those of the first column.
7153 *
7154 * RETURN:
7155 * TRUE: success
7156 * FALSE: failure
7157 */
7158 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7159 {
7160 RECT rect = { 0, 0, 0, 0 };
7161 POINT origin;
7162 INT y;
7163
7164 if (!lprc) return FALSE;
7165
7166 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7167 /* Subitem of '0' means item itself, and this works for all control view modes */
7168 if (lprc->top == 0)
7169 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7170
7171 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7172
7173 LISTVIEW_GetOrigin(infoPtr, &origin);
7174 /* this works for any item index, no matter if it exists or not */
7175 y = item * infoPtr->nItemHeight + origin.y;
7176
7177 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7178 {
7179 rect.top = 0;
7180 rect.bottom = infoPtr->nItemHeight;
7181 }
7182 else
7183 {
7184 /* Native implementation is broken for this case and garbage is left for left and right fields,
7185 we zero them to get predictable output */
7186 lprc->left = lprc->right = lprc->top = 0;
7187 lprc->bottom = infoPtr->nItemHeight;
7188 OffsetRect(lprc, origin.x, y);
7189 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7190 return TRUE;
7191 }
7192
7193 switch (lprc->left)
7194 {
7195 case LVIR_ICON:
7196 {
7197 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7198 if (infoPtr->himlSmall)
7199 rect.right = rect.left + infoPtr->iconSize.cx;
7200 else
7201 rect.right = rect.left;
7202
7203 rect.bottom = rect.top + infoPtr->iconSize.cy;
7204 break;
7205 }
7206 case LVIR_LABEL:
7207 case LVIR_BOUNDS:
7208 break;
7209
7210 default:
7211 ERR("Unknown bounds=%d\n", lprc->left);
7212 return FALSE;
7213 }
7214
7215 OffsetRect(&rect, origin.x, y);
7216 *lprc = rect;
7217 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7218
7219 return TRUE;
7220 }
7221
7222 /***
7223 * DESCRIPTION:
7224 * Retrieves the spacing between listview control items.
7225 *
7226 * PARAMETER(S):
7227 * [I] infoPtr : valid pointer to the listview structure
7228 * [I] bSmall : flag for small or large icon
7229 *
7230 * RETURN:
7231 * Horizontal + vertical spacing
7232 */
7233 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7234 {
7235 LONG lResult;
7236
7237 if (!bSmall)
7238 {
7239 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7240 }
7241 else
7242 {
7243 if (infoPtr->uView == LV_VIEW_ICON)
7244 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7245 else
7246 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7247 }
7248 return lResult;
7249 }
7250
7251 /***
7252 * DESCRIPTION:
7253 * Retrieves the state of a listview control item.
7254 *
7255 * PARAMETER(S):
7256 * [I] infoPtr : valid pointer to the listview structure
7257 * [I] nItem : item index
7258 * [I] uMask : state mask
7259 *
7260 * RETURN:
7261 * State specified by the mask.
7262 */
7263 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7264 {
7265 LVITEMW lvItem;
7266
7267 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7268
7269 lvItem.iItem = nItem;
7270 lvItem.iSubItem = 0;
7271 lvItem.mask = LVIF_STATE;
7272 lvItem.stateMask = uMask;
7273 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7274
7275 return lvItem.state & uMask;
7276 }
7277
7278 /***
7279 * DESCRIPTION:
7280 * Retrieves the text of a listview control item or subitem.
7281 *
7282 * PARAMETER(S):
7283 * [I] hwnd : window handle
7284 * [I] nItem : item index
7285 * [IO] lpLVItem : item information
7286 * [I] isW : TRUE if lpLVItem is Unicode
7287 *
7288 * RETURN:
7289 * SUCCESS : string length
7290 * FAILURE : 0
7291 */
7292 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7293 {
7294 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7295
7296 lpLVItem->mask = LVIF_TEXT;
7297 lpLVItem->iItem = nItem;
7298 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7299
7300 return textlenT(lpLVItem->pszText, isW);
7301 }
7302
7303 /***
7304 * DESCRIPTION:
7305 * Searches for an item based on properties + relationships.
7306 *
7307 * PARAMETER(S):
7308 * [I] infoPtr : valid pointer to the listview structure
7309 * [I] nItem : item index
7310 * [I] uFlags : relationship flag
7311 *
7312 * RETURN:
7313 * SUCCESS : item index
7314 * FAILURE : -1
7315 */
7316 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7317 {
7318 UINT uMask = 0;
7319 LVFINDINFOW lvFindInfo;
7320 INT nCountPerColumn;
7321 INT nCountPerRow;
7322 INT i;
7323
7324 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7325 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7326
7327 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7328
7329 if (uFlags & LVNI_CUT)
7330 uMask |= LVIS_CUT;
7331
7332 if (uFlags & LVNI_DROPHILITED)
7333 uMask |= LVIS_DROPHILITED;
7334
7335 if (uFlags & LVNI_FOCUSED)
7336 uMask |= LVIS_FOCUSED;
7337
7338 if (uFlags & LVNI_SELECTED)
7339 uMask |= LVIS_SELECTED;
7340
7341 /* if we're asked for the focused item, that's only one,
7342 * so it's worth optimizing */
7343 if (uFlags & LVNI_FOCUSED)
7344 {
7345 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7346 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7347 }
7348
7349 if (uFlags & LVNI_ABOVE)
7350 {
7351 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7352 {
7353 while (nItem >= 0)
7354 {
7355 nItem--;
7356 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7357 return nItem;
7358 }
7359 }
7360 else
7361 {
7362 /* Special case for autoarrange - move 'til the top of a list */
7363 if (is_autoarrange(infoPtr))
7364 {
7365 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7366 while (nItem - nCountPerRow >= 0)
7367 {
7368 nItem -= nCountPerRow;
7369 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7370 return nItem;
7371 }
7372 return -1;
7373 }
7374 lvFindInfo.flags = LVFI_NEARESTXY;
7375 lvFindInfo.vkDirection = VK_UP;
7376 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7377 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7378 {
7379 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7380 return nItem;
7381 }
7382 }
7383 }
7384 else if (uFlags & LVNI_BELOW)
7385 {
7386 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7387 {
7388 while (nItem < infoPtr->nItemCount)
7389 {
7390 nItem++;
7391 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7392 return nItem;
7393 }
7394 }
7395 else
7396 {
7397 /* Special case for autoarrange - move 'til the bottom of a list */
7398 if (is_autoarrange(infoPtr))
7399 {
7400 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7401 while (nItem + nCountPerRow < infoPtr->nItemCount )
7402 {
7403 nItem += nCountPerRow;
7404 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7405 return nItem;
7406 }
7407 return -1;
7408 }
7409 lvFindInfo.flags = LVFI_NEARESTXY;
7410 lvFindInfo.vkDirection = VK_DOWN;
7411 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7412 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7413 {
7414 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7415 return nItem;
7416 }
7417 }
7418 }
7419 else if (uFlags & LVNI_TOLEFT)
7420 {
7421 if (infoPtr->uView == LV_VIEW_LIST)
7422 {
7423 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7424 while (nItem - nCountPerColumn >= 0)
7425 {
7426 nItem -= nCountPerColumn;
7427 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7428 return nItem;
7429 }
7430 }
7431 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7432 {
7433 /* Special case for autoarrange - move 'til the beginning of a row */
7434 if (is_autoarrange(infoPtr))
7435 {
7436 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7437 while (nItem % nCountPerRow > 0)
7438 {
7439 nItem --;
7440 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7441 return nItem;
7442 }
7443 return -1;
7444 }
7445 lvFindInfo.flags = LVFI_NEARESTXY;
7446 lvFindInfo.vkDirection = VK_LEFT;
7447 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7448 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7449 {
7450 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7451 return nItem;
7452 }
7453 }
7454 }
7455 else if (uFlags & LVNI_TORIGHT)
7456 {
7457 if (infoPtr->uView == LV_VIEW_LIST)
7458 {
7459 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7460 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7461 {
7462 nItem += nCountPerColumn;
7463 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7464 return nItem;
7465 }
7466 }
7467 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7468 {
7469 /* Special case for autoarrange - move 'til the end of a row */
7470 if (is_autoarrange(infoPtr))
7471 {
7472 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7473 while (nItem % nCountPerRow < nCountPerRow - 1 )
7474 {
7475 nItem ++;
7476 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7477 return nItem;
7478 }
7479 return -1;
7480 }
7481 lvFindInfo.flags = LVFI_NEARESTXY;
7482 lvFindInfo.vkDirection = VK_RIGHT;
7483 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7484 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7485 {
7486 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7487 return nItem;
7488 }
7489 }
7490 }
7491 else
7492 {
7493 nItem++;
7494
7495 /* search by index */
7496 for (i = nItem; i < infoPtr->nItemCount; i++)
7497 {
7498 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7499 return i;
7500 }
7501 }
7502
7503 return -1;
7504 }
7505
7506 /* LISTVIEW_GetNumberOfWorkAreas */
7507
7508 /***
7509 * DESCRIPTION:
7510 * Retrieves the origin coordinates when in icon or small icon display mode.
7511 *
7512 * PARAMETER(S):
7513 * [I] infoPtr : valid pointer to the listview structure
7514 * [O] lpptOrigin : coordinate information
7515 *
7516 * RETURN:
7517 * None.
7518 */
7519 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7520 {
7521 INT nHorzPos = 0, nVertPos = 0;
7522 SCROLLINFO scrollInfo;
7523
7524 scrollInfo.cbSize = sizeof(SCROLLINFO);
7525 scrollInfo.fMask = SIF_POS;
7526
7527 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7528 nHorzPos = scrollInfo.nPos;
7529 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7530 nVertPos = scrollInfo.nPos;
7531
7532 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7533
7534 lpptOrigin->x = infoPtr->rcList.left;
7535 lpptOrigin->y = infoPtr->rcList.top;
7536 if (infoPtr->uView == LV_VIEW_LIST)
7537 nHorzPos *= infoPtr->nItemWidth;
7538 else if (infoPtr->uView == LV_VIEW_DETAILS)
7539 nVertPos *= infoPtr->nItemHeight;
7540
7541 lpptOrigin->x -= nHorzPos;
7542 lpptOrigin->y -= nVertPos;
7543
7544 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7545 }
7546
7547 /***
7548 * DESCRIPTION:
7549 * Retrieves the width of a string.
7550 *
7551 * PARAMETER(S):
7552 * [I] hwnd : window handle
7553 * [I] lpszText : text string to process
7554 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7555 *
7556 * RETURN:
7557 * SUCCESS : string width (in pixels)
7558 * FAILURE : zero
7559 */
7560 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7561 {
7562 SIZE stringSize;
7563
7564 stringSize.cx = 0;
7565 if (is_text(lpszText))
7566 {
7567 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7568 HDC hdc = GetDC(infoPtr->hwndSelf);
7569 HFONT hOldFont = SelectObject(hdc, hFont);
7570
7571 if (isW)
7572 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7573 else
7574 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7575 SelectObject(hdc, hOldFont);
7576 ReleaseDC(infoPtr->hwndSelf, hdc);
7577 }
7578 return stringSize.cx;
7579 }
7580
7581 /***
7582 * DESCRIPTION:
7583 * Determines which listview item is located at the specified position.
7584 *
7585 * PARAMETER(S):
7586 * [I] infoPtr : valid pointer to the listview structure
7587 * [IO] lpht : hit test information
7588 * [I] subitem : fill out iSubItem.
7589 * [I] select : return the index only if the hit selects the item
7590 *
7591 * NOTE:
7592 * (mm 20001022): We must not allow iSubItem to be touched, for
7593 * an app might pass only a structure with space up to iItem!
7594 * (MS Office 97 does that for instance in the file open dialog)
7595 *
7596 * RETURN:
7597 * SUCCESS : item index
7598 * FAILURE : -1
7599 */
7600 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7601 {
7602 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7603 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7604 POINT Origin, Position, opt;
7605 BOOL is_fullrow;
7606 LVITEMW lvItem;
7607 ITERATOR i;
7608 INT iItem;
7609
7610 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7611
7612 lpht->flags = 0;
7613 lpht->iItem = -1;
7614 if (subitem) lpht->iSubItem = 0;
7615
7616 LISTVIEW_GetOrigin(infoPtr, &Origin);
7617
7618 /* set whole list relation flags */
7619 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7620 {
7621 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7622 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7623 lpht->flags |= LVHT_TOLEFT;
7624
7625 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7626 opt.y = lpht->pt.y + infoPtr->rcList.top;
7627 else
7628 opt.y = lpht->pt.y;
7629
7630 if (infoPtr->rcList.bottom < opt.y)
7631 lpht->flags |= LVHT_BELOW;
7632 }
7633 else
7634 {
7635 if (infoPtr->rcList.left > lpht->pt.x)
7636 lpht->flags |= LVHT_TOLEFT;
7637 else if (infoPtr->rcList.right < lpht->pt.x)
7638 lpht->flags |= LVHT_TORIGHT;
7639
7640 if (infoPtr->rcList.top > lpht->pt.y)
7641 lpht->flags |= LVHT_ABOVE;
7642 else if (infoPtr->rcList.bottom < lpht->pt.y)
7643 lpht->flags |= LVHT_BELOW;
7644 }
7645
7646 /* even if item is invalid try to find subitem */
7647 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7648 {
7649 RECT *pRect;
7650 INT j;
7651
7652 opt.x = lpht->pt.x - Origin.x;
7653
7654 lpht->iSubItem = -1;
7655 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7656 {
7657 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7658
7659 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7660 {
7661 lpht->iSubItem = j;
7662 break;
7663 }
7664 }
7665 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7666
7667 /* if we're outside horizontal columns bounds there's nothing to test further */
7668 if (lpht->iSubItem == -1)
7669 {
7670 lpht->iItem = -1;
7671 lpht->flags = LVHT_NOWHERE;
7672 return -1;
7673 }
7674 }
7675
7676 TRACE("lpht->flags=0x%x\n", lpht->flags);
7677 if (lpht->flags) return -1;
7678
7679 lpht->flags |= LVHT_NOWHERE;
7680
7681 /* first deal with the large items */
7682 rcSearch.left = lpht->pt.x;
7683 rcSearch.top = lpht->pt.y;
7684 rcSearch.right = rcSearch.left + 1;
7685 rcSearch.bottom = rcSearch.top + 1;
7686
7687 iterator_frameditems(&i, infoPtr, &rcSearch);
7688 iterator_next(&i); /* go to first item in the sequence */
7689 iItem = i.nItem;
7690 iterator_destroy(&i);
7691
7692 TRACE("lpht->iItem=%d\n", iItem);
7693 if (iItem == -1) return -1;
7694
7695 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7696 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7697 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7698 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7699 lvItem.iItem = iItem;
7700 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7701 lvItem.pszText = szDispText;
7702 lvItem.cchTextMax = DISP_TEXT_SIZE;
7703 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7704 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7705
7706 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7707 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7708 opt.x = lpht->pt.x - Position.x - Origin.x;
7709
7710 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7711 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7712 else
7713 opt.y = lpht->pt.y - Position.y - Origin.y;
7714
7715 if (infoPtr->uView == LV_VIEW_DETAILS)
7716 {
7717 rcBounds = rcBox;
7718 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7719 opt.x = lpht->pt.x - Origin.x;
7720 }
7721 else
7722 {
7723 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7724 UnionRect(&rcBounds, &rcBounds, &rcState);
7725 }
7726 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7727 if (!PtInRect(&rcBounds, opt)) return -1;
7728
7729 /* That's a special case - row rectangle is used as item rectangle and
7730 returned flags contain all item parts. */
7731 is_fullrow = (infoPtr->uView == LV_VIEW_DETAILS) && ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || (infoPtr->dwStyle & LVS_OWNERDRAWFIXED));
7732
7733 if (PtInRect(&rcIcon, opt))
7734 lpht->flags |= LVHT_ONITEMICON;
7735 else if (PtInRect(&rcLabel, opt))
7736 lpht->flags |= LVHT_ONITEMLABEL;
7737 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7738 lpht->flags |= LVHT_ONITEMSTATEICON;
7739 if (is_fullrow && !(lpht->flags & LVHT_ONITEM))
7740 {
7741 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7742 }
7743 if (lpht->flags & LVHT_ONITEM)
7744 lpht->flags &= ~LVHT_NOWHERE;
7745 TRACE("lpht->flags=0x%x\n", lpht->flags);
7746
7747 if (select && !is_fullrow)
7748 {
7749 if (infoPtr->uView == LV_VIEW_DETAILS)
7750 {
7751 /* get main item bounds */
7752 lvItem.iSubItem = 0;
7753 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7754 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7755 UnionRect(&rcBounds, &rcBounds, &rcState);
7756 }
7757 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7758 }
7759 return lpht->iItem = iItem;
7760 }
7761
7762 /***
7763 * DESCRIPTION:
7764 * Inserts a new item in the listview control.
7765 *
7766 * PARAMETER(S):
7767 * [I] infoPtr : valid pointer to the listview structure
7768 * [I] lpLVItem : item information
7769 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7770 *
7771 * RETURN:
7772 * SUCCESS : new item index
7773 * FAILURE : -1
7774 */
7775 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7776 {
7777 INT nItem;
7778 HDPA hdpaSubItems;
7779 NMLISTVIEW nmlv;
7780 ITEM_INFO *lpItem;
7781 ITEM_ID *lpID;
7782 BOOL is_sorted, has_changed;
7783 LVITEMW item;
7784 HWND hwndSelf = infoPtr->hwndSelf;
7785
7786 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7787
7788 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7789
7790 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7791 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7792
7793 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7794
7795 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7796
7797 /* insert item in listview control data structure */
7798 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7799 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7800
7801 /* link with id struct */
7802 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7803 lpItem->id = lpID;
7804 lpID->item = hdpaSubItems;
7805 lpID->id = get_next_itemid(infoPtr);
7806 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7807
7808 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7809 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7810
7811 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7812
7813 /* calculate new item index */
7814 if (is_sorted)
7815 {
7816 HDPA hItem;
7817 ITEM_INFO *item_s;
7818 INT i = 0, cmpv;
7819
7820 while (i < infoPtr->nItemCount)
7821 {
7822 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7823 item_s = DPA_GetPtr(hItem, 0);
7824
7825 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, isW);
7826 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7827
7828 if (cmpv >= 0) break;
7829 i++;
7830 }
7831 nItem = i;
7832 }
7833 else
7834 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7835
7836 TRACE("inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7837 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7838 if (nItem == -1) goto fail;
7839 infoPtr->nItemCount++;
7840
7841 /* shift indices first so they don't get tangled */
7842 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7843
7844 /* set the item attributes */
7845 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7846 {
7847 /* full size structure expected - _WIN32IE >= 0x560 */
7848 item = *lpLVItem;
7849 }
7850 else if (lpLVItem->mask & LVIF_INDENT)
7851 {
7852 /* indent member expected - _WIN32IE >= 0x300 */
7853 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7854 }
7855 else
7856 {
7857 /* minimal structure expected */
7858 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7859 }
7860 item.iItem = nItem;
7861 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7862 {
7863 if (item.mask & LVIF_STATE)
7864 {
7865 item.stateMask |= LVIS_STATEIMAGEMASK;
7866 item.state &= ~LVIS_STATEIMAGEMASK;
7867 item.state |= INDEXTOSTATEIMAGEMASK(1);
7868 }
7869 else
7870 {
7871 item.mask |= LVIF_STATE;
7872 item.stateMask = LVIS_STATEIMAGEMASK;
7873 item.state = INDEXTOSTATEIMAGEMASK(1);
7874 }
7875 }
7876
7877 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7878
7879 /* make room for the position, if we are in the right mode */
7880 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7881 {
7882 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7883 goto undo;
7884 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7885 {
7886 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7887 goto undo;
7888 }
7889 }
7890
7891 /* send LVN_INSERTITEM notification */
7892 memset(&nmlv, 0, sizeof(NMLISTVIEW));
7893 nmlv.iItem = nItem;
7894 nmlv.lParam = lpItem->lParam;
7895 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7896 if (!IsWindow(hwndSelf))
7897 return -1;
7898
7899 /* align items (set position of each item) */
7900 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7901 {
7902 POINT pt;
7903
7904 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7905 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7906 else
7907 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7908
7909 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7910 }
7911
7912 /* now is the invalidation fun */
7913 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7914 return nItem;
7915
7916 undo:
7917 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7918 LISTVIEW_ShiftFocus(infoPtr, infoPtr->nFocusedItem, nItem, -1);
7919 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7920 infoPtr->nItemCount--;
7921 fail:
7922 DPA_DeletePtr(hdpaSubItems, 0);
7923 DPA_Destroy (hdpaSubItems);
7924 Free (lpItem);
7925 return -1;
7926 }
7927
7928 /***
7929 * DESCRIPTION:
7930 * Checks item visibility.
7931 *
7932 * PARAMETER(S):
7933 * [I] infoPtr : valid pointer to the listview structure
7934 * [I] nFirst : item index to check for
7935 *
7936 * RETURN:
7937 * Item visible : TRUE
7938 * Item invisible or failure : FALSE
7939 */
7940 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7941 {
7942 POINT Origin, Position;
7943 RECT rcItem;
7944 HDC hdc;
7945 BOOL ret;
7946
7947 TRACE("nItem=%d\n", nItem);
7948
7949 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7950
7951 LISTVIEW_GetOrigin(infoPtr, &Origin);
7952 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7953 rcItem.left = Position.x + Origin.x;
7954 rcItem.top = Position.y + Origin.y;
7955 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7956 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7957
7958 hdc = GetDC(infoPtr->hwndSelf);
7959 if (!hdc) return FALSE;
7960 ret = RectVisible(hdc, &rcItem);
7961 ReleaseDC(infoPtr->hwndSelf, hdc);
7962
7963 return ret;
7964 }
7965
7966 /***
7967 * DESCRIPTION:
7968 * Redraws a range of items.
7969 *
7970 * PARAMETER(S):
7971 * [I] infoPtr : valid pointer to the listview structure
7972 * [I] nFirst : first item
7973 * [I] nLast : last item
7974 *
7975 * RETURN:
7976 * SUCCESS : TRUE
7977 * FAILURE : FALSE
7978 */
7979 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7980 {
7981 INT i;
7982
7983 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
7984 max(nFirst, nLast) >= infoPtr->nItemCount)
7985 return FALSE;
7986
7987 for (i = nFirst; i <= nLast; i++)
7988 LISTVIEW_InvalidateItem(infoPtr, i);
7989
7990 return TRUE;
7991 }
7992
7993 /***
7994 * DESCRIPTION:
7995 * Scroll the content of a listview.
7996 *
7997 * PARAMETER(S):
7998 * [I] infoPtr : valid pointer to the listview structure
7999 * [I] dx : horizontal scroll amount in pixels
8000 * [I] dy : vertical scroll amount in pixels
8001 *
8002 * RETURN:
8003 * SUCCESS : TRUE
8004 * FAILURE : FALSE
8005 *
8006 * COMMENTS:
8007 * If the control is in report view (LV_VIEW_DETAILS) the control can
8008 * be scrolled only in line increments. "dy" will be rounded to the
8009 * nearest number of pixels that are a whole line. Ex: if line height
8010 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
8011 * is passed, then the scroll will be 0. (per MSDN 7/2002)
8012 */
8013 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8014 {
8015 switch(infoPtr->uView) {
8016 case LV_VIEW_DETAILS:
8017 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
8018 dy /= infoPtr->nItemHeight;
8019 break;
8020 case LV_VIEW_LIST:
8021 if (dy != 0) return FALSE;
8022 break;
8023 default: /* icon */
8024 break;
8025 }
8026
8027 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
8028 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
8029
8030 return TRUE;
8031 }
8032
8033 /***
8034 * DESCRIPTION:
8035 * Sets the background color.
8036 *
8037 * PARAMETER(S):
8038 * [I] infoPtr : valid pointer to the listview structure
8039 * [I] color : background color
8040 *
8041 * RETURN:
8042 * SUCCESS : TRUE
8043 * FAILURE : FALSE
8044 */
8045 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8046 {
8047 TRACE("(color=%x)\n", color);
8048
8049 #ifdef __REACTOS__
8050 infoPtr->bDefaultBkColor = FALSE;
8051 #endif
8052 if(infoPtr->clrBk != color) {
8053 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8054 infoPtr->clrBk = color;
8055 if (color == CLR_NONE)
8056 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
8057 else
8058 {
8059 infoPtr->hBkBrush = CreateSolidBrush(color);
8060 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
8061 }
8062 }
8063
8064 return TRUE;
8065 }
8066
8067 /* LISTVIEW_SetBkImage */
8068
8069 /*** Helper for {Insert,Set}ColumnT *only* */
8070 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
8071 const LVCOLUMNW *lpColumn, BOOL isW)
8072 {
8073 if (lpColumn->mask & LVCF_FMT)
8074 {
8075 /* format member is valid */
8076 lphdi->mask |= HDI_FORMAT;
8077
8078 /* set text alignment (leftmost column must be left-aligned) */
8079 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8080 lphdi->fmt |= HDF_LEFT;
8081 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
8082 lphdi->fmt |= HDF_RIGHT;
8083 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
8084 lphdi->fmt |= HDF_CENTER;
8085
8086 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
8087 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
8088
8089 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
8090 {
8091 lphdi->fmt |= HDF_IMAGE;
8092 lphdi->iImage = I_IMAGECALLBACK;
8093 }
8094
8095 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
8096 lphdi->fmt |= HDF_FIXEDWIDTH;
8097 }
8098
8099 if (lpColumn->mask & LVCF_WIDTH)
8100 {
8101 lphdi->mask |= HDI_WIDTH;
8102 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
8103 {
8104 /* make it fill the remainder of the controls width */
8105 RECT rcHeader;
8106 INT item_index;
8107
8108 for(item_index = 0; item_index < (nColumn - 1); item_index++)
8109 {
8110 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
8111 lphdi->cxy += rcHeader.right - rcHeader.left;
8112 }
8113
8114 /* retrieve the layout of the header */
8115 GetClientRect(infoPtr->hwndSelf, &rcHeader);
8116 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
8117
8118 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
8119 }
8120 else
8121 lphdi->cxy = lpColumn->cx;
8122 }
8123
8124 if (lpColumn->mask & LVCF_TEXT)
8125 {
8126 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8127 lphdi->fmt |= HDF_STRING;
8128 lphdi->pszText = lpColumn->pszText;
8129 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8130 }
8131
8132 if (lpColumn->mask & LVCF_IMAGE)
8133 {
8134 lphdi->mask |= HDI_IMAGE;
8135 lphdi->iImage = lpColumn->iImage;
8136 }
8137
8138 if (lpColumn->mask & LVCF_ORDER)
8139 {
8140 lphdi->mask |= HDI_ORDER;
8141 lphdi->iOrder = lpColumn->iOrder;
8142 }
8143 }
8144
8145
8146 /***
8147 * DESCRIPTION:
8148 * Inserts a new column.
8149 *
8150 * PARAMETER(S):
8151 * [I] infoPtr : valid pointer to the listview structure
8152 * [I] nColumn : column index
8153 * [I] lpColumn : column information
8154 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8155 *
8156 * RETURN:
8157 * SUCCESS : new column index
8158 * FAILURE : -1
8159 */
8160 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8161 const LVCOLUMNW *lpColumn, BOOL isW)
8162 {
8163 COLUMN_INFO *lpColumnInfo;
8164 INT nNewColumn;
8165 HDITEMW hdi;
8166
8167 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8168
8169 if (!lpColumn || nColumn < 0) return -1;
8170 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8171
8172 ZeroMemory(&hdi, sizeof(HDITEMW));
8173 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8174
8175 /*
8176 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8177 * (can be seen in SPY) otherwise column never gets added.
8178 */
8179 if (!(lpColumn->mask & LVCF_WIDTH)) {
8180 hdi.mask |= HDI_WIDTH;
8181 hdi.cxy = 10;
8182 }
8183
8184 /*
8185 * when the iSubItem is available Windows copies it to the header lParam. It seems
8186 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8187 */
8188 if (lpColumn->mask & LVCF_SUBITEM)
8189 {
8190 hdi.mask |= HDI_LPARAM;
8191 hdi.lParam = lpColumn->iSubItem;
8192 }
8193
8194 /* create header if not present */
8195 LISTVIEW_CreateHeader(infoPtr);
8196 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8197 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8198 {
8199 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8200 }
8201
8202 /* insert item in header control */
8203 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8204 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8205 nColumn, (LPARAM)&hdi);
8206 if (nNewColumn == -1) return -1;
8207 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8208
8209 /* create our own column info */
8210 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8211 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8212
8213 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8214 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8215 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8216 goto fail;
8217
8218 /* now we have to actually adjust the data */
8219 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8220 {
8221 SUBITEM_INFO *lpSubItem;
8222 HDPA hdpaSubItems;
8223 INT nItem, i;
8224 LVITEMW item;
8225 BOOL changed;
8226
8227 item.iSubItem = nNewColumn;
8228 item.mask = LVIF_TEXT | LVIF_IMAGE;
8229 item.iImage = I_IMAGECALLBACK;
8230 item.pszText = LPSTR_TEXTCALLBACKW;
8231
8232 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8233 {
8234 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8235 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8236 {
8237 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8238 if (lpSubItem->iSubItem >= nNewColumn)
8239 lpSubItem->iSubItem++;
8240 }
8241
8242 /* add new subitem for each item */
8243 item.iItem = nItem;
8244 set_sub_item(infoPtr, &item, isW, &changed);
8245 }
8246 }
8247
8248 /* make space for the new column */
8249 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8250 LISTVIEW_UpdateItemSize(infoPtr);
8251
8252 return nNewColumn;
8253
8254 fail:
8255 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8256 if (lpColumnInfo)
8257 {
8258 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8259 Free(lpColumnInfo);
8260 }
8261 return -1;
8262 }
8263
8264 /***
8265 * DESCRIPTION:
8266 * Sets the attributes of a header item.
8267 *
8268 * PARAMETER(S):
8269 * [I] infoPtr : valid pointer to the listview structure
8270 * [I] nColumn : column index
8271 * [I] lpColumn : column attributes
8272 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8273 *
8274 * RETURN:
8275 * SUCCESS : TRUE
8276 * FAILURE : FALSE
8277 */
8278 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8279 const LVCOLUMNW *lpColumn, BOOL isW)
8280 {
8281 HDITEMW hdi, hdiget;
8282 BOOL bResult;
8283
8284 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8285
8286 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8287
8288 ZeroMemory(&hdi, sizeof(HDITEMW));
8289 if (lpColumn->mask & LVCF_FMT)
8290 {
8291 hdi.mask |= HDI_FORMAT;
8292 hdiget.mask = HDI_FORMAT;
8293 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8294 hdi.fmt = hdiget.fmt & HDF_STRING;
8295 }
8296 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8297
8298 /* set header item attributes */
8299 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8300 if (!bResult) return FALSE;
8301
8302 if (lpColumn->mask & LVCF_FMT)
8303 {
8304 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8305 INT oldFmt = lpColumnInfo->fmt;
8306
8307 lpColumnInfo->fmt = lpColumn->fmt;
8308 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8309 {
8310 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8311 }
8312 }
8313
8314 if (lpColumn->mask & LVCF_MINWIDTH)
8315 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8316
8317 return TRUE;
8318 }
8319
8320 /***
8321 * DESCRIPTION:
8322 * Sets the column order array
8323 *
8324 * PARAMETERS:
8325 * [I] infoPtr : valid pointer to the listview structure
8326 * [I] iCount : number of elements in column order array
8327 * [I] lpiArray : pointer to column order array
8328 *
8329 * RETURN:
8330 * SUCCESS : TRUE
8331 * FAILURE : FALSE
8332 */
8333 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8334 {
8335 if (!infoPtr->hwndHeader) return FALSE;
8336 infoPtr->colRectsDirty = TRUE;
8337 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8338 }
8339
8340 /***
8341 * DESCRIPTION:
8342 * Sets the width of a column
8343 *
8344 * PARAMETERS:
8345 * [I] infoPtr : valid pointer to the listview structure
8346 * [I] nColumn : column index
8347 * [I] cx : column width
8348 *
8349 * RETURN:
8350 * SUCCESS : TRUE
8351 * FAILURE : FALSE
8352 */
8353 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8354 {
8355 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8356 INT max_cx = 0;
8357 HDITEMW hdi;
8358
8359 TRACE("(nColumn=%d, cx=%d)\n", nColumn, cx);
8360
8361 /* set column width only if in report or list mode */
8362 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8363
8364 /* take care of invalid cx values - LVSCW_AUTOSIZE_* values are negative,
8365 with _USEHEADER being the lowest */
8366 if (infoPtr->uView == LV_VIEW_DETAILS && cx < LVSCW_AUTOSIZE_USEHEADER) cx = LVSCW_AUTOSIZE;
8367 else if (infoPtr->uView == LV_VIEW_LIST && cx <= 0) return FALSE;
8368
8369 /* resize all columns if in LV_VIEW_LIST mode */
8370 if(infoPtr->uView == LV_VIEW_LIST)
8371 {
8372 infoPtr->nItemWidth = cx;
8373 LISTVIEW_InvalidateList(infoPtr);
8374 return TRUE;
8375 }
8376
8377 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8378
8379 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8380 {
8381 INT nLabelWidth;
8382 LVITEMW lvItem;
8383
8384 lvItem.mask = LVIF_TEXT;
8385 lvItem.iItem = 0;
8386 lvItem.iSubItem = nColumn;
8387 lvItem.cchTextMax = DISP_TEXT_SIZE;
8388 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8389 {
8390 lvItem.pszText = szDispText;
8391 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8392 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8393 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8394 }
8395 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8396 max_cx += infoPtr->iconSize.cx;
8397 max_cx += TRAILING_LABEL_PADDING;
8398 }
8399
8400 /* autosize based on listview items width */
8401 if(cx == LVSCW_AUTOSIZE)
8402 cx = max_cx;
8403 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8404 {
8405 /* if iCol is the last column make it fill the remainder of the controls width */
8406 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8407 {
8408 RECT rcHeader;
8409 POINT Origin;
8410
8411 LISTVIEW_GetOrigin(infoPtr, &Origin);
8412 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8413
8414 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8415 }
8416 else
8417 {
8418 /* Despite what the MS docs say, if this is not the last
8419 column, then MS resizes the column to the width of the
8420 largest text string in the column, including headers
8421 and items. This is different from LVSCW_AUTOSIZE in that
8422 LVSCW_AUTOSIZE ignores the header string length. */
8423 cx = 0;
8424
8425 /* retrieve header text */
8426 hdi.mask = HDI_TEXT|HDI_FORMAT|HDI_IMAGE|HDI_BITMAP;
8427 hdi.cchTextMax = DISP_TEXT_SIZE;
8428 hdi.pszText = szDispText;
8429 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8430 {
8431 HDC hdc = GetDC(infoPtr->hwndSelf);
8432 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8433 HIMAGELIST himl = (HIMAGELIST)SendMessageW(infoPtr->hwndHeader, HDM_GETIMAGELIST, 0, 0);
8434 INT bitmap_margin = 0;
8435 SIZE size;
8436
8437 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8438 cx = size.cx + TRAILING_HEADER_PADDING;
8439
8440 if (hdi.fmt & (HDF_IMAGE|HDF_BITMAP))
8441 bitmap_margin = SendMessageW(infoPtr->hwndHeader, HDM_GETBITMAPMARGIN, 0, 0);
8442
8443 if ((hdi.fmt & HDF_IMAGE) && himl)
8444 {
8445 INT icon_cx, icon_cy;
8446
8447 if (!ImageList_GetIconSize(himl, &icon_cx, &icon_cy))
8448 cx += icon_cx + 2*bitmap_margin;
8449 }
8450 else if (hdi.fmt & HDF_BITMAP)
8451 {
8452 BITMAP bmp;
8453
8454 GetObjectW(hdi.hbm, sizeof(BITMAP), &bmp);
8455 cx += bmp.bmWidth + 2*bitmap_margin;
8456 }
8457
8458 SelectObject(hdc, old_font);
8459 ReleaseDC(infoPtr->hwndSelf, hdc);
8460 }
8461 cx = max (cx, max_cx);
8462 }
8463 }
8464
8465 if (cx < 0) return FALSE;
8466
8467 /* call header to update the column change */
8468 hdi.mask = HDI_WIDTH;
8469 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8470 TRACE("hdi.cxy=%d\n", hdi.cxy);
8471 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8472 }
8473
8474 /***
8475 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8476 *
8477 */
8478 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8479 {
8480 HDC hdc_wnd, hdc;
8481 HBITMAP hbm_im, hbm_mask, hbm_orig;
8482 RECT rc;
8483 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8484 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8485 HIMAGELIST himl;
8486
8487 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8488 ILC_COLOR | ILC_MASK, 2, 2);
8489 hdc_wnd = GetDC(infoPtr->hwndSelf);
8490 hdc = CreateCompatibleDC(hdc_wnd);
8491 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8492 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8493 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8494
8495 rc.left = rc.top = 0;
8496 rc.right = GetSystemMetrics(SM_CXSMICON);
8497 rc.bottom = GetSystemMetrics(SM_CYSMICON);
8498
8499 hbm_orig = SelectObject(hdc, hbm_mask);
8500 FillRect(hdc, &rc, hbr_white);
8501 InflateRect(&rc, -2, -2);
8502 FillRect(hdc, &rc, hbr_black);
8503
8504 SelectObject(hdc, hbm_im);
8505 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8506 SelectObject(hdc, hbm_orig);
8507 ImageList_Add(himl, hbm_im, hbm_mask);
8508
8509 SelectObject(hdc, hbm_im);
8510 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8511 SelectObject(hdc, hbm_orig);
8512 ImageList_Add(himl, hbm_im, hbm_mask);
8513
8514 DeleteObject(hbm_mask);
8515 DeleteObject(hbm_im);
8516 DeleteDC(hdc);
8517
8518 return himl;
8519 }
8520
8521 /***
8522 * DESCRIPTION:
8523 * Sets the extended listview style.
8524 *
8525 * PARAMETERS:
8526 * [I] infoPtr : valid pointer to the listview structure
8527 * [I] dwMask : mask
8528 * [I] dwStyle : style
8529 *
8530 * RETURN:
8531 * SUCCESS : previous style
8532 * FAILURE : 0
8533 */
8534 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8535 {
8536 DWORD old_ex_style = infoPtr->dwLvExStyle;
8537
8538 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8539
8540 /* set new style */
8541 if (mask)
8542 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8543 else
8544 infoPtr->dwLvExStyle = ex_style;
8545
8546 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8547 {
8548 HIMAGELIST himl = 0;
8549 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8550 {
8551 LVITEMW item;
8552 item.mask = LVIF_STATE;
8553 item.stateMask = LVIS_STATEIMAGEMASK;
8554 item.state = INDEXTOSTATEIMAGEMASK(1);
8555 LISTVIEW_SetItemState(infoPtr, -1, &item);
8556
8557 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8558 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8559 ImageList_Destroy(infoPtr->himlState);
8560 }
8561 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8562 /* checkbox list replaces previous custom list or... */
8563 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8564 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8565 /* ...previous was checkbox list */
8566 (old_ex_style & LVS_EX_CHECKBOXES))
8567 ImageList_Destroy(himl);
8568 }
8569
8570 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8571 {
8572 DWORD style;
8573
8574 /* if not already created */
8575 LISTVIEW_CreateHeader(infoPtr);
8576
8577 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8578 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8579 style |= HDS_DRAGDROP;
8580 else
8581 style &= ~HDS_DRAGDROP;
8582 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8583 }
8584
8585 /* GRIDLINES adds decoration at top so changes sizes */
8586 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8587 {
8588 LISTVIEW_CreateHeader(infoPtr);
8589 LISTVIEW_UpdateSize(infoPtr);
8590 }
8591
8592 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8593 {
8594 LISTVIEW_CreateHeader(infoPtr);
8595 }
8596
8597 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8598 {
8599 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8600 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8601 }
8602
8603 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8604 {
8605 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8606 LISTVIEW_CreateHeader(infoPtr);
8607 else
8608 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8609 LISTVIEW_UpdateSize(infoPtr);
8610 LISTVIEW_UpdateScroll(infoPtr);
8611 }
8612
8613 LISTVIEW_InvalidateList(infoPtr);
8614 return old_ex_style;
8615 }
8616
8617 /***
8618 * DESCRIPTION:
8619 * Sets the new hot cursor used during hot tracking and hover selection.
8620 *
8621 * PARAMETER(S):
8622 * [I] infoPtr : valid pointer to the listview structure
8623 * [I] hCursor : the new hot cursor handle
8624 *
8625 * RETURN:
8626 * Returns the previous hot cursor
8627 */
8628 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8629 {
8630 HCURSOR oldCursor = infoPtr->hHotCursor;
8631
8632 infoPtr->hHotCursor = hCursor;
8633
8634 return oldCursor;
8635 }
8636
8637
8638 /***
8639 * DESCRIPTION:
8640 * Sets the hot item index.
8641 *
8642 * PARAMETERS:
8643 * [I] infoPtr : valid pointer to the listview structure
8644 * [I] iIndex : index
8645 *
8646 * RETURN:
8647 * SUCCESS : previous hot item index
8648 * FAILURE : -1 (no hot item)
8649 */
8650 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8651 {
8652 INT iOldIndex = infoPtr->nHotItem;
8653
8654 infoPtr->nHotItem = iIndex;
8655
8656 return iOldIndex;
8657 }
8658
8659
8660 /***
8661 * DESCRIPTION:
8662 * Sets the amount of time the cursor must hover over an item before it is selected.
8663 *
8664 * PARAMETER(S):
8665 * [I] infoPtr : valid pointer to the listview structure
8666 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8667 *
8668 * RETURN:
8669 * Returns the previous hover time
8670 */
8671 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8672 {
8673 DWORD oldHoverTime = infoPtr->dwHoverTime;
8674
8675 infoPtr->dwHoverTime = dwHoverTime;
8676
8677 return oldHoverTime;
8678 }
8679
8680 /***
8681 * DESCRIPTION:
8682 * Sets spacing for icons of LVS_ICON style.
8683 *
8684 * PARAMETER(S):
8685 * [I] infoPtr : valid pointer to the listview structure
8686 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8687 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8688 *
8689 * RETURN:
8690 * MAKELONG(oldcx, oldcy)
8691 */
8692 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8693 {
8694 INT iconWidth = 0, iconHeight = 0;
8695 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8696
8697 TRACE("requested=(%d,%d)\n", cx, cy);
8698
8699 /* set to defaults, if instructed to */
8700 if (cx == -1 && cy == -1)
8701 {
8702 infoPtr->autoSpacing = TRUE;
8703 if (infoPtr->himlNormal)
8704 ImageList_GetIconSize(infoPtr->himlNormal, &iconWidth, &iconHeight);
8705 cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON) + iconWidth;
8706 cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON) + iconHeight;
8707 }
8708 else
8709 infoPtr->autoSpacing = FALSE;
8710
8711 /* if 0 then keep width */
8712 if (cx != 0)
8713 infoPtr->iconSpacing.cx = cx;
8714
8715 /* if 0 then keep height */
8716 if (cy != 0)
8717 infoPtr->iconSpacing.cy = cy;
8718
8719 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8720 LOWORD(oldspacing), HIWORD(oldspacing), infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy,
8721 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8722 infoPtr->ntmHeight);
8723
8724 /* these depend on the iconSpacing */
8725 LISTVIEW_UpdateItemSize(infoPtr);
8726
8727 return oldspacing;
8728 }
8729
8730 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL is_small)
8731 {
8732 INT cx, cy;
8733
8734 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8735 {
8736 size->cx = cx;
8737 size->cy = cy;
8738 }
8739 else
8740 {
8741 size->cx = GetSystemMetrics(is_small ? SM_CXSMICON : SM_CXICON);
8742 size->cy = GetSystemMetrics(is_small ? SM_CYSMICON : SM_CYICON);
8743 }
8744 }
8745
8746 /***
8747 * DESCRIPTION:
8748 * Sets image lists.
8749 *
8750 * PARAMETER(S):
8751 * [I] infoPtr : valid pointer to the listview structure
8752 * [I] nType : image list type
8753 * [I] himl : image list handle
8754 *
8755 * RETURN:
8756 * SUCCESS : old image list
8757 * FAILURE : NULL
8758 */
8759 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8760 {
8761 INT oldHeight = infoPtr->nItemHeight;
8762 HIMAGELIST himlOld = 0;
8763
8764 TRACE("(nType=%d, himl=%p)\n", nType, himl);
8765
8766 switch (nType)
8767 {
8768 case LVSIL_NORMAL:
8769 himlOld = infoPtr->himlNormal;
8770 infoPtr->himlNormal = himl;
8771 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8772 if (infoPtr->autoSpacing)
8773 LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
8774 break;
8775
8776 case LVSIL_SMALL:
8777 himlOld = infoPtr->himlSmall;
8778 infoPtr->himlSmall = himl;
8779 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8780 if (infoPtr->hwndHeader)
8781 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)himl);
8782 break;
8783
8784 case LVSIL_STATE:
8785 himlOld = infoPtr->himlState;
8786 infoPtr->himlState = himl;
8787 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8788 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8789 break;
8790
8791 default:
8792 ERR("Unknown icon type=%d\n", nType);
8793 return NULL;
8794 }
8795
8796 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8797 if (infoPtr->nItemHeight != oldHeight)
8798 LISTVIEW_UpdateScroll(infoPtr);
8799
8800 return himlOld;
8801 }
8802
8803 /***
8804 * DESCRIPTION:
8805 * Preallocates memory (does *not* set the actual count of items !)
8806 *
8807 * PARAMETER(S):
8808 * [I] infoPtr : valid pointer to the listview structure
8809 * [I] nItems : item count (projected number of items to allocate)
8810 * [I] dwFlags : update flags
8811 *
8812 * RETURN:
8813 * SUCCESS : TRUE
8814 * FAILURE : FALSE
8815 */
8816 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8817 {
8818 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8819
8820 if (infoPtr->dwStyle & LVS_OWNERDATA)
8821 {
8822 INT nOldCount = infoPtr->nItemCount;
8823
8824 if (nItems < nOldCount)
8825 {
8826 RANGE range = { nItems, nOldCount };
8827 ranges_del(infoPtr->selectionRanges, range);
8828 if (infoPtr->nFocusedItem >= nItems)
8829 {
8830 LISTVIEW_SetItemFocus(infoPtr, -1);
8831 SetRectEmpty(&infoPtr->rcFocus);
8832 }
8833 }
8834
8835 infoPtr->nItemCount = nItems;
8836 LISTVIEW_UpdateScroll(infoPtr);
8837
8838 /* the flags are valid only in ownerdata report and list modes */
8839 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8840
8841 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8842 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8843
8844 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8845 LISTVIEW_InvalidateList(infoPtr);
8846 else
8847 {
8848 INT nFrom, nTo;
8849 POINT Origin;
8850 RECT rcErase;
8851
8852 LISTVIEW_GetOrigin(infoPtr, &Origin);
8853 nFrom = min(nOldCount, nItems);
8854 nTo = max(nOldCount, nItems);
8855
8856 if (infoPtr->uView == LV_VIEW_DETAILS)
8857 {
8858 rcErase.left = 0;
8859 rcErase.top = nFrom * infoPtr->nItemHeight;
8860 rcErase.right = infoPtr->nItemWidth;
8861 rcErase.bottom = nTo * infoPtr->nItemHeight;
8862 OffsetRect(&rcErase, Origin.x, Origin.y);
8863 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8864 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8865 }
8866 else /* LV_VIEW_LIST */
8867 {
8868 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8869
8870 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8871 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8872 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8873 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8874 OffsetRect(&rcErase, Origin.x, Origin.y);
8875 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8876 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8877
8878 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8879 rcErase.top = 0;
8880 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8881 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8882 OffsetRect(&rcErase, Origin.x, Origin.y);
8883 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8884 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8885 }
8886 }
8887 }
8888 else
8889 {
8890 /* According to MSDN for non-LVS_OWNERDATA this is just
8891 * a performance issue. The control allocates its internal
8892 * data structures for the number of items specified. It
8893 * cuts down on the number of memory allocations. Therefore
8894 * we will just issue a WARN here
8895 */
8896 WARN("for non-ownerdata performance option not implemented.\n");
8897 }
8898
8899 return TRUE;
8900 }
8901
8902 /***
8903 * DESCRIPTION:
8904 * Sets the position of an item.
8905 *
8906 * PARAMETER(S):
8907 * [I] infoPtr : valid pointer to the listview structure
8908 * [I] nItem : item index
8909 * [I] pt : coordinate
8910 *
8911 * RETURN:
8912 * SUCCESS : TRUE
8913 * FAILURE : FALSE
8914 */
8915 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8916 {
8917 POINT Origin, Pt;
8918
8919 TRACE("(nItem=%d, pt=%s)\n", nItem, wine_dbgstr_point(pt));
8920
8921 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8922 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8923
8924 Pt = *pt;
8925 LISTVIEW_GetOrigin(infoPtr, &Origin);
8926
8927 /* This point value seems to be an undocumented feature.
8928 * The best guess is that it means either at the origin,
8929 * or at true beginning of the list. I will assume the origin. */
8930 if ((Pt.x == -1) && (Pt.y == -1))
8931 Pt = Origin;
8932
8933 if (infoPtr->uView == LV_VIEW_ICON)
8934 {
8935 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8936 Pt.y -= ICON_TOP_PADDING;
8937 }
8938 Pt.x -= Origin.x;
8939 Pt.y -= Origin.y;
8940
8941 infoPtr->bAutoarrange = FALSE;
8942
8943 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8944 }
8945
8946 /***
8947 * DESCRIPTION:
8948 * Sets the state of one or many items.
8949 *
8950 * PARAMETER(S):
8951 * [I] infoPtr : valid pointer to the listview structure
8952 * [I] nItem : item index
8953 * [I] item : item or subitem info
8954 *
8955 * RETURN:
8956 * SUCCESS : TRUE
8957 * FAILURE : FALSE
8958 */
8959 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8960 {
8961 BOOL ret = TRUE;
8962 LVITEMW lvItem;
8963
8964 if (!item) return FALSE;
8965
8966 lvItem.iItem = nItem;
8967 lvItem.iSubItem = 0;
8968 lvItem.mask = LVIF_STATE;
8969 lvItem.state = item->state;
8970 lvItem.stateMask = item->stateMask;
8971 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8972
8973 if (nItem == -1)
8974 {
8975 UINT oldstate = 0;
8976 BOOL notify;
8977
8978 /* special case optimization for recurring attempt to deselect all */
8979 if (lvItem.state == 0 && lvItem.stateMask == LVIS_SELECTED && !LISTVIEW_GetSelectedCount(infoPtr))
8980 return TRUE;
8981
8982 /* select all isn't allowed in LVS_SINGLESEL */
8983 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8984 return FALSE;
8985
8986 /* focus all isn't allowed */
8987 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8988
8989 notify = infoPtr->bDoChangeNotify;
8990 if (infoPtr->dwStyle & LVS_OWNERDATA)
8991 {
8992 infoPtr->bDoChangeNotify = FALSE;
8993 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
8994 oldstate |= LVIS_SELECTED;
8995 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
8996 }
8997
8998 /* apply to all items */
8999 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
9000 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
9001
9002 if (infoPtr->dwStyle & LVS_OWNERDATA)
9003 {
9004 NMLISTVIEW nmlv;
9005
9006 infoPtr->bDoChangeNotify = notify;
9007
9008 nmlv.iItem = -1;
9009 nmlv.iSubItem = 0;
9010 nmlv.uNewState = lvItem.state & lvItem.stateMask;
9011 nmlv.uOldState = oldstate & lvItem.stateMask;
9012 nmlv.uChanged = LVIF_STATE;
9013 nmlv.ptAction.x = nmlv.ptAction.y = 0;
9014 nmlv.lParam = 0;
9015
9016 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
9017 }
9018 }
9019 else
9020 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
9021
9022 return ret;
9023 }
9024
9025 /***
9026 * DESCRIPTION:
9027 * Sets the text of an item or subitem.
9028 *
9029 * PARAMETER(S):
9030 * [I] hwnd : window handle
9031 * [I] nItem : item index
9032 * [I] lpLVItem : item or subitem info
9033 * [I] isW : TRUE if input is Unicode
9034 *
9035 * RETURN:
9036 * SUCCESS : TRUE
9037 * FAILURE : FALSE
9038 */
9039 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
9040 {
9041 LVITEMW lvItem;
9042
9043 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9044 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9045
9046 lvItem.iItem = nItem;
9047 lvItem.iSubItem = lpLVItem->iSubItem;
9048 lvItem.mask = LVIF_TEXT;
9049 lvItem.pszText = lpLVItem->pszText;
9050 lvItem.cchTextMax = lpLVItem->cchTextMax;
9051
9052 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
9053
9054 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
9055 }
9056
9057 /***
9058 * DESCRIPTION:
9059 * Set item index that marks the start of a multiple selection.
9060 *
9061 * PARAMETER(S):
9062 * [I] infoPtr : valid pointer to the listview structure
9063 * [I] nIndex : index
9064 *
9065 * RETURN:
9066 * Index number or -1 if there is no selection mark.
9067 */
9068 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
9069 {
9070 INT nOldIndex = infoPtr->nSelectionMark;
9071
9072 TRACE("(nIndex=%d)\n", nIndex);
9073
9074 infoPtr->nSelectionMark = nIndex;
9075
9076 return nOldIndex;
9077 }
9078
9079 /***
9080 * DESCRIPTION:
9081 * Sets the text background color.
9082 *
9083 * PARAMETER(S):
9084 * [I] infoPtr : valid pointer to the listview structure
9085 * [I] color : text background color
9086 *
9087 * RETURN:
9088 * SUCCESS : TRUE
9089 * FAILURE : FALSE
9090 */
9091 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
9092 {
9093 TRACE("(color=%x)\n", color);
9094
9095 infoPtr->clrTextBk = color;
9096 return TRUE;
9097 }
9098
9099 /***
9100 * DESCRIPTION:
9101 * Sets the text foreground color.
9102 *
9103 * PARAMETER(S):
9104 * [I] infoPtr : valid pointer to the listview structure
9105 * [I] color : text color
9106 *
9107 * RETURN:
9108 * SUCCESS : TRUE
9109 * FAILURE : FALSE
9110 */
9111 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
9112 {
9113 TRACE("(color=%x)\n", color);
9114
9115 infoPtr->clrText = color;
9116 return TRUE;
9117 }
9118
9119 /***
9120 * DESCRIPTION:
9121 * Sets new ToolTip window to ListView control.
9122 *
9123 * PARAMETER(S):
9124 * [I] infoPtr : valid pointer to the listview structure
9125 * [I] hwndNewToolTip : handle to new ToolTip
9126 *
9127 * RETURN:
9128 * old tool tip
9129 */
9130 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
9131 {
9132 HWND hwndOldToolTip = infoPtr->hwndToolTip;
9133 infoPtr->hwndToolTip = hwndNewToolTip;
9134 return hwndOldToolTip;
9135 }
9136
9137 /*
9138 * DESCRIPTION:
9139 * sets the Unicode character format flag for the control
9140 * PARAMETER(S):
9141 * [I] infoPtr :valid pointer to the listview structure
9142 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
9143 *
9144 * RETURN:
9145 * Old Unicode Format
9146 */
9147 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9148 {
9149 SHORT rc = infoPtr->notifyFormat;
9150 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9151 return rc == NFR_UNICODE;
9152 }
9153
9154 /*
9155 * DESCRIPTION:
9156 * sets the control view mode
9157 * PARAMETER(S):
9158 * [I] infoPtr :valid pointer to the listview structure
9159 * [I] nView :new view mode value
9160 *
9161 * RETURN:
9162 * SUCCESS: 1
9163 * FAILURE: -1
9164 */
9165 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9166 {
9167 HIMAGELIST himl;
9168
9169 if (infoPtr->uView == nView) return 1;
9170
9171 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9172 if (nView == LV_VIEW_TILE)
9173 {
9174 FIXME("View LV_VIEW_TILE unimplemented\n");
9175 return -1;
9176 }
9177
9178 infoPtr->uView = nView;
9179
9180 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9181 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9182
9183 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9184 SetRectEmpty(&infoPtr->rcFocus);
9185
9186 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9187 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9188
9189 switch (nView)
9190 {
9191 case LV_VIEW_ICON:
9192 case LV_VIEW_SMALLICON:
9193 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9194 break;
9195 case LV_VIEW_DETAILS:
9196 {
9197 HDLAYOUT hl;
9198 WINDOWPOS wp;
9199
9200 LISTVIEW_CreateHeader( infoPtr );
9201
9202 hl.prc = &infoPtr->rcList;
9203 hl.pwpos = &wp;
9204 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9205 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9206 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9207 break;
9208 }
9209 case LV_VIEW_LIST:
9210 break;
9211 }
9212
9213 LISTVIEW_UpdateItemSize(infoPtr);
9214 LISTVIEW_UpdateSize(infoPtr);
9215 LISTVIEW_UpdateScroll(infoPtr);
9216 LISTVIEW_InvalidateList(infoPtr);
9217
9218 TRACE("nView=%d\n", nView);
9219
9220 return 1;
9221 }
9222
9223 /* LISTVIEW_SetWorkAreas */
9224
9225 /***
9226 * DESCRIPTION:
9227 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9228 *
9229 * PARAMETER(S):
9230 * [I] first : pointer to first ITEM_INFO to compare
9231 * [I] second : pointer to second ITEM_INFO to compare
9232 * [I] lParam : HWND of control
9233 *
9234 * RETURN:
9235 * if first comes before second : negative
9236 * if first comes after second : positive
9237 * if first and second are equivalent : zero
9238 */
9239 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9240 {
9241 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9242 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9243 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9244
9245 /* Forward the call to the client defined callback */
9246 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9247 }
9248
9249 /***
9250 * DESCRIPTION:
9251 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9252 *
9253 * PARAMETER(S):
9254 * [I] first : pointer to first ITEM_INFO to compare
9255 * [I] second : pointer to second ITEM_INFO to compare
9256 * [I] lParam : HWND of control
9257 *
9258 * RETURN:
9259 * if first comes before second : negative
9260 * if first comes after second : positive
9261 * if first and second are equivalent : zero
9262 */
9263 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9264 {
9265 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9266 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9267 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9268
9269 /* Forward the call to the client defined callback */
9270 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9271 }
9272
9273 /***
9274 * DESCRIPTION:
9275 * Sorts the listview items.
9276 *
9277 * PARAMETER(S):
9278 * [I] infoPtr : valid pointer to the listview structure
9279 * [I] pfnCompare : application-defined value
9280 * [I] lParamSort : pointer to comparison callback
9281 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9282 *
9283 * RETURN:
9284 * SUCCESS : TRUE
9285 * FAILURE : FALSE
9286 */
9287 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9288 LPARAM lParamSort, BOOL IsEx)
9289 {
9290 HDPA hdpaSubItems;
9291 ITEM_INFO *lpItem;
9292 LPVOID selectionMarkItem = NULL;
9293 LPVOID focusedItem = NULL;
9294 int i;
9295
9296 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9297
9298 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9299
9300 if (!pfnCompare) return FALSE;
9301 if (!infoPtr->hdpaItems) return FALSE;
9302
9303 /* if there are 0 or 1 items, there is no need to sort */
9304 if (infoPtr->nItemCount < 2) return TRUE;
9305
9306 /* clear selection */
9307 ranges_clear(infoPtr->selectionRanges);
9308
9309 /* save selection mark and focused item */
9310 if (infoPtr->nSelectionMark >= 0)
9311 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9312 if (infoPtr->nFocusedItem >= 0)
9313 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9314
9315 infoPtr->pfnCompare = pfnCompare;
9316 infoPtr->lParamSort = lParamSort;
9317 if (IsEx)
9318 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9319 else
9320 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9321
9322 /* restore selection ranges */
9323 for (i=0; i < infoPtr->nItemCount; i++)
9324 {
9325 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9326 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9327
9328 if (lpItem->state & LVIS_SELECTED)
9329 ranges_additem(infoPtr->selectionRanges, i);
9330 }
9331 /* restore selection mark and focused item */
9332 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9333 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9334
9335 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9336
9337 /* refresh the display */
9338 LISTVIEW_InvalidateList(infoPtr);
9339 return TRUE;
9340 }
9341
9342 /***
9343 * DESCRIPTION:
9344 * Update theme handle after a theme change.
9345 *
9346 * PARAMETER(S):
9347 * [I] infoPtr : valid pointer to the listview structure
9348 *
9349 * RETURN:
9350 * SUCCESS : 0
9351 * FAILURE : something else
9352 */
9353 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9354 {
9355 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9356 CloseThemeData(theme);
9357 OpenThemeData(infoPtr->hwndSelf, themeClass);
9358 return 0;
9359 }
9360
9361 /***
9362 * DESCRIPTION:
9363 * Updates an items or rearranges the listview control.
9364 *
9365 * PARAMETER(S):
9366 * [I] infoPtr : valid pointer to the listview structure
9367 * [I] nItem : item index
9368 *
9369 * RETURN:
9370 * SUCCESS : TRUE
9371 * FAILURE : FALSE
9372 */
9373 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9374 {
9375 TRACE("(nItem=%d)\n", nItem);
9376
9377 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9378
9379 /* rearrange with default alignment style */
9380 if (is_autoarrange(infoPtr))
9381 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9382 else
9383 LISTVIEW_InvalidateItem(infoPtr, nItem);
9384
9385 return TRUE;
9386 }
9387
9388 /***
9389 * DESCRIPTION:
9390 * Draw the track line at the place defined in the infoPtr structure.
9391 * The line is drawn with a XOR pen so drawing the line for the second time
9392 * in the same place erases the line.
9393 *
9394 * PARAMETER(S):
9395 * [I] infoPtr : valid pointer to the listview structure
9396 *
9397 * RETURN:
9398 * SUCCESS : TRUE
9399 * FAILURE : FALSE
9400 */
9401 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9402 {
9403 HDC hdc;
9404
9405 if (infoPtr->xTrackLine == -1)
9406 return FALSE;
9407
9408 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9409 return FALSE;
9410 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9411 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9412 ReleaseDC(infoPtr->hwndSelf, hdc);
9413 return TRUE;
9414 }
9415
9416 /***
9417 * DESCRIPTION:
9418 * Called when an edit control should be displayed. This function is called after
9419 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9420 *
9421 * PARAMETER(S):
9422 * [I] hwnd : Handle to the listview
9423 * [I] uMsg : WM_TIMER (ignored)
9424 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9425 * [I] dwTimer : The elapsed time (ignored)
9426 *
9427 * RETURN:
9428 * None.
9429 */
9430 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9431 {
9432 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9433 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9434
9435 KillTimer(hwnd, idEvent);
9436 editItem->fEnabled = FALSE;
9437 /* check if the item is still selected */
9438 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9439 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9440 }
9441
9442 /***
9443 * DESCRIPTION:
9444 * Creates the listview control - the WM_NCCREATE phase.
9445 *
9446 * PARAMETER(S):
9447 * [I] hwnd : window handle
9448 * [I] lpcs : the create parameters
9449 *
9450 * RETURN:
9451 * Success: TRUE
9452 * Failure: FALSE
9453 */
9454 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
9455 {
9456 LISTVIEW_INFO *infoPtr;
9457 LOGFONTW logFont;
9458
9459 TRACE("(lpcs=%p)\n", lpcs);
9460
9461 /* initialize info pointer */
9462 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9463 if (!infoPtr) return FALSE;
9464
9465 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9466
9467 infoPtr->hwndSelf = hwnd;
9468 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9469 map_style_view(infoPtr);
9470 /* determine the type of structures to use */
9471 infoPtr->hwndNotify = lpcs->hwndParent;
9472 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9473
9474 /* initialize color information */
9475 infoPtr->clrBk = CLR_NONE;
9476 infoPtr->clrText = CLR_DEFAULT;
9477 infoPtr->clrTextBk = CLR_DEFAULT;
9478 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9479 #ifdef __REACTOS__
9480 infoPtr->bDefaultBkColor = TRUE;
9481 #endif
9482
9483 /* set default values */
9484 infoPtr->nFocusedItem = -1;
9485 infoPtr->nSelectionMark = -1;
9486 infoPtr->nHotItem = -1;
9487 infoPtr->bRedraw = TRUE;
9488 infoPtr->bNoItemMetrics = TRUE;
9489 infoPtr->bDoChangeNotify = TRUE;
9490 infoPtr->autoSpacing = TRUE;
9491 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON);
9492 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON);
9493 infoPtr->nEditLabelItem = -1;
9494 infoPtr->nLButtonDownItem = -1;
9495 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9496 infoPtr->cWheelRemainder = 0;
9497 infoPtr->nMeasureItemHeight = 0;
9498 infoPtr->xTrackLine = -1; /* no track line */
9499 infoPtr->itemEdit.fEnabled = FALSE;
9500 infoPtr->iVersion = COMCTL32_VERSION;
9501 infoPtr->colRectsDirty = FALSE;
9502
9503 /* get default font (icon title) */
9504 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9505 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9506 infoPtr->hFont = infoPtr->hDefaultFont;
9507 LISTVIEW_SaveTextMetrics(infoPtr);
9508
9509 /* allocate memory for the data structure */
9510 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9511 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9512 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9513 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9514 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9515 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9516 return TRUE;
9517
9518 fail:
9519 DestroyWindow(infoPtr->hwndHeader);
9520 ranges_destroy(infoPtr->selectionRanges);
9521 DPA_Destroy(infoPtr->hdpaItems);
9522 DPA_Destroy(infoPtr->hdpaItemIds);
9523 DPA_Destroy(infoPtr->hdpaPosX);
9524 DPA_Destroy(infoPtr->hdpaPosY);
9525 DPA_Destroy(infoPtr->hdpaColumns);
9526 Free(infoPtr);
9527 return FALSE;
9528 }
9529
9530 /***
9531 * DESCRIPTION:
9532 * Creates the listview control - the WM_CREATE phase. Most of the data is
9533 * already set up in LISTVIEW_NCCreate
9534 *
9535 * PARAMETER(S):
9536 * [I] hwnd : window handle
9537 * [I] lpcs : the create parameters
9538 *
9539 * RETURN:
9540 * Success: 0
9541 * Failure: -1
9542 */
9543 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9544 {
9545 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9546
9547 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9548
9549 infoPtr->dwStyle = lpcs->style;
9550 map_style_view(infoPtr);
9551
9552 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9553 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9554 /* on error defaulting to ANSI notifications */
9555 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9556 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9557
9558 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9559 {
9560 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9561 }
9562 else
9563 infoPtr->hwndHeader = 0;
9564
9565 /* init item size to avoid division by 0 */
9566 LISTVIEW_UpdateItemSize (infoPtr);
9567 LISTVIEW_UpdateSize (infoPtr);
9568
9569 if (infoPtr->uView == LV_VIEW_DETAILS)
9570 {
9571 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9572 {
9573 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9574 }
9575 LISTVIEW_UpdateScroll(infoPtr);
9576 /* send WM_MEASUREITEM notification */
9577 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9578 }
9579
9580 OpenThemeData(hwnd, themeClass);
9581
9582 /* initialize the icon sizes */
9583 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9584 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9585 return 0;
9586 }
9587
9588 /***
9589 * DESCRIPTION:
9590 * Destroys the listview control.
9591 *
9592 * PARAMETER(S):
9593 * [I] infoPtr : valid pointer to the listview structure
9594 *
9595 * RETURN:
9596 * Success: 0
9597 * Failure: -1
9598 */
9599 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9600 {
9601 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9602 CloseThemeData(theme);
9603
9604 /* delete all items */
9605 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9606
9607 return 0;
9608 }
9609
9610 /***
9611 * DESCRIPTION:
9612 * Enables the listview control.
9613 *
9614 * PARAMETER(S):
9615 * [I] infoPtr : valid pointer to the listview structure
9616 * [I] bEnable : specifies whether to enable or disable the window
9617 *
9618 * RETURN:
9619 * SUCCESS : TRUE
9620 * FAILURE : FALSE
9621 */
9622 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9623 {
9624 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9625 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9626 return TRUE;
9627 }
9628
9629 /***
9630 * DESCRIPTION:
9631 * Erases the background of the listview control.
9632 *
9633 * PARAMETER(S):
9634 * [I] infoPtr : valid pointer to the listview structure
9635 * [I] hdc : device context handle
9636 *
9637 * RETURN:
9638 * SUCCESS : TRUE
9639 * FAILURE : FALSE
9640 */
9641 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9642 {
9643 RECT rc;
9644
9645 TRACE("(hdc=%p)\n", hdc);
9646
9647 if (!GetClipBox(hdc, &rc)) return FALSE;
9648
9649 if (infoPtr->clrBk == CLR_NONE)
9650 {
9651 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9652 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9653 (WPARAM)hdc, PRF_ERASEBKGND);
9654 else
9655 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9656 }
9657
9658 /* for double buffered controls we need to do this during refresh */
9659 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9660
9661 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9662 }
9663
9664
9665 /***
9666 * DESCRIPTION:
9667 * Helper function for LISTVIEW_[HV]Scroll *only*.
9668 * Performs vertical/horizontal scrolling by a give amount.
9669 *
9670 * PARAMETER(S):
9671 * [I] infoPtr : valid pointer to the listview structure
9672 * [I] dx : amount of horizontal scroll
9673 * [I] dy : amount of vertical scroll
9674 */
9675 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9676 {
9677 /* now we can scroll the list */
9678 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9679 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9680 /* if we have focus, adjust rect */
9681 OffsetRect(&infoPtr->rcFocus, dx, dy);
9682 UpdateWindow(infoPtr->hwndSelf);
9683 }
9684
9685 /***
9686 * DESCRIPTION:
9687 * Performs vertical scrolling.
9688 *
9689 * PARAMETER(S):
9690 * [I] infoPtr : valid pointer to the listview structure
9691 * [I] nScrollCode : scroll code
9692 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9693 * [I] hScrollWnd : scrollbar control window handle
9694 *
9695 * RETURN:
9696 * Zero
9697 *
9698 * NOTES:
9699 * SB_LINEUP/SB_LINEDOWN:
9700 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9701 * for LVS_REPORT is 1 line
9702 * for LVS_LIST cannot occur
9703 *
9704 */
9705 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9706 INT nScrollDiff)
9707 {
9708 INT nOldScrollPos, nNewScrollPos;
9709 SCROLLINFO scrollInfo;
9710 BOOL is_an_icon;
9711
9712 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9713 debugscrollcode(nScrollCode), nScrollDiff);
9714
9715 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9716
9717 scrollInfo.cbSize = sizeof(SCROLLINFO);
9718 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9719
9720 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9721
9722 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9723
9724 nOldScrollPos = scrollInfo.nPos;
9725 switch (nScrollCode)
9726 {
9727 case SB_INTERNAL:
9728 break;
9729
9730 case SB_LINEUP:
9731 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9732 break;
9733
9734 case SB_LINEDOWN:
9735 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9736 break;
9737
9738 case SB_PAGEUP:
9739 nScrollDiff = -scrollInfo.nPage;
9740 break;
9741
9742 case SB_PAGEDOWN:
9743 nScrollDiff = scrollInfo.nPage;
9744 break;
9745
9746 case SB_THUMBPOSITION:
9747 case SB_THUMBTRACK:
9748 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9749 break;
9750
9751 default:
9752 nScrollDiff = 0;
9753 }
9754
9755 /* quit right away if pos isn't changing */
9756 if (nScrollDiff == 0) return 0;
9757
9758 /* calculate new position, and handle overflows */
9759 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9760 if (nScrollDiff > 0) {
9761 if (nNewScrollPos < nOldScrollPos ||
9762 nNewScrollPos > scrollInfo.nMax)
9763 nNewScrollPos = scrollInfo.nMax;
9764 } else {
9765 if (nNewScrollPos > nOldScrollPos ||
9766 nNewScrollPos < scrollInfo.nMin)
9767 nNewScrollPos = scrollInfo.nMin;
9768 }
9769
9770 /* set the new position, and reread in case it changed */
9771 scrollInfo.fMask = SIF_POS;
9772 scrollInfo.nPos = nNewScrollPos;
9773 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9774
9775 /* carry on only if it really changed */
9776 if (nNewScrollPos == nOldScrollPos) return 0;
9777
9778 /* now adjust to client coordinates */
9779 nScrollDiff = nOldScrollPos - nNewScrollPos;
9780 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9781
9782 /* and scroll the window */
9783 scroll_list(infoPtr, 0, nScrollDiff);
9784
9785 return 0;
9786 }
9787
9788 /***
9789 * DESCRIPTION:
9790 * Performs horizontal scrolling.
9791 *
9792 * PARAMETER(S):
9793 * [I] infoPtr : valid pointer to the listview structure
9794 * [I] nScrollCode : scroll code
9795 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9796 * [I] hScrollWnd : scrollbar control window handle
9797 *
9798 * RETURN:
9799 * Zero
9800 *
9801 * NOTES:
9802 * SB_LINELEFT/SB_LINERIGHT:
9803 * for LVS_ICON, LVS_SMALLICON 1 pixel
9804 * for LVS_REPORT is 1 pixel
9805 * for LVS_LIST is 1 column --> which is a 1 because the
9806 * scroll is based on columns not pixels
9807 *
9808 */
9809 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9810 INT nScrollDiff)
9811 {
9812 INT nOldScrollPos, nNewScrollPos;
9813 SCROLLINFO scrollInfo;
9814 BOOL is_an_icon;
9815
9816 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9817 debugscrollcode(nScrollCode), nScrollDiff);
9818
9819 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9820
9821 scrollInfo.cbSize = sizeof(SCROLLINFO);
9822 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9823
9824 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9825
9826 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9827
9828 nOldScrollPos = scrollInfo.nPos;
9829
9830 switch (nScrollCode)
9831 {
9832 case SB_INTERNAL:
9833 break;
9834
9835 case SB_LINELEFT:
9836 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9837 break;
9838
9839 case SB_LINERIGHT:
9840 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9841 break;
9842
9843 case SB_PAGELEFT:
9844 nScrollDiff = -scrollInfo.nPage;
9845 break;
9846
9847 case SB_PAGERIGHT:
9848 nScrollDiff = scrollInfo.nPage;
9849 break;
9850
9851 case SB_THUMBPOSITION:
9852 case SB_THUMBTRACK:
9853 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9854 break;
9855
9856 default:
9857 nScrollDiff = 0;
9858 }
9859
9860 /* quit right away if pos isn't changing */
9861 if (nScrollDiff == 0) return 0;
9862
9863 /* calculate new position, and handle overflows */
9864 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9865 if (nScrollDiff > 0) {
9866 if (nNewScrollPos < nOldScrollPos ||
9867 nNewScrollPos > scrollInfo.nMax)
9868 nNewScrollPos = scrollInfo.nMax;
9869 } else {
9870 if (nNewScrollPos > nOldScrollPos ||
9871 nNewScrollPos < scrollInfo.nMin)
9872 nNewScrollPos = scrollInfo.nMin;
9873 }
9874
9875 /* set the new position, and reread in case it changed */
9876 scrollInfo.fMask = SIF_POS;
9877 scrollInfo.nPos = nNewScrollPos;
9878 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9879
9880 /* carry on only if it really changed */
9881 if (nNewScrollPos == nOldScrollPos) return 0;
9882
9883 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9884
9885 /* now adjust to client coordinates */
9886 nScrollDiff = nOldScrollPos - nNewScrollPos;
9887 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9888
9889 /* and scroll the window */
9890 scroll_list(infoPtr, nScrollDiff, 0);
9891
9892 return 0;
9893 }
9894
9895 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9896 {
9897 UINT pulScrollLines = 3;
9898
9899 TRACE("(wheelDelta=%d)\n", wheelDelta);
9900
9901 switch(infoPtr->uView)
9902 {
9903 case LV_VIEW_ICON:
9904 case LV_VIEW_SMALLICON:
9905 /*
9906 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9907 * should be fixed in the future.
9908 */
9909 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (wheelDelta > 0) ?
9910 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9911 break;
9912
9913 case LV_VIEW_DETAILS:
9914 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9915
9916 /* if scrolling changes direction, ignore left overs */
9917 if ((wheelDelta < 0 && infoPtr->cWheelRemainder < 0) ||
9918 (wheelDelta > 0 && infoPtr->cWheelRemainder > 0))
9919 infoPtr->cWheelRemainder += wheelDelta;
9920 else
9921 infoPtr->cWheelRemainder = wheelDelta;
9922 if (infoPtr->cWheelRemainder && pulScrollLines)
9923 {
9924 int cLineScroll;
9925 pulScrollLines = min((UINT)LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9926 cLineScroll = pulScrollLines * (float)infoPtr->cWheelRemainder / WHEEL_DELTA;
9927 infoPtr->cWheelRemainder -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
9928 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, -cLineScroll);
9929 }
9930 break;
9931
9932 case LV_VIEW_LIST:
9933 LISTVIEW_HScroll(infoPtr, (wheelDelta > 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9934 break;
9935 }
9936 return 0;
9937 }
9938
9939 /***
9940 * DESCRIPTION:
9941 * ???
9942 *
9943 * PARAMETER(S):
9944 * [I] infoPtr : valid pointer to the listview structure
9945 * [I] nVirtualKey : virtual key
9946 * [I] lKeyData : key data
9947 *
9948 * RETURN:
9949 * Zero
9950 */
9951 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9952 {
9953 HWND hwndSelf = infoPtr->hwndSelf;
9954 INT nItem = -1;
9955 NMLVKEYDOWN nmKeyDown;
9956
9957 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9958
9959 /* send LVN_KEYDOWN notification */
9960 nmKeyDown.wVKey = nVirtualKey;
9961 nmKeyDown.flags = 0;
9962 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9963 if (!IsWindow(hwndSelf))
9964 return 0;
9965
9966 switch (nVirtualKey)
9967 {
9968 case VK_SPACE:
9969 nItem = infoPtr->nFocusedItem;
9970 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9971 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9972 break;
9973
9974 case VK_RETURN:
9975 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9976 {
9977 if (!notify(infoPtr, NM_RETURN)) return 0;
9978 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9979 }
9980 break;
9981
9982 case VK_HOME:
9983 if (infoPtr->nItemCount > 0)
9984 nItem = 0;
9985 break;
9986
9987 case VK_END:
9988 if (infoPtr->nItemCount > 0)
9989 nItem = infoPtr->nItemCount - 1;
9990 break;
9991
9992 case VK_LEFT:
9993 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9994 break;
9995
9996 case VK_UP:
9997 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9998 break;
9999
10000 case VK_RIGHT:
10001 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
10002 break;
10003
10004 case VK_DOWN:
10005 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
10006 break;
10007
10008 case VK_PRIOR:
10009 if (infoPtr->uView == LV_VIEW_DETAILS)
10010 {
10011 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10012 if (infoPtr->nFocusedItem == topidx)
10013 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
10014 else
10015 nItem = topidx;
10016 }
10017 else
10018 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
10019 * LISTVIEW_GetCountPerRow(infoPtr);
10020 if(nItem < 0) nItem = 0;
10021 break;
10022
10023 case VK_NEXT:
10024 if (infoPtr->uView == LV_VIEW_DETAILS)
10025 {
10026 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10027 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
10028 if (infoPtr->nFocusedItem == topidx + cnt - 1)
10029 nItem = infoPtr->nFocusedItem + cnt - 1;
10030 else
10031 nItem = topidx + cnt - 1;
10032 }
10033 else
10034 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
10035 * LISTVIEW_GetCountPerRow(infoPtr);
10036 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
10037 break;
10038 }
10039
10040 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
10041 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
10042
10043 return 0;
10044 }
10045
10046 /***
10047 * DESCRIPTION:
10048 * Kills the focus.
10049 *
10050 * PARAMETER(S):
10051 * [I] infoPtr : valid pointer to the listview structure
10052 *
10053 * RETURN:
10054 * Zero
10055 */
10056 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
10057 {
10058 TRACE("()\n");
10059
10060 /* drop any left over scroll amount */
10061 infoPtr->cWheelRemainder = 0;
10062
10063 /* if we did not have the focus, there's nothing more to do */
10064 if (!infoPtr->bFocus) return 0;
10065
10066 /* send NM_KILLFOCUS notification */
10067 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
10068
10069 /* if we have a focus rectangle, get rid of it */
10070 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
10071
10072 /* if have a marquee selection, stop it */
10073 if (infoPtr->bMarqueeSelect)
10074 {
10075 /* Remove the marquee rectangle and release our mouse capture */
10076 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
10077 ReleaseCapture();
10078
10079 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
10080
10081 infoPtr->bMarqueeSelect = FALSE;
10082 infoPtr->bScrolling = FALSE;
10083 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10084 }
10085
10086 /* set window focus flag */
10087 infoPtr->bFocus = FALSE;
10088
10089 /* invalidate the selected items before resetting focus flag */
10090 LISTVIEW_InvalidateSelectedItems(infoPtr);
10091
10092 return 0;
10093 }
10094
10095 /***
10096 * DESCRIPTION:
10097 * Processes double click messages (left mouse button).
10098 *
10099 * PARAMETER(S):
10100 * [I] infoPtr : valid pointer to the listview structure
10101 * [I] wKey : key flag
10102 * [I] x,y : mouse coordinate
10103 *
10104 * RETURN:
10105 * Zero
10106 */
10107 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10108 {
10109 LVHITTESTINFO htInfo;
10110
10111 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10112
10113 /* Cancel the item edition if any */
10114 if (infoPtr->itemEdit.fEnabled)
10115 {
10116 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
10117 infoPtr->itemEdit.fEnabled = FALSE;
10118 }
10119
10120 /* send NM_RELEASEDCAPTURE notification */
10121 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10122
10123 htInfo.pt.x = x;
10124 htInfo.pt.y = y;
10125
10126 /* send NM_DBLCLK notification */
10127 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
10128 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
10129
10130 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
10131 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
10132
10133 return 0;
10134 }
10135
10136 static LRESULT LISTVIEW_TrackMouse(const LISTVIEW_INFO *infoPtr, POINT pt)
10137 {
10138 MSG msg;
10139 RECT r;
10140
10141 r.top = r.bottom = pt.y;
10142 r.left = r.right = pt.x;
10143
10144 InflateRect(&r, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
10145
10146 SetCapture(infoPtr->hwndSelf);
10147
10148 while (1)
10149 {
10150 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
10151 {
10152 if (msg.message == WM_MOUSEMOVE)
10153 {
10154 pt.x = (short)LOWORD(msg.lParam);
10155 pt.y = (short)HIWORD(msg.lParam);
10156 if (PtInRect(&r, pt))
10157 continue;
10158 else
10159 {
10160 ReleaseCapture();
10161 return 1;
10162 }
10163 }
10164 else if (msg.message >= WM_LBUTTONDOWN &&
10165 msg.message <= WM_RBUTTONDBLCLK)
10166 {
10167 break;
10168 }
10169
10170 DispatchMessageW(&msg);
10171 }
10172
10173 if (GetCapture() != infoPtr->hwndSelf)
10174 return 0;
10175 }
10176
10177 ReleaseCapture();
10178 return 0;
10179 }
10180
10181
10182 /***
10183 * DESCRIPTION:
10184 * Processes mouse down messages (left mouse button).
10185 *
10186 * PARAMETERS:
10187 * infoPtr [I ] valid pointer to the listview structure
10188 * wKey [I ] key flag
10189 * x,y [I ] mouse coordinate
10190 *
10191 * RETURN:
10192 * Zero
10193 */
10194 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10195 {
10196 LVHITTESTINFO lvHitTestInfo;
10197 static BOOL bGroupSelect = TRUE;
10198 POINT pt = { x, y };
10199 INT nItem;
10200
10201 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10202
10203 /* send NM_RELEASEDCAPTURE notification */
10204 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10205
10206 /* set left button down flag and record the click position */
10207 infoPtr->bLButtonDown = TRUE;
10208 infoPtr->ptClickPos = pt;
10209 infoPtr->bDragging = FALSE;
10210 infoPtr->bMarqueeSelect = FALSE;
10211 infoPtr->bScrolling = FALSE;
10212
10213 lvHitTestInfo.pt.x = x;
10214 lvHitTestInfo.pt.y = y;
10215
10216 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10217 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10218 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10219 {
10220 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10221 {
10222 toggle_checkbox_state(infoPtr, nItem);
10223 return 0;
10224 }
10225
10226 if (infoPtr->dwStyle & LVS_SINGLESEL)
10227 {
10228 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10229 infoPtr->nEditLabelItem = nItem;
10230 else
10231 LISTVIEW_SetSelection(infoPtr, nItem);
10232 }
10233 else
10234 {
10235 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10236 {
10237 if (bGroupSelect)
10238 {
10239 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10240 LISTVIEW_SetItemFocus(infoPtr, nItem);
10241 infoPtr->nSelectionMark = nItem;
10242 }
10243 else
10244 {
10245 LVITEMW item;
10246
10247 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10248 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10249
10250 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10251 infoPtr->nSelectionMark = nItem;
10252 }
10253 }
10254 else if (wKey & MK_CONTROL)
10255 {
10256 LVITEMW item;
10257
10258 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10259
10260 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10261 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10262 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10263 infoPtr->nSelectionMark = nItem;
10264 }
10265 else if (wKey & MK_SHIFT)
10266 {
10267 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10268 }
10269 else
10270 {
10271 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10272 {
10273 infoPtr->nEditLabelItem = nItem;
10274 infoPtr->nLButtonDownItem = nItem;
10275
10276 LISTVIEW_SetItemFocus(infoPtr, nItem);
10277 }
10278 else
10279 /* set selection (clears other pre-existing selections) */
10280 LISTVIEW_SetSelection(infoPtr, nItem);
10281 }
10282 }
10283
10284 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10285 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10286 }
10287 else
10288 {
10289 if (!infoPtr->bFocus)
10290 SetFocus(infoPtr->hwndSelf);
10291
10292 /* remove all selections */
10293 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10294 LISTVIEW_DeselectAll(infoPtr);
10295 ReleaseCapture();
10296 }
10297
10298 return 0;
10299 }
10300
10301 /***
10302 * DESCRIPTION:
10303 * Processes mouse up messages (left mouse button).
10304 *
10305 * PARAMETERS:
10306 * infoPtr [I ] valid pointer to the listview structure
10307 * wKey [I ] key flag
10308 * x,y [I ] mouse coordinate
10309 *
10310 * RETURN:
10311 * Zero
10312 */
10313 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10314 {
10315 LVHITTESTINFO lvHitTestInfo;
10316
10317 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10318
10319 if (!infoPtr->bLButtonDown) return 0;
10320
10321 lvHitTestInfo.pt.x = x;
10322 lvHitTestInfo.pt.y = y;
10323
10324 /* send NM_CLICK notification */
10325 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10326 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10327
10328 /* set left button flag */
10329 infoPtr->bLButtonDown = FALSE;
10330
10331 /* set a single selection, reset others */
10332 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10333 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10334 infoPtr->nLButtonDownItem = -1;
10335
10336 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10337 {
10338 /* Remove the marquee rectangle and release our mouse capture */
10339 if (infoPtr->bMarqueeSelect)
10340 {
10341 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10342 ReleaseCapture();
10343 }
10344
10345 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
10346 SetRect(&infoPtr->marqueeDrawRect, 0, 0, 0, 0);
10347
10348 infoPtr->bDragging = FALSE;
10349 infoPtr->bMarqueeSelect = FALSE;
10350 infoPtr->bScrolling = FALSE;
10351
10352 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10353 return 0;
10354 }
10355
10356 /* if we clicked on a selected item, edit the label */
10357 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10358 {
10359 /* we want to make sure the user doesn't want to do a double click. So we will
10360 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10361 */
10362 infoPtr->itemEdit.fEnabled = TRUE;
10363 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10364 SetTimer(infoPtr->hwndSelf,
10365 (UINT_PTR)&infoPtr->itemEdit,
10366 GetDoubleClickTime(),
10367 LISTVIEW_DelayedEditItem);
10368 }
10369
10370 if (!infoPtr->bFocus)
10371 SetFocus(infoPtr->hwndSelf);
10372
10373 return 0;
10374 }
10375
10376 /***
10377 * DESCRIPTION:
10378 * Destroys the listview control (called after WM_DESTROY).
10379 *
10380 * PARAMETER(S):
10381 * [I] infoPtr : valid pointer to the listview structure
10382 *
10383 * RETURN:
10384 * Zero
10385 */
10386 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10387 {
10388 INT i;
10389
10390 TRACE("()\n");
10391
10392 /* destroy data structure */
10393 DPA_Destroy(infoPtr->hdpaItems);
10394 DPA_Destroy(infoPtr->hdpaItemIds);
10395 DPA_Destroy(infoPtr->hdpaPosX);
10396 DPA_Destroy(infoPtr->hdpaPosY);
10397 /* columns */
10398 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10399 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10400 DPA_Destroy(infoPtr->hdpaColumns);
10401 ranges_destroy(infoPtr->selectionRanges);
10402
10403 /* destroy image lists */
10404 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10405 {
10406 ImageList_Destroy(infoPtr->himlNormal);
10407 ImageList_Destroy(infoPtr->himlSmall);
10408 ImageList_Destroy(infoPtr->himlState);
10409 }
10410
10411 /* destroy font, bkgnd brush */
10412 infoPtr->hFont = 0;
10413 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10414 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10415
10416 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10417
10418 /* free listview info pointer*/
10419 Free(infoPtr);
10420
10421 return 0;
10422 }
10423
10424 /***
10425 * DESCRIPTION:
10426 * Handles notifications.
10427 *
10428 * PARAMETER(S):
10429 * [I] infoPtr : valid pointer to the listview structure
10430 * [I] lpnmhdr : notification information
10431 *
10432 * RETURN:
10433 * Zero
10434 */
10435 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10436 {
10437 NMHEADERW *lpnmh;
10438
10439 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10440
10441 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10442
10443 /* remember: HDN_LAST < HDN_FIRST */
10444 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10445 lpnmh = (NMHEADERW *)lpnmhdr;
10446
10447 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10448
10449 switch (lpnmhdr->code)
10450 {
10451 case HDN_TRACKW:
10452 case HDN_TRACKA:
10453 {
10454 COLUMN_INFO *lpColumnInfo;
10455 POINT ptOrigin;
10456 INT x;
10457
10458 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10459 break;
10460
10461 /* remove the old line (if any) */
10462 LISTVIEW_DrawTrackLine(infoPtr);
10463
10464 /* compute & draw the new line */
10465 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10466 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10467 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10468 infoPtr->xTrackLine = x + ptOrigin.x;
10469 LISTVIEW_DrawTrackLine(infoPtr);
10470 return notify_forward_header(infoPtr, lpnmh);
10471 }
10472
10473 case HDN_ENDTRACKA:
10474 case HDN_ENDTRACKW:
10475 /* remove the track line (if any) */
10476 LISTVIEW_DrawTrackLine(infoPtr);
10477 infoPtr->xTrackLine = -1;
10478 return notify_forward_header(infoPtr, lpnmh);
10479
10480 case HDN_BEGINDRAG:
10481 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10482 return notify_forward_header(infoPtr, lpnmh);
10483
10484 case HDN_ENDDRAG:
10485 infoPtr->colRectsDirty = TRUE;
10486 LISTVIEW_InvalidateList(infoPtr);
10487 return notify_forward_header(infoPtr, lpnmh);
10488
10489 case HDN_ITEMCHANGEDW:
10490 case HDN_ITEMCHANGEDA:
10491 {
10492 COLUMN_INFO *lpColumnInfo;
10493 HDITEMW hdi;
10494 INT dx, cxy;
10495
10496 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10497 {
10498 hdi.mask = HDI_WIDTH;
10499 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10500 cxy = hdi.cxy;
10501 }
10502 else
10503 cxy = lpnmh->pitem->cxy;
10504
10505 /* determine how much we change since the last know position */
10506 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10507 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10508 if (dx != 0)
10509 {
10510 lpColumnInfo->rcHeader.right += dx;
10511
10512 hdi.mask = HDI_ORDER;
10513 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10514
10515 /* not the rightmost one */
10516 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10517 {
10518 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10519 hdi.iOrder + 1, 0);
10520 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10521 }
10522 else
10523 {
10524 /* only needs to update the scrolls */
10525 infoPtr->nItemWidth += dx;
10526 LISTVIEW_UpdateScroll(infoPtr);
10527 }
10528 LISTVIEW_UpdateItemSize(infoPtr);
10529 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10530 {
10531 POINT ptOrigin;
10532 RECT rcCol = lpColumnInfo->rcHeader;
10533
10534 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10535 OffsetRect(&rcCol, ptOrigin.x, 0);
10536
10537 rcCol.top = infoPtr->rcList.top;
10538 rcCol.bottom = infoPtr->rcList.bottom;
10539
10540 /* resizing left-aligned columns leaves most of the left side untouched */
10541 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10542 {
10543 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10544 if (dx > 0)
10545 nMaxDirty += dx;
10546 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10547 }
10548
10549 /* when shrinking the last column clear the now unused field */
10550 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10551 {
10552 RECT right;
10553
10554 rcCol.right -= dx;
10555
10556 /* deal with right from rightmost column area */
10557 right.left = rcCol.right;
10558 right.top = rcCol.top;
10559 right.bottom = rcCol.bottom;
10560 right.right = infoPtr->rcList.right;
10561
10562 LISTVIEW_InvalidateRect(infoPtr, &right);
10563 }
10564
10565 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10566 }
10567 }
10568 break;
10569 }
10570
10571 case HDN_ITEMCLICKW:
10572 case HDN_ITEMCLICKA:
10573 {
10574 /* Handle sorting by Header Column */
10575 NMLISTVIEW nmlv;
10576
10577 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10578 nmlv.iItem = -1;
10579 nmlv.iSubItem = lpnmh->iItem;
10580 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10581 return notify_forward_header(infoPtr, lpnmh);
10582 }
10583
10584 case HDN_DIVIDERDBLCLICKW:
10585 case HDN_DIVIDERDBLCLICKA:
10586 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10587 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10588 split needed for that */
10589 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10590 return notify_forward_header(infoPtr, lpnmh);
10591 }
10592 return 0;
10593 }
10594
10595 /***
10596 * DESCRIPTION:
10597 * Paint non-client area of control.
10598 *
10599 * PARAMETER(S):
10600 * [I] infoPtr : valid pointer to the listview structureof the sender
10601 * [I] region : update region
10602 *
10603 * RETURN:
10604 * TRUE - frame was painted
10605 * FALSE - call default window proc
10606 */
10607 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10608 {
10609 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10610 HDC dc;
10611 RECT r;
10612 HRGN cliprgn;
10613 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10614 cyEdge = GetSystemMetrics (SM_CYEDGE);
10615
10616 if (!theme)
10617 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10618
10619 GetWindowRect(infoPtr->hwndSelf, &r);
10620
10621 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10622 r.right - cxEdge, r.bottom - cyEdge);
10623 if (region != (HRGN)1)
10624 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10625 OffsetRect(&r, -r.left, -r.top);
10626
10627 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10628 OffsetRect(&r, -r.left, -r.top);
10629
10630 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10631 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10632 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10633 ReleaseDC(infoPtr->hwndSelf, dc);
10634
10635 /* Call default proc to get the scrollbars etc. painted */
10636 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10637
10638 return FALSE;
10639 }
10640
10641 /***
10642 * DESCRIPTION:
10643 * Determines the type of structure to use.
10644 *
10645 * PARAMETER(S):
10646 * [I] infoPtr : valid pointer to the listview structureof the sender
10647 * [I] hwndFrom : listview window handle
10648 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10649 *
10650 * RETURN:
10651 * Zero
10652 */
10653 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10654 {
10655 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10656
10657 if (nCommand == NF_REQUERY)
10658 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10659
10660 return infoPtr->notifyFormat;
10661 }
10662
10663 /***
10664 * DESCRIPTION:
10665 * Paints/Repaints the listview control. Internal use.
10666 *
10667 * PARAMETER(S):
10668 * [I] infoPtr : valid pointer to the listview structure
10669 * [I] hdc : device context handle
10670 *
10671 * RETURN:
10672 * Zero
10673 */
10674 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10675 {
10676 TRACE("(hdc=%p)\n", hdc);
10677
10678 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10679 {
10680 infoPtr->bNoItemMetrics = FALSE;
10681 LISTVIEW_UpdateItemSize(infoPtr);
10682 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10683 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10684 LISTVIEW_UpdateScroll(infoPtr);
10685 }
10686
10687 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10688
10689 if (hdc)
10690 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10691 else
10692 {
10693 PAINTSTRUCT ps;
10694
10695 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10696 if (!hdc) return 1;
10697 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10698 EndPaint(infoPtr->hwndSelf, &ps);
10699 }
10700
10701 return 0;
10702 }
10703
10704 /***
10705 * DESCRIPTION:
10706 * Paints/Repaints the listview control, WM_PAINT handler.
10707 *
10708 * PARAMETER(S):
10709 * [I] infoPtr : valid pointer to the listview structure
10710 * [I] hdc : device context handle
10711 *
10712 * RETURN:
10713 * Zero
10714 */
10715 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10716 {
10717 TRACE("(hdc=%p)\n", hdc);
10718
10719 if (!is_redrawing(infoPtr))
10720 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10721
10722 return LISTVIEW_Paint(infoPtr, hdc);
10723 }
10724
10725 /***
10726 * DESCRIPTION:
10727 * Paints/Repaints the listview control.
10728 *
10729 * PARAMETER(S):
10730 * [I] infoPtr : valid pointer to the listview structure
10731 * [I] hdc : device context handle
10732 * [I] options : drawing options
10733 *
10734 * RETURN:
10735 * Zero
10736 */
10737 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10738 {
10739 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
10740
10741 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10742 return 0;
10743
10744 if (options & PRF_ERASEBKGND)
10745 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10746
10747 if (options & PRF_CLIENT)
10748 LISTVIEW_Paint(infoPtr, hdc);
10749
10750 return 0;
10751 }
10752
10753
10754 /***
10755 * DESCRIPTION:
10756 * Processes double click messages (right mouse button).
10757 *
10758 * PARAMETER(S):
10759 * [I] infoPtr : valid pointer to the listview structure
10760 * [I] wKey : key flag
10761 * [I] x,y : mouse coordinate
10762 *
10763 * RETURN:
10764 * Zero
10765 */
10766 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10767 {
10768 LVHITTESTINFO lvHitTestInfo;
10769
10770 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10771
10772 /* send NM_RELEASEDCAPTURE notification */
10773 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10774
10775 /* send NM_RDBLCLK notification */
10776 lvHitTestInfo.pt.x = x;
10777 lvHitTestInfo.pt.y = y;
10778 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10779 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10780
10781 return 0;
10782 }
10783
10784 /***
10785 * DESCRIPTION:
10786 * Processes WM_RBUTTONDOWN message and corresponding drag operation.
10787 *
10788 * PARAMETER(S):
10789 * [I] infoPtr : valid pointer to the listview structure
10790 * [I] wKey : key flag
10791 * [I] x, y : mouse coordinate
10792 *
10793 * RETURN:
10794 * Zero
10795 */
10796 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10797 {
10798 LVHITTESTINFO ht;
10799 INT item;
10800
10801 TRACE("(key=%hu, x=%d, y=%d)\n", wKey, x, y);
10802
10803 /* send NM_RELEASEDCAPTURE notification */
10804 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10805
10806 /* determine the index of the selected item */
10807 ht.pt.x = x;
10808 ht.pt.y = y;
10809 item = LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
10810
10811 /* make sure the listview control window has the focus */
10812 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10813
10814 if ((item >= 0) && (item < infoPtr->nItemCount))
10815 {
10816 LISTVIEW_SetItemFocus(infoPtr, item);
10817 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10818 !LISTVIEW_GetItemState(infoPtr, item, LVIS_SELECTED))
10819 LISTVIEW_SetSelection(infoPtr, item);
10820 }
10821 else
10822 LISTVIEW_DeselectAll(infoPtr);
10823
10824 if (LISTVIEW_TrackMouse(infoPtr, ht.pt))
10825 {
10826 if (ht.iItem != -1)
10827 {
10828 NMLISTVIEW nmlv;
10829
10830 memset(&nmlv, 0, sizeof(nmlv));
10831 nmlv.iItem = ht.iItem;
10832 nmlv.ptAction = ht.pt;
10833
10834 notify_listview(infoPtr, LVN_BEGINRDRAG, &nmlv);
10835 }
10836 }
10837 else
10838 {
10839 SetFocus(infoPtr->hwndSelf);
10840
10841 ht.pt.x = x;
10842 ht.pt.y = y;
10843 LISTVIEW_HitTest(infoPtr, &ht, TRUE, FALSE);
10844
10845 if (notify_click(infoPtr, NM_RCLICK, &ht))
10846 {
10847 /* Send a WM_CONTEXTMENU message in response to the WM_RBUTTONUP */
10848 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10849 (WPARAM)infoPtr->hwndSelf, (LPARAM)GetMessagePos());
10850 }
10851 }
10852
10853 return 0;
10854 }
10855
10856 /***
10857 * DESCRIPTION:
10858 * Sets the cursor.
10859 *
10860 * PARAMETER(S):
10861 * [I] infoPtr : valid pointer to the listview structure
10862 * [I] hwnd : window handle of window containing the cursor
10863 * [I] nHittest : hit-test code
10864 * [I] wMouseMsg : ideintifier of the mouse message
10865 *
10866 * RETURN:
10867 * TRUE if cursor is set
10868 * FALSE otherwise
10869 */
10870 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10871 {
10872 LVHITTESTINFO lvHitTestInfo;
10873
10874 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10875
10876 if (!infoPtr->hHotCursor) goto forward;
10877
10878 GetCursorPos(&lvHitTestInfo.pt);
10879 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10880
10881 SetCursor(infoPtr->hHotCursor);
10882
10883 return TRUE;
10884
10885 forward:
10886
10887 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10888 }
10889
10890 /***
10891 * DESCRIPTION:
10892 * Sets the focus.
10893 *
10894 * PARAMETER(S):
10895 * [I] infoPtr : valid pointer to the listview structure
10896 * [I] hwndLoseFocus : handle of previously focused window
10897 *
10898 * RETURN:
10899 * Zero
10900 */
10901 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10902 {
10903 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10904
10905 /* if we have the focus already, there's nothing to do */
10906 if (infoPtr->bFocus) return 0;
10907
10908 /* send NM_SETFOCUS notification */
10909 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10910
10911 /* set window focus flag */
10912 infoPtr->bFocus = TRUE;
10913
10914 /* put the focus rect back on */
10915 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10916
10917 /* redraw all visible selected items */
10918 LISTVIEW_InvalidateSelectedItems(infoPtr);
10919
10920 return 0;
10921 }
10922
10923 /***
10924 * DESCRIPTION:
10925 * Sets the font.
10926 *
10927 * PARAMETER(S):
10928 * [I] infoPtr : valid pointer to the listview structure
10929 * [I] fRedraw : font handle
10930 * [I] fRedraw : redraw flag
10931 *
10932 * RETURN:
10933 * Zero
10934 */
10935 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10936 {
10937 HFONT oldFont = infoPtr->hFont;
10938 INT oldHeight = infoPtr->nItemHeight;
10939
10940 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10941
10942 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10943 if (infoPtr->hFont == oldFont) return 0;
10944
10945 LISTVIEW_SaveTextMetrics(infoPtr);
10946
10947 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
10948
10949 if (infoPtr->uView == LV_VIEW_DETAILS)
10950 {
10951 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10952 LISTVIEW_UpdateSize(infoPtr);
10953 LISTVIEW_UpdateScroll(infoPtr);
10954 }
10955 else if (infoPtr->nItemHeight != oldHeight)
10956 LISTVIEW_UpdateScroll(infoPtr);
10957
10958 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10959
10960 return 0;
10961 }
10962
10963 /***
10964 * DESCRIPTION:
10965 * Message handling for WM_SETREDRAW.
10966 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10967 *
10968 * PARAMETER(S):
10969 * [I] infoPtr : valid pointer to the listview structure
10970 * [I] bRedraw: state of redraw flag
10971 *
10972 * RETURN:
10973 * DefWinProc return value
10974 */
10975 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
10976 {
10977 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
10978
10979 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
10980 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
10981
10982 infoPtr->bRedraw = bRedraw;
10983
10984 if(!bRedraw) return 0;
10985
10986 if (is_autoarrange(infoPtr))
10987 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10988 LISTVIEW_UpdateScroll(infoPtr);
10989
10990 /* despite what the WM_SETREDRAW docs says, apps expect us
10991 * to invalidate the listview here... stupid! */
10992 LISTVIEW_InvalidateList(infoPtr);
10993
10994 return 0;
10995 }
10996
10997 /***
10998 * DESCRIPTION:
10999 * Resizes the listview control. This function processes WM_SIZE
11000 * messages. At this time, the width and height are not used.
11001 *
11002 * PARAMETER(S):
11003 * [I] infoPtr : valid pointer to the listview structure
11004 * [I] Width : new width
11005 * [I] Height : new height
11006 *
11007 * RETURN:
11008 * Zero
11009 */
11010 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
11011 {
11012 RECT rcOld = infoPtr->rcList;
11013
11014 TRACE("(width=%d, height=%d)\n", Width, Height);
11015
11016 LISTVIEW_UpdateSize(infoPtr);
11017 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
11018
11019 /* do not bother with display related stuff if we're not redrawing */
11020 if (!is_redrawing(infoPtr)) return 0;
11021
11022 if (is_autoarrange(infoPtr))
11023 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11024
11025 LISTVIEW_UpdateScroll(infoPtr);
11026
11027 /* refresh all only for lists whose height changed significantly */
11028 if ((infoPtr->uView == LV_VIEW_LIST) &&
11029 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
11030 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
11031 LISTVIEW_InvalidateList(infoPtr);
11032
11033 return 0;
11034 }
11035
11036 /***
11037 * DESCRIPTION:
11038 * Sets the size information.
11039 *
11040 * PARAMETER(S):
11041 * [I] infoPtr : valid pointer to the listview structure
11042 *
11043 * RETURN:
11044 * None
11045 */
11046 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
11047 {
11048 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
11049
11050 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
11051
11052 if (infoPtr->uView == LV_VIEW_LIST)
11053 {
11054 /* Apparently the "LIST" style is supposed to have the same
11055 * number of items in a column even if there is no scroll bar.
11056 * Since if a scroll bar already exists then the bottom is already
11057 * reduced, only reduce if the scroll bar does not currently exist.
11058 * The "2" is there to mimic the native control. I think it may be
11059 * related to either padding or edges. (GLA 7/2002)
11060 */
11061 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
11062 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
11063 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
11064 }
11065
11066 /* if control created invisible header isn't created */
11067 if (infoPtr->hwndHeader)
11068 {
11069 HDLAYOUT hl;
11070 WINDOWPOS wp;
11071
11072 hl.prc = &infoPtr->rcList;
11073 hl.pwpos = &wp;
11074 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11075 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
11076
11077 if (LISTVIEW_IsHeaderEnabled(infoPtr))
11078 wp.flags |= SWP_SHOWWINDOW;
11079 else
11080 {
11081 wp.flags |= SWP_HIDEWINDOW;
11082 wp.cy = 0;
11083 }
11084
11085 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
11086 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
11087
11088 infoPtr->rcList.top = max(wp.cy, 0);
11089 }
11090 /* extra padding for grid */
11091 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
11092 infoPtr->rcList.top += 2;
11093
11094 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
11095 }
11096
11097 /***
11098 * DESCRIPTION:
11099 * Processes WM_STYLECHANGED messages.
11100 *
11101 * PARAMETER(S):
11102 * [I] infoPtr : valid pointer to the listview structure
11103 * [I] wStyleType : window style type (normal or extended)
11104 * [I] lpss : window style information
11105 *
11106 * RETURN:
11107 * Zero
11108 */
11109 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
11110 const STYLESTRUCT *lpss)
11111 {
11112 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
11113 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
11114 UINT style;
11115
11116 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11117 wStyleType, lpss->styleOld, lpss->styleNew);
11118
11119 if (wStyleType != GWL_STYLE) return 0;
11120
11121 infoPtr->dwStyle = lpss->styleNew;
11122
11123 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
11124 ((lpss->styleNew & WS_HSCROLL) == 0))
11125 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
11126
11127 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
11128 ((lpss->styleNew & WS_VSCROLL) == 0))
11129 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
11130
11131 if (uNewView != uOldView)
11132 {
11133 HIMAGELIST himl;
11134
11135 /* LVM_SETVIEW doesn't change window style bits within LVS_TYPEMASK,
11136 changing style updates current view only when view bits change. */
11137 map_style_view(infoPtr);
11138 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
11139 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
11140
11141 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
11142 SetRectEmpty(&infoPtr->rcFocus);
11143
11144 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
11145 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
11146
11147 if (uNewView == LVS_REPORT)
11148 {
11149 HDLAYOUT hl;
11150 WINDOWPOS wp;
11151
11152 LISTVIEW_CreateHeader( infoPtr );
11153
11154 hl.prc = &infoPtr->rcList;
11155 hl.pwpos = &wp;
11156 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11157 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
11158 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
11159 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
11160 }
11161
11162 LISTVIEW_UpdateItemSize(infoPtr);
11163 }
11164
11165 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
11166 {
11167 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
11168 {
11169 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
11170 {
11171 /* Turn off the header control */
11172 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
11173 TRACE("Hide header control, was 0x%08x\n", style);
11174 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
11175 } else {
11176 /* Turn on the header control */
11177 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11178 {
11179 TRACE("Show header control, was 0x%08x\n", style);
11180 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11181 }
11182 }
11183 }
11184 }
11185
11186 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11187 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11188 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11189
11190 /* update the size of the client area */
11191 LISTVIEW_UpdateSize(infoPtr);
11192
11193 /* add scrollbars if needed */
11194 LISTVIEW_UpdateScroll(infoPtr);
11195
11196 /* invalidate client area + erase background */
11197 LISTVIEW_InvalidateList(infoPtr);
11198
11199 return 0;
11200 }
11201
11202 /***
11203 * DESCRIPTION:
11204 * Processes WM_STYLECHANGING messages.
11205 *
11206 * PARAMETER(S):
11207 * [I] wStyleType : window style type (normal or extended)
11208 * [I0] lpss : window style information
11209 *
11210 * RETURN:
11211 * Zero
11212 */
11213 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11214 STYLESTRUCT *lpss)
11215 {
11216 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11217 wStyleType, lpss->styleOld, lpss->styleNew);
11218
11219 /* don't forward LVS_OWNERDATA only if not already set to */
11220 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11221 {
11222 if (lpss->styleOld & LVS_OWNERDATA)
11223 lpss->styleNew |= LVS_OWNERDATA;
11224 else
11225 lpss->styleNew &= ~LVS_OWNERDATA;
11226 }
11227
11228 return 0;
11229 }
11230
11231 /***
11232 * DESCRIPTION:
11233 * Processes WM_SHOWWINDOW messages.
11234 *
11235 * PARAMETER(S):
11236 * [I] infoPtr : valid pointer to the listview structure
11237 * [I] bShown : window is being shown (FALSE when hidden)
11238 * [I] iStatus : window show status
11239 *
11240 * RETURN:
11241 * Zero
11242 */
11243 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11244 {
11245 /* header delayed creation */
11246 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11247 {
11248 LISTVIEW_CreateHeader(infoPtr);
11249
11250 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11251 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11252 }
11253
11254 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11255 }
11256
11257 /***
11258 * DESCRIPTION:
11259 * Processes CCM_GETVERSION messages.
11260 *
11261 * PARAMETER(S):
11262 * [I] infoPtr : valid pointer to the listview structure
11263 *
11264 * RETURN:
11265 * Current version
11266 */
11267 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11268 {
11269 return infoPtr->iVersion;
11270 }
11271
11272 /***
11273 * DESCRIPTION:
11274 * Processes CCM_SETVERSION messages.
11275 *
11276 * PARAMETER(S):
11277 * [I] infoPtr : valid pointer to the listview structure
11278 * [I] iVersion : version to be set
11279 *
11280 * RETURN:
11281 * -1 when requested version is greater than DLL version;
11282 * previous version otherwise
11283 */
11284 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11285 {
11286 INT iOldVersion = infoPtr->iVersion;
11287
11288 if (iVersion > COMCTL32_VERSION)
11289 return -1;
11290
11291 infoPtr->iVersion = iVersion;
11292
11293 TRACE("new version %d\n", iVersion);
11294
11295 return iOldVersion;
11296 }
11297
11298 /***
11299 * DESCRIPTION:
11300 * Window procedure of the listview control.
11301 *
11302 */
11303 static LRESULT WINAPI
11304 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11305 {
11306 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11307
11308 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11309
11310 if (!infoPtr && (uMsg != WM_NCCREATE))
11311 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11312
11313 switch (uMsg)
11314 {
11315 case LVM_APPROXIMATEVIEWRECT:
11316 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11317 LOWORD(lParam), HIWORD(lParam));
11318 case LVM_ARRANGE:
11319 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11320
11321 case LVM_CANCELEDITLABEL:
11322 return LISTVIEW_CancelEditLabel(infoPtr);
11323
11324 case LVM_CREATEDRAGIMAGE:
11325 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11326
11327 case LVM_DELETEALLITEMS:
11328 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11329
11330 case LVM_DELETECOLUMN:
11331 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11332
11333 case LVM_DELETEITEM:
11334 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11335
11336 case LVM_EDITLABELA:
11337 case LVM_EDITLABELW:
11338 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11339 uMsg == LVM_EDITLABELW);
11340 /* case LVM_ENABLEGROUPVIEW: */
11341
11342 case LVM_ENSUREVISIBLE:
11343 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11344
11345 case LVM_FINDITEMW:
11346 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11347
11348 case LVM_FINDITEMA:
11349 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11350
11351 case LVM_GETBKCOLOR:
11352 return infoPtr->clrBk;
11353
11354 /* case LVM_GETBKIMAGE: */
11355
11356 case LVM_GETCALLBACKMASK:
11357 return infoPtr->uCallbackMask;
11358
11359 case LVM_GETCOLUMNA:
11360 case LVM_GETCOLUMNW:
11361 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11362 uMsg == LVM_GETCOLUMNW);
11363
11364 case LVM_GETCOLUMNORDERARRAY:
11365 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11366
11367 case LVM_GETCOLUMNWIDTH:
11368 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11369
11370 case LVM_GETCOUNTPERPAGE:
11371 return LISTVIEW_GetCountPerPage(infoPtr);
11372
11373 case LVM_GETEDITCONTROL:
11374 return (LRESULT)infoPtr->hwndEdit;
11375
11376 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11377 return infoPtr->dwLvExStyle;
11378
11379 /* case LVM_GETGROUPINFO: */
11380
11381 /* case LVM_GETGROUPMETRICS: */
11382
11383 case LVM_GETHEADER:
11384 return (LRESULT)infoPtr->hwndHeader;
11385
11386 case LVM_GETHOTCURSOR:
11387 return (LRESULT)infoPtr->hHotCursor;
11388
11389 case LVM_GETHOTITEM:
11390 return infoPtr->nHotItem;
11391
11392 case LVM_GETHOVERTIME:
11393 return infoPtr->dwHoverTime;
11394
11395 case LVM_GETIMAGELIST:
11396 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11397
11398 /* case LVM_GETINSERTMARK: */
11399
11400 /* case LVM_GETINSERTMARKCOLOR: */
11401
11402 /* case LVM_GETINSERTMARKRECT: */
11403
11404 case LVM_GETISEARCHSTRINGA:
11405 case LVM_GETISEARCHSTRINGW:
11406 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11407 return FALSE;
11408
11409 case LVM_GETITEMA:
11410 case LVM_GETITEMW:
11411 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11412
11413 case LVM_GETITEMCOUNT:
11414 return infoPtr->nItemCount;
11415
11416 case LVM_GETITEMPOSITION:
11417 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11418
11419 case LVM_GETITEMRECT:
11420 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11421
11422 case LVM_GETITEMSPACING:
11423 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11424
11425 case LVM_GETITEMSTATE:
11426 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11427
11428 case LVM_GETITEMTEXTA:
11429 case LVM_GETITEMTEXTW:
11430 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11431 uMsg == LVM_GETITEMTEXTW);
11432
11433 case LVM_GETNEXTITEM:
11434 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11435
11436 case LVM_GETNUMBEROFWORKAREAS:
11437 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11438 return 1;
11439
11440 case LVM_GETORIGIN:
11441 if (!lParam) return FALSE;
11442 if (infoPtr->uView == LV_VIEW_DETAILS ||
11443 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11444 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11445 return TRUE;
11446
11447 /* case LVM_GETOUTLINECOLOR: */
11448
11449 /* case LVM_GETSELECTEDCOLUMN: */
11450
11451 case LVM_GETSELECTEDCOUNT:
11452 return LISTVIEW_GetSelectedCount(infoPtr);
11453
11454 case LVM_GETSELECTIONMARK:
11455 return infoPtr->nSelectionMark;
11456
11457 case LVM_GETSTRINGWIDTHA:
11458 case LVM_GETSTRINGWIDTHW:
11459 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11460 uMsg == LVM_GETSTRINGWIDTHW);
11461
11462 case LVM_GETSUBITEMRECT:
11463 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11464
11465 case LVM_GETTEXTBKCOLOR:
11466 return infoPtr->clrTextBk;
11467
11468 case LVM_GETTEXTCOLOR:
11469 return infoPtr->clrText;
11470
11471 /* case LVM_GETTILEINFO: */
11472
11473 /* case LVM_GETTILEVIEWINFO: */
11474
11475 case LVM_GETTOOLTIPS:
11476 if( !infoPtr->hwndToolTip )
11477 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11478 return (LRESULT)infoPtr->hwndToolTip;
11479
11480 case LVM_GETTOPINDEX:
11481 return LISTVIEW_GetTopIndex(infoPtr);
11482
11483 case LVM_GETUNICODEFORMAT:
11484 return (infoPtr->notifyFormat == NFR_UNICODE);
11485
11486 case LVM_GETVIEW:
11487 return infoPtr->uView;
11488
11489 case LVM_GETVIEWRECT:
11490 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11491
11492 case LVM_GETWORKAREAS:
11493 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11494 return FALSE;
11495
11496 /* case LVM_HASGROUP: */
11497
11498 case LVM_HITTEST:
11499 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11500
11501 case LVM_INSERTCOLUMNA:
11502 case LVM_INSERTCOLUMNW:
11503 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11504 uMsg == LVM_INSERTCOLUMNW);
11505
11506 /* case LVM_INSERTGROUP: */
11507
11508 /* case LVM_INSERTGROUPSORTED: */
11509
11510 case LVM_INSERTITEMA:
11511 case LVM_INSERTITEMW:
11512 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11513
11514 /* case LVM_INSERTMARKHITTEST: */
11515
11516 /* case LVM_ISGROUPVIEWENABLED: */
11517
11518 case LVM_ISITEMVISIBLE:
11519 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11520
11521 case LVM_MAPIDTOINDEX:
11522 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11523
11524 case LVM_MAPINDEXTOID:
11525 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11526
11527 /* case LVM_MOVEGROUP: */
11528
11529 /* case LVM_MOVEITEMTOGROUP: */
11530
11531 case LVM_REDRAWITEMS:
11532 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11533
11534 /* case LVM_REMOVEALLGROUPS: */
11535
11536 /* case LVM_REMOVEGROUP: */
11537
11538 case LVM_SCROLL:
11539 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11540
11541 case LVM_SETBKCOLOR:
11542 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11543
11544 /* case LVM_SETBKIMAGE: */
11545
11546 case LVM_SETCALLBACKMASK:
11547 infoPtr->uCallbackMask = (UINT)wParam;
11548 return TRUE;
11549
11550 case LVM_SETCOLUMNA:
11551 case LVM_SETCOLUMNW:
11552 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11553 uMsg == LVM_SETCOLUMNW);
11554
11555 case LVM_SETCOLUMNORDERARRAY:
11556 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11557
11558 case LVM_SETCOLUMNWIDTH:
11559 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11560
11561 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11562 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11563
11564 /* case LVM_SETGROUPINFO: */
11565
11566 /* case LVM_SETGROUPMETRICS: */
11567
11568 case LVM_SETHOTCURSOR:
11569 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11570
11571 case LVM_SETHOTITEM:
11572 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11573
11574 case LVM_SETHOVERTIME:
11575 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11576
11577 case LVM_SETICONSPACING:
11578 if(lParam == -1)
11579 return LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
11580 return LISTVIEW_SetIconSpacing(infoPtr, LOWORD(lParam), HIWORD(lParam));
11581
11582 case LVM_SETIMAGELIST:
11583 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11584
11585 /* case LVM_SETINFOTIP: */
11586
11587 /* case LVM_SETINSERTMARK: */
11588
11589 /* case LVM_SETINSERTMARKCOLOR: */
11590
11591 case LVM_SETITEMA:
11592 case LVM_SETITEMW:
11593 {
11594 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11595 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11596 }
11597
11598 case LVM_SETITEMCOUNT:
11599 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11600
11601 case LVM_SETITEMPOSITION:
11602 {
11603 POINT pt;
11604 pt.x = (short)LOWORD(lParam);
11605 pt.y = (short)HIWORD(lParam);
11606 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11607 }
11608
11609 case LVM_SETITEMPOSITION32:
11610 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11611
11612 case LVM_SETITEMSTATE:
11613 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11614
11615 case LVM_SETITEMTEXTA:
11616 case LVM_SETITEMTEXTW:
11617 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11618 uMsg == LVM_SETITEMTEXTW);
11619
11620 /* case LVM_SETOUTLINECOLOR: */
11621
11622 /* case LVM_SETSELECTEDCOLUMN: */
11623
11624 case LVM_SETSELECTIONMARK:
11625 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11626
11627 case LVM_SETTEXTBKCOLOR:
11628 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11629
11630 case LVM_SETTEXTCOLOR:
11631 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11632
11633 /* case LVM_SETTILEINFO: */
11634
11635 /* case LVM_SETTILEVIEWINFO: */
11636
11637 /* case LVM_SETTILEWIDTH: */
11638
11639 case LVM_SETTOOLTIPS:
11640 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11641
11642 case LVM_SETUNICODEFORMAT:
11643 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11644
11645 case LVM_SETVIEW:
11646 return LISTVIEW_SetView(infoPtr, wParam);
11647
11648 /* case LVM_SETWORKAREAS: */
11649
11650 /* case LVM_SORTGROUPS: */
11651
11652 case LVM_SORTITEMS:
11653 case LVM_SORTITEMSEX:
11654 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11655 uMsg == LVM_SORTITEMSEX);
11656 case LVM_SUBITEMHITTEST:
11657 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11658
11659 case LVM_UPDATE:
11660 return LISTVIEW_Update(infoPtr, (INT)wParam);
11661
11662 case CCM_GETVERSION:
11663 return LISTVIEW_GetVersion(infoPtr);
11664
11665 case CCM_SETVERSION:
11666 return LISTVIEW_SetVersion(infoPtr, wParam);
11667
11668 case WM_CHAR:
11669 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11670
11671 case WM_COMMAND:
11672 return LISTVIEW_Command(infoPtr, wParam, lParam);
11673
11674 case WM_NCCREATE:
11675 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
11676
11677 case WM_CREATE:
11678 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11679
11680 case WM_DESTROY:
11681 return LISTVIEW_Destroy(infoPtr);
11682
11683 case WM_ENABLE:
11684 return LISTVIEW_Enable(infoPtr);
11685
11686 case WM_ERASEBKGND:
11687 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11688
11689 case WM_GETDLGCODE:
11690 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11691
11692 case WM_GETFONT:
11693 return (LRESULT)infoPtr->hFont;
11694
11695 case WM_HSCROLL:
11696 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11697
11698 case WM_KEYDOWN:
11699 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11700
11701 case WM_KILLFOCUS:
11702 return LISTVIEW_KillFocus(infoPtr);
11703
11704 case WM_LBUTTONDBLCLK:
11705 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11706
11707 case WM_LBUTTONDOWN:
11708 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11709
11710 case WM_LBUTTONUP:
11711 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11712
11713 case WM_MOUSEMOVE:
11714 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11715
11716 case WM_MOUSEHOVER:
11717 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11718
11719 case WM_NCDESTROY:
11720 return LISTVIEW_NCDestroy(infoPtr);
11721
11722 case WM_NCPAINT:
11723 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11724
11725 case WM_NOTIFY:
11726 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11727
11728 case WM_NOTIFYFORMAT:
11729 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11730
11731 case WM_PRINTCLIENT:
11732 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11733
11734 case WM_PAINT:
11735 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11736
11737 case WM_RBUTTONDBLCLK:
11738 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11739
11740 case WM_RBUTTONDOWN:
11741 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11742
11743 case WM_SETCURSOR:
11744 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11745
11746 case WM_SETFOCUS:
11747 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11748
11749 case WM_SETFONT:
11750 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11751
11752 case WM_SETREDRAW:
11753 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11754
11755 case WM_SHOWWINDOW:
11756 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11757
11758 case WM_STYLECHANGED:
11759 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11760
11761 case WM_STYLECHANGING:
11762 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11763
11764 case WM_SYSCOLORCHANGE:
11765 COMCTL32_RefreshSysColors();
11766 #ifdef __REACTOS__
11767 if (infoPtr->bDefaultBkColor)
11768 {
11769 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
11770 infoPtr->bDefaultBkColor = TRUE;
11771 LISTVIEW_InvalidateList(infoPtr);
11772 }
11773 #endif
11774 return 0;
11775
11776 /* case WM_TIMER: */
11777 case WM_THEMECHANGED:
11778 return LISTVIEW_ThemeChanged(infoPtr);
11779
11780 case WM_VSCROLL:
11781 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11782
11783 case WM_MOUSEWHEEL:
11784 if (wParam & (MK_SHIFT | MK_CONTROL))
11785 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11786 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11787
11788 case WM_WINDOWPOSCHANGED:
11789 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11790 {
11791 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11792 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11793
11794 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11795 {
11796 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11797 }
11798 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11799 }
11800 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11801
11802 /* case WM_WININICHANGE: */
11803
11804 default:
11805 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11806 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11807
11808 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11809 }
11810
11811 }
11812
11813 /***
11814 * DESCRIPTION:
11815 * Registers the window class.
11816 *
11817 * PARAMETER(S):
11818 * None
11819 *
11820 * RETURN:
11821 * None
11822 */
11823 void LISTVIEW_Register(void)
11824 {
11825 WNDCLASSW wndClass;
11826
11827 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11828 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11829 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11830 wndClass.cbClsExtra = 0;
11831 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11832 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11833 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11834 wndClass.lpszClassName = WC_LISTVIEWW;
11835 RegisterClassW(&wndClass);
11836 }
11837
11838 /***
11839 * DESCRIPTION:
11840 * Unregisters the window class.
11841 *
11842 * PARAMETER(S):
11843 * None
11844 *
11845 * RETURN:
11846 * None
11847 */
11848 void LISTVIEW_Unregister(void)
11849 {
11850 UnregisterClassW(WC_LISTVIEWW, NULL);
11851 }
11852
11853 /***
11854 * DESCRIPTION:
11855 * Handle any WM_COMMAND messages
11856 *
11857 * PARAMETER(S):
11858 * [I] infoPtr : valid pointer to the listview structure
11859 * [I] wParam : the first message parameter
11860 * [I] lParam : the second message parameter
11861 *
11862 * RETURN:
11863 * Zero.
11864 */
11865 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11866 {
11867
11868 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11869
11870 if (!infoPtr->hwndEdit) return 0;
11871
11872 switch (HIWORD(wParam))
11873 {
11874 case EN_UPDATE:
11875 {
11876 /*
11877 * Adjust the edit window size
11878 */
11879 WCHAR buffer[1024];
11880 HDC hdc = GetDC(infoPtr->hwndEdit);
11881 HFONT hFont, hOldFont = 0;
11882 RECT rect;
11883 SIZE sz;
11884
11885 if (!infoPtr->hwndEdit || !hdc) return 0;
11886 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11887 GetWindowRect(infoPtr->hwndEdit, &rect);
11888
11889 /* Select font to get the right dimension of the string */
11890 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11891 if (hFont)
11892 {
11893 hOldFont = SelectObject(hdc, hFont);
11894 }
11895
11896 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11897 {
11898 TEXTMETRICW textMetric;
11899
11900 /* Add Extra spacing for the next character */
11901 GetTextMetricsW(hdc, &textMetric);
11902 sz.cx += (textMetric.tmMaxCharWidth * 2);
11903
11904 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11905 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11906 }
11907 if (hFont)
11908 SelectObject(hdc, hOldFont);
11909
11910 ReleaseDC(infoPtr->hwndEdit, hdc);
11911
11912 break;
11913 }
11914 case EN_KILLFOCUS:
11915 {
11916 LISTVIEW_CancelEditLabel(infoPtr);
11917 break;
11918 }
11919
11920 default:
11921 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
11922 }
11923
11924 return 0;
11925 }