1 /*
2 * Listview control
3 *
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 *
24 * NOTES
25 *
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 *
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
32 *
33 * TODO:
34 *
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
40 * color is CLR_NONE.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
45 *
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
51 *
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
62 *
63 * Speedups
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
72 *
73 * Flags
74 * -- LVIF_COLUMNS
75 * -- LVIF_GROUPID
76 * -- LVIF_NORECOMPUTE
77 *
78 * States
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
80 * -- LVIS_CUT
81 * -- LVIS_DROPHILITED
82 * -- LVIS_OVERLAYMASK
83 *
84 * Styles
85 * -- LVS_NOLABELWRAP
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
88 * -- LVS_ALIGNTOP
89 * -- LVS_TYPESTYLEMASK
90 *
91 * Extended Styles
92 * -- LVS_EX_BORDERSELECT
93 * -- LVS_EX_FLATSB
94 * -- LVS_EX_HEADERDRAGDROP
95 * -- LVS_EX_INFOTIP
96 * -- LVS_EX_LABELTIP
97 * -- LVS_EX_MULTIWORKAREAS
98 * -- LVS_EX_ONECLICKACTIVATE
99 * -- LVS_EX_REGIONAL
100 * -- LVS_EX_SIMPLESELECT
101 * -- LVS_EX_TRACKSELECT
102 * -- LVS_EX_TWOCLICKACTIVATE
103 * -- LVS_EX_UNDERLINECOLD
104 * -- LVS_EX_UNDERLINEHOT
105 *
106 * Notifications:
107 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
108 * -- LVN_GETINFOTIP
109 * -- LVN_HOTTRACK
110 * -- LVN_MARQUEEBEGIN
111 * -- LVN_ODFINDITEM
112 * -- LVN_SETDISPINFO
113 * -- NM_HOVER
114 * -- LVN_BEGINRDRAG
115 *
116 * Messages:
117 * -- LVM_CANCELEDITLABEL
118 * -- LVM_ENABLEGROUPVIEW
119 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
120 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
121 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
122 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
123 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
124 * -- LVM_GETINSERTMARKRECT
125 * -- LVM_GETNUMBEROFWORKAREAS
126 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
127 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
128 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
129 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
130 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
131 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
132 * -- LVM_GETVIEW, LVM_SETVIEW
133 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
134 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
135 * -- LVM_INSERTGROUPSORTED
136 * -- LVM_INSERTMARKHITTEST
137 * -- LVM_ISGROUPVIEWENABLED
138 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
139 * -- LVM_MOVEGROUP
140 * -- LVM_MOVEITEMTOGROUP
141 * -- LVM_SETINFOTIP
142 * -- LVM_SETTILEWIDTH
143 * -- LVM_SORTGROUPS
144 * -- LVM_SORTITEMSEX
145 *
146 * Macros:
147 * -- ListView_GetCheckSate, ListView_SetCheckState
148 * -- ListView_GetHoverTime, ListView_SetHoverTime
149 * -- ListView_GetISearchString
150 * -- ListView_GetNumberOfWorkAreas
151 * -- ListView_GetOrigin
152 * -- ListView_GetTextBkColor
153 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
154 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
155 * -- ListView_SortItemsEx
156 *
157 * Functions:
158 * -- LVGroupComparE
159 *
160 * Known differences in message stream from native control (not known if
161 * these differences cause problems):
162 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
163 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
164 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
165 * processing for "USEDOUBLECLICKTIME".
166 */
167
168 #include "config.h"
169 #include "wine/port.h"
170
171 #include <assert.h>
172 #include <ctype.h>
173 #include <string.h>
174 #include <stdlib.h>
175 #include <stdarg.h>
176 #include <stdio.h>
177
178 #include "windef.h"
179 #include "winbase.h"
180 #include "winnt.h"
181 #include "wingdi.h"
182 #include "winuser.h"
183 #include "winnls.h"
184 #include "commctrl.h"
185 #include "comctl32.h"
186 #include "uxtheme.h"
187
188 #include "wine/debug.h"
189 #include "wine/unicode.h"
190
191 WINE_DEFAULT_DEBUG_CHANNEL(listview);
192
193 /* make sure you set this to 0 for production use! */
194 #define DEBUG_RANGES 1
195
196 typedef struct tagCOLUMN_INFO
197 {
198 RECT rcHeader; /* tracks the header's rectangle */
199 int fmt; /* same as LVCOLUMN.fmt */
200 } COLUMN_INFO;
201
202 typedef struct tagITEMHDR
203 {
204 LPWSTR pszText;
205 INT iImage;
206 } ITEMHDR, *LPITEMHDR;
207
208 typedef struct tagSUBITEM_INFO
209 {
210 ITEMHDR hdr;
211 INT iSubItem;
212 } SUBITEM_INFO;
213
214 typedef struct tagITEM_INFO
215 {
216 ITEMHDR hdr;
217 UINT state;
218 LPARAM lParam;
219 INT iIndent;
220 } ITEM_INFO;
221
222 typedef struct tagRANGE
223 {
224 INT lower;
225 INT upper;
226 } RANGE;
227
228 typedef struct tagRANGES
229 {
230 HDPA hdpa;
231 } *RANGES;
232
233 typedef struct tagITERATOR
234 {
235 INT nItem;
236 INT nSpecial;
237 RANGE range;
238 RANGES ranges;
239 INT index;
240 } ITERATOR;
241
242 typedef struct tagDELAYED_ITEM_EDIT
243 {
244 BOOL fEnabled;
245 INT iItem;
246 } DELAYED_ITEM_EDIT;
247
248 typedef struct tagLISTVIEW_INFO
249 {
250 HWND hwndSelf;
251 HBRUSH hBkBrush;
252 COLORREF clrBk;
253 COLORREF clrText;
254 COLORREF clrTextBk;
255 HIMAGELIST himlNormal;
256 HIMAGELIST himlSmall;
257 HIMAGELIST himlState;
258 BOOL bLButtonDown;
259 BOOL bRButtonDown;
260 POINT ptClickPos; /* point where the user clicked */
261 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
262 INT nItemHeight;
263 INT nItemWidth;
264 RANGES selectionRanges;
265 INT nSelectionMark;
266 INT nHotItem;
267 SHORT notifyFormat;
268 HWND hwndNotify;
269 RECT rcList; /* This rectangle is really the window
270 * client rectangle possibly reduced by the
271 * horizontal scroll bar and/or header - see
272 * LISTVIEW_UpdateSize. This rectangle offset
273 * by the LISTVIEW_GetOrigin value is in
274 * client coordinates */
275 SIZE iconSize;
276 SIZE iconSpacing;
277 SIZE iconStateSize;
278 UINT uCallbackMask;
279 HWND hwndHeader;
280 HCURSOR hHotCursor;
281 HFONT hDefaultFont;
282 HFONT hFont;
283 INT ntmHeight; /* Some cached metrics of the font used */
284 INT ntmMaxCharWidth; /* by the listview to draw items */
285 INT nEllipsisWidth;
286 BOOL bRedraw; /* Turns on/off repaints & invalidations */
287 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
288 BOOL bFocus;
289 BOOL bDoChangeNotify; /* send change notification messages? */
290 INT nFocusedItem;
291 RECT rcFocus;
292 DWORD dwStyle; /* the cached window GWL_STYLE */
293 DWORD dwLvExStyle; /* extended listview style */
294 INT nItemCount; /* the number of items in the list */
295 HDPA hdpaItems; /* array ITEM_INFO pointers */
296 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
297 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
298 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
299 POINT currIconPos; /* this is the position next icon will be placed */
300 PFNLVCOMPARE pfnCompare;
301 LPARAM lParamSort;
302 HWND hwndEdit;
303 WNDPROC EditWndProc;
304 INT nEditLabelItem;
305 DWORD dwHoverTime;
306 HWND hwndToolTip;
307
308 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
309
310 DWORD lastKeyPressTimestamp;
311 WPARAM charCode;
312 INT nSearchParamLength;
313 WCHAR szSearchParam[ MAX_PATH ];
314 BOOL bIsDrawing;
315 INT nMeasureItemHeight;
316 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
317 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
318 } LISTVIEW_INFO;
319
320 /*
321 * constants
322 */
323 /* How many we debug buffer to allocate */
324 #define DEBUG_BUFFERS 20
325 /* The size of a single debug bbuffer */
326 #define DEBUG_BUFFER_SIZE 256
327
328 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
329 #define SB_INTERNAL -1
330
331 /* maximum size of a label */
332 #define DISP_TEXT_SIZE 512
333
334 /* padding for items in list and small icon display modes */
335 #define WIDTH_PADDING 12
336
337 /* padding for items in list, report and small icon display modes */
338 #define HEIGHT_PADDING 1
339
340 /* offset of items in report display mode */
341 #define REPORT_MARGINX 2
342
343 /* padding for icon in large icon display mode
344 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
345 * that HITTEST will see.
346 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
347 * ICON_TOP_PADDING - sum of the two above.
348 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
349 * LABEL_HOR_PADDING - between text and sides of box
350 * LABEL_VERT_PADDING - between bottom of text and end of box
351 *
352 * ICON_LR_PADDING - additional width above icon size.
353 * ICON_LR_HALF - half of the above value
354 */
355 #define ICON_TOP_PADDING_NOTHITABLE 2
356 #define ICON_TOP_PADDING_HITABLE 2
357 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
358 #define ICON_BOTTOM_PADDING 4
359 #define LABEL_HOR_PADDING 5
360 #define LABEL_VERT_PADDING 7
361 #define ICON_LR_PADDING 16
362 #define ICON_LR_HALF (ICON_LR_PADDING/2)
363
364 /* default label width for items in list and small icon display modes */
365 #define DEFAULT_LABEL_WIDTH 40
366
367 /* default column width for items in list display mode */
368 #define DEFAULT_COLUMN_WIDTH 128
369
370 /* Size of "line" scroll for V & H scrolls */
371 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
372
373 /* Padding between image and label */
374 #define IMAGE_PADDING 2
375
376 /* Padding behind the label */
377 #define TRAILING_LABEL_PADDING 12
378 #define TRAILING_HEADER_PADDING 11
379
380 /* Border for the icon caption */
381 #define CAPTION_BORDER 2
382
383 /* Standard DrawText flags */
384 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
385 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
386 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
387
388 /* Image index from state */
389 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
390
391 /* The time in milliseconds to reset the search in the list */
392 #define KEY_DELAY 450
393
394 /* Dump the LISTVIEW_INFO structure to the debug channel */
395 #define LISTVIEW_DUMP(iP) do { \
396 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
397 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
398 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
399 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
400 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
401 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
402 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
403 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
404 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
405 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
406 } while(0)
407
408 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
409
410 /*
411 * forward declarations
412 */
413 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
414 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
415 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
416 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
417 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
418 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
419 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
420 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
421 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
422 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
423 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
424 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
425 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
426 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
427 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
428 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
429 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
430 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
431 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
432 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
433 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
434 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
435 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
436 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
437 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
438 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
439 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
440
441 /******** Text handling functions *************************************/
442
443 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
444 * text string. The string may be ANSI or Unicode, in which case
445 * the boolean isW tells us the type of the string.
446 *
447 * The name of the function tell what type of strings it expects:
448 * W: Unicode, T: ANSI/Unicode - function of isW
449 */
450
451 static inline BOOL is_textW(LPCWSTR text)
452 {
453 return text != NULL && text != LPSTR_TEXTCALLBACKW;
454 }
455
456 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
457 {
458 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
459 return is_textW(text);
460 }
461
462 static inline int textlenT(LPCWSTR text, BOOL isW)
463 {
464 return !is_textT(text, isW) ? 0 :
465 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
466 }
467
468 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
469 {
470 if (isDestW)
471 if (isSrcW) lstrcpynW(dest, src, max);
472 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
473 else
474 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
475 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
476 }
477
478 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
479 {
480 LPWSTR wstr = (LPWSTR)text;
481
482 if (!isW && is_textT(text, isW))
483 {
484 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
485 wstr = Alloc(len * sizeof(WCHAR));
486 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
487 }
488 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
489 return wstr;
490 }
491
492 static inline void textfreeT(LPWSTR wstr, BOOL isW)
493 {
494 if (!isW && is_textT(wstr, isW)) Free (wstr);
495 }
496
497 /*
498 * dest is a pointer to a Unicode string
499 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
500 */
501 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
502 {
503 BOOL bResult = TRUE;
504
505 if (src == LPSTR_TEXTCALLBACKW)
506 {
507 if (is_textW(*dest)) Free(*dest);
508 *dest = LPSTR_TEXTCALLBACKW;
509 }
510 else
511 {
512 LPWSTR pszText = textdupTtoW(src, isW);
513 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
514 bResult = Str_SetPtrW(dest, pszText);
515 textfreeT(pszText, isW);
516 }
517 return bResult;
518 }
519
520 /*
521 * compares a Unicode to a Unicode/ANSI text string
522 */
523 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
524 {
525 if (!aw) return bt ? -1 : 0;
526 if (!bt) return aw ? 1 : 0;
527 if (aw == LPSTR_TEXTCALLBACKW)
528 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
529 if (bt != LPSTR_TEXTCALLBACKW)
530 {
531 LPWSTR bw = textdupTtoW(bt, isW);
532 int r = bw ? lstrcmpW(aw, bw) : 1;
533 textfreeT(bw, isW);
534 return r;
535 }
536
537 return 1;
538 }
539
540 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
541 {
542 int res;
543
544 n = min(min(n, strlenW(s1)), strlenW(s2));
545 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
546 return res ? res - sizeof(WCHAR) : res;
547 }
548
549 /******** Debugging functions *****************************************/
550
551 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
552 {
553 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
554 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
555 }
556
557 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
558 {
559 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
560 n = min(textlenT(text, isW), n);
561 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
562 }
563
564 static char* debug_getbuf(void)
565 {
566 static int index = 0;
567 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
568 return buffers[index++ % DEBUG_BUFFERS];
569 }
570
571 static inline const char* debugrange(const RANGE *lprng)
572 {
573 if (!lprng) return "(null)";
574 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
575 }
576
577 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
578 {
579 char* buf = debug_getbuf(), *text = buf;
580 int len, size = DEBUG_BUFFER_SIZE;
581
582 if (pScrollInfo == NULL) return "(null)";
583 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
584 if (len == -1) goto end; buf += len; size -= len;
585 if (pScrollInfo->fMask & SIF_RANGE)
586 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
587 else len = 0;
588 if (len == -1) goto end; buf += len; size -= len;
589 if (pScrollInfo->fMask & SIF_PAGE)
590 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
591 else len = 0;
592 if (len == -1) goto end; buf += len; size -= len;
593 if (pScrollInfo->fMask & SIF_POS)
594 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
595 else len = 0;
596 if (len == -1) goto end; buf += len; size -= len;
597 if (pScrollInfo->fMask & SIF_TRACKPOS)
598 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
599 else len = 0;
600 if (len == -1) goto end; buf += len; size -= len;
601 goto undo;
602 end:
603 buf = text + strlen(text);
604 undo:
605 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
606 return text;
607 }
608
609 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
610 {
611 if (!plvnm) return "(null)";
612 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
613 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
614 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
615 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
616 }
617
618 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
619 {
620 char* buf = debug_getbuf(), *text = buf;
621 int len, size = DEBUG_BUFFER_SIZE;
622
623 if (lpLVItem == NULL) return "(null)";
624 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
625 if (len == -1) goto end; buf += len; size -= len;
626 if (lpLVItem->mask & LVIF_STATE)
627 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
628 else len = 0;
629 if (len == -1) goto end; buf += len; size -= len;
630 if (lpLVItem->mask & LVIF_TEXT)
631 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
632 else len = 0;
633 if (len == -1) goto end; buf += len; size -= len;
634 if (lpLVItem->mask & LVIF_IMAGE)
635 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
636 else len = 0;
637 if (len == -1) goto end; buf += len; size -= len;
638 if (lpLVItem->mask & LVIF_PARAM)
639 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
640 else len = 0;
641 if (len == -1) goto end; buf += len; size -= len;
642 if (lpLVItem->mask & LVIF_INDENT)
643 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
644 else len = 0;
645 if (len == -1) goto end; buf += len; size -= len;
646 goto undo;
647 end:
648 buf = text + strlen(text);
649 undo:
650 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
651 return text;
652 }
653
654 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
655 {
656 char* buf = debug_getbuf(), *text = buf;
657 int len, size = DEBUG_BUFFER_SIZE;
658
659 if (lpColumn == NULL) return "(null)";
660 len = snprintf(buf, size, "{");
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpColumn->mask & LVCF_SUBITEM)
663 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
664 else len = 0;
665 if (len == -1) goto end; buf += len; size -= len;
666 if (lpColumn->mask & LVCF_FMT)
667 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
668 else len = 0;
669 if (len == -1) goto end; buf += len; size -= len;
670 if (lpColumn->mask & LVCF_WIDTH)
671 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
672 else len = 0;
673 if (len == -1) goto end; buf += len; size -= len;
674 if (lpColumn->mask & LVCF_TEXT)
675 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
676 else len = 0;
677 if (len == -1) goto end; buf += len; size -= len;
678 if (lpColumn->mask & LVCF_IMAGE)
679 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
680 else len = 0;
681 if (len == -1) goto end; buf += len; size -= len;
682 if (lpColumn->mask & LVCF_ORDER)
683 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
684 else len = 0;
685 if (len == -1) goto end; buf += len; size -= len;
686 goto undo;
687 end:
688 buf = text + strlen(text);
689 undo:
690 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
691 return text;
692 }
693
694 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
695 {
696 if (!lpht) return "(null)";
697
698 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
699 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
700 }
701
702 /* Return the corresponding text for a given scroll value */
703 static inline LPCSTR debugscrollcode(int nScrollCode)
704 {
705 switch(nScrollCode)
706 {
707 case SB_LINELEFT: return "SB_LINELEFT";
708 case SB_LINERIGHT: return "SB_LINERIGHT";
709 case SB_PAGELEFT: return "SB_PAGELEFT";
710 case SB_PAGERIGHT: return "SB_PAGERIGHT";
711 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
712 case SB_THUMBTRACK: return "SB_THUMBTRACK";
713 case SB_ENDSCROLL: return "SB_ENDSCROLL";
714 case SB_INTERNAL: return "SB_INTERNAL";
715 default: return "unknown";
716 }
717 }
718
719
720 /******** Notification functions i************************************/
721
722 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
723 {
724 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
725 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
726 }
727
728 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
729 {
730 LRESULT result;
731
732 TRACE("(code=%d)\n", code);
733
734 pnmh->hwndFrom = infoPtr->hwndSelf;
735 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
736 pnmh->code = code;
737 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
738
739 TRACE(" <= %ld\n", result);
740
741 return result;
742 }
743
744 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
745 {
746 NMHDR nmh;
747 HWND hwnd = infoPtr->hwndSelf;
748 notify_hdr(infoPtr, code, &nmh);
749 return IsWindow(hwnd);
750 }
751
752 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
753 {
754 NMITEMACTIVATE nmia;
755 LVITEMW item;
756
757 if (htInfo) {
758 nmia.uNewState = 0;
759 nmia.uOldState = 0;
760 nmia.uChanged = 0;
761 nmia.uKeyFlags = 0;
762
763 item.mask = LVIF_PARAM|LVIF_STATE;
764 item.iItem = htInfo->iItem;
765 item.iSubItem = 0;
766 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
767 nmia.lParam = item.lParam;
768 nmia.uOldState = item.state;
769 nmia.uNewState = item.state | LVIS_ACTIVATING;
770 nmia.uChanged = LVIF_STATE;
771 }
772
773 nmia.iItem = htInfo->iItem;
774 nmia.iSubItem = htInfo->iSubItem;
775 nmia.ptAction = htInfo->pt;
776
777 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
778 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
779 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
780 }
781 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
782 }
783
784 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
785 {
786 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
787 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
788 }
789
790 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
791 {
792 NMLISTVIEW nmlv;
793 LVITEMW item;
794 HWND hwnd = infoPtr->hwndSelf;
795
796 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
797 ZeroMemory(&nmlv, sizeof(nmlv));
798 nmlv.iItem = lvht->iItem;
799 nmlv.iSubItem = lvht->iSubItem;
800 nmlv.ptAction = lvht->pt;
801 item.mask = LVIF_PARAM;
802 item.iItem = lvht->iItem;
803 item.iSubItem = 0;
804 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
805 notify_listview(infoPtr, code, &nmlv);
806 return IsWindow(hwnd);
807 }
808
809 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
810 {
811 NMLISTVIEW nmlv;
812 LVITEMW item;
813 HWND hwnd = infoPtr->hwndSelf;
814
815 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
816 nmlv.iItem = nItem;
817 item.mask = LVIF_PARAM;
818 item.iItem = nItem;
819 item.iSubItem = 0;
820 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
821 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
822 return IsWindow(hwnd);
823 }
824
825 static int get_ansi_notification(INT unicodeNotificationCode)
826 {
827 switch (unicodeNotificationCode)
828 {
829 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
830 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
831 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
832 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
833 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
834 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
835 }
836 ERR("unknown notification %x\n", unicodeNotificationCode);
837 assert(FALSE);
838 return 0;
839 }
840
841 /*
842 Send notification. depends on dispinfoW having same
843 structure as dispinfoA.
844 infoPtr : listview struct
845 notificationCode : *Unicode* notification code
846 pdi : dispinfo structure (can be unicode or ansi)
847 isW : TRUE if dispinfo is Unicode
848 */
849 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
850 {
851 BOOL bResult = FALSE;
852 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
853 INT cchTempBufMax = 0, savCchTextMax = 0, realNotifCode;
854 LPWSTR pszTempBuf = NULL, savPszText = NULL;
855
856 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
857 {
858 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
859 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
860 }
861
862 if (convertToAnsi || convertToUnicode)
863 {
864 if (notificationCode != LVN_GETDISPINFOW)
865 {
866 cchTempBufMax = convertToUnicode ?