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