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