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