[COMCTL32] Sync with Wine Staging 1.9.4. CORE-10912
[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)