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