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