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