Implementation of a simple linked list (static) that stores and counts
[wine/wine-kai.git] / controls / listbox.c
blobf47b1252f1fd5f0915cfcbd096a9e326fbff7e21
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 */
7 #include <string.h>
8 #include "wine/winuser16.h"
9 #include "winuser.h"
10 #include "winerror.h"
11 #include "drive.h"
12 #include "heap.h"
13 #include "spy.h"
14 #include "win.h"
15 #include "combo.h"
16 #include "debug.h"
17 #include "tweak.h"
19 /* Unimplemented yet:
20 * - LBS_NOSEL
21 * - LBS_USETABSTOPS
22 * - Unicode
23 * - Locale handling
26 /* Items array granularity */
27 #define LB_ARRAY_GRANULARITY 16
29 /* Scrolling timeout in ms */
30 #define LB_SCROLL_TIMEOUT 50
32 /* Listbox system timer id */
33 #define LB_TIMER_ID 2
35 /* Item structure */
36 typedef struct
38 LPSTR str; /* Item text */
39 BOOL32 selected; /* Is item selected? */
40 UINT32 height; /* Item height (only for OWNERDRAWVARIABLE) */
41 DWORD data; /* User data */
42 } LB_ITEMDATA;
44 /* Listbox structure */
45 typedef struct
47 HANDLE32 heap; /* Heap for this listbox */
48 HWND32 owner; /* Owner window to send notifications to */
49 UINT32 style; /* Window style */
50 INT32 width; /* Window width */
51 INT32 height; /* Window height */
52 LB_ITEMDATA *items; /* Array of items */
53 INT32 nb_items; /* Number of items */
54 INT32 top_item; /* Top visible item */
55 INT32 selected_item; /* Selected item */
56 INT32 focus_item; /* Item that has the focus */
57 INT32 anchor_item; /* Anchor item for extended selection */
58 INT32 item_height; /* Default item height */
59 INT32 page_size; /* Items per listbox page */
60 INT32 column_width; /* Column width for multi-column listboxes */
61 INT32 horz_extent; /* Horizontal extent (0 if no hscroll) */
62 INT32 horz_pos; /* Horizontal position */
63 INT32 nb_tabs; /* Number of tabs in array */
64 INT32 *tabs; /* Array of tabs */
65 BOOL32 caret_on; /* Is caret on? */
66 HFONT32 font; /* Current font */
67 LCID locale; /* Current locale for string comparisons */
68 LPHEADCOMBO lphc; /* ComboLBox */
69 } LB_DESCR;
72 #define IS_OWNERDRAW(descr) \
73 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
75 #define HAS_STRINGS(descr) \
76 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
78 #define SEND_NOTIFICATION(wnd,descr,code) \
79 (SendMessage32A( (descr)->owner, WM_COMMAND, \
80 MAKEWPARAM((((descr)->lphc)?ID_CB_LISTBOX:(wnd)->wIDmenu), (code) ), (wnd)->hwndSelf ))
82 /* Current timer status */
83 typedef enum
85 LB_TIMER_NONE,
86 LB_TIMER_UP,
87 LB_TIMER_LEFT,
88 LB_TIMER_DOWN,
89 LB_TIMER_RIGHT
90 } TIMER_DIRECTION;
92 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
95 /***********************************************************************
96 * LISTBOX_Dump
98 void LISTBOX_Dump( WND *wnd )
100 INT32 i;
101 LB_ITEMDATA *item;
102 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
104 DUMP( "Listbox:\n" );
105 DUMP( "hwnd=%04x descr=%08x heap=%08x items=%d top=%d\n",
106 wnd->hwndSelf, (UINT32)descr, descr->heap, descr->nb_items,
107 descr->top_item );
108 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
110 DUMP( "%4d: %-40s %d %08lx %3d\n",
111 i, item->str, item->selected, item->data, item->height );
116 /***********************************************************************
117 * LISTBOX_GetCurrentPageSize
119 * Return the current page size
121 static INT32 LISTBOX_GetCurrentPageSize( WND *wnd, LB_DESCR *descr )
123 INT32 i, height;
124 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
125 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
127 if ((height += descr->items[i].height) > descr->height) break;
129 if (i == descr->top_item) return 1;
130 else return i - descr->top_item;
134 /***********************************************************************
135 * LISTBOX_GetMaxTopIndex
137 * Return the maximum possible index for the top of the listbox.
139 static INT32 LISTBOX_GetMaxTopIndex( WND *wnd, LB_DESCR *descr )
141 INT32 max, page;
143 if (descr->style & LBS_OWNERDRAWVARIABLE)
145 page = descr->height;
146 for (max = descr->nb_items - 1; max >= 0; max--)
147 if ((page -= descr->items[max].height) < 0) break;
148 if (max < descr->nb_items - 1) max++;
150 else if (descr->style & LBS_MULTICOLUMN)
152 if ((page = descr->width / descr->column_width) < 1) page = 1;
153 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
154 max = (max - page) * descr->page_size;
156 else
158 max = descr->nb_items - descr->page_size;
160 if (max < 0) max = 0;
161 return max;
165 /***********************************************************************
166 * LISTBOX_UpdateScroll
168 * Update the scrollbars. Should be called whenever the content
169 * of the listbox changes.
171 static void LISTBOX_UpdateScroll( WND *wnd, LB_DESCR *descr )
173 SCROLLINFO info;
175 if (!(descr->style & WS_VSCROLL)) return;
176 /* It is important that we check descr->style, and not wnd->dwStyle,
177 for WS_VSCROLL, as the former is exactly the one passed in
178 argument to CreateWindow.
179 In Windows (and from now on in Wine :) a listbox created
180 with such a style (no WS_SCROLL) does not update
181 the scrollbar with listbox-related data, thus letting
182 the programmer use it for his/her own purposes. */
184 if (descr->style & LBS_NOREDRAW) return;
185 info.cbSize = sizeof(info);
187 if (descr->style & LBS_MULTICOLUMN)
189 info.nMin = 0;
190 info.nMax = (descr->nb_items - 1) / descr->page_size;
191 info.nPos = descr->top_item / descr->page_size;
192 info.nPage = descr->width / descr->column_width;
193 if (info.nPage < 1) info.nPage = 1;
194 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
195 if (descr->style & LBS_DISABLENOSCROLL)
196 info.fMask |= SIF_DISABLENOSCROLL;
197 SetScrollInfo32( wnd->hwndSelf, SB_HORZ, &info, TRUE );
198 info.nMax = 0;
199 info.fMask = SIF_RANGE;
200 SetScrollInfo32( wnd->hwndSelf, SB_VERT, &info, TRUE );
202 else
204 info.nMin = 0;
205 info.nMax = descr->nb_items - 1;
206 info.nPos = descr->top_item;
207 info.nPage = LISTBOX_GetCurrentPageSize( wnd, descr );
208 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
209 if (descr->style & LBS_DISABLENOSCROLL)
210 info.fMask |= SIF_DISABLENOSCROLL;
211 SetScrollInfo32( wnd->hwndSelf, SB_VERT, &info, TRUE );
213 if (descr->horz_extent)
215 info.nMin = 0;
216 info.nMax = descr->horz_extent - 1;
217 info.nPos = descr->horz_pos;
218 info.nPage = descr->width;
219 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
220 if (descr->style & LBS_DISABLENOSCROLL)
221 info.fMask |= SIF_DISABLENOSCROLL;
222 SetScrollInfo32( wnd->hwndSelf, SB_HORZ, &info, TRUE );
228 /***********************************************************************
229 * LISTBOX_SetTopItem
231 * Set the top item of the listbox, scrolling up or down if necessary.
233 static LRESULT LISTBOX_SetTopItem( WND *wnd, LB_DESCR *descr, INT32 index,
234 BOOL32 scroll )
236 INT32 max = LISTBOX_GetMaxTopIndex( wnd, descr );
237 if (index > max) index = max;
238 if (index < 0) index = 0;
239 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
240 if (descr->top_item == index) return LB_OKAY;
241 if (descr->style & LBS_MULTICOLUMN)
243 INT32 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
244 if (scroll && (abs(diff) < descr->width))
245 ScrollWindowEx32( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
246 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
248 else
249 scroll = FALSE;
251 else if (scroll)
253 INT32 diff;
254 if (descr->style & LBS_OWNERDRAWVARIABLE)
256 INT32 i;
257 diff = 0;
258 if (index > descr->top_item)
260 for (i = index - 1; i >= descr->top_item; i--)
261 diff -= descr->items[i].height;
263 else
265 for (i = index; i < descr->top_item; i++)
266 diff += descr->items[i].height;
269 else
270 diff = (descr->top_item - index) * descr->item_height;
272 if (abs(diff) < descr->height)
273 ScrollWindowEx32( wnd->hwndSelf, 0, diff, NULL, NULL, 0, NULL,
274 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
275 else
276 scroll = FALSE;
278 if (!scroll) InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
279 descr->top_item = index;
280 LISTBOX_UpdateScroll( wnd, descr );
281 return LB_OKAY;
285 /***********************************************************************
286 * LISTBOX_UpdatePage
288 * Update the page size. Should be called when the size of
289 * the client area or the item height changes.
291 static void LISTBOX_UpdatePage( WND *wnd, LB_DESCR *descr )
293 INT32 page_size;
295 if ((page_size = descr->height / descr->item_height) < 1) page_size = 1;
296 if (page_size == descr->page_size) return;
297 descr->page_size = page_size;
298 if (descr->style & LBS_MULTICOLUMN)
299 InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
300 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
304 /***********************************************************************
305 * LISTBOX_UpdateSize
307 * Update the size of the listbox. Should be called when the size of
308 * the client area changes.
310 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
312 RECT32 rect;
314 GetClientRect32( wnd->hwndSelf, &rect );
315 descr->width = rect.right - rect.left;
316 descr->height = rect.bottom - rect.top;
317 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !IS_OWNERDRAW(descr))
319 if ((descr->height > descr->item_height) &&
320 (descr->height % descr->item_height))
322 TRACE(listbox, "[%04x]: changing height %d -> %d\n",
323 wnd->hwndSelf, descr->height,
324 descr->height - descr->height%descr->item_height );
325 SetWindowPos32( wnd->hwndSelf, 0, 0, 0,
326 wnd->rectWindow.right - wnd->rectWindow.left,
327 wnd->rectWindow.bottom - wnd->rectWindow.top -
328 (descr->height % descr->item_height),
329 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
330 return;
333 TRACE(listbox, "[%04x]: new size = %d,%d\n",
334 wnd->hwndSelf, descr->width, descr->height );
335 LISTBOX_UpdatePage( wnd, descr );
336 LISTBOX_UpdateScroll( wnd, descr );
340 /***********************************************************************
341 * LISTBOX_GetItemRect
343 * Get the rectangle enclosing an item, in listbox client coordinates.
344 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
346 static LRESULT LISTBOX_GetItemRect( WND *wnd, LB_DESCR *descr, INT32 index,
347 RECT32 *rect )
349 /* Index <= 0 is legal even on empty listboxes */
350 if (index && (index >= descr->nb_items)) return -1;
351 SetRect32( rect, 0, 0, descr->width, descr->height );
352 if (descr->style & LBS_MULTICOLUMN)
354 INT32 col = (index / descr->page_size) -
355 (descr->top_item / descr->page_size);
356 rect->left += col * descr->column_width;
357 rect->right = rect->left + descr->column_width;
358 rect->top += (index % descr->page_size) * descr->item_height;
359 rect->bottom = rect->top + descr->item_height;
361 else if (descr->style & LBS_OWNERDRAWVARIABLE)
363 INT32 i;
364 rect->right += descr->horz_pos;
365 if ((index >= 0) && (index < descr->nb_items))
367 if (index < descr->top_item)
369 for (i = descr->top_item-1; i >= index; i--)
370 rect->top -= descr->items[i].height;
372 else
374 for (i = descr->top_item; i < index; i++)
375 rect->top += descr->items[i].height;
377 rect->bottom = rect->top + descr->items[index].height;
381 else
383 rect->top += (index - descr->top_item) * descr->item_height;
384 rect->bottom = rect->top + descr->item_height;
385 rect->right += descr->horz_pos;
388 return ((rect->left < descr->width) && (rect->right > 0) &&
389 (rect->top < descr->height) && (rect->bottom > 0));
393 /***********************************************************************
394 * LISTBOX_GetItemFromPoint
396 * Return the item nearest from point (x,y) (in client coordinates).
398 static INT32 LISTBOX_GetItemFromPoint( WND *wnd, LB_DESCR *descr,
399 INT32 x, INT32 y )
401 INT32 index = descr->top_item;
403 if (!descr->nb_items) return -1; /* No items */
404 if (descr->style & LBS_OWNERDRAWVARIABLE)
406 INT32 pos = 0;
407 if (y >= 0)
409 while (index < descr->nb_items)
411 if ((pos += descr->items[index].height) > y) break;
412 index++;
415 else
417 while (index > 0)
419 index--;
420 if ((pos -= descr->items[index].height) <= y) break;
424 else if (descr->style & LBS_MULTICOLUMN)
426 if (y >= descr->item_height * descr->page_size) return -1;
427 if (y >= 0) index += y / descr->item_height;
428 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
429 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
431 else
433 index += (y / descr->item_height);
435 if (index < 0) return 0;
436 if (index >= descr->nb_items) return -1;
437 return index;
441 /***********************************************************************
442 * LISTBOX_PaintItem
444 * Paint an item.
446 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC32 hdc,
447 const RECT32 *rect, INT32 index, UINT32 action )
449 LB_ITEMDATA *item = NULL;
450 if (index < descr->nb_items) item = &descr->items[index];
452 if (IS_OWNERDRAW(descr))
454 DRAWITEMSTRUCT32 dis;
455 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
457 if (!item)
459 if (action == ODA_FOCUS)
460 DrawFocusRect32( hdc, rect );
461 else
462 FIXME(listbox,"called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
463 return;
465 dis.CtlType = ODT_LISTBOX;
466 dis.CtlID = id;
467 dis.hwndItem = wnd->hwndSelf;
468 dis.itemAction = action;
469 dis.hDC = hdc;
470 dis.itemID = index;
471 dis.itemState = 0;
472 if (item && item->selected) dis.itemState |= ODS_SELECTED;
473 if ((descr->focus_item == index) &&
474 (descr->caret_on) &&
475 (GetFocus32() == wnd->hwndSelf)) dis.itemState |= ODS_FOCUS;
476 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
477 dis.itemData = item ? item->data : 0;
478 dis.rcItem = *rect;
479 TRACE(listbox, "[%04x]: drawitem %d (%s) action=%02x "
480 "state=%02x rect=%d,%d-%d,%d\n",
481 wnd->hwndSelf, index, item ? item->str : "", action,
482 dis.itemState, rect->left, rect->top,
483 rect->right, rect->bottom );
484 SendMessage32A(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
486 else
488 COLORREF oldText = 0, oldBk = 0;
490 if (action == ODA_FOCUS)
492 DrawFocusRect32( hdc, rect );
493 return;
495 if (item && item->selected)
497 oldBk = SetBkColor32( hdc, GetSysColor32( COLOR_HIGHLIGHT ) );
498 oldText = SetTextColor32( hdc, GetSysColor32(COLOR_HIGHLIGHTTEXT));
501 TRACE(listbox, "[%04x]: painting %d (%s) action=%02x "
502 "rect=%d,%d-%d,%d\n",
503 wnd->hwndSelf, index, item ? item->str : "", action,
504 rect->left, rect->top, rect->right, rect->bottom );
505 if (!item)
506 ExtTextOut32A( hdc, rect->left + 1, rect->top + 1,
507 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
508 else if (!(descr->style & LBS_USETABSTOPS))
509 ExtTextOut32A( hdc, rect->left + 1, rect->top + 1,
510 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
511 strlen(item->str), NULL );
512 else
514 /* Output empty string to paint background in the full width. */
515 ExtTextOut32A( hdc, rect->left + 1, rect->top + 1,
516 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
517 TabbedTextOut32A( hdc, rect->left + 1 , rect->top + 1,
518 item->str, strlen(item->str),
519 descr->nb_tabs, descr->tabs, 0);
521 if (item && item->selected)
523 SetBkColor32( hdc, oldBk );
524 SetTextColor32( hdc, oldText );
526 if ((descr->focus_item == index) &&
527 (descr->caret_on) &&
528 (GetFocus32() == wnd->hwndSelf)) DrawFocusRect32( hdc, rect );
533 /***********************************************************************
534 * LISTBOX_SetRedraw
536 * Change the redraw flag.
538 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL32 on )
540 if (on)
542 if (!(descr->style & LBS_NOREDRAW)) return;
543 descr->style &= ~LBS_NOREDRAW;
544 LISTBOX_UpdateScroll( wnd, descr );
546 else descr->style |= LBS_NOREDRAW;
550 /***********************************************************************
551 * LISTBOX_RepaintItem
553 * Repaint a single item synchronously.
555 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT32 index,
556 UINT32 action )
558 HDC32 hdc;
559 RECT32 rect;
560 HFONT32 oldFont = 0;
561 HBRUSH32 hbrush, oldBrush = 0;
563 if (descr->style & LBS_NOREDRAW) return;
564 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) != 1) return;
565 if (!(hdc = GetDCEx32( wnd->hwndSelf, 0, DCX_CACHE ))) return;
566 if (descr->font) oldFont = SelectObject32( hdc, descr->font );
567 hbrush = SendMessage32A( descr->owner, WM_CTLCOLORLISTBOX,
568 hdc, (LPARAM)wnd->hwndSelf );
569 if (hbrush) oldBrush = SelectObject32( hdc, hbrush );
570 if (wnd->dwStyle & WS_DISABLED)
571 SetTextColor32( hdc, GetSysColor32( COLOR_GRAYTEXT ) );
572 SetWindowOrgEx32( hdc, descr->horz_pos, 0, NULL );
573 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action );
574 if (oldFont) SelectObject32( hdc, oldFont );
575 if (oldBrush) SelectObject32( hdc, oldBrush );
576 ReleaseDC32( wnd->hwndSelf, hdc );
580 /***********************************************************************
581 * LISTBOX_InitStorage
583 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT32 nb_items,
584 DWORD bytes )
586 LB_ITEMDATA *item;
588 nb_items += LB_ARRAY_GRANULARITY - 1;
589 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
590 if (descr->items)
591 nb_items += HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
592 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
593 nb_items * sizeof(LB_ITEMDATA) )))
595 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
596 return LB_ERRSPACE;
598 descr->items = item;
599 return LB_OKAY;
603 /***********************************************************************
604 * LISTBOX_SetTabStops
606 static BOOL32 LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT32 count,
607 LPINT32 tabs, BOOL32 short_ints )
609 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
610 if (descr->tabs) HeapFree( descr->heap, 0, descr->tabs );
611 if (!(descr->nb_tabs = count))
613 descr->tabs = NULL;
614 return TRUE;
616 /* FIXME: count = 1 */
617 if (!(descr->tabs = (INT32 *)HeapAlloc( descr->heap, 0,
618 descr->nb_tabs * sizeof(INT32) )))
619 return FALSE;
620 if (short_ints)
622 INT32 i;
623 LPINT16 p = (LPINT16)tabs;
624 dbg_decl_str(listbox, 256);
626 for (i = 0; i < descr->nb_tabs; i++) {
627 descr->tabs[i] = *p++<<1; /* FIXME */
628 if(TRACE_ON(listbox))
629 dsprintf(listbox, "%hd ", descr->tabs[i]);
631 TRACE(listbox, "[%04x]: settabstops %s\n",
632 wnd->hwndSelf, dbg_str(listbox));
634 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT32) );
635 /* FIXME: repaint the window? */
636 return TRUE;
640 /***********************************************************************
641 * LISTBOX_GetText
643 static LRESULT LISTBOX_GetText( WND *wnd, LB_DESCR *descr, INT32 index,
644 LPSTR buffer )
646 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
647 if (HAS_STRINGS(descr))
649 if (!buffer)
650 return strlen(descr->items[index].str);
651 lstrcpy32A( buffer, descr->items[index].str );
652 return strlen(buffer);
653 } else {
654 if (buffer)
655 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
656 return sizeof(DWORD);
661 /***********************************************************************
662 * LISTBOX_FindStringPos
664 * Find the nearest string located before a given string in sort order.
665 * If 'exact' is TRUE, return an error if we don't get an exact match.
667 static INT32 LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCSTR str,
668 BOOL32 exact )
670 INT32 index, min, max, res = -1;
672 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
673 min = 0;
674 max = descr->nb_items;
675 while (min != max)
677 index = (min + max) / 2;
678 if (HAS_STRINGS(descr))
679 res = lstrcmpi32A( descr->items[index].str, str );
680 else
682 COMPAREITEMSTRUCT32 cis;
683 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
685 cis.CtlType = ODT_LISTBOX;
686 cis.CtlID = id;
687 cis.hwndItem = wnd->hwndSelf;
688 cis.itemID1 = index;
689 cis.itemData1 = descr->items[index].data;
690 cis.itemID2 = -1;
691 cis.itemData2 = (DWORD)str;
692 cis.dwLocaleId = descr->locale;
693 res = SendMessage32A( descr->owner, WM_COMPAREITEM,
694 id, (LPARAM)&cis );
696 if (!res) return index;
697 if (res > 0) max = index;
698 else min = index + 1;
700 return exact ? -1 : max;
704 /***********************************************************************
705 * LISTBOX_FindFileStrPos
707 * Find the nearest string located before a given string in directory
708 * sort order (i.e. first files, then directories, then drives).
710 static INT32 LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCSTR str )
712 INT32 min, max, res = -1;
714 if (!HAS_STRINGS(descr))
715 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
716 min = 0;
717 max = descr->nb_items;
718 while (min != max)
720 INT32 index = (min + max) / 2;
721 const char *p = descr->items[index].str;
722 if (*p == '[') /* drive or directory */
724 if (*str != '[') res = -1;
725 else if (p[1] == '-') /* drive */
727 if (str[1] == '-') res = str[2] - p[2];
728 else res = -1;
730 else /* directory */
732 if (str[1] == '-') res = 1;
733 else res = lstrcmpi32A( str, p );
736 else /* filename */
738 if (*str == '[') res = 1;
739 else res = lstrcmpi32A( str, p );
741 if (!res) return index;
742 if (res < 0) max = index;
743 else min = index + 1;
745 return max;
749 /***********************************************************************
750 * LISTBOX_FindString
752 * Find the item beginning with a given string.
754 static INT32 LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT32 start,
755 LPCSTR str, BOOL32 exact )
757 INT32 i;
758 LB_ITEMDATA *item;
760 if (start >= descr->nb_items) start = -1;
761 item = descr->items + start + 1;
762 if (HAS_STRINGS(descr))
764 if (!str) return LB_ERR;
765 if (exact)
767 for (i = start + 1; i < descr->nb_items; i++, item++)
768 if (!lstrcmpi32A( str, item->str )) return i;
769 for (i = 0, item = descr->items; i <= start; i++, item++)
770 if (!lstrcmpi32A( str, item->str )) return i;
772 else
774 /* Special case for drives and directories: ignore prefix */
775 #define CHECK_DRIVE(item) \
776 if ((item)->str[0] == '[') \
778 if (!lstrncmpi32A( str, (item)->str+1, len )) return i; \
779 if (((item)->str[1] == '-') && !lstrncmpi32A(str,(item)->str+2,len)) \
780 return i; \
783 INT32 len = strlen(str);
784 for (i = start + 1; i < descr->nb_items; i++, item++)
786 if (!lstrncmpi32A( str, item->str, len )) return i;
787 CHECK_DRIVE(item);
789 for (i = 0, item = descr->items; i <= start; i++, item++)
791 if (!lstrncmpi32A( str, item->str, len )) return i;
792 CHECK_DRIVE(item);
794 #undef CHECK_DRIVE
797 else
799 if (exact && (descr->style & LBS_SORT))
800 /* If sorted, use a WM_COMPAREITEM binary search */
801 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
803 /* Otherwise use a linear search */
804 for (i = start + 1; i < descr->nb_items; i++, item++)
805 if (item->data == (DWORD)str) return i;
806 for (i = 0, item = descr->items; i <= start; i++, item++)
807 if (item->data == (DWORD)str) return i;
809 return LB_ERR;
813 /***********************************************************************
814 * LISTBOX_GetSelCount
816 static LRESULT LISTBOX_GetSelCount( WND *wnd, LB_DESCR *descr )
818 INT32 i, count;
819 LB_ITEMDATA *item = descr->items;
821 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
822 for (i = count = 0; i < descr->nb_items; i++, item++)
823 if (item->selected) count++;
824 return count;
828 /***********************************************************************
829 * LISTBOX_GetSelItems16
831 static LRESULT LISTBOX_GetSelItems16( WND *wnd, LB_DESCR *descr, INT16 max,
832 LPINT16 array )
834 INT32 i, count;
835 LB_ITEMDATA *item = descr->items;
837 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
838 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
839 if (item->selected) array[count++] = (INT16)i;
840 return count;
844 /***********************************************************************
845 * LISTBOX_GetSelItems32
847 static LRESULT LISTBOX_GetSelItems32( WND *wnd, LB_DESCR *descr, INT32 max,
848 LPINT32 array )
850 INT32 i, count;
851 LB_ITEMDATA *item = descr->items;
853 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
854 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
855 if (item->selected) array[count++] = i;
856 return count;
860 /***********************************************************************
861 * LISTBOX_Paint
863 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC32 hdc )
865 INT32 i, col_pos = descr->page_size - 1;
866 RECT32 rect;
867 HFONT32 oldFont = 0;
868 HBRUSH32 hbrush, oldBrush = 0;
870 SetRect32( &rect, 0, 0, descr->width, descr->height );
871 if (descr->style & LBS_NOREDRAW) return 0;
872 if (descr->style & LBS_MULTICOLUMN)
873 rect.right = rect.left + descr->column_width;
874 else if (descr->horz_pos)
876 SetWindowOrgEx32( hdc, descr->horz_pos, 0, NULL );
877 rect.right += descr->horz_pos;
880 if (descr->font) oldFont = SelectObject32( hdc, descr->font );
881 hbrush = SendMessage32A( descr->owner, WM_CTLCOLORLISTBOX,
882 hdc, (LPARAM)wnd->hwndSelf );
883 if (hbrush) oldBrush = SelectObject32( hdc, hbrush );
884 if (wnd->dwStyle & WS_DISABLED)
885 SetTextColor32( hdc, GetSysColor32( COLOR_GRAYTEXT ) );
887 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
888 (GetFocus32() == wnd->hwndSelf))
890 /* Special case for empty listbox: paint focus rect */
891 rect.bottom = rect.top + descr->item_height;
892 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
893 ODA_FOCUS );
894 rect.top = rect.bottom;
897 for (i = descr->top_item; i < descr->nb_items; i++)
899 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
900 rect.bottom = rect.top + descr->item_height;
901 else
902 rect.bottom = rect.top + descr->items[i].height;
904 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE );
905 rect.top = rect.bottom;
907 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
909 if (!IS_OWNERDRAW(descr))
911 /* Clear the bottom of the column */
912 SetBkColor32( hdc, GetSysColor32( COLOR_WINDOW ) );
913 if (rect.top < descr->height)
915 rect.bottom = descr->height;
916 ExtTextOut32A( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
917 &rect, NULL, 0, NULL );
921 /* Go to the next column */
922 rect.left += descr->column_width;
923 rect.right += descr->column_width;
924 rect.top = 0;
925 col_pos = descr->page_size - 1;
927 else
929 col_pos--;
930 if (rect.top >= descr->height) break;
934 if (!IS_OWNERDRAW(descr))
936 /* Clear the remainder of the client area */
937 SetBkColor32( hdc, GetSysColor32( COLOR_WINDOW ) );
938 if (rect.top < descr->height)
940 rect.bottom = descr->height;
941 ExtTextOut32A( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
942 &rect, NULL, 0, NULL );
944 if (rect.right < descr->width)
946 rect.left = rect.right;
947 rect.right = descr->width;
948 rect.top = 0;
949 rect.bottom = descr->height;
950 ExtTextOut32A( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
951 &rect, NULL, 0, NULL );
954 if (oldFont) SelectObject32( hdc, oldFont );
955 if (oldBrush) SelectObject32( hdc, oldBrush );
956 return 0;
960 /***********************************************************************
961 * LISTBOX_InvalidateItems
963 * Invalidate all items from a given item. If the specified item is not
964 * visible, nothing happens.
966 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT32 index )
968 RECT32 rect;
970 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) == 1)
972 rect.bottom = descr->height;
973 InvalidateRect32( wnd->hwndSelf, &rect, TRUE );
974 if (descr->style & LBS_MULTICOLUMN)
976 /* Repaint the other columns */
977 rect.left = rect.right;
978 rect.right = descr->width;
979 rect.top = 0;
980 InvalidateRect32( wnd->hwndSelf, &rect, TRUE );
986 /***********************************************************************
987 * LISTBOX_GetItemHeight
989 static LRESULT LISTBOX_GetItemHeight( WND *wnd, LB_DESCR *descr, INT32 index )
991 if (descr->style & LBS_OWNERDRAWVARIABLE)
993 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
994 return descr->items[index].height;
996 else return descr->item_height;
1000 /***********************************************************************
1001 * LISTBOX_SetItemHeight
1003 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT32 index,
1004 UINT32 height )
1006 if (!height) height = 1;
1008 if (descr->style & LBS_OWNERDRAWVARIABLE)
1010 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1011 TRACE(listbox, "[%04x]: item %d height = %d\n",
1012 wnd->hwndSelf, index, height );
1013 descr->items[index].height = height;
1014 LISTBOX_UpdateScroll( wnd, descr );
1015 LISTBOX_InvalidateItems( wnd, descr, index );
1017 else if (height != descr->item_height)
1019 TRACE(listbox, "[%04x]: new height = %d\n",
1020 wnd->hwndSelf, height );
1021 descr->item_height = height;
1022 LISTBOX_UpdatePage( wnd, descr );
1023 LISTBOX_UpdateScroll( wnd, descr );
1024 InvalidateRect32( wnd->hwndSelf, 0, TRUE );
1026 return LB_OKAY;
1030 /***********************************************************************
1031 * LISTBOX_SetHorizontalPos
1033 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT32 pos )
1035 INT32 diff;
1037 if (pos > descr->horz_extent - descr->width)
1038 pos = descr->horz_extent - descr->width;
1039 if (pos < 0) pos = 0;
1040 if (!(diff = descr->horz_pos - pos)) return;
1041 TRACE(listbox, "[%04x]: new horz pos = %d\n",
1042 wnd->hwndSelf, pos );
1043 descr->horz_pos = pos;
1044 LISTBOX_UpdateScroll( wnd, descr );
1045 if (abs(diff) < descr->width)
1046 ScrollWindowEx32( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1047 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1048 else
1049 InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
1053 /***********************************************************************
1054 * LISTBOX_SetHorizontalExtent
1056 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1057 UINT32 extent )
1059 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1060 return LB_OKAY;
1061 if (extent <= 0) extent = 1;
1062 if (extent == descr->horz_extent) return LB_OKAY;
1063 TRACE(listbox, "[%04x]: new horz extent = %d\n",
1064 wnd->hwndSelf, extent );
1065 descr->horz_extent = extent;
1066 if (descr->horz_pos > extent - descr->width)
1067 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1068 else
1069 LISTBOX_UpdateScroll( wnd, descr );
1070 return LB_OKAY;
1074 /***********************************************************************
1075 * LISTBOX_SetColumnWidth
1077 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, UINT32 width)
1079 width += 2; /* For left and right margin */
1080 if (width == descr->column_width) return LB_OKAY;
1081 TRACE(listbox, "[%04x]: new column width = %d\n",
1082 wnd->hwndSelf, width );
1083 descr->column_width = width;
1084 LISTBOX_UpdatePage( wnd, descr );
1085 return LB_OKAY;
1089 /***********************************************************************
1090 * LISTBOX_SetFont
1092 * Returns the item height.
1094 static INT32 LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT32 font )
1096 HDC32 hdc;
1097 HFONT32 oldFont = 0;
1098 TEXTMETRIC32A tm;
1100 descr->font = font;
1102 if (!(hdc = GetDCEx32( wnd->hwndSelf, 0, DCX_CACHE )))
1104 ERR(listbox, "unable to get DC.\n" );
1105 return 16;
1107 if (font) oldFont = SelectObject32( hdc, font );
1108 GetTextMetrics32A( hdc, &tm );
1109 if (oldFont) SelectObject32( hdc, oldFont );
1110 ReleaseDC32( wnd->hwndSelf, hdc );
1111 if (!IS_OWNERDRAW(descr))
1112 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1113 return tm.tmHeight ;
1117 /***********************************************************************
1118 * LISTBOX_MakeItemVisible
1120 * Make sure that a given item is partially or fully visible.
1122 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT32 index,
1123 BOOL32 fully )
1125 INT32 top;
1127 if (index <= descr->top_item) top = index;
1128 else if (descr->style & LBS_MULTICOLUMN)
1130 INT32 cols = descr->width;
1131 if (!fully) cols += descr->column_width - 1;
1132 if (cols >= descr->column_width) cols /= descr->column_width;
1133 else cols = 1;
1134 if (index < descr->top_item + (descr->page_size * cols)) return;
1135 top = index - descr->page_size * (cols - 1);
1137 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1139 INT32 height = fully ? descr->items[index].height : 1;
1140 for (top = index; top > descr->top_item; top--)
1141 if ((height += descr->items[top-1].height) > descr->height) break;
1143 else
1145 if (index < descr->top_item + descr->page_size) return;
1146 if (!fully && (index == descr->top_item + descr->page_size) &&
1147 (descr->height > (descr->page_size * descr->item_height))) return;
1148 top = index - descr->page_size + 1;
1150 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1154 /***********************************************************************
1155 * LISTBOX_SelectItemRange
1157 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1159 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT32 first,
1160 INT32 last, BOOL32 on )
1162 INT32 i;
1164 /* A few sanity checks */
1166 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1167 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1168 if (last == -1) last = descr->nb_items - 1;
1169 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1170 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1171 /* selected_item reflects last selected/unselected item on multiple sel */
1172 descr->selected_item = last;
1174 if (on) /* Turn selection on */
1176 for (i = first; i <= last; i++)
1178 if (descr->items[i].selected) continue;
1179 descr->items[i].selected = TRUE;
1180 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1183 else /* Turn selection off */
1185 for (i = first; i <= last; i++)
1187 if (!descr->items[i].selected) continue;
1188 descr->items[i].selected = FALSE;
1189 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1192 return LB_OKAY;
1196 /***********************************************************************
1197 * LISTBOX_SetCaretIndex
1199 * NOTES
1200 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1203 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT32 index,
1204 BOOL32 fully_visible )
1206 INT32 oldfocus = descr->focus_item;
1208 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1209 if (index == oldfocus) return LB_OKAY;
1210 descr->focus_item = index;
1211 if ((oldfocus != -1) && descr->caret_on && (GetFocus32() == wnd->hwndSelf))
1212 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1214 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1215 if (descr->caret_on && (GetFocus32() == wnd->hwndSelf))
1216 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1218 return LB_OKAY;
1222 /***********************************************************************
1223 * LISTBOX_SetSelection
1225 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT32 index,
1226 BOOL32 on, BOOL32 send_notify )
1228 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1229 if (descr->style & LBS_MULTIPLESEL)
1231 if (index == -1) /* Select all items */
1232 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1233 else /* Only one item */
1234 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1236 else
1238 INT32 oldsel = descr->selected_item;
1239 if (index == oldsel) return LB_OKAY;
1240 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1241 if (index != -1) descr->items[index].selected = TRUE;
1242 descr->selected_item = index;
1243 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT);
1244 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1245 if (send_notify) SEND_NOTIFICATION( wnd, descr,
1246 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1247 else
1248 if( descr->lphc ) /* set selection change flag for parent combo */
1249 descr->lphc->wState |= CBF_SELCHANGE;
1251 return LB_OKAY;
1255 /***********************************************************************
1256 * LISTBOX_MoveCaret
1258 * Change the caret position and extend the selection to the new caret.
1260 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT32 index,
1261 BOOL32 fully_visible )
1263 LISTBOX_SetCaretIndex( wnd, descr, index, fully_visible );
1264 if (descr->style & LBS_EXTENDEDSEL)
1266 if (descr->anchor_item != -1)
1268 INT32 first = MIN( descr->focus_item, descr->anchor_item );
1269 INT32 last = MAX( descr->focus_item, descr->anchor_item );
1270 if (first > 0)
1271 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1272 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1273 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1276 else if (!(descr->style & LBS_MULTIPLESEL) && (descr->selected_item != -1))
1278 /* Set selection to new caret item */
1279 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1284 /***********************************************************************
1285 * LISTBOX_InsertItem
1287 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT32 index,
1288 LPSTR str, DWORD data )
1290 LB_ITEMDATA *item;
1291 INT32 max_items;
1293 if (index == -1) index = descr->nb_items;
1294 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1295 if (!descr->items) max_items = 0;
1296 else max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
1297 if (descr->nb_items == max_items)
1299 /* We need to grow the array */
1300 max_items += LB_ARRAY_GRANULARITY;
1301 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
1302 max_items * sizeof(LB_ITEMDATA) )))
1304 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1305 return LB_ERRSPACE;
1307 descr->items = item;
1310 /* Insert the item structure */
1312 item = &descr->items[index];
1313 if (index < descr->nb_items)
1314 RtlMoveMemory( item + 1, item,
1315 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1316 item->str = str;
1317 item->data = data;
1318 item->height = 0;
1319 item->selected = FALSE;
1320 descr->nb_items++;
1322 /* Get item height */
1324 if (descr->style & LBS_OWNERDRAWVARIABLE)
1326 MEASUREITEMSTRUCT32 mis;
1327 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1329 mis.CtlType = ODT_LISTBOX;
1330 mis.CtlID = id;
1331 mis.itemID = index;
1332 mis.itemData = descr->items[index].data;
1333 mis.itemHeight = descr->item_height;
1334 SendMessage32A( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1335 item->height = mis.itemHeight ? mis.itemHeight : 1;
1336 TRACE(listbox, "[%04x]: measure item %d (%s) = %d\n",
1337 wnd->hwndSelf, index, str ? str : "", item->height );
1340 /* Repaint the items */
1342 LISTBOX_UpdateScroll( wnd, descr );
1343 LISTBOX_InvalidateItems( wnd, descr, index );
1345 /* Move selection and focused item */
1347 if (index <= descr->selected_item) descr->selected_item++;
1348 if (index <= descr->focus_item)
1350 descr->focus_item++;
1351 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1354 /* If listbox was empty, set focus to the first item */
1356 if (descr->nb_items == 1) LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1357 return LB_OKAY;
1361 /***********************************************************************
1362 * LISTBOX_InsertString
1364 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT32 index,
1365 LPCSTR str )
1367 LPSTR new_str = NULL;
1368 DWORD data = 0;
1369 LRESULT ret;
1371 if (HAS_STRINGS(descr))
1373 if (!(new_str = HEAP_strdupA( descr->heap, 0, str )))
1375 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1376 return LB_ERRSPACE;
1379 else data = (DWORD)str;
1381 if (index == -1) index = descr->nb_items;
1382 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1384 if (new_str) HeapFree( descr->heap, 0, new_str );
1385 return ret;
1388 TRACE(listbox, "[%04x]: added item %d '%s'\n",
1389 wnd->hwndSelf, index, HAS_STRINGS(descr) ? new_str : "" );
1390 return index;
1394 /***********************************************************************
1395 * LISTBOX_DeleteItem
1397 * Delete the content of an item. 'index' must be a valid index.
1399 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT32 index )
1401 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1402 * while Win95 sends it for all items with user data.
1403 * It's probably better to send it too often than not
1404 * often enough, so this is what we do here.
1406 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1408 DELETEITEMSTRUCT32 dis;
1409 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1411 dis.CtlType = ODT_LISTBOX;
1412 dis.CtlID = id;
1413 dis.itemID = index;
1414 dis.hwndItem = wnd->hwndSelf;
1415 dis.itemData = descr->items[index].data;
1416 SendMessage32A( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1418 if (HAS_STRINGS(descr) && descr->items[index].str)
1419 HeapFree( descr->heap, 0, descr->items[index].str );
1423 /***********************************************************************
1424 * LISTBOX_RemoveItem
1426 * Remove an item from the listbox and delete its content.
1428 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT32 index )
1430 LB_ITEMDATA *item;
1431 INT32 max_items;
1433 if (index == -1) index = descr->nb_items - 1;
1434 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1435 LISTBOX_DeleteItem( wnd, descr, index );
1437 /* Remove the item */
1439 item = &descr->items[index];
1440 if (index < descr->nb_items-1)
1441 RtlMoveMemory( item, item + 1,
1442 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1443 descr->nb_items--;
1444 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1446 /* Shrink the item array if possible */
1448 max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(LB_ITEMDATA);
1449 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1451 max_items -= LB_ARRAY_GRANULARITY;
1452 item = HeapReAlloc( descr->heap, 0, descr->items,
1453 max_items * sizeof(LB_ITEMDATA) );
1454 if (item) descr->items = item;
1457 /* Repaint the items */
1459 LISTBOX_UpdateScroll( wnd, descr );
1460 LISTBOX_InvalidateItems( wnd, descr, index );
1462 /* Move selection and focused item */
1464 if (index <= descr->selected_item) descr->selected_item--;
1465 if (index <= descr->focus_item)
1467 descr->focus_item--;
1468 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1470 return LB_OKAY;
1474 /***********************************************************************
1475 * LISTBOX_ResetContent
1477 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1479 INT32 i;
1481 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1482 if (descr->items) HeapFree( descr->heap, 0, descr->items );
1483 descr->nb_items = 0;
1484 descr->top_item = 0;
1485 descr->selected_item = -1;
1486 descr->focus_item = 0;
1487 descr->anchor_item = -1;
1488 descr->items = NULL;
1489 LISTBOX_UpdateScroll( wnd, descr );
1490 InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
1494 /***********************************************************************
1495 * LISTBOX_SetCount
1497 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT32 count )
1499 LRESULT ret;
1501 if (HAS_STRINGS(descr)) return LB_ERR;
1502 /* FIXME: this is far from optimal... */
1503 if (count > descr->nb_items)
1505 while (count > descr->nb_items)
1506 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1507 return ret;
1509 else if (count < descr->nb_items)
1511 while (count < descr->nb_items)
1512 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1513 return ret;
1515 return LB_OKAY;
1519 /***********************************************************************
1520 * LISTBOX_Directory
1522 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT32 attrib,
1523 LPCSTR filespec, BOOL32 long_names )
1525 HANDLE32 handle;
1526 LRESULT ret = LB_OKAY;
1527 WIN32_FIND_DATA32A entry;
1528 int pos;
1530 if ((handle = FindFirstFile32A(filespec,&entry)) == INVALID_HANDLE_VALUE32)
1532 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1534 else
1538 char buffer[270];
1539 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1541 if (!(attrib & DDL_DIRECTORY) ||
1542 !strcmp( entry.cAlternateFileName, "." )) continue;
1543 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1544 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1546 else /* not a directory */
1548 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1549 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1551 if ((attrib & DDL_EXCLUSIVE) &&
1552 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1553 continue;
1554 #undef ATTRIBS
1555 if (long_names) strcpy( buffer, entry.cFileName );
1556 else strcpy( buffer, entry.cAlternateFileName );
1558 if (!long_names) CharLower32A( buffer );
1559 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1560 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1561 break;
1562 } while (FindNextFile32A( handle, &entry ));
1563 FindClose32( handle );
1566 if ((ret >= 0) && (attrib & DDL_DRIVES))
1568 char buffer[] = "[-a-]";
1569 int drive;
1570 for (drive = 0; drive < MAX_DOS_DRIVES; drive++, buffer[2]++)
1572 if (!DRIVE_IsValid(drive)) continue;
1573 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1574 break;
1577 return ret;
1581 /***********************************************************************
1582 * LISTBOX_HandleVScroll
1584 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1585 WPARAM32 wParam, LPARAM lParam )
1587 SCROLLINFO info;
1589 if (descr->style & LBS_MULTICOLUMN) return 0;
1590 switch(LOWORD(wParam))
1592 case SB_LINEUP:
1593 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1594 break;
1595 case SB_LINEDOWN:
1596 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1597 break;
1598 case SB_PAGEUP:
1599 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1600 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1601 break;
1602 case SB_PAGEDOWN:
1603 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1604 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1605 break;
1606 case SB_THUMBPOSITION:
1607 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1608 break;
1609 case SB_THUMBTRACK:
1610 info.cbSize = sizeof(info);
1611 info.fMask = SIF_TRACKPOS;
1612 GetScrollInfo32( wnd->hwndSelf, SB_VERT, &info );
1613 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1614 break;
1615 case SB_TOP:
1616 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1617 break;
1618 case SB_BOTTOM:
1619 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1620 break;
1622 return 0;
1626 /***********************************************************************
1627 * LISTBOX_HandleHScroll
1629 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1630 WPARAM32 wParam, LPARAM lParam )
1632 SCROLLINFO info;
1633 INT32 page;
1635 if (descr->style & LBS_MULTICOLUMN)
1637 switch(LOWORD(wParam))
1639 case SB_LINELEFT:
1640 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1641 TRUE );
1642 break;
1643 case SB_LINERIGHT:
1644 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1645 TRUE );
1646 break;
1647 case SB_PAGELEFT:
1648 page = descr->width / descr->column_width;
1649 if (page < 1) page = 1;
1650 LISTBOX_SetTopItem( wnd, descr,
1651 descr->top_item - page * descr->page_size, TRUE );
1652 break;
1653 case SB_PAGERIGHT:
1654 page = descr->width / descr->column_width;
1655 if (page < 1) page = 1;
1656 LISTBOX_SetTopItem( wnd, descr,
1657 descr->top_item + page * descr->page_size, TRUE );
1658 break;
1659 case SB_THUMBPOSITION:
1660 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1661 TRUE );
1662 break;
1663 case SB_THUMBTRACK:
1664 info.cbSize = sizeof(info);
1665 info.fMask = SIF_TRACKPOS;
1666 GetScrollInfo32( wnd->hwndSelf, SB_VERT, &info );
1667 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1668 TRUE );
1669 break;
1670 case SB_LEFT:
1671 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1672 break;
1673 case SB_RIGHT:
1674 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1675 break;
1678 else if (descr->horz_extent)
1680 switch(LOWORD(wParam))
1682 case SB_LINELEFT:
1683 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1684 break;
1685 case SB_LINERIGHT:
1686 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1687 break;
1688 case SB_PAGELEFT:
1689 LISTBOX_SetHorizontalPos( wnd, descr,
1690 descr->horz_pos - descr->width );
1691 break;
1692 case SB_PAGERIGHT:
1693 LISTBOX_SetHorizontalPos( wnd, descr,
1694 descr->horz_pos + descr->width );
1695 break;
1696 case SB_THUMBPOSITION:
1697 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1698 break;
1699 case SB_THUMBTRACK:
1700 info.cbSize = sizeof(info);
1701 info.fMask = SIF_TRACKPOS;
1702 GetScrollInfo32( wnd->hwndSelf, SB_HORZ, &info );
1703 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1704 break;
1705 case SB_LEFT:
1706 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1707 break;
1708 case SB_RIGHT:
1709 LISTBOX_SetHorizontalPos( wnd, descr,
1710 descr->horz_extent - descr->width );
1711 break;
1714 return 0;
1718 /***********************************************************************
1719 * LISTBOX_HandleLButtonDown
1721 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1722 WPARAM32 wParam, INT32 x, INT32 y )
1724 INT32 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1725 TRACE(listbox, "[%04x]: lbuttondown %d,%d item %d\n",
1726 wnd->hwndSelf, x, y, index );
1727 if (!descr->caret_on && (GetFocus32() == wnd->hwndSelf)) return 0;
1728 if (index != -1)
1730 if (descr->style & LBS_EXTENDEDSEL)
1732 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1733 if (wParam & MK_CONTROL)
1735 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1736 LISTBOX_SetSelection( wnd, descr, index,
1737 !descr->items[index].selected, FALSE );
1739 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1741 else
1743 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1744 LISTBOX_SetSelection( wnd, descr, index,
1745 (!(descr->style & LBS_MULTIPLESEL) ||
1746 !descr->items[index].selected), FALSE );
1750 if( !descr->lphc ) SetFocus32( wnd->hwndSelf );
1751 else SetFocus32( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1752 : descr->lphc->self->hwndSelf ) ;
1754 SetCapture32( wnd->hwndSelf );
1755 if (index != -1 && !descr->lphc)
1757 if (descr->style & LBS_NOTIFY )
1758 SendMessage32A( descr->owner, WM_LBTRACKPOINT, index,
1759 MAKELPARAM( x, y ) );
1760 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1762 POINT32 pt = { x, y };
1763 if (DragDetect32( wnd->hwndSelf, pt ))
1764 SendMessage32A( descr->owner, WM_BEGINDRAG, 0, 0 );
1767 return 0;
1771 /***********************************************************************
1772 * LISTBOX_HandleLButtonUp
1774 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
1776 if (LISTBOX_Timer != LB_TIMER_NONE)
1777 KillSystemTimer32( wnd->hwndSelf, LB_TIMER_ID );
1778 LISTBOX_Timer = LB_TIMER_NONE;
1779 if (GetCapture32() == wnd->hwndSelf)
1781 ReleaseCapture();
1782 if (descr->style & LBS_NOTIFY)
1783 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
1785 return 0;
1789 /***********************************************************************
1790 * LISTBOX_HandleTimer
1792 * Handle scrolling upon a timer event.
1793 * Return TRUE if scrolling should continue.
1795 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
1796 INT32 index, TIMER_DIRECTION dir )
1798 switch(dir)
1800 case LB_TIMER_UP:
1801 if (descr->top_item) index = descr->top_item - 1;
1802 else index = 0;
1803 break;
1804 case LB_TIMER_LEFT:
1805 if (descr->top_item) index -= descr->page_size;
1806 break;
1807 case LB_TIMER_DOWN:
1808 index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
1809 if (index == descr->focus_item) index++;
1810 if (index >= descr->nb_items) index = descr->nb_items - 1;
1811 break;
1812 case LB_TIMER_RIGHT:
1813 if (index + descr->page_size < descr->nb_items)
1814 index += descr->page_size;
1815 break;
1816 case LB_TIMER_NONE:
1817 break;
1819 if (index == descr->focus_item) return FALSE;
1820 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1821 return TRUE;
1825 /***********************************************************************
1826 * LISTBOX_HandleSystemTimer
1828 * WM_SYSTIMER handler.
1830 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
1832 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
1834 KillSystemTimer32( wnd->hwndSelf, LB_TIMER_ID );
1835 LISTBOX_Timer = LB_TIMER_NONE;
1837 return 0;
1841 /***********************************************************************
1842 * LISTBOX_HandleMouseMove
1844 * WM_MOUSEMOVE handler.
1846 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
1847 INT32 x, INT32 y )
1849 INT32 index;
1850 TIMER_DIRECTION dir;
1852 if (descr->style & LBS_MULTICOLUMN)
1854 if (y < 0) y = 0;
1855 else if (y >= descr->item_height * descr->page_size)
1856 y = descr->item_height * descr->page_size - 1;
1858 if (x < 0)
1860 dir = LB_TIMER_LEFT;
1861 x = 0;
1863 else if (x >= descr->width)
1865 dir = LB_TIMER_RIGHT;
1866 x = descr->width - 1;
1868 else dir = LB_TIMER_NONE; /* inside */
1870 else
1872 if (y < 0) dir = LB_TIMER_UP; /* above */
1873 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
1874 else dir = LB_TIMER_NONE; /* inside */
1877 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1878 if (index == -1) index = descr->focus_item;
1879 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
1881 /* Start/stop the system timer */
1883 if (dir != LB_TIMER_NONE)
1884 SetSystemTimer32( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
1885 else if (LISTBOX_Timer != LB_TIMER_NONE)
1886 KillSystemTimer32( wnd->hwndSelf, LB_TIMER_ID );
1887 LISTBOX_Timer = dir;
1891 /***********************************************************************
1892 * LISTBOX_HandleKeyDown
1894 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM32 wParam )
1896 INT32 caret = -1;
1897 if (descr->style & LBS_WANTKEYBOARDINPUT)
1899 caret = SendMessage32A( descr->owner, WM_VKEYTOITEM,
1900 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
1901 wnd->hwndSelf );
1902 if (caret == -2) return 0;
1904 if (caret == -1) switch(wParam)
1906 case VK_LEFT:
1907 if (descr->style & LBS_MULTICOLUMN)
1909 if (descr->focus_item >= descr->page_size)
1910 caret = descr->focus_item - descr->page_size;
1911 break;
1913 /* fall through */
1914 case VK_UP:
1915 caret = descr->focus_item - 1;
1916 if (caret < 0) caret = 0;
1917 break;
1918 case VK_RIGHT:
1919 if (descr->style & LBS_MULTICOLUMN)
1921 if (descr->focus_item + descr->page_size < descr->nb_items)
1922 caret = descr->focus_item + descr->page_size;
1923 break;
1925 /* fall through */
1926 case VK_DOWN:
1927 caret = descr->focus_item + 1;
1928 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1929 break;
1930 case VK_PRIOR:
1931 if (descr->style & LBS_MULTICOLUMN)
1933 INT32 page = descr->width / descr->column_width;
1934 if (page < 1) page = 1;
1935 caret = descr->focus_item - (page * descr->page_size) + 1;
1937 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
1938 if (caret < 0) caret = 0;
1939 break;
1940 case VK_NEXT:
1941 if (descr->style & LBS_MULTICOLUMN)
1943 INT32 page = descr->width / descr->column_width;
1944 if (page < 1) page = 1;
1945 caret = descr->focus_item + (page * descr->page_size) - 1;
1947 else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
1948 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1949 break;
1950 case VK_HOME:
1951 caret = 0;
1952 break;
1953 case VK_END:
1954 caret = descr->nb_items - 1;
1955 break;
1956 case VK_SPACE:
1957 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
1958 else if (descr->style & LBS_MULTIPLESEL)
1960 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
1961 !descr->items[descr->focus_item].selected,
1962 (descr->style & LBS_NOTIFY) != 0 );
1964 else if (descr->selected_item == -1)
1966 LISTBOX_SetSelection( wnd, descr, descr->focus_item, TRUE,
1967 (descr->style & LBS_NOTIFY) != 0 );
1969 break;
1971 if (caret >= 0)
1973 if ((descr->style & LBS_EXTENDEDSEL) &&
1974 !(GetKeyState32( VK_SHIFT ) & 0x8000))
1975 descr->anchor_item = caret;
1976 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
1977 if (descr->style & LBS_NOTIFY)
1979 if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
1981 /* make sure that combo parent doesn't hide us */
1982 descr->lphc->wState |= CBF_NOROLLUP;
1984 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
1987 return 0;
1991 /***********************************************************************
1992 * LISTBOX_HandleChar
1994 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
1995 WPARAM32 wParam )
1997 INT32 caret = -1;
1998 char str[2] = { wParam & 0xff, '\0' };
2000 if (descr->style & LBS_WANTKEYBOARDINPUT)
2002 caret = SendMessage32A( descr->owner, WM_CHARTOITEM,
2003 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2004 wnd->hwndSelf );
2005 if (caret == -2) return 0;
2007 if (caret == -1)
2008 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2009 if (caret != -1)
2011 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2012 if (descr->style & LBS_NOTIFY)
2013 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2015 return 0;
2019 /***********************************************************************
2020 * LISTBOX_Create
2022 static BOOL32 LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2024 LB_DESCR *descr;
2025 MEASUREITEMSTRUCT32 mis;
2026 RECT32 rect;
2028 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2029 return FALSE;
2030 if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2032 HeapFree( GetProcessHeap(), 0, descr );
2033 return FALSE;
2035 GetClientRect32( wnd->hwndSelf, &rect );
2036 descr->owner = GetParent32( wnd->hwndSelf );
2037 descr->style = wnd->dwStyle;
2038 descr->width = rect.right - rect.left;
2039 descr->height = rect.bottom - rect.top;
2040 descr->items = NULL;
2041 descr->nb_items = 0;
2042 descr->top_item = 0;
2043 descr->selected_item = -1;
2044 descr->focus_item = 0;
2045 descr->anchor_item = -1;
2046 descr->item_height = 1;
2047 descr->page_size = 1;
2048 descr->column_width = 150;
2049 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2050 descr->horz_pos = 0;
2051 descr->nb_tabs = 0;
2052 descr->tabs = NULL;
2053 descr->caret_on = TRUE;
2054 descr->font = 0;
2055 descr->locale = 0; /* FIXME */
2056 descr->lphc = lphc;
2058 if( lphc )
2060 TRACE(combo,"[%04x]: resetting owner %04x -> %04x\n",
2061 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2062 descr->owner = lphc->self->hwndSelf;
2065 *(LB_DESCR **)wnd->wExtra = descr;
2067 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2069 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2070 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2071 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2072 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2074 if (descr->style & LBS_OWNERDRAWFIXED)
2076 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2078 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2079 descr->item_height = lphc->RectButton.bottom - lphc->RectButton.top - 6;
2081 else
2083 UINT32 id = (descr->lphc ) ? ID_CB_LISTBOX : wnd->wIDmenu;
2085 mis.CtlType = ODT_LISTBOX;
2086 mis.CtlID = id;
2087 mis.itemID = -1;
2088 mis.itemWidth = 0;
2089 mis.itemData = 0;
2090 mis.itemHeight = descr->item_height;
2091 SendMessage32A( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2092 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2096 return TRUE;
2100 /***********************************************************************
2101 * LISTBOX_Destroy
2103 static BOOL32 LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2105 LISTBOX_ResetContent( wnd, descr );
2106 HeapDestroy( descr->heap );
2107 HeapFree( GetProcessHeap(), 0, descr );
2108 wnd->wExtra[0] = 0;
2109 return TRUE;
2113 /***********************************************************************
2114 * ListBoxWndProc
2116 LRESULT WINAPI ListBoxWndProc( HWND32 hwnd, UINT32 msg,
2117 WPARAM32 wParam, LPARAM lParam )
2119 LRESULT ret;
2120 LB_DESCR *descr;
2121 WND *wnd = WIN_FindWndPtr( hwnd );
2123 if (!wnd) return 0;
2124 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2126 if (msg == WM_CREATE)
2128 if (!LISTBOX_Create( wnd, NULL )) return -1;
2129 TRACE(listbox, "creating wnd=%04x descr=%p\n",
2130 hwnd, *(LB_DESCR **)wnd->wExtra );
2131 return 0;
2133 /* Ignore all other messages before we get a WM_CREATE */
2134 return DefWindowProc32A( hwnd, msg, wParam, lParam );
2137 TRACE(listbox, "[%04x]: msg %s wp %08x lp %08lx\n",
2138 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2139 switch(msg)
2141 case LB_RESETCONTENT16:
2142 case LB_RESETCONTENT32:
2143 LISTBOX_ResetContent( wnd, descr );
2144 return 0;
2146 case LB_ADDSTRING16:
2147 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2148 /* fall through */
2149 case LB_ADDSTRING32:
2150 wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2151 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2153 case LB_INSERTSTRING16:
2154 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2155 wParam = (INT32)(INT16)wParam;
2156 /* fall through */
2157 case LB_INSERTSTRING32:
2158 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2160 case LB_ADDFILE16:
2161 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2162 /* fall through */
2163 case LB_ADDFILE32:
2164 wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2165 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2167 case LB_DELETESTRING16:
2168 case LB_DELETESTRING32:
2169 return LISTBOX_RemoveItem( wnd, descr, wParam );
2171 case LB_GETITEMDATA16:
2172 case LB_GETITEMDATA32:
2173 if (((INT32)wParam < 0) || ((INT32)wParam >= descr->nb_items))
2174 return LB_ERR;
2175 return descr->items[wParam].data;
2177 case LB_SETITEMDATA16:
2178 case LB_SETITEMDATA32:
2179 if (((INT32)wParam < 0) || ((INT32)wParam >= descr->nb_items))
2180 return LB_ERR;
2181 descr->items[wParam].data = (DWORD)lParam;
2182 return LB_OKAY;
2184 case LB_GETCOUNT16:
2185 case LB_GETCOUNT32:
2186 return descr->nb_items;
2188 case LB_GETTEXT16:
2189 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2190 /* fall through */
2191 case LB_GETTEXT32:
2192 return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2194 case LB_GETTEXTLEN16:
2195 /* fall through */
2196 case LB_GETTEXTLEN32:
2197 if (wParam >= descr->nb_items) return LB_ERR;
2198 return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2199 : sizeof(DWORD));
2201 case LB_GETCURSEL16:
2202 case LB_GETCURSEL32:
2203 return descr->selected_item;
2205 case LB_GETTOPINDEX16:
2206 case LB_GETTOPINDEX32:
2207 return descr->top_item;
2209 case LB_GETITEMHEIGHT16:
2210 case LB_GETITEMHEIGHT32:
2211 return LISTBOX_GetItemHeight( wnd, descr, wParam );
2213 case LB_SETITEMHEIGHT16:
2214 lParam = LOWORD(lParam);
2215 /* fall through */
2216 case LB_SETITEMHEIGHT32:
2217 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2219 case LB_ITEMFROMPOINT32:
2221 POINT32 pt = { LOWORD(lParam), HIWORD(lParam) };
2222 RECT32 rect = { 0, 0, descr->width, descr->height };
2223 return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2224 PtInRect32( &rect, pt ) );
2227 case LB_SETCARETINDEX16:
2228 case LB_SETCARETINDEX32:
2229 return LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam );
2231 case LB_GETCARETINDEX16:
2232 case LB_GETCARETINDEX32:
2233 return descr->focus_item;
2235 case LB_SETTOPINDEX16:
2236 case LB_SETTOPINDEX32:
2237 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2239 case LB_SETCOLUMNWIDTH16:
2240 case LB_SETCOLUMNWIDTH32:
2241 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2243 case LB_GETITEMRECT16:
2245 RECT32 rect;
2246 ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2247 CONV_RECT32TO16( &rect, (RECT16 *)PTR_SEG_TO_LIN(lParam) );
2249 return ret;
2251 case LB_GETITEMRECT32:
2252 return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT32 *)lParam );
2254 case LB_FINDSTRING16:
2255 wParam = (INT32)(INT16)wParam;
2256 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2257 /* fall through */
2258 case LB_FINDSTRING32:
2259 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2261 case LB_FINDSTRINGEXACT16:
2262 wParam = (INT32)(INT16)wParam;
2263 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2264 /* fall through */
2265 case LB_FINDSTRINGEXACT32:
2266 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2268 case LB_SELECTSTRING16:
2269 wParam = (INT32)(INT16)wParam;
2270 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2271 /* fall through */
2272 case LB_SELECTSTRING32:
2274 INT32 index = LISTBOX_FindString( wnd, descr, wParam,
2275 (LPCSTR)lParam, FALSE );
2276 if (index == LB_ERR) return LB_ERR;
2277 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2278 return index;
2281 case LB_GETSEL16:
2282 wParam = (INT32)(INT16)wParam;
2283 /* fall through */
2284 case LB_GETSEL32:
2285 if (((INT32)wParam < 0) || ((INT32)wParam >= descr->nb_items))
2286 return LB_ERR;
2287 return descr->items[wParam].selected;
2289 case LB_SETSEL16:
2290 lParam = (INT32)(INT16)lParam;
2291 /* fall through */
2292 case LB_SETSEL32:
2293 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2295 case LB_SETCURSEL16:
2296 wParam = (INT32)(INT16)wParam;
2297 /* fall through */
2298 case LB_SETCURSEL32:
2299 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2300 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2302 case LB_GETSELCOUNT16:
2303 case LB_GETSELCOUNT32:
2304 return LISTBOX_GetSelCount( wnd, descr );
2306 case LB_GETSELITEMS16:
2307 return LISTBOX_GetSelItems16( wnd, descr, wParam,
2308 (LPINT16)PTR_SEG_TO_LIN(lParam) );
2310 case LB_GETSELITEMS32:
2311 return LISTBOX_GetSelItems32( wnd, descr, wParam, (LPINT32)lParam );
2313 case LB_SELITEMRANGE16:
2314 case LB_SELITEMRANGE32:
2315 if (LOWORD(lParam) <= HIWORD(lParam))
2316 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2317 HIWORD(lParam), wParam );
2318 else
2319 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2320 LOWORD(lParam), wParam );
2322 case LB_SELITEMRANGEEX16:
2323 case LB_SELITEMRANGEEX32:
2324 if ((INT32)lParam >= (INT32)wParam)
2325 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2326 else
2327 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2329 case LB_GETHORIZONTALEXTENT16:
2330 case LB_GETHORIZONTALEXTENT32:
2331 return descr->horz_extent;
2333 case LB_SETHORIZONTALEXTENT16:
2334 case LB_SETHORIZONTALEXTENT32:
2335 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2337 case LB_GETANCHORINDEX16:
2338 case LB_GETANCHORINDEX32:
2339 return descr->anchor_item;
2341 case LB_SETANCHORINDEX16:
2342 wParam = (INT32)(INT16)wParam;
2343 /* fall through */
2344 case LB_SETANCHORINDEX32:
2345 if (((INT32)wParam < -1) || ((INT32)wParam >= descr->nb_items))
2346 return LB_ERR;
2347 descr->anchor_item = (INT32)wParam;
2348 return LB_OKAY;
2350 case LB_DIR16:
2351 return LISTBOX_Directory( wnd, descr, wParam,
2352 (LPCSTR)PTR_SEG_TO_LIN(lParam), FALSE );
2354 case LB_DIR32:
2355 return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2357 case LB_GETLOCALE32:
2358 return descr->locale;
2360 case LB_SETLOCALE32:
2361 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2362 return LB_OKAY;
2364 case LB_INITSTORAGE32:
2365 return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2367 case LB_SETCOUNT32:
2368 return LISTBOX_SetCount( wnd, descr, (INT32)wParam );
2370 case LB_SETTABSTOPS16:
2371 return LISTBOX_SetTabStops( wnd, descr, (INT32)(INT16)wParam,
2372 (LPINT32)PTR_SEG_TO_LIN(lParam), TRUE );
2374 case LB_SETTABSTOPS32:
2375 return LISTBOX_SetTabStops( wnd, descr, wParam,
2376 (LPINT32)lParam, FALSE );
2378 case LB_CARETON16:
2379 case LB_CARETON32:
2380 if (descr->caret_on) return LB_OKAY;
2381 descr->caret_on = TRUE;
2382 if ((descr->focus_item != -1) && (GetFocus32() == wnd->hwndSelf))
2383 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2384 return LB_OKAY;
2386 case LB_CARETOFF16:
2387 case LB_CARETOFF32:
2388 if (!descr->caret_on) return LB_OKAY;
2389 descr->caret_on = FALSE;
2390 if ((descr->focus_item != -1) && (GetFocus32() == wnd->hwndSelf))
2391 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2392 return LB_OKAY;
2394 case WM_DESTROY:
2395 return LISTBOX_Destroy( wnd, descr );
2397 case WM_ENABLE:
2398 InvalidateRect32( hwnd, NULL, TRUE );
2399 return 0;
2401 case WM_SETREDRAW:
2402 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2403 return 0;
2405 case WM_GETDLGCODE:
2406 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2408 case WM_PAINT:
2410 PAINTSTRUCT32 ps;
2411 HDC32 hdc = ( wParam ) ? ((HDC32)wParam)
2412 : BeginPaint32( hwnd, &ps );
2413 ret = LISTBOX_Paint( wnd, descr, hdc );
2414 if( !wParam ) EndPaint32( hwnd, &ps );
2416 return ret;
2418 case WM_SIZE:
2419 LISTBOX_UpdateSize( wnd, descr );
2420 return 0;
2422 case WM_GETFONT:
2423 return descr->font;
2425 case WM_SETFONT:
2426 LISTBOX_SetFont( wnd, descr, (HFONT32)wParam );
2427 if (lParam) InvalidateRect32( wnd->hwndSelf, 0, TRUE );
2428 return 0;
2430 case WM_SETFOCUS:
2431 descr->caret_on = TRUE;
2432 if (descr->focus_item != -1)
2433 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2434 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2435 return 0;
2437 case WM_KILLFOCUS:
2438 if ((descr->focus_item != -1) && descr->caret_on)
2439 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2440 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2441 return 0;
2443 case WM_HSCROLL:
2444 return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2446 case WM_VSCROLL:
2447 return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2449 case WM_LBUTTONDOWN:
2450 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2451 (INT16)LOWORD(lParam),
2452 (INT16)HIWORD(lParam) );
2454 case WM_LBUTTONDBLCLK:
2455 if (descr->style & LBS_NOTIFY)
2456 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2457 return 0;
2459 case WM_MOUSEMOVE:
2460 if (GetCapture32() == hwnd)
2461 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2462 (INT16)HIWORD(lParam) );
2463 return 0;
2465 case WM_LBUTTONUP:
2466 return LISTBOX_HandleLButtonUp( wnd, descr );
2468 case WM_KEYDOWN:
2469 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2471 case WM_CHAR:
2472 return LISTBOX_HandleChar( wnd, descr, wParam );
2474 case WM_SYSTIMER:
2475 return LISTBOX_HandleSystemTimer( wnd, descr );
2477 case WM_ERASEBKGND:
2478 if (IS_OWNERDRAW(descr))
2480 RECT32 rect = { 0, 0, descr->width, descr->height };
2481 HBRUSH32 hbrush = SendMessage32A( descr->owner, WM_CTLCOLORLISTBOX,
2482 wParam, (LPARAM)wnd->hwndSelf );
2483 if (hbrush) FillRect32( (HDC32)wParam, &rect, hbrush );
2485 return 1;
2487 case WM_DROPFILES:
2488 if( !descr->lphc )
2489 return SendMessage32A( descr->owner, msg, wParam, lParam );
2490 break;
2492 case WM_DROPOBJECT:
2493 case WM_QUERYDROPOBJECT:
2494 case WM_DRAGSELECT:
2495 case WM_DRAGMOVE:
2496 if( !descr->lphc )
2498 LPDRAGINFO dragInfo = (LPDRAGINFO)PTR_SEG_TO_LIN( (SEGPTR)lParam );
2499 dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2500 dragInfo->pt.y );
2501 return SendMessage32A( descr->owner, msg, wParam, lParam );
2503 break;
2505 case WM_NCCREATE:
2506 if (TWEAK_WineLook > WIN31_LOOK)
2507 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2508 return DefWindowProc32A( hwnd, msg, wParam, lParam );
2510 default:
2511 if ((msg >= WM_USER) && (msg < 0xc000))
2512 WARN(listbox, "[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2513 hwnd, msg, wParam, lParam );
2514 return DefWindowProc32A( hwnd, msg, wParam, lParam );
2516 return 0;
2519 /***********************************************************************
2520 * COMBO_Directory
2522 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT32 attrib, LPSTR dir, BOOL32 bLong)
2524 WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2526 if( wnd )
2528 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2529 if( descr )
2531 LRESULT lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2533 RedrawWindow32( lphc->self->hwndSelf, NULL, 0,
2534 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2535 return lRet;
2538 return CB_ERR;
2541 /***********************************************************************
2542 * ComboLBWndProc
2544 * NOTE: in Windows, winproc address of the ComboLBox is the same
2545 * as that of the Listbox.
2547 LRESULT WINAPI ComboLBWndProc( HWND32 hwnd, UINT32 msg,
2548 WPARAM32 wParam, LPARAM lParam )
2550 LRESULT lRet = 0;
2551 WND *wnd = WIN_FindWndPtr( hwnd );
2553 if (wnd)
2555 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2557 TRACE(combo, "[%04x]: msg %s wp %08x lp %08lx\n",
2558 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2560 if( descr || msg == WM_CREATE )
2562 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2564 switch( msg )
2566 case WM_CREATE:
2567 #define lpcs ((LPCREATESTRUCT32A)lParam)
2568 TRACE(combo, "\tpassed parent handle = 0x%08x\n",
2569 (UINT32)lpcs->lpCreateParams);
2571 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2572 #undef lpcs
2573 return LISTBOX_Create( wnd, lphc );
2575 case WM_LBUTTONDOWN:
2576 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2577 (INT16)LOWORD(lParam), (INT16)HIWORD(lParam));
2579 /* avoid activation at all costs */
2581 case WM_MOUSEACTIVATE:
2582 return MA_NOACTIVATE;
2584 case WM_NCACTIVATE:
2585 return FALSE;
2587 case WM_KEYDOWN:
2588 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2590 /* for some reason(?) Windows makes it possible to
2591 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2593 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2594 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2595 && (wParam == VK_DOWN || wParam == VK_UP)) )
2597 COMBO_FlipListbox( lphc, FALSE );
2598 return 0;
2601 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2603 case LB_SETCURSEL16:
2604 case LB_SETCURSEL32:
2605 lRet = ListBoxWndProc( hwnd, msg, wParam, lParam );
2606 return (lRet == LB_ERR) ? lRet : descr->selected_item;
2608 case WM_NCDESTROY:
2609 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2610 lphc->hWndLBox = 0;
2611 /* fall through */
2613 default:
2614 return ListBoxWndProc( hwnd, msg, wParam, lParam );
2617 lRet = DefWindowProc32A( hwnd, msg, wParam, lParam );
2619 TRACE(combo,"\t default on msg [%04x]\n", (UINT16)msg );
2622 return lRet;