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