updated listbox control from wine 0.9.2
[reactos.git] / reactos / lib / user32 / controls / listbox.c
1 /*
2 * Listbox controls
3 *
4 * Copyright 1996 Alexandre Julliard
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <user32.h>
22 #define NDEBUG
23 #include <debug.h>
24
25 /* Start of hack section -------------------------------- */
26
27 typedef short *LPINT16;
28
29 BOOL is_old_app(HWND hwnd)
30 {
31 return FALSE;
32 }
33
34 #define WM_LBTRACKPOINT 0x0131
35 #define WS_EX_DRAGDETECT 0x00000002L
36 #define WM_BEGINDRAG 0x022C
37
38 UINT STDCALL SetSystemTimer(HWND,UINT_PTR,UINT,TIMERPROC);
39 BOOL STDCALL KillSystemTimer(HWND,UINT_PTR);
40
41 /* End of hack section -------------------------------- */
42
43 /* Unimplemented yet:
44 * - LBS_USETABSTOPS
45 * - Locale handling
46 *
47 * Probably needs improvement:
48 * - LBS_NOSEL
49 */
50
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
53
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
56
57 /* Listbox system timer id */
58 #define LB_TIMER_ID 2
59
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
62
63 /* Item structure */
64 typedef struct
65 {
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 DWORD data; /* User data */
70 } LB_ITEMDATA;
71
72 /* Listbox structure */
73 typedef struct
74 {
75 HWND self; /* Our own window handle */
76 HWND owner; /* Owner window to send notifications to */
77 UINT style; /* Window style */
78 INT width; /* Window width */
79 INT height; /* Window height */
80 LB_ITEMDATA *items; /* Array of items */
81 INT nb_items; /* Number of items */
82 INT top_item; /* Top visible item */
83 INT selected_item; /* Selected item */
84 INT focus_item; /* Item that has the focus */
85 INT anchor_item; /* Anchor item for extended selection */
86 INT item_height; /* Default item height */
87 INT page_size; /* Items per listbox page */
88 INT column_width; /* Column width for multi-column listboxes */
89 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
90 INT horz_pos; /* Horizontal position */
91 INT nb_tabs; /* Number of tabs in array */
92 INT *tabs; /* Array of tabs */
93 INT avg_char_width; /* Average width of characters */
94 BOOL caret_on; /* Is caret on? */
95 BOOL captured; /* Is mouse captured? */
96 BOOL in_focus;
97 HFONT font; /* Current font */
98 LCID locale; /* Current locale for string comparisons */
99 LPHEADCOMBO lphc; /* ComboLBox */
100 } LB_DESCR;
101
102
103 #define IS_OWNERDRAW(descr) \
104 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105
106 #define HAS_STRINGS(descr) \
107 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108
109
110 #define IS_MULTISELECT(descr) \
111 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
112 !((descr)->style & LBS_NOSEL))
113
114 #define SEND_NOTIFICATION(descr,code) \
115 (SendMessageW( (descr)->owner, WM_COMMAND, \
116 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117
118 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119
120 /* Current timer status */
121 typedef enum
122 {
123 LB_TIMER_NONE,
124 LB_TIMER_UP,
125 LB_TIMER_LEFT,
126 LB_TIMER_DOWN,
127 LB_TIMER_RIGHT
128 } TIMER_DIRECTION;
129
130 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131
132 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
133 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
134
135 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
136
137 /*********************************************************************
138 * listbox class descriptor
139 */
140 const struct builtin_class_descr LISTBOX_builtin_class =
141 {
142 #ifdef __REACTOS__
143 L"ListBox", /* name */
144 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
145 (WNDPROC)ListBoxWndProcW, /* procW */
146 (WNDPROC)ListBoxWndProcA, /* procA */
147 sizeof(LB_DESCR *), /* extra */
148 (LPCWSTR) IDC_ARROW, /* cursor */
149 0 /* brush */
150 #else
151 "ListBox", /* name */
152 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
153 ListBoxWndProcA, /* procA */
154 ListBoxWndProcW, /* procW */
155 sizeof(LB_DESCR *), /* extra */
156 IDC_ARROW, /* cursor */
157 0 /* brush */
158 #endif
159 };
160
161
162 /*********************************************************************
163 * combolbox class descriptor
164 */
165 const struct builtin_class_descr COMBOLBOX_builtin_class =
166 {
167 #ifdef __REACTOS__
168 L"ComboLBox", /* name */
169 CS_DBLCLKS | CS_SAVEBITS, /* style */
170 (WNDPROC)ListBoxWndProcW, /* procW */
171 (WNDPROC)ListBoxWndProcA, /* procA */
172 sizeof(LB_DESCR *), /* extra */
173 (LPCWSTR) IDC_ARROW, /* cursor */
174 0 /* brush */
175 #else
176 "ComboLBox", /* name */
177 CS_DBLCLKS | CS_SAVEBITS, /* style */
178 ListBoxWndProcA, /* procA */
179 ListBoxWndProcW, /* procW */
180 sizeof(LB_DESCR *), /* extra */
181 IDC_ARROW, /* cursor */
182 0 /* brush */
183 #endif
184 };
185
186 #ifndef __REACTOS__
187 /* check whether app is a Win 3.1 app */
188 inline static BOOL is_old_app( HWND hwnd )
189 {
190 return (GetExpWinVer16( GetWindowLongA(hwnd,GWL_HINSTANCE) ) & 0xFF00 ) == 0x0300;
191 }
192 #endif
193
194
195 /***********************************************************************
196 * LISTBOX_Dump
197 */
198 void LISTBOX_Dump( HWND hwnd )
199 {
200 INT i;
201 LB_ITEMDATA *item;
202 LB_DESCR *descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 );
203
204 TRACE( "Listbox:\n" );
205 TRACE( "hwnd=%p descr=%08x items=%d top=%d\n",
206 hwnd, (UINT)descr, descr->nb_items, descr->top_item );
207 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
208 {
209 TRACE( "%4d: %-40s %d %08lx %3d\n",
210 i, debugstr_w(item->str), item->selected, item->data, item->height );
211 }
212 }
213
214
215 /***********************************************************************
216 * LISTBOX_GetCurrentPageSize
217 *
218 * Return the current page size
219 */
220 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
221 {
222 INT i, height;
223 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
224 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
225 {
226 if ((height += descr->items[i].height) > descr->height) break;
227 }
228 if (i == descr->top_item) return 1;
229 else return i - descr->top_item;
230 }
231
232
233 /***********************************************************************
234 * LISTBOX_GetMaxTopIndex
235 *
236 * Return the maximum possible index for the top of the listbox.
237 */
238 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
239 {
240 INT max, page;
241
242 if (descr->style & LBS_OWNERDRAWVARIABLE)
243 {
244 page = descr->height;
245 for (max = descr->nb_items - 1; max >= 0; max--)
246 if ((page -= descr->items[max].height) < 0) break;
247 if (max < descr->nb_items - 1) max++;
248 }
249 else if (descr->style & LBS_MULTICOLUMN)
250 {
251 if ((page = descr->width / descr->column_width) < 1) page = 1;
252 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
253 max = (max - page) * descr->page_size;
254 }
255 else
256 {
257 max = descr->nb_items - descr->page_size;
258 }
259 if (max < 0) max = 0;
260 return max;
261 }
262
263
264 /***********************************************************************
265 * LISTBOX_UpdateScroll
266 *
267 * Update the scrollbars. Should be called whenever the content
268 * of the listbox changes.
269 */
270 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
271 {
272 SCROLLINFO info;
273
274 /* Check the listbox scroll bar flags individually before we call
275 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
276 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
277 scroll bar when we do not need one.
278 if (!(descr->style & WS_VSCROLL)) return;
279 */
280
281 /* It is important that we check descr->style, and not wnd->dwStyle,
282 for WS_VSCROLL, as the former is exactly the one passed in
283 argument to CreateWindow.
284 In Windows (and from now on in Wine :) a listbox created
285 with such a style (no WS_SCROLL) does not update
286 the scrollbar with listbox-related data, thus letting
287 the programmer use it for his/her own purposes. */
288
289 if (descr->style & LBS_NOREDRAW) return;
290 info.cbSize = sizeof(info);
291
292 if (descr->style & LBS_MULTICOLUMN)
293 {
294 info.nMin = 0;
295 info.nMax = (descr->nb_items - 1) / descr->page_size;
296 info.nPos = descr->top_item / descr->page_size;
297 info.nPage = descr->width / descr->column_width;
298 if (info.nPage < 1) info.nPage = 1;
299 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
300 if (descr->style & LBS_DISABLENOSCROLL)
301 info.fMask |= SIF_DISABLENOSCROLL;
302 if (descr->style & WS_HSCROLL)
303 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
304 info.nMax = 0;
305 info.fMask = SIF_RANGE;
306 if (descr->style & WS_VSCROLL)
307 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
308 }
309 else
310 {
311 info.nMin = 0;
312 info.nMax = descr->nb_items - 1;
313 info.nPos = descr->top_item;
314 info.nPage = LISTBOX_GetCurrentPageSize( descr );
315 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
316 if (descr->style & LBS_DISABLENOSCROLL)
317 info.fMask |= SIF_DISABLENOSCROLL;
318 if (descr->style & WS_VSCROLL)
319 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
320
321 if (descr->horz_extent)
322 {
323 info.nMin = 0;
324 info.nMax = descr->horz_extent - 1;
325 info.nPos = descr->horz_pos;
326 info.nPage = descr->width;
327 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
328 if (descr->style & LBS_DISABLENOSCROLL)
329 info.fMask |= SIF_DISABLENOSCROLL;
330 if (descr->style & WS_HSCROLL)
331 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
332 }
333 }
334 }
335
336
337 /***********************************************************************
338 * LISTBOX_SetTopItem
339 *
340 * Set the top item of the listbox, scrolling up or down if necessary.
341 */
342 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index,
343 BOOL scroll )
344 {
345 INT max = LISTBOX_GetMaxTopIndex( descr );
346 if (index > max) index = max;
347 if (index < 0) index = 0;
348 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
349 if (descr->top_item == index) return LB_OKAY;
350 if (descr->style & LBS_MULTICOLUMN)
351 {
352 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
353 if (scroll && (abs(diff) < descr->width))
354 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
355 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
356
357 else
358 scroll = FALSE;
359 }
360 else if (scroll)
361 {
362 INT diff;
363 if (descr->style & LBS_OWNERDRAWVARIABLE)
364 {
365 INT i;
366 diff = 0;
367 if (index > descr->top_item)
368 {
369 for (i = index - 1; i >= descr->top_item; i--)
370 diff -= descr->items[i].height;
371 }
372 else
373 {
374 for (i = index; i < descr->top_item; i++)
375 diff += descr->items[i].height;
376 }
377 }
378 else
379 diff = (descr->top_item - index) * descr->item_height;
380
381 if (abs(diff) < descr->height)
382 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
383 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
384 else
385 scroll = FALSE;
386 }
387 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
388 descr->top_item = index;
389 LISTBOX_UpdateScroll( descr );
390 return LB_OKAY;
391 }
392
393
394 /***********************************************************************
395 * LISTBOX_UpdatePage
396 *
397 * Update the page size. Should be called when the size of
398 * the client area or the item height changes.
399 */
400 static void LISTBOX_UpdatePage( LB_DESCR *descr )
401 {
402 INT page_size;
403
404 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
405 page_size = 1;
406 if (page_size == descr->page_size) return;
407 descr->page_size = page_size;
408 if (descr->style & LBS_MULTICOLUMN)
409 InvalidateRect( descr->self, NULL, TRUE );
410 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
411 }
412
413
414 /***********************************************************************
415 * LISTBOX_UpdateSize
416 *
417 * Update the size of the listbox. Should be called when the size of
418 * the client area changes.
419 */
420 static void LISTBOX_UpdateSize( LB_DESCR *descr )
421 {
422 RECT rect;
423
424 GetClientRect( descr->self, &rect );
425 descr->width = rect.right - rect.left;
426 descr->height = rect.bottom - rect.top;
427 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
428 {
429 INT remaining;
430 RECT rect;
431
432 GetWindowRect( descr->self, &rect );
433 if(descr->item_height != 0)
434 remaining = descr->height % descr->item_height;
435 else
436 remaining = 0;
437 if ((descr->height > descr->item_height) && remaining)
438 {
439 #ifndef __REACTOS__
440 if (is_old_app(hwnd))
441 { /* give a margin for error to 16 bits programs - if we need
442 less than the height of the nonclient area, round to the
443 *next* number of items */
444 int ncheight = rect.bottom - rect.top - descr->height;
445 if ((descr->item_height - remaining) <= ncheight)
446 remaining = remaining - descr->item_height;
447 }
448 #endif
449 TRACE("[%p]: changing height %d -> %d\n",
450 descr->self, descr->height, descr->height - remaining );
451 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
452 rect.bottom - rect.top - remaining,
453 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
454 return;
455 }
456 }
457 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
458 LISTBOX_UpdatePage( descr );
459 LISTBOX_UpdateScroll( descr );
460
461 /* Invalidate the focused item so it will be repainted correctly */
462 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
463 {
464 InvalidateRect( descr->self, &rect, FALSE );
465 }
466 }
467
468
469 /***********************************************************************
470 * LISTBOX_GetItemRect
471 *
472 * Get the rectangle enclosing an item, in listbox client coordinates.
473 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
474 */
475 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
476 {
477 /* Index <= 0 is legal even on empty listboxes */
478 if (index && (index >= descr->nb_items))
479 {
480 memset(rect, 0, sizeof(*rect));
481 SetLastError(ERROR_INVALID_INDEX);
482 return LB_ERR;
483 }
484 SetRect( rect, 0, 0, descr->width, descr->height );
485 if (descr->style & LBS_MULTICOLUMN)
486 {
487 INT col = (index / descr->page_size) -
488 (descr->top_item / descr->page_size);
489 rect->left += col * descr->column_width;
490 rect->right = rect->left + descr->column_width;
491 rect->top += (index % descr->page_size) * descr->item_height;
492 rect->bottom = rect->top + descr->item_height;
493 }
494 else if (descr->style & LBS_OWNERDRAWVARIABLE)
495 {
496 INT i;
497 rect->right += descr->horz_pos;
498 if ((index >= 0) && (index < descr->nb_items))
499 {
500 if (index < descr->top_item)
501 {
502 for (i = descr->top_item-1; i >= index; i--)
503 rect->top -= descr->items[i].height;
504 }
505 else
506 {
507 for (i = descr->top_item; i < index; i++)
508 rect->top += descr->items[i].height;
509 }
510 rect->bottom = rect->top + descr->items[index].height;
511
512 }
513 }
514 else
515 {
516 rect->top += (index - descr->top_item) * descr->item_height;
517 rect->bottom = rect->top + descr->item_height;
518 rect->right += descr->horz_pos;
519 }
520
521 return ((rect->left < descr->width) && (rect->right > 0) &&
522 (rect->top < descr->height) && (rect->bottom > 0));
523 }
524
525
526 /***********************************************************************
527 * LISTBOX_GetItemFromPoint
528 *
529 * Return the item nearest from point (x,y) (in client coordinates).
530 */
531 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
532 {
533 INT index = descr->top_item;
534
535 if (!descr->nb_items) return -1; /* No items */
536 if (descr->style & LBS_OWNERDRAWVARIABLE)
537 {
538 INT pos = 0;
539 if (y >= 0)
540 {
541 while (index < descr->nb_items)
542 {
543 if ((pos += descr->items[index].height) > y) break;
544 index++;
545 }
546 }
547 else
548 {
549 while (index > 0)
550 {
551 index--;
552 if ((pos -= descr->items[index].height) <= y) break;
553 }
554 }
555 }
556 else if (descr->style & LBS_MULTICOLUMN)
557 {
558 if (y >= descr->item_height * descr->page_size) return -1;
559 if (y >= 0) index += y / descr->item_height;
560 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
561 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
562 }
563 else
564 {
565 index += (y / descr->item_height);
566 }
567 if (index < 0) return 0;
568 if (index >= descr->nb_items) return -1;
569 return index;
570 }
571
572
573 /***********************************************************************
574 * LISTBOX_PaintItem
575 *
576 * Paint an item.
577 */
578 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc,
579 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
580 {
581 LB_ITEMDATA *item = NULL;
582 if (index < descr->nb_items) item = &descr->items[index];
583
584 if (IS_OWNERDRAW(descr))
585 {
586 DRAWITEMSTRUCT dis;
587 RECT r;
588 HRGN hrgn;
589
590 if (!item)
591 {
592 if (action == ODA_FOCUS)
593 DrawFocusRect( hdc, rect );
594 else
595 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
596 return;
597 }
598
599 /* some programs mess with the clipping region when
600 drawing the item, *and* restore the previous region
601 after they are done, so a region has better to exist
602 else everything ends clipped */
603 GetClientRect(descr->self, &r);
604 hrgn = CreateRectRgnIndirect(&r);
605 SelectClipRgn( hdc, hrgn);
606 DeleteObject( hrgn );
607
608 dis.CtlType = ODT_LISTBOX;
609 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
610 dis.hwndItem = descr->self;
611 dis.itemAction = action;
612 dis.hDC = hdc;
613 dis.itemID = index;
614 dis.itemState = 0;
615 if (item && item->selected) dis.itemState |= ODS_SELECTED;
616 if (!ignoreFocus && (descr->focus_item == index) &&
617 (descr->caret_on) &&
618 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
619 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
620 dis.itemData = item ? item->data : 0;
621 dis.rcItem = *rect;
622 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
623 descr->self, index, item ? debugstr_w(item->str) : "", action,
624 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
625 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
626 }
627 else
628 {
629 COLORREF oldText = 0, oldBk = 0;
630
631 if (action == ODA_FOCUS)
632 {
633 DrawFocusRect( hdc, rect );
634 return;
635 }
636 if (item && item->selected)
637 {
638 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
639 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
640 }
641
642 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
643 descr->self, index, item ? debugstr_w(item->str) : "", action,
644 rect->left, rect->top, rect->right, rect->bottom );
645 if (!item)
646 ExtTextOutW( hdc, rect->left + 1, rect->top,
647 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
648 else if (!(descr->style & LBS_USETABSTOPS))
649 ExtTextOutW( hdc, rect->left + 1, rect->top,
650 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
651 strlenW(item->str), NULL );
652 else
653 {
654 /* Output empty string to paint background in the full width. */
655 ExtTextOutW( hdc, rect->left + 1, rect->top,
656 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
657 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
658 item->str, strlenW(item->str),
659 descr->nb_tabs, descr->tabs, 0);
660 }
661 if (item && item->selected)
662 {
663 SetBkColor( hdc, oldBk );
664 SetTextColor( hdc, oldText );
665 }
666 if (!ignoreFocus && (descr->focus_item == index) &&
667 (descr->caret_on) &&
668 (descr->in_focus)) DrawFocusRect( hdc, rect );
669 }
670 }
671
672
673 /***********************************************************************
674 * LISTBOX_SetRedraw
675 *
676 * Change the redraw flag.
677 */
678 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
679 {
680 if (on)
681 {
682 if (!(descr->style & LBS_NOREDRAW)) return;
683 descr->style &= ~LBS_NOREDRAW;
684 if (descr->style & LBS_DISPLAYCHANGED)
685 { /* page was changed while setredraw false, refresh automatically */
686 InvalidateRect(descr->self, NULL, TRUE);
687 if ((descr->top_item + descr->page_size) > descr->nb_items)
688 { /* reset top of page if less than number of items/page */
689 descr->top_item = descr->nb_items - descr->page_size;
690 if (descr->top_item < 0) descr->top_item = 0;
691 }
692 descr->style &= ~LBS_DISPLAYCHANGED;
693 }
694 LISTBOX_UpdateScroll( descr );
695 }
696 else descr->style |= LBS_NOREDRAW;
697 }
698
699
700 /***********************************************************************
701 * LISTBOX_RepaintItem
702 *
703 * Repaint a single item synchronously.
704 */
705 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index,
706 UINT action )
707 {
708 HDC hdc;
709 RECT rect;
710 HFONT oldFont = 0;
711 HBRUSH hbrush, oldBrush = 0;
712
713 /* Do not repaint the item if the item is not visible */
714 if (!IsWindowVisible(descr->self)) return;
715 if (descr->style & LBS_NOREDRAW)
716 {
717 descr->style |= LBS_DISPLAYCHANGED;
718 return;
719 }
720 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
721 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
722 if (descr->font) oldFont = SelectObject( hdc, descr->font );
723 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
724 (WPARAM)hdc, (LPARAM)descr->self );
725 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
726 if (!IsWindowEnabled(descr->self))
727 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
728 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
729 LISTBOX_PaintItem( descr, hdc, &rect, index, action, FALSE );
730 if (oldFont) SelectObject( hdc, oldFont );
731 if (oldBrush) SelectObject( hdc, oldBrush );
732 ReleaseDC( descr->self, hdc );
733 }
734
735
736 /***********************************************************************
737 * LISTBOX_InitStorage
738 */
739 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
740 {
741 LB_ITEMDATA *item;
742
743 nb_items += LB_ARRAY_GRANULARITY - 1;
744 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
745 if (descr->items) {
746 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
747 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
748 nb_items * sizeof(LB_ITEMDATA));
749 }
750 else {
751 item = HeapAlloc( GetProcessHeap(), 0,
752 nb_items * sizeof(LB_ITEMDATA));
753 }
754
755 if (!item)
756 {
757 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
758 return LB_ERRSPACE;
759 }
760 descr->items = item;
761 return LB_OKAY;
762 }
763
764
765 /***********************************************************************
766 * LISTBOX_SetTabStops
767 */
768 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count,
769 LPINT tabs, BOOL short_ints )
770 {
771 INT i;
772
773 if (!(descr->style & LBS_USETABSTOPS))
774 {
775 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
776 return FALSE;
777 }
778
779 HeapFree( GetProcessHeap(), 0, descr->tabs );
780 if (!(descr->nb_tabs = count))
781 {
782 descr->tabs = NULL;
783 return TRUE;
784 }
785 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
786 descr->nb_tabs * sizeof(INT) )))
787 return FALSE;
788 #ifndef __REACTOS__
789 if (short_ints)
790 {
791 INT i;
792 LPINT16 p = (LPINT16)tabs;
793
794 TRACE("[%p]: settabstops ", hwnd );
795 for (i = 0; i < descr->nb_tabs; i++) {
796 descr->tabs[i] = *p++<<1; /* FIXME */
797 if (TRACE_ON(listbox)) TRACE("%hd ", descr->tabs[i]);
798 }
799 if (TRACE_ON(listbox)) TRACE("\n");
800 }
801 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
802 #else
803 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
804 #endif
805
806 /* convert into "dialog units"*/
807 for (i = 0; i < descr->nb_tabs; i++)
808 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
809
810 return TRUE;
811 }
812
813
814 /***********************************************************************
815 * LISTBOX_GetText
816 */
817 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
818 {
819 if ((index < 0) || (index >= descr->nb_items))
820 {
821 SetLastError(ERROR_INVALID_INDEX);
822 return LB_ERR;
823 }
824 if (HAS_STRINGS(descr))
825 {
826 if (!buffer)
827 {
828 DWORD len = strlenW(descr->items[index].str);
829 if( unicode )
830 return len;
831 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
832 NULL, 0, NULL, NULL );
833 }
834
835 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
836
837 if(unicode)
838 {
839 strcpyW( buffer, descr->items[index].str );
840 return strlenW(buffer);
841 }
842 else
843 {
844 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
845 }
846 } else {
847 if (buffer)
848 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
849 return sizeof(DWORD);
850 }
851 }
852
853 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
854 {
855 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
856 if (ret == CSTR_LESS_THAN)
857 return -1;
858 if (ret == CSTR_EQUAL)
859 return 0;
860 if (ret == CSTR_GREATER_THAN)
861 return 1;
862 return -1;
863 }
864
865 /***********************************************************************
866 * LISTBOX_FindStringPos
867 *
868 * Find the nearest string located before a given string in sort order.
869 * If 'exact' is TRUE, return an error if we don't get an exact match.
870 */
871 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
872 {
873 INT index, min, max, res = -1;
874
875 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
876 min = 0;
877 max = descr->nb_items;
878 while (min != max)
879 {
880 index = (min + max) / 2;
881 if (HAS_STRINGS(descr))
882 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
883 else
884 {
885 COMPAREITEMSTRUCT cis;
886 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
887
888 cis.CtlType = ODT_LISTBOX;
889 cis.CtlID = id;
890 cis.hwndItem = descr->self;
891 /* note that some application (MetaStock) expects the second item
892 * to be in the listbox */
893 cis.itemID1 = -1;
894 cis.itemData1 = (DWORD)str;
895 cis.itemID2 = index;
896 cis.itemData2 = descr->items[index].data;
897 cis.dwLocaleId = descr->locale;
898 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
899 }
900 if (!res) return index;
901 if (res < 0) max = index;
902 else min = index + 1;
903 }
904 return exact ? -1 : max;
905 }
906
907 /***********************************************************************
908 * LISTBOX_FindFileStrPos
909 *
910 * Find the nearest string located before a given string in directory
911 * sort order (i.e. first files, then directories, then drives).
912 */
913 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
914 {
915 INT min, max, res = -1;
916
917 if (!HAS_STRINGS(descr))
918 return LISTBOX_FindStringPos( descr, str, FALSE );
919 min = 0;
920 max = descr->nb_items;
921 while (min != max)
922 {
923 INT index = (min + max) / 2;
924 LPCWSTR p = descr->items[index].str;
925 if (*p == '[') /* drive or directory */
926 {
927 if (*str != '[') res = -1;
928 else if (p[1] == '-') /* drive */
929 {
930 if (str[1] == '-') res = str[2] - p[2];
931 else res = -1;
932 }
933 else /* directory */
934 {
935 if (str[1] == '-') res = 1;
936 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
937 }
938 }
939 else /* filename */
940 {
941 if (*str == '[') res = 1;
942 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
943 }
944 if (!res) return index;
945 if (res < 0) max = index;
946 else min = index + 1;
947 }
948 return max;
949 }
950
951
952 /***********************************************************************
953 * LISTBOX_FindString
954 *
955 * Find the item beginning with a given string.
956 */
957 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
958 {
959 INT i;
960 LB_ITEMDATA *item;
961
962 if (start >= descr->nb_items) start = -1;
963 item = descr->items + start + 1;
964 if (HAS_STRINGS(descr))
965 {
966 if (!str || ! str[0] ) return LB_ERR;
967 if (exact)
968 {
969 for (i = start + 1; i < descr->nb_items; i++, item++)
970 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
971 for (i = 0, item = descr->items; i <= start; i++, item++)
972 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
973 }
974 else
975 {
976 /* Special case for drives and directories: ignore prefix */
977 #define CHECK_DRIVE(item) \
978 if ((item)->str[0] == '[') \
979 { \
980 if (!strncmpiW( str, (item)->str+1, len )) return i; \
981 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
982 return i; \
983 }
984
985 INT len = strlenW(str);
986 for (i = start + 1; i < descr->nb_items; i++, item++)
987 {
988 if (!strncmpiW( str, item->str, len )) return i;
989 CHECK_DRIVE(item);
990 }
991 for (i = 0, item = descr->items; i <= start; i++, item++)
992 {
993 if (!strncmpiW( str, item->str, len )) return i;
994 CHECK_DRIVE(item);
995 }
996 #undef CHECK_DRIVE
997 }
998 }
999 else
1000 {
1001 if (exact && (descr->style & LBS_SORT))
1002 /* If sorted, use a WM_COMPAREITEM binary search */
1003 return LISTBOX_FindStringPos( descr, str, TRUE );
1004
1005 /* Otherwise use a linear search */
1006 for (i = start + 1; i < descr->nb_items; i++, item++)
1007 if (item->data == (ULONG_PTR)str) return i;
1008 for (i = 0, item = descr->items; i <= start; i++, item++)
1009 if (item->data == (ULONG_PTR)str) return i;
1010 }
1011 return LB_ERR;
1012 }
1013
1014
1015 /***********************************************************************
1016 * LISTBOX_GetSelCount
1017 */
1018 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
1019 {
1020 INT i, count;
1021 LB_ITEMDATA *item = descr->items;
1022
1023 if (!(descr->style & LBS_MULTIPLESEL) ||
1024 (descr->style & LBS_NOSEL))
1025 return LB_ERR;
1026 for (i = count = 0; i < descr->nb_items; i++, item++)
1027 if (item->selected) count++;
1028 return count;
1029 }
1030
1031
1032 #ifndef __REACTOS__
1033 /***********************************************************************
1034 * LISTBOX_GetSelItems16
1035 */
1036 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
1037 {
1038 INT i, count;
1039 LB_ITEMDATA *item = descr->items;
1040
1041 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1042 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1043 if (item->selected) array[count++] = (INT16)i;
1044 return count;
1045 }
1046 #endif
1047
1048
1049 /***********************************************************************
1050 * LISTBOX_GetSelItems
1051 */
1052 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
1053 {
1054 INT i, count;
1055 LB_ITEMDATA *item = descr->items;
1056
1057 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1058 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1059 if (item->selected) array[count++] = i;
1060 return count;
1061 }
1062
1063
1064 /***********************************************************************
1065 * LISTBOX_Paint
1066 */
1067 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1068 {
1069 INT i, col_pos = descr->page_size - 1;
1070 RECT rect;
1071 RECT focusRect = {-1, -1, -1, -1};
1072 HFONT oldFont = 0;
1073 HBRUSH hbrush, oldBrush = 0;
1074
1075 if (descr->style & LBS_NOREDRAW) return 0;
1076
1077 SetRect( &rect, 0, 0, descr->width, descr->height );
1078 if (descr->style & LBS_MULTICOLUMN)
1079 rect.right = rect.left + descr->column_width;
1080 else if (descr->horz_pos)
1081 {
1082 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1083 rect.right += descr->horz_pos;
1084 }
1085
1086 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1087 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1088 (WPARAM)hdc, (LPARAM)descr->self );
1089 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1090 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1091
1092 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1093 (descr->in_focus))
1094 {
1095 /* Special case for empty listbox: paint focus rect */
1096 rect.bottom = rect.top + descr->item_height;
1097 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1098 &rect, NULL, 0, NULL );
1099 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item,
1100 ODA_FOCUS, FALSE );
1101 rect.top = rect.bottom;
1102 }
1103
1104 /* Paint all the item, regarding the selection
1105 Focus state will be painted after */
1106
1107 for (i = descr->top_item; i < descr->nb_items; i++)
1108 {
1109 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1110 rect.bottom = rect.top + descr->item_height;
1111 else
1112 rect.bottom = rect.top + descr->items[i].height;
1113
1114 if (i == descr->focus_item)
1115 {
1116 /* keep the focus rect, to paint the focus item after */
1117 focusRect.left = rect.left;
1118 focusRect.right = rect.right;
1119 focusRect.top = rect.top;
1120 focusRect.bottom = rect.bottom;
1121 }
1122 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1123 rect.top = rect.bottom;
1124
1125 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1126 {
1127 if (!IS_OWNERDRAW(descr))
1128 {
1129 /* Clear the bottom of the column */
1130 if (rect.top < descr->height)
1131 {
1132 rect.bottom = descr->height;
1133 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1134 &rect, NULL, 0, NULL );
1135 }
1136 }
1137
1138 /* Go to the next column */
1139 rect.left += descr->column_width;
1140 rect.right += descr->column_width;
1141 rect.top = 0;
1142 col_pos = descr->page_size - 1;
1143 }
1144 else
1145 {
1146 col_pos--;
1147 if (rect.top >= descr->height) break;
1148 }
1149 }
1150
1151 /* Paint the focus item now */
1152 if (focusRect.top != focusRect.bottom &&
1153 descr->caret_on && descr->in_focus)
1154 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1155
1156 if (!IS_OWNERDRAW(descr))
1157 {
1158 /* Clear the remainder of the client area */
1159 if (rect.top < descr->height)
1160 {
1161 rect.bottom = descr->height;
1162 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1163 &rect, NULL, 0, NULL );
1164 }
1165 if (rect.right < descr->width)
1166 {
1167 rect.left = rect.right;
1168 rect.right = descr->width;
1169 rect.top = 0;
1170 rect.bottom = descr->height;
1171 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1172 &rect, NULL, 0, NULL );
1173 }
1174 }
1175 if (oldFont) SelectObject( hdc, oldFont );
1176 if (oldBrush) SelectObject( hdc, oldBrush );
1177 return 0;
1178 }
1179
1180
1181 /***********************************************************************
1182 * LISTBOX_InvalidateItems
1183 *
1184 * Invalidate all items from a given item. If the specified item is not
1185 * visible, nothing happens.
1186 */
1187 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1188 {
1189 RECT rect;
1190
1191 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1192 {
1193 if (descr->style & LBS_NOREDRAW)
1194 {
1195 descr->style |= LBS_DISPLAYCHANGED;
1196 return;
1197 }
1198 rect.bottom = descr->height;
1199 InvalidateRect( descr->self, &rect, TRUE );
1200 if (descr->style & LBS_MULTICOLUMN)
1201 {
1202 /* Repaint the other columns */
1203 rect.left = rect.right;
1204 rect.right = descr->width;
1205 rect.top = 0;
1206 InvalidateRect( descr->self, &rect, TRUE );
1207 }
1208 }
1209 }
1210
1211 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1212 {
1213 RECT rect;
1214
1215 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1216 InvalidateRect( descr->self, &rect, TRUE );
1217 }
1218
1219 /***********************************************************************
1220 * LISTBOX_GetItemHeight
1221 */
1222 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1223 {
1224 if (descr->style & LBS_OWNERDRAWVARIABLE)
1225 {
1226 if ((index < 0) || (index >= descr->nb_items))
1227 {
1228 SetLastError(ERROR_INVALID_INDEX);
1229 return LB_ERR;
1230 }
1231 return descr->items[index].height;
1232 }
1233 else return descr->item_height;
1234 }
1235
1236
1237 /***********************************************************************
1238 * LISTBOX_SetItemHeight
1239 */
1240 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index,
1241 INT height, BOOL repaint )
1242 {
1243 if (!height) height = 1;
1244
1245 if (descr->style & LBS_OWNERDRAWVARIABLE)
1246 {
1247 if ((index < 0) || (index >= descr->nb_items))
1248 {
1249 SetLastError(ERROR_INVALID_INDEX);
1250 return LB_ERR;
1251 }
1252 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1253 descr->items[index].height = height;
1254 LISTBOX_UpdateScroll( descr );
1255 if (repaint)
1256 LISTBOX_InvalidateItems( descr, index );
1257 }
1258 else if (height != descr->item_height)
1259 {
1260 TRACE("[%p]: new height = %d\n", descr->self, height );
1261 descr->item_height = height;
1262 LISTBOX_UpdatePage( descr );
1263 LISTBOX_UpdateScroll( descr );
1264 if (repaint)
1265 InvalidateRect( descr->self, 0, TRUE );
1266 }
1267 return LB_OKAY;
1268 }
1269
1270
1271 /***********************************************************************
1272 * LISTBOX_SetHorizontalPos
1273 */
1274 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1275 {
1276 INT diff;
1277
1278 if (pos > descr->horz_extent - descr->width)
1279 pos = descr->horz_extent - descr->width;
1280 if (pos < 0) pos = 0;
1281 if (!(diff = descr->horz_pos - pos)) return;
1282 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1283 descr->horz_pos = pos;
1284 LISTBOX_UpdateScroll( descr );
1285 if (abs(diff) < descr->width)
1286 {
1287 RECT rect;
1288 /* Invalidate the focused item so it will be repainted correctly */
1289 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1290 InvalidateRect( descr->self, &rect, TRUE );
1291 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1292 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1293 }
1294 else
1295 InvalidateRect( descr->self, NULL, TRUE );
1296 }
1297
1298
1299 /***********************************************************************
1300 * LISTBOX_SetHorizontalExtent
1301 */
1302 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr,
1303 INT extent )
1304 {
1305 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1306 return LB_OKAY;
1307 if (extent <= 0) extent = 1;
1308 if (extent == descr->horz_extent) return LB_OKAY;
1309 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1310 descr->horz_extent = extent;
1311 if (descr->horz_pos > extent - descr->width)
1312 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1313 else
1314 LISTBOX_UpdateScroll( descr );
1315 return LB_OKAY;
1316 }
1317
1318
1319 /***********************************************************************
1320 * LISTBOX_SetColumnWidth
1321 */
1322 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1323 {
1324 if (width == descr->column_width) return LB_OKAY;
1325 TRACE("[%p]: new column width = %d\n", descr->self, width );
1326 descr->column_width = width;
1327 LISTBOX_UpdatePage( descr );
1328 return LB_OKAY;
1329 }
1330
1331
1332 /***********************************************************************
1333 * LISTBOX_SetFont
1334 *
1335 * Returns the item height.
1336 */
1337 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1338 {
1339 HDC hdc;
1340 HFONT oldFont = 0;
1341 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1342 SIZE sz;
1343
1344 descr->font = font;
1345
1346 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1347 {
1348 ERR("unable to get DC.\n" );
1349 return 16;
1350 }
1351 if (font) oldFont = SelectObject( hdc, font );
1352 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1353 if (oldFont) SelectObject( hdc, oldFont );
1354 ReleaseDC( descr->self, hdc );
1355
1356 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1357 if (!IS_OWNERDRAW(descr))
1358 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1359 return sz.cy;
1360 }
1361
1362
1363 /***********************************************************************
1364 * LISTBOX_MakeItemVisible
1365 *
1366 * Make sure that a given item is partially or fully visible.
1367 */
1368 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index,
1369 BOOL fully )
1370 {
1371 INT top;
1372
1373 if (index <= descr->top_item) top = index;
1374 else if (descr->style & LBS_MULTICOLUMN)
1375 {
1376 INT cols = descr->width;
1377 if (!fully) cols += descr->column_width - 1;
1378 if (cols >= descr->column_width) cols /= descr->column_width;
1379 else cols = 1;
1380 if (index < descr->top_item + (descr->page_size * cols)) return;
1381 top = index - descr->page_size * (cols - 1);
1382 }
1383 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1384 {
1385 INT height = fully ? descr->items[index].height : 1;
1386 for (top = index; top > descr->top_item; top--)
1387 if ((height += descr->items[top-1].height) > descr->height) break;
1388 }
1389 else
1390 {
1391 if (index < descr->top_item + descr->page_size) return;
1392 if (!fully && (index == descr->top_item + descr->page_size) &&
1393 (descr->height > (descr->page_size * descr->item_height))) return;
1394 top = index - descr->page_size + 1;
1395 }
1396 LISTBOX_SetTopItem( descr, top, TRUE );
1397 }
1398
1399 /***********************************************************************
1400 * LISTBOX_SetCaretIndex
1401 *
1402 * NOTES
1403 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1404 *
1405 */
1406 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index,
1407 BOOL fully_visible )
1408 {
1409 INT oldfocus = descr->focus_item;
1410
1411 if (descr->style & LBS_NOSEL) return LB_ERR;
1412 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1413 if (index == oldfocus) return LB_OKAY;
1414 descr->focus_item = index;
1415 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1416 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1417
1418 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1419 if (descr->caret_on && (descr->in_focus))
1420 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1421
1422 return LB_OKAY;
1423 }
1424
1425
1426 /***********************************************************************
1427 * LISTBOX_SelectItemRange
1428 *
1429 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1430 */
1431 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1432 INT last, BOOL on )
1433 {
1434 INT i;
1435
1436 /* A few sanity checks */
1437
1438 if (descr->style & LBS_NOSEL) return LB_ERR;
1439 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1440 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1441 if (last == -1) last = descr->nb_items - 1;
1442 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1443 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1444
1445 if (on) /* Turn selection on */
1446 {
1447 for (i = first; i <= last; i++)
1448 {
1449 if (descr->items[i].selected) continue;
1450 descr->items[i].selected = TRUE;
1451 LISTBOX_InvalidateItemRect(descr, i);
1452 }
1453 }
1454 else /* Turn selection off */
1455 {
1456 for (i = first; i <= last; i++)
1457 {
1458 if (!descr->items[i].selected) continue;
1459 descr->items[i].selected = FALSE;
1460 LISTBOX_InvalidateItemRect(descr, i);
1461 }
1462 }
1463 return LB_OKAY;
1464 }
1465
1466 /***********************************************************************
1467 * LISTBOX_SetSelection
1468 */
1469 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1470 BOOL on, BOOL send_notify )
1471 {
1472 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1473
1474 if (descr->style & LBS_NOSEL)
1475 {
1476 descr->selected_item = index;
1477 return LB_ERR;
1478 }
1479 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1480 if (descr->style & LBS_MULTIPLESEL)
1481 {
1482 if (index == -1) /* Select all items */
1483 return LISTBOX_SelectItemRange( descr, 0, -1, on );
1484 else /* Only one item */
1485 return LISTBOX_SelectItemRange( descr, index, index, on );
1486 }
1487 else
1488 {
1489 INT oldsel = descr->selected_item;
1490 if (index == oldsel) return LB_OKAY;
1491 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1492 if (index != -1) descr->items[index].selected = TRUE;
1493 descr->selected_item = index;
1494 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1495 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1496 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1497 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1498 else
1499 if( descr->lphc ) /* set selection change flag for parent combo */
1500 descr->lphc->wState |= CBF_SELCHANGE;
1501 }
1502 return LB_OKAY;
1503 }
1504
1505
1506 /***********************************************************************
1507 * LISTBOX_MoveCaret
1508 *
1509 * Change the caret position and extend the selection to the new caret.
1510 */
1511 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index,
1512 BOOL fully_visible )
1513 {
1514 INT oldfocus = descr->focus_item;
1515
1516 if ((index < 0) || (index >= descr->nb_items))
1517 return;
1518
1519 /* Important, repaint needs to be done in this order if
1520 you want to mimic Windows behavior:
1521 1. Remove the focus and paint the item
1522 2. Remove the selection and paint the item(s)
1523 3. Set the selection and repaint the item(s)
1524 4. Set the focus to 'index' and repaint the item */
1525
1526 /* 1. remove the focus and repaint the item */
1527 descr->focus_item = -1;
1528 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1529 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1530
1531 /* 2. then turn off the previous selection */
1532 /* 3. repaint the new selected item */
1533 if (descr->style & LBS_EXTENDEDSEL)
1534 {
1535 if (descr->anchor_item != -1)
1536 {
1537 INT first = min( index, descr->anchor_item );
1538 INT last = max( index, descr->anchor_item );
1539 if (first > 0)
1540 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1541 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1542 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1543 }
1544 }
1545 else if (!(descr->style & LBS_MULTIPLESEL))
1546 {
1547 /* Set selection to new caret item */
1548 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1549 }
1550
1551 /* 4. repaint the new item with the focus */
1552 descr->focus_item = index;
1553 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1554 if (descr->caret_on && (descr->in_focus))
1555 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1556 }
1557
1558
1559 /***********************************************************************
1560 * LISTBOX_InsertItem
1561 */
1562 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1563 LPWSTR str, DWORD data )
1564 {
1565 LB_ITEMDATA *item;
1566 INT max_items;
1567 INT oldfocus = descr->focus_item;
1568
1569 if (index == -1) index = descr->nb_items;
1570 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1571 if (!descr->items) max_items = 0;
1572 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1573 if (descr->nb_items == max_items)
1574 {
1575 /* We need to grow the array */
1576 max_items += LB_ARRAY_GRANULARITY;
1577 if (descr->items)
1578 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1579 max_items * sizeof(LB_ITEMDATA) );
1580 else
1581 item = HeapAlloc( GetProcessHeap(), 0,
1582 max_items * sizeof(LB_ITEMDATA) );
1583 if (!item)
1584 {
1585 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1586 return LB_ERRSPACE;
1587 }
1588 descr->items = item;
1589 }
1590
1591 /* Insert the item structure */
1592
1593 item = &descr->items[index];
1594 if (index < descr->nb_items)
1595 RtlMoveMemory( item + 1, item,
1596 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1597 item->str = str;
1598 item->data = data;
1599 item->height = 0;
1600 item->selected = FALSE;
1601 descr->nb_items++;
1602
1603 /* Get item height */
1604
1605 if (descr->style & LBS_OWNERDRAWVARIABLE)
1606 {
1607 MEASUREITEMSTRUCT mis;
1608 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1609
1610 mis.CtlType = ODT_LISTBOX;
1611 mis.CtlID = id;
1612 mis.itemID = index;
1613 mis.itemData = descr->items[index].data;
1614 mis.itemHeight = descr->item_height;
1615 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1616 item->height = mis.itemHeight ? mis.itemHeight : 1;
1617 TRACE("[%p]: measure item %d (%s) = %d\n",
1618 descr->self, index, str ? debugstr_w(str) : "", item->height );
1619 }
1620
1621 /* Repaint the items */
1622
1623 LISTBOX_UpdateScroll( descr );
1624 LISTBOX_InvalidateItems( descr, index );
1625
1626 /* Move selection and focused item */
1627 /* If listbox was empty, set focus to the first item */
1628 if (descr->nb_items == 1)
1629 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1630 /* single select don't change selection index in win31 */
1631 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1632 {
1633 descr->selected_item++;
1634 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1635 }
1636 else
1637 {
1638 if (index <= descr->selected_item)
1639 {
1640 descr->selected_item++;
1641 descr->focus_item = oldfocus; /* focus not changed */
1642 }
1643 }
1644 return LB_OKAY;
1645 }
1646
1647
1648 /***********************************************************************
1649 * LISTBOX_InsertString
1650 */
1651 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index,
1652 LPCWSTR str )
1653 {
1654 LPWSTR new_str = NULL;
1655 DWORD data = 0;
1656 LRESULT ret;
1657
1658 if (HAS_STRINGS(descr))
1659 {
1660 static const WCHAR empty_stringW[] = { 0 };
1661 if (!str) str = empty_stringW;
1662 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1663 {
1664 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1665 return LB_ERRSPACE;
1666 }
1667 strcpyW(new_str, str);
1668 }
1669 else data = (DWORD)str;
1670
1671 if (index == -1) index = descr->nb_items;
1672 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1673 {
1674 HeapFree( GetProcessHeap(), 0, new_str );
1675 return ret;
1676 }
1677
1678 TRACE("[%p]: added item %d %s\n",
1679 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1680 return index;
1681 }
1682
1683
1684 /***********************************************************************
1685 * LISTBOX_DeleteItem
1686 *
1687 * Delete the content of an item. 'index' must be a valid index.
1688 */
1689 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1690 {
1691 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1692 * while Win95 sends it for all items with user data.
1693 * It's probably better to send it too often than not
1694 * often enough, so this is what we do here.
1695 */
1696 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1697 {
1698 DELETEITEMSTRUCT dis;
1699 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1700
1701 dis.CtlType = ODT_LISTBOX;
1702 dis.CtlID = id;
1703 dis.itemID = index;
1704 dis.hwndItem = descr->self;
1705 dis.itemData = descr->items[index].data;
1706 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1707 }
1708 if (HAS_STRINGS(descr) && descr->items[index].str)
1709 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1710 }
1711
1712
1713 /***********************************************************************
1714 * LISTBOX_RemoveItem
1715 *
1716 * Remove an item from the listbox and delete its content.
1717 */
1718 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1719 {
1720 LB_ITEMDATA *item;
1721 INT max_items;
1722
1723 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1724
1725 /* We need to invalidate the original rect instead of the updated one. */
1726 LISTBOX_InvalidateItems( descr, index );
1727
1728 LISTBOX_DeleteItem( descr, index );
1729
1730 /* Remove the item */
1731
1732 item = &descr->items[index];
1733 if (index < descr->nb_items-1)
1734 RtlMoveMemory( item, item + 1,
1735 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1736 descr->nb_items--;
1737 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1738
1739 /* Shrink the item array if possible */
1740
1741 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1742 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1743 {
1744 max_items -= LB_ARRAY_GRANULARITY;
1745 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1746 max_items * sizeof(LB_ITEMDATA) );
1747 if (item) descr->items = item;
1748 }
1749 /* Repaint the items */
1750
1751 LISTBOX_UpdateScroll( descr );
1752 /* if we removed the scrollbar, reset the top of the list
1753 (correct for owner-drawn ???) */
1754 if (descr->nb_items == descr->page_size)
1755 LISTBOX_SetTopItem( descr, 0, TRUE );
1756
1757 /* Move selection and focused item */
1758 if (!IS_MULTISELECT(descr))
1759 {
1760 if (index == descr->selected_item)
1761 descr->selected_item = -1;
1762 else if (index < descr->selected_item)
1763 {
1764 descr->selected_item--;
1765 if (ISWIN31) /* win 31 do not change the selected item number */
1766 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1767 }
1768 }
1769
1770 if (descr->focus_item >= descr->nb_items)
1771 {
1772 descr->focus_item = descr->nb_items - 1;
1773 if (descr->focus_item < 0) descr->focus_item = 0;
1774 }
1775 return LB_OKAY;
1776 }
1777
1778
1779 /***********************************************************************
1780 * LISTBOX_ResetContent
1781 */
1782 static void LISTBOX_ResetContent( LB_DESCR *descr )
1783 {
1784 INT i;
1785
1786 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( descr, i );
1787 HeapFree( GetProcessHeap(), 0, descr->items );
1788 descr->nb_items = 0;
1789 descr->top_item = 0;
1790 descr->selected_item = -1;
1791 descr->focus_item = 0;
1792 descr->anchor_item = -1;
1793 descr->items = NULL;
1794 }
1795
1796
1797 /***********************************************************************
1798 * LISTBOX_SetCount
1799 */
1800 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1801 {
1802 LRESULT ret;
1803
1804 if (HAS_STRINGS(descr))
1805 {
1806 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1807 return LB_ERR;
1808 }
1809
1810 /* FIXME: this is far from optimal... */
1811 if (count > descr->nb_items)
1812 {
1813 while (count > descr->nb_items)
1814 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1815 return ret;
1816 }
1817 else if (count < descr->nb_items)
1818 {
1819 while (count < descr->nb_items)
1820 if ((ret = LISTBOX_RemoveItem( descr, -1 )) < 0)
1821 return ret;
1822 }
1823 return LB_OKAY;
1824 }
1825
1826
1827 /***********************************************************************
1828 * LISTBOX_Directory
1829 */
1830 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1831 LPCWSTR filespec, BOOL long_names )
1832 {
1833 HANDLE handle;
1834 LRESULT ret = LB_OKAY;
1835 WIN32_FIND_DATAW entry;
1836 int pos;
1837
1838 /* don't scan directory if we just want drives exclusively */
1839 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1840 /* scan directory */
1841 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1842 {
1843 int le = GetLastError();
1844 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1845 }
1846 else
1847 {
1848 do
1849 {
1850 WCHAR buffer[270];
1851 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1852 {
1853 static const WCHAR bracketW[] = { ']',0 };
1854 static const WCHAR dotW[] = { '.',0 };
1855 if (!(attrib & DDL_DIRECTORY) ||
1856 !strcmpW( entry.cFileName, dotW )) continue;
1857 buffer[0] = '[';
1858 if (!long_names && entry.cAlternateFileName[0])
1859 strcpyW( buffer + 1, entry.cAlternateFileName );
1860 else
1861 strcpyW( buffer + 1, entry.cFileName );
1862 strcatW(buffer, bracketW);
1863 }
1864 else /* not a directory */
1865 {
1866 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1867 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1868
1869 if ((attrib & DDL_EXCLUSIVE) &&
1870 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1871 continue;
1872 #undef ATTRIBS
1873 if (!long_names && entry.cAlternateFileName[0])
1874 strcpyW( buffer, entry.cAlternateFileName );
1875 else
1876 strcpyW( buffer, entry.cFileName );
1877 }
1878 if (!long_names) CharLowerW( buffer );
1879 pos = LISTBOX_FindFileStrPos( descr, buffer );
1880 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1881 break;
1882 } while (FindNextFileW( handle, &entry ));
1883 FindClose( handle );
1884 }
1885 }
1886
1887 /* scan drives */
1888 if ((ret >= 0) && (attrib & DDL_DRIVES))
1889 {
1890 WCHAR buffer[] = {'[','-','a','-',']',0};
1891 WCHAR root[] = {'A',':','\\',0};
1892 int drive;
1893 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1894 {
1895 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1896 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1897 break;
1898 }
1899 }
1900 return ret;
1901 }
1902
1903
1904 /***********************************************************************
1905 * LISTBOX_HandleVScroll
1906 */
1907 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1908 {
1909 SCROLLINFO info;
1910
1911 if (descr->style & LBS_MULTICOLUMN) return 0;
1912 switch(scrollReq)
1913 {
1914 case SB_LINEUP:
1915 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1916 break;
1917 case SB_LINEDOWN:
1918 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1919 break;
1920 case SB_PAGEUP:
1921 LISTBOX_SetTopItem( descr, descr->top_item -
1922 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1923 break;
1924 case SB_PAGEDOWN:
1925 LISTBOX_SetTopItem( descr, descr->top_item +
1926 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1927 break;
1928 case SB_THUMBPOSITION:
1929 LISTBOX_SetTopItem( descr, pos, TRUE );
1930 break;
1931 case SB_THUMBTRACK:
1932 info.cbSize = sizeof(info);
1933 info.fMask = SIF_TRACKPOS;
1934 GetScrollInfo( descr->self, SB_VERT, &info );
1935 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1936 break;
1937 case SB_TOP:
1938 LISTBOX_SetTopItem( descr, 0, TRUE );
1939 break;
1940 case SB_BOTTOM:
1941 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1942 break;
1943 }
1944 return 0;
1945 }
1946
1947
1948 /***********************************************************************
1949 * LISTBOX_HandleHScroll
1950 */
1951 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1952 {
1953 SCROLLINFO info;
1954 INT page;
1955
1956 if (descr->style & LBS_MULTICOLUMN)
1957 {
1958 switch(scrollReq)
1959 {
1960 case SB_LINELEFT:
1961 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1962 TRUE );
1963 break;
1964 case SB_LINERIGHT:
1965 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1966 TRUE );
1967 break;
1968 case SB_PAGELEFT:
1969 page = descr->width / descr->column_width;
1970 if (page < 1) page = 1;
1971 LISTBOX_SetTopItem( descr,
1972 descr->top_item - page * descr->page_size, TRUE );
1973 break;
1974 case SB_PAGERIGHT:
1975 page = descr->width / descr->column_width;
1976 if (page < 1) page = 1;
1977 LISTBOX_SetTopItem( descr,
1978 descr->top_item + page * descr->page_size, TRUE );
1979 break;
1980 case SB_THUMBPOSITION:
1981 LISTBOX_SetTopItem( descr, pos*descr->page_size,
1982 TRUE );
1983 break;
1984 case SB_THUMBTRACK:
1985 info.cbSize = sizeof(info);
1986 info.fMask = SIF_TRACKPOS;
1987 GetScrollInfo( descr->self, SB_VERT, &info );
1988 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1989 TRUE );
1990 break;
1991 case SB_LEFT:
1992 LISTBOX_SetTopItem( descr, 0, TRUE );
1993 break;
1994 case SB_RIGHT:
1995 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1996 break;
1997 }
1998 }
1999 else if (descr->horz_extent)
2000 {
2001 switch(scrollReq)
2002 {
2003 case SB_LINELEFT:
2004 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2005 break;
2006 case SB_LINERIGHT:
2007 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2008 break;
2009 case SB_PAGELEFT:
2010 LISTBOX_SetHorizontalPos( descr,
2011 descr->horz_pos - descr->width );
2012 break;
2013 case SB_PAGERIGHT:
2014 LISTBOX_SetHorizontalPos( descr,
2015 descr->horz_pos + descr->width );
2016 break;
2017 case SB_THUMBPOSITION:
2018 LISTBOX_SetHorizontalPos( descr, pos );
2019 break;
2020 case SB_THUMBTRACK:
2021 info.cbSize = sizeof(info);
2022 info.fMask = SIF_TRACKPOS;
2023 GetScrollInfo( descr->self, SB_HORZ, &info );
2024 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2025 break;
2026 case SB_LEFT:
2027 LISTBOX_SetHorizontalPos( descr, 0 );
2028 break;
2029 case SB_RIGHT:
2030 LISTBOX_SetHorizontalPos( descr,
2031 descr->horz_extent - descr->width );
2032 break;
2033 }
2034 }
2035 return 0;
2036 }
2037
2038 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2039 {
2040 short gcWheelDelta = 0;
2041 UINT pulScrollLines = 3;
2042
2043 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2044
2045 gcWheelDelta -= delta;
2046
2047 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2048 {
2049 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2050 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2051 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2052 }
2053 return 0;
2054 }
2055
2056 /***********************************************************************
2057 * LISTBOX_HandleLButtonDown
2058 */
2059 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr,
2060 DWORD keys, INT x, INT y )
2061 {
2062 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2063 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
2064 if (!descr->caret_on && (descr->in_focus)) return 0;
2065
2066 if (!descr->in_focus)
2067 {
2068 if( !descr->lphc ) SetFocus( descr->self );
2069 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2070 }
2071
2072 if (index == -1) return 0;
2073
2074 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2075 {
2076 /* we should perhaps make sure that all items are deselected
2077 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2078 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2079 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2080 */
2081
2082 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2083 if (keys & MK_CONTROL)
2084 {
2085 LISTBOX_SetCaretIndex( descr, index, FALSE );
2086 LISTBOX_SetSelection( descr, index,
2087 !descr->items[index].selected,
2088 (descr->style & LBS_NOTIFY) != 0);
2089 }
2090 else
2091 {
2092 LISTBOX_MoveCaret( descr, index, FALSE );
2093
2094 if (descr->style & LBS_EXTENDEDSEL)
2095 {
2096 LISTBOX_SetSelection( descr, index,
2097 descr->items[index].selected,
2098 (descr->style & LBS_NOTIFY) != 0 );
2099 }
2100 else
2101 {
2102 LISTBOX_SetSelection( descr, index,
2103 !descr->items[index].selected,
2104 (descr->style & LBS_NOTIFY) != 0 );
2105 }
2106 }
2107 }
2108 else
2109 {
2110 descr->anchor_item = index;
2111 LISTBOX_MoveCaret( descr, index, FALSE );
2112 LISTBOX_SetSelection( descr, index,
2113 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2114 }
2115
2116 descr->captured = TRUE;
2117 SetCapture( descr->self );
2118
2119 if (!descr->lphc)
2120 {
2121 if (descr->style & LBS_NOTIFY )
2122 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2123 MAKELPARAM( x, y ) );
2124 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2125 {
2126 POINT pt;
2127
2128 pt.x = x;
2129 pt.y = y;
2130
2131 if (DragDetect( descr->self, pt ))
2132 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2133 }
2134 }
2135 return 0;
2136 }
2137
2138
2139 /*************************************************************************
2140 * LISTBOX_HandleLButtonDownCombo [Internal]
2141 *
2142 * Process LButtonDown message for the ComboListBox
2143 *
2144 nn * PARAMS
2145 * pWnd [I] The windows internal structure
2146 * pDescr [I] The ListBox internal structure
2147 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2148 * x [I] X Mouse Coordinate
2149 * y [I] Y Mouse Coordinate
2150 *
2151 * RETURNS
2152 * 0 since we are processing the WM_LBUTTONDOWN Message
2153 *
2154 * NOTES
2155 * This function is only to be used when a ListBox is a ComboListBox
2156 */
2157
2158 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *pDescr,
2159 UINT msg, DWORD keys, INT x, INT y)
2160 {
2161 RECT clientRect, screenRect;
2162 POINT mousePos;
2163
2164 mousePos.x = x;
2165 mousePos.y = y;
2166
2167 GetClientRect(pDescr->self, &clientRect);
2168
2169 if(PtInRect(&clientRect, mousePos))
2170 {
2171 /* MousePos is in client, resume normal processing */
2172 if (msg == WM_LBUTTONDOWN)
2173 {
2174 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2175 return LISTBOX_HandleLButtonDown( pDescr, keys, x, y);
2176 }
2177 else if (pDescr->style & LBS_NOTIFY)
2178 SEND_NOTIFICATION( pDescr, LBN_DBLCLK );
2179 }
2180 else
2181 {
2182 POINT screenMousePos;
2183 HWND hWndOldCapture;
2184
2185 /* Check the Non-Client Area */
2186 screenMousePos = mousePos;
2187 hWndOldCapture = GetCapture();
2188 ReleaseCapture();
2189 GetWindowRect(pDescr->self, &screenRect);
2190 ClientToScreen(pDescr->self, &screenMousePos);
2191
2192 if(!PtInRect(&screenRect, screenMousePos))
2193 {
2194 LISTBOX_SetCaretIndex( pDescr, pDescr->lphc->droppedIndex, FALSE );
2195 LISTBOX_SetSelection( pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2196 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2197 }
2198 else
2199 {
2200 /* Check to see the NC is a scrollbar */
2201 INT nHitTestType=0;
2202 LONG style = GetWindowLongW( pDescr->self, GWL_STYLE );
2203 /* Check Vertical scroll bar */
2204 if (style & WS_VSCROLL)
2205 {
2206
2207 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2208 if (PtInRect( &clientRect, mousePos ))
2209 nHitTestType = HTVSCROLL;
2210 }
2211 /* Check horizontal scroll bar */
2212 if (style & WS_HSCROLL)
2213 {
2214 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2215 if (PtInRect( &clientRect, mousePos ))
2216 nHitTestType = HTHSCROLL;
2217 }
2218 /* Windows sends this message when a scrollbar is clicked
2219 */
2220
2221 if(nHitTestType != 0)
2222 {
2223 SendMessageW(pDescr->self, WM_NCLBUTTONDOWN, nHitTestType,
2224 MAKELONG(screenMousePos.x, screenMousePos.y));
2225 }
2226 /* Resume the Capture after scrolling is complete
2227 */
2228 if(hWndOldCapture != 0)
2229 SetCapture(hWndOldCapture);
2230 }
2231 }
2232 return 0;
2233 }
2234
2235 /***********************************************************************
2236 * LISTBOX_HandleLButtonUp
2237 */
2238 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2239 {
2240 if (LISTBOX_Timer != LB_TIMER_NONE)
2241 KillSystemTimer( descr->self, LB_TIMER_ID );
2242 LISTBOX_Timer = LB_TIMER_NONE;
2243 if (descr->captured)
2244 {
2245 descr->captured = FALSE;
2246 if (GetCapture() == descr->self) ReleaseCapture();
2247 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2248 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2249 }
2250 return 0;
2251 }
2252
2253
2254 /***********************************************************************
2255 * LISTBOX_HandleTimer
2256 *
2257 * Handle scrolling upon a timer event.
2258 * Return TRUE if scrolling should continue.
2259 */
2260 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr,
2261 INT index, TIMER_DIRECTION dir )
2262 {
2263 switch(dir)
2264 {
2265 case LB_TIMER_UP:
2266 if (descr->top_item) index = descr->top_item - 1;
2267 else index = 0;
2268 break;
2269 case LB_TIMER_LEFT:
2270 if (descr->top_item) index -= descr->page_size;
2271 break;
2272 case LB_TIMER_DOWN:
2273 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2274 if (index == descr->focus_item) index++;
2275 if (index >= descr->nb_items) index = descr->nb_items - 1;
2276 break;
2277 case LB_TIMER_RIGHT:
2278 if (index + descr->page_size < descr->nb_items)
2279 index += descr->page_size;
2280 break;
2281 case LB_TIMER_NONE:
2282 break;
2283 }
2284 if (index == descr->focus_item) return FALSE;
2285 LISTBOX_MoveCaret( descr, index, FALSE );
2286 return TRUE;
2287 }
2288
2289
2290 /***********************************************************************
2291 * LISTBOX_HandleSystemTimer
2292 *
2293 * WM_SYSTIMER handler.
2294 */
2295 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2296 {
2297 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2298 {
2299 KillSystemTimer( descr->self, LB_TIMER_ID );
2300 LISTBOX_Timer = LB_TIMER_NONE;
2301 }
2302 return 0;
2303 }
2304
2305
2306 /***********************************************************************
2307 * LISTBOX_HandleMouseMove
2308 *
2309 * WM_MOUSEMOVE handler.
2310 */
2311 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2312 INT x, INT y )
2313 {
2314 INT index;
2315 TIMER_DIRECTION dir = LB_TIMER_NONE;
2316
2317 if (!descr->captured) return;
2318
2319 if (descr->style & LBS_MULTICOLUMN)
2320 {
2321 if (y < 0) y = 0;
2322 else if (y >= descr->item_height * descr->page_size)
2323 y = descr->item_height * descr->page_size - 1;
2324
2325 if (x < 0)
2326 {
2327 dir = LB_TIMER_LEFT;
2328 x = 0;
2329 }
2330 else if (x >= descr->width)
2331 {
2332 dir = LB_TIMER_RIGHT;
2333 x = descr->width - 1;
2334 }
2335 }
2336 else
2337 {
2338 if (y < 0) dir = LB_TIMER_UP; /* above */
2339 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2340 }
2341
2342 index = LISTBOX_GetItemFromPoint( descr, x, y );
2343 if (index == -1) index = descr->focus_item;
2344 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2345
2346 /* Start/stop the system timer */
2347
2348 if (dir != LB_TIMER_NONE)
2349 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2350 else if (LISTBOX_Timer != LB_TIMER_NONE)
2351 KillSystemTimer( descr->self, LB_TIMER_ID );
2352 LISTBOX_Timer = dir;
2353 }
2354
2355
2356 /***********************************************************************
2357 * LISTBOX_HandleKeyDown
2358 */
2359 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2360 {
2361 INT caret = -1;
2362 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2363 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2364 bForceSelection = FALSE; /* only for single select list */
2365
2366 if (descr->style & LBS_WANTKEYBOARDINPUT)
2367 {
2368 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2369 MAKEWPARAM(LOWORD(key), descr->focus_item),
2370 (LPARAM)descr->self );
2371 if (caret == -2) return 0;
2372 }
2373 if (caret == -1) switch(key)
2374 {
2375 case VK_LEFT:
2376 if (descr->style & LBS_MULTICOLUMN)
2377 {
2378 bForceSelection = FALSE;
2379 if (descr->focus_item >= descr->page_size)
2380 caret = descr->focus_item - descr->page_size;
2381 break;
2382 }
2383 /* fall through */
2384 case VK_UP:
2385 caret = descr->focus_item - 1;
2386 if (caret < 0) caret = 0;
2387 break;
2388 case VK_RIGHT:
2389 if (descr->style & LBS_MULTICOLUMN)
2390 {
2391 bForceSelection = FALSE;
2392 if (descr->focus_item + descr->page_size < descr->nb_items)
2393 caret = descr->focus_item + descr->page_size;
2394 break;
2395 }
2396 /* fall through */
2397 case VK_DOWN:
2398 caret = descr->focus_item + 1;
2399 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2400 break;
2401
2402 case VK_PRIOR:
2403 if (descr->style & LBS_MULTICOLUMN)
2404 {
2405 INT page = descr->width / descr->column_width;
2406 if (page < 1) page = 1;
2407 caret = descr->focus_item - (page * descr->page_size) + 1;
2408 }
2409 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2410 if (caret < 0) caret = 0;
2411 break;
2412 case VK_NEXT:
2413 if (descr->style & LBS_MULTICOLUMN)
2414 {
2415 INT page = descr->width / descr->column_width;
2416 if (page < 1) page = 1;
2417 caret = descr->focus_item + (page * descr->page_size) - 1;
2418 }
2419 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2420 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2421 break;
2422 case VK_HOME:
2423 caret = 0;
2424 break;
2425 case VK_END:
2426 caret = descr->nb_items - 1;
2427 break;
2428 case VK_SPACE:
2429 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2430 else if (descr->style & LBS_MULTIPLESEL)
2431 {
2432 LISTBOX_SetSelection( descr, descr->focus_item,
2433 !descr->items[descr->focus_item].selected,
2434 (descr->style & LBS_NOTIFY) != 0 );
2435 }
2436 break;
2437 default:
2438 bForceSelection = FALSE;
2439 }
2440 if (bForceSelection) /* focused item is used instead of key */
2441 caret = descr->focus_item;
2442 if (caret >= 0)
2443 {
2444 if (((descr->style & LBS_EXTENDEDSEL) &&
2445 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2446 !IS_MULTISELECT(descr))
2447 descr->anchor_item = caret;
2448 LISTBOX_MoveCaret( descr, caret, TRUE );
2449
2450 if (descr->style & LBS_MULTIPLESEL)
2451 descr->selected_item = caret;
2452 else
2453 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2454 if (descr->style & LBS_NOTIFY)
2455 {
2456 if( descr->lphc )
2457 {
2458 /* make sure that combo parent doesn't hide us */
2459 descr->lphc->wState |= CBF_NOROLLUP;
2460 }
2461 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2462 }
2463 }
2464 return 0;
2465
2466 }
2467
2468
2469 /***********************************************************************
2470 * LISTBOX_HandleChar
2471 */
2472 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2473 {
2474 INT caret = -1;
2475 WCHAR str[2];
2476
2477 str[0] = charW;
2478 str[1] = '\0';
2479
2480 if (descr->style & LBS_WANTKEYBOARDINPUT)
2481 {
2482 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2483 MAKEWPARAM(charW, descr->focus_item),
2484 (LPARAM)descr->self );
2485 if (caret == -2) return 0;
2486 }
2487 if (caret == -1)
2488 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2489 if (caret != -1)
2490 {
2491 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2492 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2493 LISTBOX_MoveCaret( descr, caret, TRUE );
2494 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2495 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2496 }
2497 return 0;
2498 }
2499
2500
2501 /***********************************************************************
2502 * LISTBOX_Create
2503 */
2504 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2505 {
2506 LB_DESCR *descr;
2507 MEASUREITEMSTRUCT mis;
2508 RECT rect;
2509
2510 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2511 return FALSE;
2512
2513 GetClientRect( hwnd, &rect );
2514 descr->self = hwnd;
2515 descr->owner = GetParent( descr->self );
2516 descr->style = GetWindowLongA( descr->self, GWL_STYLE );
2517 descr->width = rect.right - rect.left;
2518 descr->height = rect.bottom - rect.top;
2519 descr->items = NULL;
2520 descr->nb_items = 0;
2521 descr->top_item = 0;
2522 descr->selected_item = -1;
2523 descr->focus_item = 0;
2524 descr->anchor_item = -1;
2525 descr->item_height = 1;
2526 descr->page_size = 1;
2527 descr->column_width = 150;
2528 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2529 descr->horz_pos = 0;
2530 descr->nb_tabs = 0;
2531 descr->tabs = NULL;
2532 descr->caret_on = lphc ? FALSE : TRUE;
2533 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2534 descr->in_focus = FALSE;
2535 descr->captured = FALSE;
2536 descr->font = 0;
2537 descr->locale = GetUserDefaultLCID();
2538 descr->lphc = lphc;
2539
2540 #ifndef __REACTOS__
2541 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2542 {
2543 /* Win95 document "List Box Differences" from MSDN:
2544 If a list box in a version 3.x application has either the
2545 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2546 horizontal and vertical scroll bars.
2547 */
2548 descr->style |= WS_VSCROLL | WS_HSCROLL;
2549 }
2550 #endif
2551
2552 if( lphc )
2553 {
2554 TRACE_(combo)("[%p]: resetting owner %p -> %p\n", hwnd, descr->owner, lphc->self );
2555 descr->owner = lphc->self;
2556 }
2557
2558 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2559
2560 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2561 */
2562 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2563 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2564 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2565 descr->item_height = LISTBOX_SetFont( descr, 0 );
2566
2567 if (descr->style & LBS_OWNERDRAWFIXED)
2568 {
2569 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2570 {
2571 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2572 descr->item_height = lphc->fixedOwnerDrawHeight;
2573 }
2574 else
2575 {
2576 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2577 mis.CtlType = ODT_LISTBOX;
2578 mis.CtlID = id;
2579 mis.itemID = -1;
2580 mis.itemWidth = 0;
2581 mis.itemData = 0;
2582 mis.itemHeight = descr->item_height;
2583 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2584 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2585 }
2586 }
2587
2588 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2589 return TRUE;
2590 }
2591
2592
2593 /***********************************************************************
2594 * LISTBOX_Destroy
2595 */
2596 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2597 {
2598 LISTBOX_ResetContent( descr );
2599 SetWindowLongPtrW( descr->self, 0, 0 );
2600 HeapFree( GetProcessHeap(), 0, descr );
2601 return TRUE;
2602 }
2603
2604
2605 /***********************************************************************
2606 * ListBoxWndProc_common
2607 */
2608 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2609 WPARAM wParam, LPARAM lParam, BOOL unicode )
2610 {
2611 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2612 LRESULT ret;
2613 LPHEADCOMBO lphc = 0;
2614
2615 if (!descr)
2616 {
2617 if (!IsWindow(hwnd)) return 0;
2618
2619 if (msg == WM_CREATE)
2620 {
2621 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2622 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2623 if (!LISTBOX_Create( hwnd, lphc ))
2624 return -1;
2625 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2626 return 0;
2627 }
2628 /* Ignore all other messages before we get a WM_CREATE */
2629 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2630 DefWindowProcA( hwnd, msg, wParam, lParam );
2631 }
2632 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2633
2634 //TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2635 // hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2636 switch(msg)
2637 {
2638 #ifndef __REACTOS__
2639 case LB_RESETCONTENT16:
2640 #endif
2641 case LB_RESETCONTENT:
2642 LISTBOX_ResetContent( descr );
2643 LISTBOX_UpdateScroll( descr );
2644 InvalidateRect( descr->self, NULL, TRUE );
2645 return 0;
2646
2647 #ifndef __REACTOS__
2648 case LB_ADDSTRING16:
2649 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2650 /* fall through */
2651 #endif
2652 case LB_ADDSTRING:
2653 {
2654 INT ret;
2655 LPWSTR textW;
2656 if(unicode || !HAS_STRINGS(descr))
2657 textW = (LPWSTR)lParam;
2658 else
2659 {
2660 LPSTR textA = (LPSTR)lParam;
2661 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2662 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2663 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2664 }
2665 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2666 ret = LISTBOX_InsertString( descr, wParam, textW );
2667 if (!unicode && HAS_STRINGS(descr))
2668 HeapFree(GetProcessHeap(), 0, textW);
2669 return ret;
2670 }
2671
2672 #ifndef __REACTOS__
2673 case LB_INSERTSTRING16:
2674 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2675 wParam = (INT)(INT16)wParam;
2676 /* fall through */
2677 #endif
2678 case LB_INSERTSTRING:
2679 {
2680 INT ret;
2681 LPWSTR textW;
2682 if(unicode || !HAS_STRINGS(descr))
2683 textW = (LPWSTR)lParam;
2684 else
2685 {
2686 LPSTR textA = (LPSTR)lParam;
2687 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2688 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2689 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2690 }
2691 ret = LISTBOX_InsertString( descr, wParam, textW );
2692 if(!unicode && HAS_STRINGS(descr))
2693 HeapFree(GetProcessHeap(), 0, textW);
2694 return ret;
2695 }
2696
2697 #ifndef __REACTOS__
2698 case LB_ADDFILE16:
2699 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2700 /* fall through */
2701 #endif
2702 case LB_ADDFILE:
2703 {
2704 INT ret;
2705 LPWSTR textW;
2706 if(unicode || !HAS_STRINGS(descr))
2707 textW = (LPWSTR)lParam;
2708 else
2709 {
2710 LPSTR textA = (LPSTR)lParam;
2711 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2712 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2713 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2714 }
2715 wParam = LISTBOX_FindFileStrPos( descr, textW );
2716 ret = LISTBOX_InsertString( descr, wParam, textW );
2717 if(!unicode && HAS_STRINGS(descr))
2718 HeapFree(GetProcessHeap(), 0, textW);
2719 return ret;
2720 }
2721
2722 #ifndef __REACTOS__
2723 case LB_DELETESTRING16:
2724 #endif
2725 case LB_DELETESTRING:
2726 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2727 return descr->nb_items;
2728 else
2729 {
2730 SetLastError(ERROR_INVALID_INDEX);
2731 return LB_ERR;
2732 }
2733
2734 #ifndef __REACTOS__
2735 case LB_GETITEMDATA16:
2736 #endif
2737 case LB_GETITEMDATA:
2738 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2739 {
2740 SetLastError(ERROR_INVALID_INDEX);
2741 return LB_ERR;
2742 }
2743 return descr->items[wParam].data;
2744
2745 #ifndef __REACTOS__
2746 case LB_SETITEMDATA16:
2747 #endif
2748 case LB_SETITEMDATA:
2749 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2750 {
2751 SetLastError(ERROR_INVALID_INDEX);
2752 return LB_ERR;
2753 }
2754 descr->items[wParam].data = (DWORD)lParam;
2755 return LB_OKAY;
2756
2757 #ifndef __REACTOS__
2758 case LB_GETCOUNT16:
2759 #endif
2760 case LB_GETCOUNT:
2761 return descr->nb_items;
2762
2763 #ifndef __REACTOS__
2764 case LB_GETTEXT16:
2765 lParam = (LPARAM)MapSL(lParam);
2766 /* fall through */
2767 #endif
2768 case LB_GETTEXT:
2769 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2770
2771 #ifndef __REACTOS__
2772 case LB_GETTEXTLEN16:
2773 /* fall through */
2774 #endif
2775 case LB_GETTEXTLEN:
2776 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2777 {
2778 SetLastError(ERROR_INVALID_INDEX);
2779 return LB_ERR;
2780 }
2781 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2782 if (unicode) return strlenW( descr->items[wParam].str );
2783 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2784 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2785
2786 #ifndef __REACTOS__
2787 case LB_GETCURSEL16:
2788 #endif
2789 case LB_GETCURSEL:
2790 if (descr->nb_items==0)
2791 return LB_ERR;
2792 if (!IS_MULTISELECT(descr))
2793 return descr->selected_item;
2794 /* else */
2795 if (descr->selected_item!=-1)
2796 return descr->selected_item;
2797 /* else */
2798 return descr->focus_item;
2799 /* otherwise, if the user tries to move the selection with the */
2800 /* arrow keys, we will give the application something to choke on */
2801 #ifndef __REACTOS__
2802 case LB_GETTOPINDEX16:
2803 #endif
2804 case LB_GETTOPINDEX:
2805 return descr->top_item;
2806
2807 #ifndef __REACTOS__
2808 case LB_GETITEMHEIGHT16:
2809 #endif
2810 case LB_GETITEMHEIGHT:
2811 return LISTBOX_GetItemHeight( descr, wParam );
2812
2813 #ifndef __REACTOS__
2814 case LB_SETITEMHEIGHT16:
2815 lParam = LOWORD(lParam);
2816 /* fall through */
2817 #endif
2818 case LB_SETITEMHEIGHT:
2819 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2820
2821 case LB_ITEMFROMPOINT:
2822 {
2823 POINT pt;
2824 RECT rect;
2825
2826 pt.x = LOWORD(lParam);
2827 pt.y = HIWORD(lParam);
2828 rect.left = 0;
2829 rect.top = 0;
2830 rect.right = descr->width;
2831 rect.bottom = descr->height;
2832
2833 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2834 !PtInRect( &rect, pt ) );
2835 }
2836
2837 #ifndef __REACTOS__
2838 case LB_SETCARETINDEX16:
2839 #endif
2840 case LB_SETCARETINDEX:
2841 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2842 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2843 return LB_ERR;
2844 else if (ISWIN31)
2845 return wParam;
2846 else
2847 return LB_OKAY;
2848
2849 #ifndef __REACTOS__
2850 case LB_GETCARETINDEX16:
2851 #endif
2852 case LB_GETCARETINDEX:
2853 return descr->focus_item;
2854
2855 #ifndef __REACTOS__
2856 case LB_SETTOPINDEX16:
2857 #endif
2858 case LB_SETTOPINDEX:
2859 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2860
2861 #ifndef __REACTOS__
2862 case LB_SETCOLUMNWIDTH16:
2863 #endif
2864 case LB_SETCOLUMNWIDTH:
2865 return LISTBOX_SetColumnWidth( descr, wParam );
2866
2867 #ifndef __REACTOS__
2868 case LB_GETITEMRECT16:
2869 {
2870 RECT rect;
2871 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2872 CONV_RECT32TO16( &rect, MapSL(lParam) );
2873 }
2874 return ret;
2875 #endif
2876
2877 case LB_GETITEMRECT:
2878 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2879
2880 #ifndef __REACTOS__
2881 case LB_FINDSTRING16:
2882 wParam = (INT)(INT16)wParam;
2883 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2884 /* fall through */
2885 #endif
2886 case LB_FINDSTRING:
2887 {
2888 INT ret;
2889 LPWSTR textW;
2890 if(unicode || !HAS_STRINGS(descr))
2891 textW = (LPWSTR)lParam;
2892 else
2893 {
2894 LPSTR textA = (LPSTR)lParam;
2895 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2896 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2897 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2898 }
2899 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2900 if(!unicode && HAS_STRINGS(descr))
2901 HeapFree(GetProcessHeap(), 0, textW);
2902 return ret;
2903 }
2904
2905 #ifndef __REACTOS__
2906 case LB_FINDSTRINGEXACT16:
2907 wParam = (INT)(INT16)wParam;
2908 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2909 /* fall through */
2910 #endif
2911 case LB_FINDSTRINGEXACT:
2912 {
2913 INT ret;
2914 LPWSTR textW;
2915 if(unicode || !HAS_STRINGS(descr))
2916 textW = (LPWSTR)lParam;
2917 else
2918 {
2919 LPSTR textA = (LPSTR)lParam;
2920 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2921 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2922 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2923 }
2924 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2925 if(!unicode && HAS_STRINGS(descr))
2926 HeapFree(GetProcessHeap(), 0, textW);
2927 return ret;
2928 }
2929
2930 #ifndef __REACTOS__
2931 case LB_SELECTSTRING16:
2932 wParam = (INT)(INT16)wParam;
2933 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2934 /* fall through */
2935 #endif
2936 case LB_SELECTSTRING:
2937 {
2938 INT index;
2939 LPWSTR textW;
2940
2941 if(HAS_STRINGS(descr))
2942 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2943 debugstr_a((LPSTR)lParam));
2944 if(unicode || !HAS_STRINGS(descr))
2945 textW = (LPWSTR)lParam;
2946 else
2947 {
2948 LPSTR textA = (LPSTR)lParam;
2949 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2950 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2951 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2952 }
2953 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2954 if(!unicode && HAS_STRINGS(descr))
2955 HeapFree(GetProcessHeap(), 0, textW);
2956 if (index != LB_ERR)
2957 {
2958 LISTBOX_MoveCaret( descr, index, TRUE );
2959 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2960 }
2961 return index;
2962 }
2963
2964 #ifndef __REACTOS__
2965 case LB_GETSEL16:
2966 wParam = (INT)(INT16)wParam;
2967 /* fall through */
2968 #endif
2969 case LB_GETSEL:
2970 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2971 return LB_ERR;
2972 return descr->items[wParam].selected;
2973
2974 #ifndef __REACTOS__
2975 case LB_SETSEL16:
2976 lParam = (INT)(INT16)lParam;
2977 /* fall through */
2978 #endif
2979 case LB_SETSEL:
2980 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2981
2982 #ifndef __REACTOS__
2983 case LB_SETCURSEL16:
2984 wParam = (INT)(INT16)wParam;
2985 /* fall through */
2986 #endif
2987 case LB_SETCURSEL:
2988 if (IS_MULTISELECT(descr)) return LB_ERR;
2989 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2990 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2991 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2992 return ret;
2993
2994 #ifndef __REACTOS__
2995 case LB_GETSELCOUNT16:
2996 #endif
2997 case LB_GETSELCOUNT:
2998 return LISTBOX_GetSelCount( descr );
2999
3000 #ifndef __REACTOS__
3001 case LB_GETSELITEMS16:
3002 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
3003 #endif
3004
3005 case LB_GETSELITEMS:
3006 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
3007
3008 #ifndef __REACTOS__
3009 case LB_SELITEMRANGE16:
3010 #endif
3011 case LB_SELITEMRANGE:
3012 if (LOWORD(lParam) <= HIWORD(lParam))
3013 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
3014 HIWORD(lParam), wParam );
3015 else
3016 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
3017 LOWORD(lParam), wParam );
3018
3019 #ifndef __REACTOS__
3020 case LB_SELITEMRANGEEX16:
3021 #endif
3022 case LB_SELITEMRANGEEX:
3023 if ((INT)lParam >= (INT)wParam)
3024 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
3025 else
3026 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
3027
3028 #ifndef __REACTOS__
3029 case LB_GETHORIZONTALEXTENT16:
3030 #endif
3031 case LB_GETHORIZONTALEXTENT:
3032 return descr->horz_extent;
3033
3034 #ifndef __REACTOS__
3035 case LB_SETHORIZONTALEXTENT16:
3036 #endif
3037 case LB_SETHORIZONTALEXTENT:
3038 return LISTBOX_SetHorizontalExtent( descr, wParam );