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