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