Recovery of release 990110 after disk crash.
[wine/multimedia.git] / controls / listbox.c
blob74b16eb1c6ed8acde4b690075e6238d98226979f
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 );
247 else
248 scroll = FALSE;
250 else if (scroll)
252 INT32 diff;
253 if (descr->style & LBS_OWNERDRAWVARIABLE)
255 INT32 i;
256 diff = 0;
257 if (index > descr->top_item)
259 for (i = index - 1; i >= descr->top_item; i--)
260 diff -= descr->items[i].height;
262 else
264 for (i = index; i < descr->top_item; i++)
265 diff += descr->items[i].height;
268 else
269 diff = (descr->top_item - index) * descr->item_height;
271 if (abs(diff) < descr->height)
272 ScrollWindowEx32( wnd->hwndSelf, 0, diff, NULL, NULL, 0, NULL,
273 SW_INVALIDATE | SW_ERASE );
274 else
275 scroll = FALSE;
277 if (!scroll) InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
278 descr->top_item = index;
279 LISTBOX_UpdateScroll( wnd, descr );
280 return LB_OKAY;
284 /***********************************************************************
285 * LISTBOX_UpdatePage
287 * Update the page size. Should be called when the size of
288 * the client area or the item height changes.
290 static void LISTBOX_UpdatePage( WND *wnd, LB_DESCR *descr )
292 INT32 page_size;
294 if ((page_size = descr->height / descr->item_height) < 1) page_size = 1;
295 if (page_size == descr->page_size) return;
296 descr->page_size = page_size;
297 if (descr->style & LBS_MULTICOLUMN)
298 InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
299 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
303 /***********************************************************************
304 * LISTBOX_UpdateSize
306 * Update the size of the listbox. Should be called when the size of
307 * the client area changes.
309 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
311 RECT32 rect;
313 GetClientRect32( wnd->hwndSelf, &rect );
314 descr->width = rect.right - rect.left;
315 descr->height = rect.bottom - rect.top;
316 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !IS_OWNERDRAW(descr))
318 if ((descr->height > descr->item_height) &&
319 (descr->height % descr->item_height))
321 TRACE(listbox, "[%04x]: changing height %d -> %d\n",
322 wnd->hwndSelf, descr->height,
323 descr->height - descr->height%descr->item_height );
324 SetWindowPos32( wnd->hwndSelf, 0, 0, 0,
325 wnd->rectWindow.right - wnd->rectWindow.left,
326 wnd->rectWindow.bottom - wnd->rectWindow.top -
327 (descr->height % descr->item_height),
328 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
329 return;
332 TRACE(listbox, "[%04x]: new size = %d,%d\n",
333 wnd->hwndSelf, descr->width, descr->height );
334 LISTBOX_UpdatePage( wnd, descr );
335 LISTBOX_UpdateScroll( wnd, descr );
339 /***********************************************************************
340 * LISTBOX_GetItemRect
342 * Get the rectangle enclosing an item, in listbox client coordinates.
343 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
345 static LRESULT LISTBOX_GetItemRect( WND *wnd, LB_DESCR *descr, INT32 index,
346 RECT32 *rect )
348 /* Index <= 0 is legal even on empty listboxes */
349 if (index && (index >= descr->nb_items)) return -1;
350 SetRect32( rect, 0, 0, descr->width, descr->height );
351 if (descr->style & LBS_MULTICOLUMN)
353 INT32 col = (index / descr->page_size) -
354 (descr->top_item / descr->page_size);
355 rect->left += col * descr->column_width;
356 rect->right = rect->left + descr->column_width;
357 rect->top += (index % descr->page_size) * descr->item_height;
358 rect->bottom = rect->top + descr->item_height;
360 else if (descr->style & LBS_OWNERDRAWVARIABLE)
362 INT32 i;
363 rect->right += descr->horz_pos;
364 if ((index >= 0) && (index < descr->nb_items))
366 if (index < descr->top_item)
368 for (i = descr->top_item-1; i >= index; i--)
369 rect->top -= descr->items[i].height;
371 else
373 for (i = descr->top_item; i < index; i++)
374 rect->top += descr->items[i].height;
376 rect->bottom = rect->top + descr->items[index].height;
380 else
382 rect->top += (index - descr->top_item) * descr->item_height;
383 rect->bottom = rect->top + descr->item_height;
384 rect->right += descr->horz_pos;
387 return ((rect->left < descr->width) && (rect->right > 0) &&
388 (rect->top < descr->height) && (rect->bottom > 0));
392 /***********************************************************************
393 * LISTBOX_GetItemFromPoint
395 * Return the item nearest from point (x,y) (in client coordinates).
397 static INT32 LISTBOX_GetItemFromPoint( WND *wnd, LB_DESCR *descr,
398 INT32 x, INT32 y )
400 INT32 index = descr->top_item;
402 if (!descr->nb_items) return -1; /* No items */
403 if (descr->style & LBS_OWNERDRAWVARIABLE)
405 INT32 pos = 0;
406 if (y >= 0)
408 while (index < descr->nb_items)
410 if ((pos += descr->items[index].height) > y) break;
411 index++;
414 else
416 while (index > 0)
418 index--;
419 if ((pos -= descr->items[index].height) <= y) break;
423 else if (descr->style & LBS_MULTICOLUMN)
425 if (y >= descr->item_height * descr->page_size) return -1;
426 if (y >= 0) index += y / descr->item_height;
427 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
428 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
430 else
432 index += (y / descr->item_height);
434 if (index < 0) return 0;
435 if (index >= descr->nb_items) return -1;
436 return index;
440 /***********************************************************************
441 * LISTBOX_PaintItem
443 * Paint an item.
445 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC32 hdc,
446 const RECT32 *rect, INT32 index, UINT32 action )
448 LB_ITEMDATA *item = NULL;
449 if (index < descr->nb_items) item = &descr->items[index];
451 if (IS_OWNERDRAW(descr))
453 DRAWITEMSTRUCT32 dis;
454 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
456 dis.CtlType = ODT_LISTBOX;
457 dis.CtlID = id;
458 dis.hwndItem = wnd->hwndSelf;
459 dis.itemAction = action;
460 dis.hDC = hdc;
461 dis.itemID = index;
462 dis.itemState = 0;
463 if (item && item->selected) dis.itemState |= ODS_SELECTED;
464 if ((descr->focus_item == index) &&
465 (descr->caret_on) &&
466 (GetFocus32() == wnd->hwndSelf)) dis.itemState |= ODS_FOCUS;
467 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
468 dis.itemData = item ? item->data : 0;
469 dis.rcItem = *rect;
470 TRACE(listbox, "[%04x]: drawitem %d (%s) action=%02x "
471 "state=%02x rect=%d,%d-%d,%d\n",
472 wnd->hwndSelf, index, item ? item->str : "", action,
473 dis.itemState, rect->left, rect->top,
474 rect->right, rect->bottom );
475 SendMessage32A(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
477 else
479 COLORREF oldText = 0, oldBk = 0;
481 if (action == ODA_FOCUS)
483 DrawFocusRect32( hdc, rect );
484 return;
486 if (item && item->selected)
488 oldBk = SetBkColor32( hdc, GetSysColor32( COLOR_HIGHLIGHT ) );
489 oldText = SetTextColor32( hdc, GetSysColor32(COLOR_HIGHLIGHTTEXT));
492 TRACE(listbox, "[%04x]: painting %d (%s) action=%02x "
493 "rect=%d,%d-%d,%d\n",
494 wnd->hwndSelf, index, item ? item->str : "", action,
495 rect->left, rect->top, rect->right, rect->bottom );
496 if (!item)
497 ExtTextOut32A( hdc, rect->left + 1, rect->top + 1,
498 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
499 else if (!(descr->style & LBS_USETABSTOPS))
500 ExtTextOut32A( hdc, rect->left + 1, rect->top + 1,
501 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
502 strlen(item->str), NULL );
503 else
505 /* Output empty string to paint background in the full width. */
506 ExtTextOut32A( hdc, rect->left + 1, rect->top + 1,
507 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
508 TabbedTextOut32A( hdc, rect->left + 1 , rect->top + 1,
509 item->str, strlen(item->str),
510 descr->nb_tabs, descr->tabs, 0);
512 if (item && item->selected)
514 SetBkColor32( hdc, oldBk );
515 SetTextColor32( hdc, oldText );
517 if ((descr->focus_item == index) &&
518 (descr->caret_on) &&
519 (GetFocus32() == wnd->hwndSelf)) DrawFocusRect32( hdc, rect );
524 /***********************************************************************
525 * LISTBOX_SetRedraw
527 * Change the redraw flag.
529 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL32 on )
531 if (on)
533 if (!(descr->style & LBS_NOREDRAW)) return;
534 descr->style &= ~LBS_NOREDRAW;
535 LISTBOX_UpdateScroll( wnd, descr );
537 else descr->style |= LBS_NOREDRAW;
541 /***********************************************************************
542 * LISTBOX_RepaintItem
544 * Repaint a single item synchronously.
546 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT32 index,
547 UINT32 action )
549 HDC32 hdc;
550 RECT32 rect;
551 HFONT32 oldFont = 0;
552 HBRUSH32 hbrush, oldBrush = 0;
554 if (descr->style & LBS_NOREDRAW) return;
555 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) != 1) return;
556 if (!(hdc = GetDCEx32( wnd->hwndSelf, 0, DCX_CACHE ))) return;
557 if (descr->font) oldFont = SelectObject32( hdc, descr->font );
558 hbrush = SendMessage32A( descr->owner, WM_CTLCOLORLISTBOX,
559 hdc, (LPARAM)wnd->hwndSelf );
560 if (hbrush) oldBrush = SelectObject32( hdc, hbrush );
561 if (wnd->dwStyle & WS_DISABLED)
562 SetTextColor32( hdc, GetSysColor32( COLOR_GRAYTEXT ) );
563 SetWindowOrgEx32( hdc, descr->horz_pos, 0, NULL );
564 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action );
565 if (oldFont) SelectObject32( hdc, oldFont );
566 if (oldBrush) SelectObject32( hdc, oldBrush );
567 ReleaseDC32( wnd->hwndSelf, hdc );
571 /***********************************************************************
572 * LISTBOX_InitStorage
574 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT32 nb_items,
575 DWORD bytes )
577 LB_ITEMDATA *item;
579 nb_items += LB_ARRAY_GRANULARITY - 1;
580 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
581 if (descr->items)
582 nb_items += HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
583 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
584 nb_items * sizeof(LB_ITEMDATA) )))
586 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
587 return LB_ERRSPACE;
589 descr->items = item;
590 return LB_OKAY;
594 /***********************************************************************
595 * LISTBOX_SetTabStops
597 static BOOL32 LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT32 count,
598 LPINT32 tabs, BOOL32 short_ints )
600 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
601 if (descr->tabs) HeapFree( descr->heap, 0, descr->tabs );
602 if (!(descr->nb_tabs = count))
604 descr->tabs = NULL;
605 return TRUE;
607 /* FIXME: count = 1 */
608 if (!(descr->tabs = (INT32 *)HeapAlloc( descr->heap, 0,
609 descr->nb_tabs * sizeof(INT32) )))
610 return FALSE;
611 if (short_ints)
613 INT32 i;
614 LPINT16 p = (LPINT16)tabs;
615 dbg_decl_str(listbox, 256);
617 for (i = 0; i < descr->nb_tabs; i++) {
618 descr->tabs[i] = *p++<<1; /* FIXME */
619 if(TRACE_ON(listbox))
620 dsprintf(listbox, "%hd ", descr->tabs[i]);
622 TRACE(listbox, "[%04x]: settabstops %s\n",
623 wnd->hwndSelf, dbg_str(listbox));
625 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT32) );
626 /* FIXME: repaint the window? */
627 return TRUE;
631 /***********************************************************************
632 * LISTBOX_GetText
634 static LRESULT LISTBOX_GetText( WND *wnd, LB_DESCR *descr, INT32 index,
635 LPSTR buffer )
637 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
638 if (HAS_STRINGS(descr))
640 if (!buffer)
641 return strlen(descr->items[index].str);
642 lstrcpy32A( buffer, descr->items[index].str );
643 return strlen(buffer);
644 } else {
645 if (buffer)
646 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
647 return sizeof(DWORD);
652 /***********************************************************************
653 * LISTBOX_FindStringPos
655 * Find the nearest string located before a given string in sort order.
656 * If 'exact' is TRUE, return an error if we don't get an exact match.
658 static INT32 LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCSTR str,
659 BOOL32 exact )
661 INT32 index, min, max, res = -1;
663 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
664 min = 0;
665 max = descr->nb_items;
666 while (min != max)
668 index = (min + max) / 2;
669 if (HAS_STRINGS(descr))
670 res = lstrcmpi32A( descr->items[index].str, str );
671 else
673 COMPAREITEMSTRUCT32 cis;
674 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
676 cis.CtlType = ODT_LISTBOX;
677 cis.CtlID = id;
678 cis.hwndItem = wnd->hwndSelf;
679 cis.itemID1 = index;
680 cis.itemData1 = descr->items[index].data;
681 cis.itemID2 = -1;
682 cis.itemData2 = (DWORD)str;
683 cis.dwLocaleId = descr->locale;
684 res = SendMessage32A( descr->owner, WM_COMPAREITEM,
685 id, (LPARAM)&cis );
687 if (!res) return index;
688 if (res > 0) max = index;
689 else min = index + 1;
691 return exact ? -1 : max;
695 /***********************************************************************
696 * LISTBOX_FindFileStrPos
698 * Find the nearest string located before a given string in directory
699 * sort order (i.e. first files, then directories, then drives).
701 static INT32 LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCSTR str )
703 INT32 min, max, res = -1;
705 if (!HAS_STRINGS(descr))
706 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
707 min = 0;
708 max = descr->nb_items;
709 while (min != max)
711 INT32 index = (min + max) / 2;
712 const char *p = descr->items[index].str;
713 if (*p == '[') /* drive or directory */
715 if (*str != '[') res = -1;
716 else if (p[1] == '-') /* drive */
718 if (str[1] == '-') res = str[2] - p[2];
719 else res = -1;
721 else /* directory */
723 if (str[1] == '-') res = 1;
724 else res = lstrcmpi32A( str, p );
727 else /* filename */
729 if (*str == '[') res = 1;
730 else res = lstrcmpi32A( str, p );
732 if (!res) return index;
733 if (res < 0) max = index;
734 else min = index + 1;
736 return max;
740 /***********************************************************************
741 * LISTBOX_FindString
743 * Find the item beginning with a given string.
745 static INT32 LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT32 start,
746 LPCSTR str, BOOL32 exact )
748 INT32 i;
749 LB_ITEMDATA *item;
751 if (start >= descr->nb_items) start = -1;
752 item = descr->items + start + 1;
753 if (HAS_STRINGS(descr))
755 if (!str) return LB_ERR;
756 if (exact)
758 for (i = start + 1; i < descr->nb_items; i++, item++)
759 if (!lstrcmpi32A( str, item->str )) return i;
760 for (i = 0, item = descr->items; i <= start; i++, item++)
761 if (!lstrcmpi32A( str, item->str )) return i;
763 else
765 /* Special case for drives and directories: ignore prefix */
766 #define CHECK_DRIVE(item) \
767 if ((item)->str[0] == '[') \
769 if (!lstrncmpi32A( str, (item)->str+1, len )) return i; \
770 if (((item)->str[1] == '-') && !lstrncmpi32A(str,(item)->str+2,len)) \
771 return i; \
774 INT32 len = strlen(str);
775 for (i = start + 1; i < descr->nb_items; i++, item++)
777 if (!lstrncmpi32A( str, item->str, len )) return i;
778 CHECK_DRIVE(item);
780 for (i = 0, item = descr->items; i <= start; i++, item++)
782 if (!lstrncmpi32A( str, item->str, len )) return i;
783 CHECK_DRIVE(item);
785 #undef CHECK_DRIVE
788 else
790 if (exact && (descr->style & LBS_SORT))
791 /* If sorted, use a WM_COMPAREITEM binary search */
792 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
794 /* Otherwise use a linear search */
795 for (i = start + 1; i < descr->nb_items; i++, item++)
796 if (item->data == (DWORD)str) return i;
797 for (i = 0, item = descr->items; i <= start; i++, item++)
798 if (item->data == (DWORD)str) return i;
800 return LB_ERR;
804 /***********************************************************************
805 * LISTBOX_GetSelCount
807 static LRESULT LISTBOX_GetSelCount( WND *wnd, LB_DESCR *descr )
809 INT32 i, count;
810 LB_ITEMDATA *item = descr->items;
812 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
813 for (i = count = 0; i < descr->nb_items; i++, item++)
814 if (item->selected) count++;
815 return count;
819 /***********************************************************************
820 * LISTBOX_GetSelItems16
822 static LRESULT LISTBOX_GetSelItems16( WND *wnd, LB_DESCR *descr, INT16 max,
823 LPINT16 array )
825 INT32 i, count;
826 LB_ITEMDATA *item = descr->items;
828 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
829 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
830 if (item->selected) array[count++] = (INT16)i;
831 return count;
835 /***********************************************************************
836 * LISTBOX_GetSelItems32
838 static LRESULT LISTBOX_GetSelItems32( WND *wnd, LB_DESCR *descr, INT32 max,
839 LPINT32 array )
841 INT32 i, count;
842 LB_ITEMDATA *item = descr->items;
844 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
845 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
846 if (item->selected) array[count++] = i;
847 return count;
851 /***********************************************************************
852 * LISTBOX_Paint
854 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC32 hdc )
856 INT32 i, col_pos = descr->page_size - 1;
857 RECT32 rect;
858 HFONT32 oldFont = 0;
859 HBRUSH32 hbrush, oldBrush = 0;
861 SetRect32( &rect, 0, 0, descr->width, descr->height );
862 if (descr->style & LBS_NOREDRAW) return 0;
863 if (descr->style & LBS_MULTICOLUMN)
864 rect.right = rect.left + descr->column_width;
865 else if (descr->horz_pos)
867 SetWindowOrgEx32( hdc, descr->horz_pos, 0, NULL );
868 rect.right += descr->horz_pos;
871 if (descr->font) oldFont = SelectObject32( hdc, descr->font );
872 hbrush = SendMessage32A( descr->owner, WM_CTLCOLORLISTBOX,
873 hdc, (LPARAM)wnd->hwndSelf );
874 if (hbrush) oldBrush = SelectObject32( hdc, hbrush );
875 if (wnd->dwStyle & WS_DISABLED)
876 SetTextColor32( hdc, GetSysColor32( COLOR_GRAYTEXT ) );
878 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
879 (GetFocus32() == wnd->hwndSelf))
881 /* Special case for empty listbox: paint focus rect */
882 rect.bottom = rect.top + descr->item_height;
883 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
884 ODA_FOCUS );
885 rect.top = rect.bottom;
888 for (i = descr->top_item; i < descr->nb_items; i++)
890 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
891 rect.bottom = rect.top + descr->item_height;
892 else
893 rect.bottom = rect.top + descr->items[i].height;
895 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE );
896 rect.top = rect.bottom;
898 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
900 if (!IS_OWNERDRAW(descr))
902 /* Clear the bottom of the column */
903 SetBkColor32( hdc, GetSysColor32( COLOR_WINDOW ) );
904 if (rect.top < descr->height)
906 rect.bottom = descr->height;
907 ExtTextOut32A( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
908 &rect, NULL, 0, NULL );
912 /* Go to the next column */
913 rect.left += descr->column_width;
914 rect.right += descr->column_width;
915 rect.top = 0;
916 col_pos = descr->page_size - 1;
918 else
920 col_pos--;
921 if (rect.top >= descr->height) break;
925 if (!IS_OWNERDRAW(descr))
927 /* Clear the remainder of the client area */
928 SetBkColor32( hdc, GetSysColor32( COLOR_WINDOW ) );
929 if (rect.top < descr->height)
931 rect.bottom = descr->height;
932 ExtTextOut32A( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
933 &rect, NULL, 0, NULL );
935 if (rect.right < descr->width)
937 rect.left = rect.right;
938 rect.right = descr->width;
939 rect.top = 0;
940 rect.bottom = descr->height;
941 ExtTextOut32A( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
942 &rect, NULL, 0, NULL );
945 if (oldFont) SelectObject32( hdc, oldFont );
946 if (oldBrush) SelectObject32( hdc, oldBrush );
947 return 0;
951 /***********************************************************************
952 * LISTBOX_InvalidateItems
954 * Invalidate all items from a given item. If the specified item is not
955 * visible, nothing happens.
957 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT32 index )
959 RECT32 rect;
961 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) == 1)
963 rect.bottom = descr->height;
964 InvalidateRect32( wnd->hwndSelf, &rect, TRUE );
965 if (descr->style & LBS_MULTICOLUMN)
967 /* Repaint the other columns */
968 rect.left = rect.right;
969 rect.right = descr->width;
970 rect.top = 0;
971 InvalidateRect32( wnd->hwndSelf, &rect, TRUE );
977 /***********************************************************************
978 * LISTBOX_GetItemHeight
980 static LRESULT LISTBOX_GetItemHeight( WND *wnd, LB_DESCR *descr, INT32 index )
982 if (descr->style & LBS_OWNERDRAWVARIABLE)
984 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
985 return descr->items[index].height;
987 else return descr->item_height;
991 /***********************************************************************
992 * LISTBOX_SetItemHeight
994 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT32 index,
995 UINT32 height )
997 if (!height) height = 1;
999 if (descr->style & LBS_OWNERDRAWVARIABLE)
1001 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1002 TRACE(listbox, "[%04x]: item %d height = %d\n",
1003 wnd->hwndSelf, index, height );
1004 descr->items[index].height = height;
1005 LISTBOX_UpdateScroll( wnd, descr );
1006 LISTBOX_InvalidateItems( wnd, descr, index );
1008 else if (height != descr->item_height)
1010 TRACE(listbox, "[%04x]: new height = %d\n",
1011 wnd->hwndSelf, height );
1012 descr->item_height = height;
1013 LISTBOX_UpdatePage( wnd, descr );
1014 LISTBOX_UpdateScroll( wnd, descr );
1015 InvalidateRect32( wnd->hwndSelf, 0, TRUE );
1017 return LB_OKAY;
1021 /***********************************************************************
1022 * LISTBOX_SetHorizontalPos
1024 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT32 pos )
1026 INT32 diff;
1028 if (pos > descr->horz_extent - descr->width)
1029 pos = descr->horz_extent - descr->width;
1030 if (pos < 0) pos = 0;
1031 if (!(diff = descr->horz_pos - pos)) return;
1032 TRACE(listbox, "[%04x]: new horz pos = %d\n",
1033 wnd->hwndSelf, pos );
1034 descr->horz_pos = pos;
1035 LISTBOX_UpdateScroll( wnd, descr );
1036 if (abs(diff) < descr->width)
1037 ScrollWindowEx32( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1038 SW_INVALIDATE | SW_ERASE );
1039 else
1040 InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
1044 /***********************************************************************
1045 * LISTBOX_SetHorizontalExtent
1047 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1048 UINT32 extent )
1050 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1051 return LB_OKAY;
1052 if (extent <= 0) extent = 1;
1053 if (extent == descr->horz_extent) return LB_OKAY;
1054 TRACE(listbox, "[%04x]: new horz extent = %d\n",
1055 wnd->hwndSelf, extent );
1056 descr->horz_extent = extent;
1057 if (descr->horz_pos > extent - descr->width)
1058 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1059 else
1060 LISTBOX_UpdateScroll( wnd, descr );
1061 return LB_OKAY;
1065 /***********************************************************************
1066 * LISTBOX_SetColumnWidth
1068 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, UINT32 width)
1070 width += 2; /* For left and right margin */
1071 if (width == descr->column_width) return LB_OKAY;
1072 TRACE(listbox, "[%04x]: new column width = %d\n",
1073 wnd->hwndSelf, width );
1074 descr->column_width = width;
1075 LISTBOX_UpdatePage( wnd, descr );
1076 return LB_OKAY;
1080 /***********************************************************************
1081 * LISTBOX_SetFont
1083 * Returns the item height.
1085 static INT32 LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT32 font )
1087 HDC32 hdc;
1088 HFONT32 oldFont = 0;
1089 TEXTMETRIC32A tm;
1091 descr->font = font;
1093 if (!(hdc = GetDCEx32( wnd->hwndSelf, 0, DCX_CACHE )))
1095 ERR(listbox, "unable to get DC.\n" );
1096 return 16;
1098 if (font) oldFont = SelectObject32( hdc, font );
1099 GetTextMetrics32A( hdc, &tm );
1100 if (oldFont) SelectObject32( hdc, oldFont );
1101 ReleaseDC32( wnd->hwndSelf, hdc );
1102 if (!IS_OWNERDRAW(descr))
1103 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1104 return tm.tmHeight ;
1108 /***********************************************************************
1109 * LISTBOX_MakeItemVisible
1111 * Make sure that a given item is partially or fully visible.
1113 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT32 index,
1114 BOOL32 fully )
1116 INT32 top;
1118 if (index <= descr->top_item) top = index;
1119 else if (descr->style & LBS_MULTICOLUMN)
1121 INT32 cols = descr->width;
1122 if (!fully) cols += descr->column_width - 1;
1123 if (cols >= descr->column_width) cols /= descr->column_width;
1124 else cols = 1;
1125 if (index < descr->top_item + (descr->page_size * cols)) return;
1126 top = index - descr->page_size * (cols - 1);
1128 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1130 INT32 height = fully ? descr->items[index].height : 1;
1131 for (top = index; top > descr->top_item; top--)
1132 if ((height += descr->items[top-1].height) > descr->height) break;
1134 else
1136 if (index < descr->top_item + descr->page_size) return;
1137 if (!fully && (index == descr->top_item + descr->page_size) &&
1138 (descr->height > (descr->page_size * descr->item_height))) return;
1139 top = index - descr->page_size + 1;
1141 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1145 /***********************************************************************
1146 * LISTBOX_SelectItemRange
1148 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1150 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT32 first,
1151 INT32 last, BOOL32 on )
1153 INT32 i;
1155 /* A few sanity checks */
1157 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1158 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1159 if (last == -1) last = descr->nb_items - 1;
1160 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1161 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1162 /* selected_item reflects last selected/unselected item on multiple sel */
1163 descr->selected_item = last;
1165 if (on) /* Turn selection on */
1167 for (i = first; i <= last; i++)
1169 if (descr->items[i].selected) continue;
1170 descr->items[i].selected = TRUE;
1171 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1174 else /* Turn selection off */
1176 for (i = first; i <= last; i++)
1178 if (!descr->items[i].selected) continue;
1179 descr->items[i].selected = FALSE;
1180 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1183 return LB_OKAY;
1187 /***********************************************************************
1188 * LISTBOX_SetCaretIndex
1190 * NOTES
1191 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1194 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT32 index,
1195 BOOL32 fully_visible )
1197 INT32 oldfocus = descr->focus_item;
1199 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1200 if (index == oldfocus) return LB_OKAY;
1201 descr->focus_item = index;
1202 if ((oldfocus != -1) && descr->caret_on && (GetFocus32() == wnd->hwndSelf))
1203 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1205 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1206 if (descr->caret_on && (GetFocus32() == wnd->hwndSelf))
1207 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1209 return LB_OKAY;
1213 /***********************************************************************
1214 * LISTBOX_SetSelection
1216 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT32 index,
1217 BOOL32 on, BOOL32 send_notify )
1219 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1220 if (descr->style & LBS_MULTIPLESEL)
1222 if (index == -1) /* Select all items */
1223 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1224 else /* Only one item */
1225 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1227 else
1229 INT32 oldsel = descr->selected_item;
1230 if (index == oldsel) return LB_OKAY;
1231 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1232 if (index != -1) descr->items[index].selected = TRUE;
1233 descr->selected_item = index;
1234 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT);
1235 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1236 if (send_notify) SEND_NOTIFICATION( wnd, descr,
1237 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1238 else
1239 if( descr->lphc ) /* set selection change flag for parent combo */
1240 descr->lphc->wState |= CBF_SELCHANGE;
1242 return LB_OKAY;
1246 /***********************************************************************
1247 * LISTBOX_MoveCaret
1249 * Change the caret position and extend the selection to the new caret.
1251 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT32 index,
1252 BOOL32 fully_visible )
1254 LISTBOX_SetCaretIndex( wnd, descr, index, fully_visible );
1255 if (descr->style & LBS_EXTENDEDSEL)
1257 if (descr->anchor_item != -1)
1259 INT32 first = MIN( descr->focus_item, descr->anchor_item );
1260 INT32 last = MAX( descr->focus_item, descr->anchor_item );
1261 if (first > 0)
1262 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1263 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1264 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1267 else if (!(descr->style & LBS_MULTIPLESEL) && (descr->selected_item != -1))
1269 /* Set selection to new caret item */
1270 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1275 /***********************************************************************
1276 * LISTBOX_InsertItem
1278 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT32 index,
1279 LPSTR str, DWORD data )
1281 LB_ITEMDATA *item;
1282 INT32 max_items;
1284 if (index == -1) index = descr->nb_items;
1285 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1286 if (!descr->items) max_items = 0;
1287 else max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
1288 if (descr->nb_items == max_items)
1290 /* We need to grow the array */
1291 max_items += LB_ARRAY_GRANULARITY;
1292 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
1293 max_items * sizeof(LB_ITEMDATA) )))
1295 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1296 return LB_ERRSPACE;
1298 descr->items = item;
1301 /* Insert the item structure */
1303 item = &descr->items[index];
1304 if (index < descr->nb_items)
1305 RtlMoveMemory( item + 1, item,
1306 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1307 item->str = str;
1308 item->data = data;
1309 item->height = 0;
1310 item->selected = FALSE;
1311 descr->nb_items++;
1313 /* Get item height */
1315 if (descr->style & LBS_OWNERDRAWVARIABLE)
1317 MEASUREITEMSTRUCT32 mis;
1318 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1320 mis.CtlType = ODT_LISTBOX;
1321 mis.CtlID = id;
1322 mis.itemID = index;
1323 mis.itemData = descr->items[index].data;
1324 mis.itemHeight = descr->item_height;
1325 SendMessage32A( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1326 item->height = mis.itemHeight ? mis.itemHeight : 1;
1327 TRACE(listbox, "[%04x]: measure item %d (%s) = %d\n",
1328 wnd->hwndSelf, index, str ? str : "", item->height );
1331 /* Repaint the items */
1333 LISTBOX_UpdateScroll( wnd, descr );
1334 LISTBOX_InvalidateItems( wnd, descr, index );
1336 /* Move selection and focused item */
1338 if (index <= descr->selected_item) descr->selected_item++;
1339 if (index <= descr->focus_item)
1341 descr->focus_item++;
1342 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1345 /* If listbox was empty, set focus to the first item */
1347 if (descr->nb_items == 1) LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1348 return LB_OKAY;
1352 /***********************************************************************
1353 * LISTBOX_InsertString
1355 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT32 index,
1356 LPCSTR str )
1358 LPSTR new_str = NULL;
1359 DWORD data = 0;
1360 LRESULT ret;
1362 if (HAS_STRINGS(descr))
1364 if (!(new_str = HEAP_strdupA( descr->heap, 0, str )))
1366 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1367 return LB_ERRSPACE;
1370 else data = (DWORD)str;
1372 if (index == -1) index = descr->nb_items;
1373 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1375 if (new_str) HeapFree( descr->heap, 0, new_str );
1376 return ret;
1379 TRACE(listbox, "[%04x]: added item %d '%s'\n",
1380 wnd->hwndSelf, index, HAS_STRINGS(descr) ? new_str : "" );
1381 return index;
1385 /***********************************************************************
1386 * LISTBOX_DeleteItem
1388 * Delete the content of an item. 'index' must be a valid index.
1390 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT32 index )
1392 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1393 * while Win95 sends it for all items with user data.
1394 * It's probably better to send it too often than not
1395 * often enough, so this is what we do here.
1397 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1399 DELETEITEMSTRUCT32 dis;
1400 UINT32 id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1402 dis.CtlType = ODT_LISTBOX;
1403 dis.CtlID = id;
1404 dis.itemID = index;
1405 dis.hwndItem = wnd->hwndSelf;
1406 dis.itemData = descr->items[index].data;
1407 SendMessage32A( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1409 if (HAS_STRINGS(descr) && descr->items[index].str)
1410 HeapFree( descr->heap, 0, descr->items[index].str );
1414 /***********************************************************************
1415 * LISTBOX_RemoveItem
1417 * Remove an item from the listbox and delete its content.
1419 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT32 index )
1421 LB_ITEMDATA *item;
1422 INT32 max_items;
1424 if (index == -1) index = descr->nb_items - 1;
1425 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1426 LISTBOX_DeleteItem( wnd, descr, index );
1428 /* Remove the item */
1430 item = &descr->items[index];
1431 if (index < descr->nb_items-1)
1432 RtlMoveMemory( item, item + 1,
1433 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1434 descr->nb_items--;
1435 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1437 /* Shrink the item array if possible */
1439 max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(LB_ITEMDATA);
1440 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1442 max_items -= LB_ARRAY_GRANULARITY;
1443 item = HeapReAlloc( descr->heap, 0, descr->items,
1444 max_items * sizeof(LB_ITEMDATA) );
1445 if (item) descr->items = item;
1448 /* Repaint the items */
1450 LISTBOX_UpdateScroll( wnd, descr );
1451 LISTBOX_InvalidateItems( wnd, descr, index );
1453 /* Move selection and focused item */
1455 if (index <= descr->selected_item) descr->selected_item--;
1456 if (index <= descr->focus_item)
1458 descr->focus_item--;
1459 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1461 return LB_OKAY;
1465 /***********************************************************************
1466 * LISTBOX_ResetContent
1468 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1470 INT32 i;
1472 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1473 if (descr->items) HeapFree( descr->heap, 0, descr->items );
1474 descr->nb_items = 0;
1475 descr->top_item = 0;
1476 descr->selected_item = -1;
1477 descr->focus_item = 0;
1478 descr->anchor_item = -1;
1479 descr->items = NULL;
1480 LISTBOX_UpdateScroll( wnd, descr );
1481 InvalidateRect32( wnd->hwndSelf, NULL, TRUE );
1485 /***********************************************************************
1486 * LISTBOX_SetCount
1488 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT32 count )
1490 LRESULT ret;
1492 if (HAS_STRINGS(descr)) return LB_ERR;
1493 /* FIXME: this is far from optimal... */
1494 if (count > descr->nb_items)
1496 while (count > descr->nb_items)
1497 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1498 return ret;
1500 else if (count < descr->nb_items)
1502 while (count < descr->nb_items)
1503 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1504 return ret;
1506 return LB_OKAY;
1510 /***********************************************************************
1511 * LISTBOX_Directory
1513 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT32 attrib,
1514 LPCSTR filespec, BOOL32 long_names )
1516 HANDLE32 handle;
1517 LRESULT ret = LB_OKAY;
1518 WIN32_FIND_DATA32A entry;
1519 int pos;
1521 if ((handle = FindFirstFile32A(filespec,&entry)) == INVALID_HANDLE_VALUE32)
1523 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1525 else
1529 char buffer[270];
1530 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1532 if (!(attrib & DDL_DIRECTORY) ||
1533 !strcmp( entry.cAlternateFileName, "." )) continue;
1534 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1535 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1537 else /* not a directory */
1539 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1540 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1542 if ((attrib & DDL_EXCLUSIVE) &&
1543 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1544 continue;
1545 #undef ATTRIBS
1546 if (long_names) strcpy( buffer, entry.cFileName );
1547 else strcpy( buffer, entry.cAlternateFileName );
1549 if (!long_names) CharLower32A( buffer );
1550 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1551 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1552 break;
1553 } while (FindNextFile32A( handle, &entry ));
1554 FindClose32( handle );
1557 if ((ret >= 0) && (attrib & DDL_DRIVES))
1559 char buffer[] = "[-a-]";
1560 int drive;
1561 for (drive = 0; drive < MAX_DOS_DRIVES; drive++, buffer[2]++)
1563 if (!DRIVE_IsValid(drive)) continue;
1564 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1565 break;
1568 return ret;
1572 /***********************************************************************
1573 * LISTBOX_HandleVScroll
1575 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1576 WPARAM32 wParam, LPARAM lParam )
1578 SCROLLINFO info;
1580 if (descr->style & LBS_MULTICOLUMN) return 0;
1581 switch(LOWORD(wParam))
1583 case SB_LINEUP:
1584 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1585 break;
1586 case SB_LINEDOWN:
1587 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1588 break;
1589 case SB_PAGEUP:
1590 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1591 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1592 break;
1593 case SB_PAGEDOWN:
1594 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1595 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1596 break;
1597 case SB_THUMBPOSITION:
1598 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1599 break;
1600 case SB_THUMBTRACK:
1601 info.cbSize = sizeof(info);
1602 info.fMask = SIF_TRACKPOS;
1603 GetScrollInfo32( wnd->hwndSelf, SB_VERT, &info );
1604 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1605 break;
1606 case SB_TOP:
1607 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1608 break;
1609 case SB_BOTTOM:
1610 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1611 break;
1613 return 0;
1617 /***********************************************************************
1618 * LISTBOX_HandleHScroll
1620 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1621 WPARAM32 wParam, LPARAM lParam )
1623 SCROLLINFO info;
1624 INT32 page;
1626 if (descr->style & LBS_MULTICOLUMN)
1628 switch(LOWORD(wParam))
1630 case SB_LINELEFT:
1631 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1632 TRUE );
1633 break;
1634 case SB_LINERIGHT:
1635 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1636 TRUE );
1637 break;
1638 case SB_PAGELEFT:
1639 page = descr->width / descr->column_width;
1640 if (page < 1) page = 1;
1641 LISTBOX_SetTopItem( wnd, descr,
1642 descr->top_item - page * descr->page_size, TRUE );
1643 break;
1644 case SB_PAGERIGHT:
1645 page = descr->width / descr->column_width;
1646 if (page < 1) page = 1;
1647 LISTBOX_SetTopItem( wnd, descr,
1648 descr->top_item + page * descr->page_size, TRUE );
1649 break;
1650 case SB_THUMBPOSITION:
1651 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1652 TRUE );
1653 break;
1654 case SB_THUMBTRACK:
1655 info.cbSize = sizeof(info);
1656 info.fMask = SIF_TRACKPOS;
1657 GetScrollInfo32( wnd->hwndSelf, SB_VERT, &info );
1658 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1659 TRUE );
1660 break;
1661 case SB_LEFT:
1662 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1663 break;
1664 case SB_RIGHT:
1665 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1666 break;
1669 else if (descr->horz_extent)
1671 switch(LOWORD(wParam))
1673 case SB_LINELEFT:
1674 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1675 break;
1676 case SB_LINERIGHT:
1677 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1678 break;
1679 case SB_PAGELEFT:
1680 LISTBOX_SetHorizontalPos( wnd, descr,
1681 descr->horz_pos - descr->width );
1682 break;
1683 case SB_PAGERIGHT:
1684 LISTBOX_SetHorizontalPos( wnd, descr,
1685 descr->horz_pos + descr->width );
1686 break;
1687 case SB_THUMBPOSITION:
1688 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1689 break;
1690 case SB_THUMBTRACK:
1691 info.cbSize = sizeof(info);
1692 info.fMask = SIF_TRACKPOS;
1693 GetScrollInfo32( wnd->hwndSelf, SB_HORZ, &info );
1694 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1695 break;
1696 case SB_LEFT:
1697 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1698 break;
1699 case SB_RIGHT:
1700 LISTBOX_SetHorizontalPos( wnd, descr,
1701 descr->horz_extent - descr->width );
1702 break;
1705 return 0;
1709 /***********************************************************************
1710 * LISTBOX_HandleLButtonDown
1712 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1713 WPARAM32 wParam, INT32 x, INT32 y )
1715 INT32 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1716 TRACE(listbox, "[%04x]: lbuttondown %d,%d item %d\n",
1717 wnd->hwndSelf, x, y, index );
1718 if (!descr->caret_on && (GetFocus32() == wnd->hwndSelf)) return 0;
1719 if (index != -1)
1721 if (descr->style & LBS_EXTENDEDSEL)
1723 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1724 if (wParam & MK_CONTROL)
1726 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1727 LISTBOX_SetSelection( wnd, descr, index,
1728 !descr->items[index].selected, FALSE );
1730 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1732 else
1734 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1735 LISTBOX_SetSelection( wnd, descr, index,
1736 (!(descr->style & LBS_MULTIPLESEL) ||
1737 !descr->items[index].selected), FALSE );
1741 if( !descr->lphc ) SetFocus32( wnd->hwndSelf );
1742 else SetFocus32( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1743 : descr->lphc->self->hwndSelf ) ;
1745 SetCapture32( wnd->hwndSelf );
1746 if (index != -1 && !descr->lphc)
1748 if (descr->style & LBS_NOTIFY )
1749 SendMessage32A( descr->owner, WM_LBTRACKPOINT, index,
1750 MAKELPARAM( x, y ) );
1751 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1753 POINT32 pt = { x, y };
1754 if (DragDetect32( wnd->hwndSelf, pt ))
1755 SendMessage32A( descr->owner, WM_BEGINDRAG, 0, 0 );
1758 return 0;
1762 /***********************************************************************
1763 * LISTBOX_HandleLButtonUp
1765 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
1767 if (LISTBOX_Timer != LB_TIMER_NONE)
1768 KillSystemTimer32( wnd->hwndSelf, LB_TIMER_ID );
1769 LISTBOX_Timer = LB_TIMER_NONE;
1770 if (GetCapture32() == wnd->hwndSelf)
1772 ReleaseCapture();
1773 if (descr->style & LBS_NOTIFY)
1774 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
1776 return 0;
1780 /***********************************************************************
1781 * LISTBOX_HandleTimer
1783 * Handle scrolling upon a timer event.
1784 * Return TRUE if scrolling should continue.
1786 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
1787 INT32 index, TIMER_DIRECTION dir )
1789 switch(dir)
1791 case LB_TIMER_UP:
1792 if (descr->top_item) index = descr->top_item - 1;
1793 else index = 0;
1794 break;
1795 case LB_TIMER_LEFT:
1796 if (descr->top_item) index -= descr->page_size;
1797 break;
1798 case LB_TIMER_DOWN:
1799 index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
1800 if (index == descr->focus_item) index++;
1801 if (index >= descr->nb_items) index = descr->nb_items - 1;
1802 break;
1803 case LB_TIMER_RIGHT:
1804 if (index + descr->page_size < descr->nb_items)
1805 index += descr->page_size;
1806 break;
1807 case LB_TIMER_NONE:
1808 break;
1810 if (index == descr->focus_item) return FALSE;
1811 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1812 return TRUE;
1816 /***********************************************************************
1817 * LISTBOX_HandleSystemTimer
1819 * WM_SYSTIMER handler.
1821 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
1823 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
1825 KillSystemTimer32( wnd->hwndSelf, LB_TIMER_ID );
1826 LISTBOX_Timer = LB_TIMER_NONE;
1828 return 0;
1832 /***********************************************************************
1833 * LISTBOX_HandleMouseMove
1835 * WM_MOUSEMOVE handler.
1837 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
1838 INT32 x, INT32 y )
1840 INT32 index;
1841 TIMER_DIRECTION dir;
1843 if (descr->style & LBS_MULTICOLUMN)
1845 if (y < 0) y = 0;
1846 else if (y >= descr->item_height * descr->page_size)
1847 y = descr->item_height * descr->page_size - 1;
1849 if (x < 0)
1851 dir = LB_TIMER_LEFT;
1852 x = 0;
1854 else if (x >= descr->width)
1856 dir = LB_TIMER_RIGHT;
1857 x = descr->width - 1;
1859 else dir = LB_TIMER_NONE; /* inside */
1861 else
1863 if (y < 0) dir = LB_TIMER_UP; /* above */
1864 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
1865 else dir = LB_TIMER_NONE; /* inside */
1868 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1869 if (index == -1) index = descr->focus_item;
1870 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
1872 /* Start/stop the system timer */
1874 if (dir != LB_TIMER_NONE)
1875 SetSystemTimer32( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
1876 else if (LISTBOX_Timer != LB_TIMER_NONE)
1877 KillSystemTimer32( wnd->hwndSelf, LB_TIMER_ID );
1878 LISTBOX_Timer = dir;
1882 /***********************************************************************
1883 * LISTBOX_HandleKeyDown
1885 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM32 wParam )
1887 INT32 caret = -1;
1888 if (descr->style & LBS_WANTKEYBOARDINPUT)
1890 caret = SendMessage32A( descr->owner, WM_VKEYTOITEM,
1891 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
1892 wnd->hwndSelf );
1893 if (caret == -2) return 0;
1895 if (caret == -1) switch(wParam)
1897 case VK_LEFT:
1898 if (descr->style & LBS_MULTICOLUMN)
1900 if (descr->focus_item >= descr->page_size)
1901 caret = descr->focus_item - descr->page_size;
1902 break;
1904 /* fall through */
1905 case VK_UP:
1906 caret = descr->focus_item - 1;
1907 if (caret < 0) caret = 0;
1908 break;
1909 case VK_RIGHT:
1910 if (descr->style & LBS_MULTICOLUMN)
1912 if (descr->focus_item + descr->page_size < descr->nb_items)
1913 caret = descr->focus_item + descr->page_size;
1914 break;
1916 /* fall through */
1917 case VK_DOWN:
1918 caret = descr->focus_item + 1;
1919 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1920 break;
1921 case VK_PRIOR:
1922 if (descr->style & LBS_MULTICOLUMN)
1924 INT32 page = descr->width / descr->column_width;
1925 if (page < 1) page = 1;
1926 caret = descr->focus_item - (page * descr->page_size) + 1;
1928 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
1929 if (caret < 0) caret = 0;
1930 break;
1931 case VK_NEXT:
1932 if (descr->style & LBS_MULTICOLUMN)
1934 INT32 page = descr->width / descr->column_width;
1935 if (page < 1) page = 1;
1936 caret = descr->focus_item + (page * descr->page_size) - 1;
1938 else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
1939 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1940 break;
1941 case VK_HOME:
1942 caret = 0;
1943 break;
1944 case VK_END:
1945 caret = descr->nb_items - 1;
1946 break;
1947 case VK_SPACE:
1948 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
1949 else if (descr->style & LBS_MULTIPLESEL)
1951 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
1952 !descr->items[descr->focus_item].selected,
1953 (descr->style & LBS_NOTIFY) != 0 );
1955 else if (descr->selected_item == -1)
1957 LISTBOX_SetSelection( wnd, descr, descr->focus_item, TRUE,
1958 (descr->style & LBS_NOTIFY) != 0 );
1960 break;
1962 if (caret >= 0)
1964 if ((descr->style & LBS_EXTENDEDSEL) &&
1965 !(GetKeyState32( VK_SHIFT ) & 0x8000))
1966 descr->anchor_item = caret;
1967 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
1968 if (descr->style & LBS_NOTIFY)
1970 if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
1972 /* make sure that combo parent doesn't hide us */
1973 descr->lphc->wState |= CBF_NOROLLUP;
1975 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
1978 return 0;
1982 /***********************************************************************
1983 * LISTBOX_HandleChar
1985 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
1986 WPARAM32 wParam )
1988 INT32 caret = -1;
1989 char str[2] = { wParam & 0xff, '\0' };
1991 if (descr->style & LBS_WANTKEYBOARDINPUT)
1993 caret = SendMessage32A( descr->owner, WM_CHARTOITEM,
1994 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
1995 wnd->hwndSelf );
1996 if (caret == -2) return 0;
1998 if (caret == -1)
1999 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2000 if (caret != -1)
2002 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2003 if (descr->style & LBS_NOTIFY)
2004 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2006 return 0;
2010 /***********************************************************************
2011 * LISTBOX_Create
2013 static BOOL32 LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2015 LB_DESCR *descr;
2016 MEASUREITEMSTRUCT32 mis;
2017 RECT32 rect;
2019 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2020 return FALSE;
2021 if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2023 HeapFree( GetProcessHeap(), 0, descr );
2024 return FALSE;
2026 GetClientRect32( wnd->hwndSelf, &rect );
2027 descr->owner = GetParent32( wnd->hwndSelf );
2028 descr->style = wnd->dwStyle;
2029 descr->width = rect.right - rect.left;
2030 descr->height = rect.bottom - rect.top;
2031 descr->items = NULL;
2032 descr->nb_items = 0;
2033 descr->top_item = 0;
2034 descr->selected_item = -1;
2035 descr->focus_item = 0;
2036 descr->anchor_item = -1;
2037 descr->item_height = 1;
2038 descr->page_size = 1;
2039 descr->column_width = 150;
2040 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2041 descr->horz_pos = 0;
2042 descr->nb_tabs = 0;
2043 descr->tabs = NULL;
2044 descr->caret_on = TRUE;
2045 descr->font = 0;
2046 descr->locale = 0; /* FIXME */
2047 descr->lphc = lphc;
2049 if( lphc )
2051 TRACE(combo,"[%04x]: resetting owner %04x -> %04x\n",
2052 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2053 descr->owner = lphc->self->hwndSelf;
2056 *(LB_DESCR **)wnd->wExtra = descr;
2058 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2060 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2061 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2062 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2063 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2065 if (descr->style & LBS_OWNERDRAWFIXED)
2067 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2069 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2070 descr->item_height = lphc->RectButton.bottom - lphc->RectButton.top - 6;
2072 else
2074 UINT32 id = (descr->lphc ) ? ID_CB_LISTBOX : wnd->wIDmenu;
2076 mis.CtlType = ODT_LISTBOX;
2077 mis.CtlID = id;
2078 mis.itemID = -1;
2079 mis.itemWidth = 0;
2080 mis.itemData = 0;
2081 mis.itemHeight = descr->item_height;
2082 SendMessage32A( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2083 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2087 return TRUE;
2091 /***********************************************************************
2092 * LISTBOX_Destroy
2094 static BOOL32 LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2096 LISTBOX_ResetContent( wnd, descr );
2097 HeapDestroy( descr->heap );
2098 HeapFree( GetProcessHeap(), 0, descr );
2099 wnd->wExtra[0] = 0;
2100 return TRUE;
2104 /***********************************************************************
2105 * ListBoxWndProc
2107 LRESULT WINAPI ListBoxWndProc( HWND32 hwnd, UINT32 msg,
2108 WPARAM32 wParam, LPARAM lParam )
2110 LRESULT ret;
2111 LB_DESCR *descr;
2112 WND *wnd = WIN_FindWndPtr( hwnd );
2114 if (!wnd) return 0;
2115 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2117 if (msg == WM_CREATE)
2119 if (!LISTBOX_Create( wnd, NULL )) return -1;
2120 TRACE(listbox, "creating wnd=%04x descr=%p\n",
2121 hwnd, *(LB_DESCR **)wnd->wExtra );
2122 return 0;
2124 /* Ignore all other messages before we get a WM_CREATE */
2125 return DefWindowProc32A( hwnd, msg, wParam, lParam );
2128 TRACE(listbox, "[%04x]: msg %s wp %08x lp %08lx\n",
2129 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2130 switch(msg)
2132 case LB_RESETCONTENT16:
2133 case LB_RESETCONTENT32:
2134 LISTBOX_ResetContent( wnd, descr );
2135 return 0;
2137 case LB_ADDSTRING16:
2138 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2139 /* fall through */
2140 case LB_ADDSTRING32:
2141 wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2142 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2144 case LB_INSERTSTRING16:
2145 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2146 wParam = (INT32)(INT16)wParam;
2147 /* fall through */
2148 case LB_INSERTSTRING32:
2149 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2151 case LB_ADDFILE16:
2152 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2153 /* fall through */
2154 case LB_ADDFILE32:
2155 wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2156 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2158 case LB_DELETESTRING16:
2159 case LB_DELETESTRING32:
2160 return LISTBOX_RemoveItem( wnd, descr, wParam );
2162 case LB_GETITEMDATA16:
2163 case LB_GETITEMDATA32:
2164 if (((INT32)wParam < 0) || ((INT32)wParam >= descr->nb_items))
2165 return LB_ERR;
2166 return descr->items[wParam].data;
2168 case LB_SETITEMDATA16:
2169 case LB_SETITEMDATA32:
2170 if (((INT32)wParam < 0) || ((INT32)wParam >= descr->nb_items))
2171 return LB_ERR;
2172 descr->items[wParam].data = (DWORD)lParam;
2173 return LB_OKAY;
2175 case LB_GETCOUNT16:
2176 case LB_GETCOUNT32:
2177 return descr->nb_items;
2179 case LB_GETTEXT16:
2180 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2181 /* fall through */
2182 case LB_GETTEXT32:
2183 return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2185 case LB_GETTEXTLEN16:
2186 /* fall through */
2187 case LB_GETTEXTLEN32:
2188 if (wParam >= descr->nb_items) return LB_ERR;
2189 return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2190 : sizeof(DWORD));
2192 case LB_GETCURSEL16:
2193 case LB_GETCURSEL32:
2194 return descr->selected_item;
2196 case LB_GETTOPINDEX16:
2197 case LB_GETTOPINDEX32:
2198 return descr->top_item;
2200 case LB_GETITEMHEIGHT16:
2201 case LB_GETITEMHEIGHT32:
2202 return LISTBOX_GetItemHeight( wnd, descr, wParam );
2204 case LB_SETITEMHEIGHT16:
2205 lParam = LOWORD(lParam);
2206 /* fall through */
2207 case LB_SETITEMHEIGHT32:
2208 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2210 case LB_ITEMFROMPOINT32:
2212 POINT32 pt = { LOWORD(lParam), HIWORD(lParam) };
2213 RECT32 rect = { 0, 0, descr->width, descr->height };
2214 return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2215 PtInRect32( &rect, pt ) );
2218 case LB_SETCARETINDEX16:
2219 case LB_SETCARETINDEX32:
2220 return LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam );
2222 case LB_GETCARETINDEX16:
2223 case LB_GETCARETINDEX32:
2224 return descr->focus_item;
2226 case LB_SETTOPINDEX16:
2227 case LB_SETTOPINDEX32:
2228 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2230 case LB_SETCOLUMNWIDTH16:
2231 case LB_SETCOLUMNWIDTH32:
2232 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2234 case LB_GETITEMRECT16:
2236 RECT32 rect;
2237 ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2238 CONV_RECT32TO16( &rect, (RECT16 *)PTR_SEG_TO_LIN(lParam) );
2240 return ret;
2242 case LB_GETITEMRECT32:
2243 return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT32 *)lParam );
2245 case LB_FINDSTRING16:
2246 wParam = (INT32)(INT16)wParam;
2247 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2248 /* fall through */
2249 case LB_FINDSTRING32:
2250 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2252 case LB_FINDSTRINGEXACT16:
2253 wParam = (INT32)(INT16)wParam;
2254 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2255 /* fall through */
2256 case LB_FINDSTRINGEXACT32:
2257 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2259 case LB_SELECTSTRING16:
2260 wParam = (INT32)(INT16)wParam;
2261 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2262 /* fall through */
2263 case LB_SELECTSTRING32:
2265 INT32 index = LISTBOX_FindString( wnd, descr, wParam,
2266 (LPCSTR)lParam, FALSE );
2267 if (index == LB_ERR) return LB_ERR;
2268 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2269 return index;
2272 case LB_GETSEL16:
2273 wParam = (INT32)(INT16)wParam;
2274 /* fall through */
2275 case LB_GETSEL32:
2276 if (((INT32)wParam < 0) || ((INT32)wParam >= descr->nb_items))
2277 return LB_ERR;
2278 return descr->items[wParam].selected;
2280 case LB_SETSEL16:
2281 lParam = (INT32)(INT16)lParam;
2282 /* fall through */
2283 case LB_SETSEL32:
2284 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2286 case LB_SETCURSEL16:
2287 wParam = (INT32)(INT16)wParam;
2288 /* fall through */
2289 case LB_SETCURSEL32:
2290 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2291 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2293 case LB_GETSELCOUNT16:
2294 case LB_GETSELCOUNT32:
2295 return LISTBOX_GetSelCount( wnd, descr );
2297 case LB_GETSELITEMS16:
2298 return LISTBOX_GetSelItems16( wnd, descr, wParam,
2299 (LPINT16)PTR_SEG_TO_LIN(lParam) );
2301 case LB_GETSELITEMS32:
2302 return LISTBOX_GetSelItems32( wnd, descr, wParam, (LPINT32)lParam );
2304 case LB_SELITEMRANGE16:
2305 case LB_SELITEMRANGE32:
2306 if (LOWORD(lParam) <= HIWORD(lParam))
2307 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2308 HIWORD(lParam), wParam );
2309 else
2310 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2311 LOWORD(lParam), wParam );
2313 case LB_SELITEMRANGEEX16:
2314 case LB_SELITEMRANGEEX32:
2315 if ((INT32)lParam >= (INT32)wParam)
2316 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2317 else
2318 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2320 case LB_GETHORIZONTALEXTENT16:
2321 case LB_GETHORIZONTALEXTENT32:
2322 return descr->horz_extent;
2324 case LB_SETHORIZONTALEXTENT16:
2325 case LB_SETHORIZONTALEXTENT32:
2326 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2328 case LB_GETANCHORINDEX16:
2329 case LB_GETANCHORINDEX32:
2330 return descr->anchor_item;
2332 case LB_SETANCHORINDEX16:
2333 wParam = (INT32)(INT16)wParam;
2334 /* fall through */
2335 case LB_SETANCHORINDEX32:
2336 if (((INT32)wParam < -1) || ((INT32)wParam >= descr->nb_items))
2337 return LB_ERR;
2338 descr->anchor_item = (INT32)wParam;
2339 return LB_OKAY;
2341 case LB_DIR16:
2342 return LISTBOX_Directory( wnd, descr, wParam,
2343 (LPCSTR)PTR_SEG_TO_LIN(lParam), FALSE );
2345 case LB_DIR32:
2346 return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2348 case LB_GETLOCALE32:
2349 return descr->locale;
2351 case LB_SETLOCALE32:
2352 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2353 return LB_OKAY;
2355 case LB_INITSTORAGE32:
2356 return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2358 case LB_SETCOUNT32:
2359 return LISTBOX_SetCount( wnd, descr, (INT32)wParam );
2361 case LB_SETTABSTOPS16:
2362 return LISTBOX_SetTabStops( wnd, descr, (INT32)(INT16)wParam,
2363 (LPINT32)PTR_SEG_TO_LIN(lParam), TRUE );
2365 case LB_SETTABSTOPS32:
2366 return LISTBOX_SetTabStops( wnd, descr, wParam,
2367 (LPINT32)lParam, FALSE );
2369 case LB_CARETON16:
2370 case LB_CARETON32:
2371 if (descr->caret_on) return LB_OKAY;
2372 descr->caret_on = TRUE;
2373 if ((descr->focus_item != -1) && (GetFocus32() == wnd->hwndSelf))
2374 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2375 return LB_OKAY;
2377 case LB_CARETOFF16:
2378 case LB_CARETOFF32:
2379 if (!descr->caret_on) return LB_OKAY;
2380 descr->caret_on = FALSE;
2381 if ((descr->focus_item != -1) && (GetFocus32() == wnd->hwndSelf))
2382 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2383 return LB_OKAY;
2385 case WM_DESTROY:
2386 return LISTBOX_Destroy( wnd, descr );
2388 case WM_ENABLE:
2389 InvalidateRect32( hwnd, NULL, TRUE );
2390 return 0;
2392 case WM_SETREDRAW:
2393 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2394 return 0;
2396 case WM_GETDLGCODE:
2397 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2399 case WM_PAINT:
2401 PAINTSTRUCT32 ps;
2402 HDC32 hdc = ( wParam ) ? ((HDC32)wParam)
2403 : BeginPaint32( hwnd, &ps );
2404 ret = LISTBOX_Paint( wnd, descr, hdc );
2405 if( !wParam ) EndPaint32( hwnd, &ps );
2407 return ret;
2409 case WM_SIZE:
2410 LISTBOX_UpdateSize( wnd, descr );
2411 return 0;
2413 case WM_GETFONT:
2414 return descr->font;
2416 case WM_SETFONT:
2417 LISTBOX_SetFont( wnd, descr, (HFONT32)wParam );
2418 if (lParam) InvalidateRect32( wnd->hwndSelf, 0, TRUE );
2419 return 0;
2421 case WM_SETFOCUS:
2422 descr->caret_on = TRUE;
2423 if (descr->focus_item != -1)
2424 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2425 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2426 return 0;
2428 case WM_KILLFOCUS:
2429 if ((descr->focus_item != -1) && descr->caret_on)
2430 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2431 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2432 return 0;
2434 case WM_HSCROLL:
2435 return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2437 case WM_VSCROLL:
2438 return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2440 case WM_LBUTTONDOWN:
2441 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2442 (INT16)LOWORD(lParam),
2443 (INT16)HIWORD(lParam) );
2445 case WM_LBUTTONDBLCLK:
2446 if (descr->style & LBS_NOTIFY)
2447 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2448 return 0;
2450 case WM_MOUSEMOVE:
2451 if (GetCapture32() == hwnd)
2452 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2453 (INT16)HIWORD(lParam) );
2454 return 0;
2456 case WM_LBUTTONUP:
2457 return LISTBOX_HandleLButtonUp( wnd, descr );
2459 case WM_KEYDOWN:
2460 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2462 case WM_CHAR:
2463 return LISTBOX_HandleChar( wnd, descr, wParam );
2465 case WM_SYSTIMER:
2466 return LISTBOX_HandleSystemTimer( wnd, descr );
2468 case WM_ERASEBKGND:
2469 if (IS_OWNERDRAW(descr))
2471 RECT32 rect = { 0, 0, descr->width, descr->height };
2472 HBRUSH32 hbrush = SendMessage32A( descr->owner, WM_CTLCOLORLISTBOX,
2473 wParam, (LPARAM)wnd->hwndSelf );
2474 if (hbrush) FillRect32( (HDC32)wParam, &rect, hbrush );
2476 return 1;
2478 case WM_DROPFILES:
2479 if( !descr->lphc )
2480 return SendMessage32A( descr->owner, msg, wParam, lParam );
2481 break;
2483 case WM_DROPOBJECT:
2484 case WM_QUERYDROPOBJECT:
2485 case WM_DRAGSELECT:
2486 case WM_DRAGMOVE:
2487 if( !descr->lphc )
2489 LPDRAGINFO dragInfo = (LPDRAGINFO)PTR_SEG_TO_LIN( (SEGPTR)lParam );
2490 dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2491 dragInfo->pt.y );
2492 return SendMessage32A( descr->owner, msg, wParam, lParam );
2494 break;
2496 case WM_NCCREATE:
2497 if (TWEAK_WineLook > WIN31_LOOK)
2498 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2499 return DefWindowProc32A( hwnd, msg, wParam, lParam );
2501 default:
2502 if ((msg >= WM_USER) && (msg < 0xc000))
2503 WARN(listbox, "[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2504 hwnd, msg, wParam, lParam );
2505 return DefWindowProc32A( hwnd, msg, wParam, lParam );
2507 return 0;
2510 /***********************************************************************
2511 * COMBO_Directory
2513 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT32 attrib, LPSTR dir, BOOL32 bLong)
2515 WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2517 if( wnd )
2519 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2520 if( descr )
2522 LRESULT lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2524 RedrawWindow32( lphc->self->hwndSelf, NULL, 0,
2525 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2526 return lRet;
2529 return CB_ERR;
2532 /***********************************************************************
2533 * ComboLBWndProc
2535 * NOTE: in Windows, winproc address of the ComboLBox is the same
2536 * as that of the Listbox.
2538 LRESULT WINAPI ComboLBWndProc( HWND32 hwnd, UINT32 msg,
2539 WPARAM32 wParam, LPARAM lParam )
2541 LRESULT lRet = 0;
2542 WND *wnd = WIN_FindWndPtr( hwnd );
2544 if (wnd)
2546 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2548 TRACE(combo, "[%04x]: msg %s wp %08x lp %08lx\n",
2549 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2551 if( descr || msg == WM_CREATE )
2553 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2555 switch( msg )
2557 case WM_CREATE:
2558 #define lpcs ((LPCREATESTRUCT32A)lParam)
2559 TRACE(combo, "\tpassed parent handle = 0x%08x\n",
2560 (UINT32)lpcs->lpCreateParams);
2562 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2563 #undef lpcs
2564 return LISTBOX_Create( wnd, lphc );
2566 case WM_LBUTTONDOWN:
2567 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2568 (INT16)LOWORD(lParam), (INT16)HIWORD(lParam));
2570 /* avoid activation at all costs */
2572 case WM_MOUSEACTIVATE:
2573 return MA_NOACTIVATE;
2575 case WM_NCACTIVATE:
2576 return FALSE;
2578 case WM_KEYDOWN:
2579 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2581 /* for some reason(?) Windows makes it possible to
2582 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2584 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2585 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2586 && (wParam == VK_DOWN || wParam == VK_UP)) )
2588 COMBO_FlipListbox( lphc, FALSE );
2589 return 0;
2592 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2594 case LB_SETCURSEL16:
2595 case LB_SETCURSEL32:
2596 lRet = ListBoxWndProc( hwnd, msg, wParam, lParam );
2597 return (lRet == LB_ERR) ? lRet : descr->selected_item;
2599 case WM_NCDESTROY:
2600 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2601 lphc->hWndLBox = 0;
2602 /* fall through */
2604 default:
2605 return ListBoxWndProc( hwnd, msg, wParam, lParam );
2608 lRet = DefWindowProc32A( hwnd, msg, wParam, lParam );
2610 TRACE(combo,"\t default on msg [%04x]\n", (UINT16)msg );
2613 return lRet;