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