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