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