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