Release 990523.
[wine/multimedia.git] / controls / listbox.c
blob7cb45b97d2ede5b64c339d68a91d5101d69c86eb
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 */
7 #include <string.h>
8 #include <stdlib.h>
9 #include "wine/winuser16.h"
10 #include "winuser.h"
11 #include "winerror.h"
12 #include "drive.h"
13 #include "heap.h"
14 #include "spy.h"
15 #include "win.h"
16 #include "combo.h"
17 #include "debugtools.h"
18 #include "tweak.h"
20 DEFAULT_DEBUG_CHANNEL(listbox)
21 DECLARE_DEBUG_CHANNEL(combo)
23 /* Unimplemented yet:
24 * - LBS_NOSEL
25 * - LBS_USETABSTOPS
26 * - Unicode
27 * - Locale handling
30 /* Items array granularity */
31 #define LB_ARRAY_GRANULARITY 16
33 /* Scrolling timeout in ms */
34 #define LB_SCROLL_TIMEOUT 50
36 /* Listbox system timer id */
37 #define LB_TIMER_ID 2
39 /* Item structure */
40 typedef struct
42 LPSTR str; /* Item text */
43 BOOL selected; /* Is item selected? */
44 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
45 DWORD data; /* User data */
46 } LB_ITEMDATA;
48 /* Listbox structure */
49 typedef struct
51 HANDLE heap; /* Heap for this listbox */
52 HWND owner; /* Owner window to send notifications to */
53 UINT style; /* Window style */
54 INT width; /* Window width */
55 INT height; /* Window height */
56 LB_ITEMDATA *items; /* Array of items */
57 INT nb_items; /* Number of items */
58 INT top_item; /* Top visible item */
59 INT selected_item; /* Selected item */
60 INT focus_item; /* Item that has the focus */
61 INT anchor_item; /* Anchor item for extended selection */
62 INT item_height; /* Default item height */
63 INT page_size; /* Items per listbox page */
64 INT column_width; /* Column width for multi-column listboxes */
65 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
66 INT horz_pos; /* Horizontal position */
67 INT nb_tabs; /* Number of tabs in array */
68 INT *tabs; /* Array of tabs */
69 BOOL caret_on; /* Is caret on? */
70 BOOL captured; /* Is mouse captured? */
71 HFONT font; /* Current font */
72 LCID locale; /* Current locale for string comparisons */
73 LPHEADCOMBO lphc; /* ComboLBox */
74 } LB_DESCR;
77 #define IS_OWNERDRAW(descr) \
78 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
80 #define HAS_STRINGS(descr) \
81 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
83 #define SEND_NOTIFICATION(wnd,descr,code) \
84 (SendMessageA( (descr)->owner, WM_COMMAND, \
85 MAKEWPARAM((((descr)->lphc)?ID_CB_LISTBOX:(wnd)->wIDmenu), (code) ), (wnd)->hwndSelf ))
87 /* Current timer status */
88 typedef enum
90 LB_TIMER_NONE,
91 LB_TIMER_UP,
92 LB_TIMER_LEFT,
93 LB_TIMER_DOWN,
94 LB_TIMER_RIGHT
95 } TIMER_DIRECTION;
97 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
100 /***********************************************************************
101 * LISTBOX_Dump
103 void LISTBOX_Dump( WND *wnd )
105 INT i;
106 LB_ITEMDATA *item;
107 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
109 TRACE( "Listbox:\n" );
110 TRACE( "hwnd=%04x descr=%08x heap=%08x items=%d top=%d\n",
111 wnd->hwndSelf, (UINT)descr, descr->heap, descr->nb_items,
112 descr->top_item );
113 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
115 TRACE( "%4d: %-40s %d %08lx %3d\n",
116 i, item->str, item->selected, item->data, item->height );
121 /***********************************************************************
122 * LISTBOX_GetCurrentPageSize
124 * Return the current page size
126 static INT LISTBOX_GetCurrentPageSize( WND *wnd, LB_DESCR *descr )
128 INT i, height;
129 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
130 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
132 if ((height += descr->items[i].height) > descr->height) break;
134 if (i == descr->top_item) return 1;
135 else return i - descr->top_item;
139 /***********************************************************************
140 * LISTBOX_GetMaxTopIndex
142 * Return the maximum possible index for the top of the listbox.
144 static INT LISTBOX_GetMaxTopIndex( WND *wnd, LB_DESCR *descr )
146 INT max, page;
148 if (descr->style & LBS_OWNERDRAWVARIABLE)
150 page = descr->height;
151 for (max = descr->nb_items - 1; max >= 0; max--)
152 if ((page -= descr->items[max].height) < 0) break;
153 if (max < descr->nb_items - 1) max++;
155 else if (descr->style & LBS_MULTICOLUMN)
157 if ((page = descr->width / descr->column_width) < 1) page = 1;
158 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
159 max = (max - page) * descr->page_size;
161 else
163 max = descr->nb_items - descr->page_size;
165 if (max < 0) max = 0;
166 return max;
170 /***********************************************************************
171 * LISTBOX_UpdateScroll
173 * Update the scrollbars. Should be called whenever the content
174 * of the listbox changes.
176 static void LISTBOX_UpdateScroll( WND *wnd, LB_DESCR *descr )
178 SCROLLINFO info;
180 if (!(descr->style & WS_VSCROLL)) return;
181 /* It is important that we check descr->style, and not wnd->dwStyle,
182 for WS_VSCROLL, as the former is exactly the one passed in
183 argument to CreateWindow.
184 In Windows (and from now on in Wine :) a listbox created
185 with such a style (no WS_SCROLL) does not update
186 the scrollbar with listbox-related data, thus letting
187 the programmer use it for his/her own purposes. */
189 if (descr->style & LBS_NOREDRAW) return;
190 info.cbSize = sizeof(info);
192 if (descr->style & LBS_MULTICOLUMN)
194 info.nMin = 0;
195 info.nMax = (descr->nb_items - 1) / descr->page_size;
196 info.nPos = descr->top_item / descr->page_size;
197 info.nPage = descr->width / descr->column_width;
198 if (info.nPage < 1) info.nPage = 1;
199 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
200 if (descr->style & LBS_DISABLENOSCROLL)
201 info.fMask |= SIF_DISABLENOSCROLL;
202 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
203 info.nMax = 0;
204 info.fMask = SIF_RANGE;
205 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
207 else
209 info.nMin = 0;
210 info.nMax = descr->nb_items - 1;
211 info.nPos = descr->top_item;
212 info.nPage = LISTBOX_GetCurrentPageSize( wnd, descr );
213 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
214 if (descr->style & LBS_DISABLENOSCROLL)
215 info.fMask |= SIF_DISABLENOSCROLL;
216 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
218 if (descr->horz_extent)
220 info.nMin = 0;
221 info.nMax = descr->horz_extent - 1;
222 info.nPos = descr->horz_pos;
223 info.nPage = descr->width;
224 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
225 if (descr->style & LBS_DISABLENOSCROLL)
226 info.fMask |= SIF_DISABLENOSCROLL;
227 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
233 /***********************************************************************
234 * LISTBOX_SetTopItem
236 * Set the top item of the listbox, scrolling up or down if necessary.
238 static LRESULT LISTBOX_SetTopItem( WND *wnd, LB_DESCR *descr, INT index,
239 BOOL scroll )
241 INT max = LISTBOX_GetMaxTopIndex( wnd, descr );
242 if (index > max) index = max;
243 if (index < 0) index = 0;
244 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
245 if (descr->top_item == index) return LB_OKAY;
246 if (descr->style & LBS_MULTICOLUMN)
248 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
249 if (scroll && (abs(diff) < descr->width))
250 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
251 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
253 else
254 scroll = FALSE;
256 else if (scroll)
258 INT diff;
259 if (descr->style & LBS_OWNERDRAWVARIABLE)
261 INT i;
262 diff = 0;
263 if (index > descr->top_item)
265 for (i = index - 1; i >= descr->top_item; i--)
266 diff -= descr->items[i].height;
268 else
270 for (i = index; i < descr->top_item; i++)
271 diff += descr->items[i].height;
274 else
275 diff = (descr->top_item - index) * descr->item_height;
277 if (abs(diff) < descr->height)
278 ScrollWindowEx( wnd->hwndSelf, 0, diff, NULL, NULL, 0, NULL,
279 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
280 else
281 scroll = FALSE;
283 if (!scroll) InvalidateRect( wnd->hwndSelf, NULL, TRUE );
284 descr->top_item = index;
285 LISTBOX_UpdateScroll( wnd, descr );
286 return LB_OKAY;
290 /***********************************************************************
291 * LISTBOX_UpdatePage
293 * Update the page size. Should be called when the size of
294 * the client area or the item height changes.
296 static void LISTBOX_UpdatePage( WND *wnd, LB_DESCR *descr )
298 INT page_size;
300 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
301 page_size = 1;
302 if (page_size == descr->page_size) return;
303 descr->page_size = page_size;
304 if (descr->style & LBS_MULTICOLUMN)
305 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
306 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
310 /***********************************************************************
311 * LISTBOX_UpdateSize
313 * Update the size of the listbox. Should be called when the size of
314 * the client area changes.
316 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
318 RECT rect;
320 GetClientRect( wnd->hwndSelf, &rect );
321 descr->width = rect.right - rect.left;
322 descr->height = rect.bottom - rect.top;
323 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !IS_OWNERDRAW(descr))
325 if ((descr->height > descr->item_height) &&
326 (descr->height % descr->item_height))
328 TRACE("[%04x]: changing height %d -> %d\n",
329 wnd->hwndSelf, descr->height,
330 descr->height - descr->height%descr->item_height );
331 SetWindowPos( wnd->hwndSelf, 0, 0, 0,
332 wnd->rectWindow.right - wnd->rectWindow.left,
333 wnd->rectWindow.bottom - wnd->rectWindow.top -
334 (descr->height % descr->item_height),
335 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
336 return;
339 TRACE("[%04x]: new size = %d,%d\n",
340 wnd->hwndSelf, descr->width, descr->height );
341 LISTBOX_UpdatePage( wnd, descr );
342 LISTBOX_UpdateScroll( wnd, descr );
346 /***********************************************************************
347 * LISTBOX_GetItemRect
349 * Get the rectangle enclosing an item, in listbox client coordinates.
350 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
352 static LRESULT LISTBOX_GetItemRect( WND *wnd, LB_DESCR *descr, INT index,
353 RECT *rect )
355 /* Index <= 0 is legal even on empty listboxes */
356 if (index && (index >= descr->nb_items)) return -1;
357 SetRect( rect, 0, 0, descr->width, descr->height );
358 if (descr->style & LBS_MULTICOLUMN)
360 INT col = (index / descr->page_size) -
361 (descr->top_item / descr->page_size);
362 rect->left += col * descr->column_width;
363 rect->right = rect->left + descr->column_width;
364 rect->top += (index % descr->page_size) * descr->item_height;
365 rect->bottom = rect->top + descr->item_height;
367 else if (descr->style & LBS_OWNERDRAWVARIABLE)
369 INT i;
370 rect->right += descr->horz_pos;
371 if ((index >= 0) && (index < descr->nb_items))
373 if (index < descr->top_item)
375 for (i = descr->top_item-1; i >= index; i--)
376 rect->top -= descr->items[i].height;
378 else
380 for (i = descr->top_item; i < index; i++)
381 rect->top += descr->items[i].height;
383 rect->bottom = rect->top + descr->items[index].height;
387 else
389 rect->top += (index - descr->top_item) * descr->item_height;
390 rect->bottom = rect->top + descr->item_height;
391 rect->right += descr->horz_pos;
394 return ((rect->left < descr->width) && (rect->right > 0) &&
395 (rect->top < descr->height) && (rect->bottom > 0));
399 /***********************************************************************
400 * LISTBOX_GetItemFromPoint
402 * Return the item nearest from point (x,y) (in client coordinates).
404 static INT LISTBOX_GetItemFromPoint( WND *wnd, LB_DESCR *descr,
405 INT x, INT y )
407 INT index = descr->top_item;
409 if (!descr->nb_items) return -1; /* No items */
410 if (descr->style & LBS_OWNERDRAWVARIABLE)
412 INT pos = 0;
413 if (y >= 0)
415 while (index < descr->nb_items)
417 if ((pos += descr->items[index].height) > y) break;
418 index++;
421 else
423 while (index > 0)
425 index--;
426 if ((pos -= descr->items[index].height) <= y) break;
430 else if (descr->style & LBS_MULTICOLUMN)
432 if (y >= descr->item_height * descr->page_size) return -1;
433 if (y >= 0) index += y / descr->item_height;
434 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
435 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
437 else
439 index += (y / descr->item_height);
441 if (index < 0) return 0;
442 if (index >= descr->nb_items) return -1;
443 return index;
447 /***********************************************************************
448 * LISTBOX_PaintItem
450 * Paint an item.
452 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC hdc,
453 const RECT *rect, INT index, UINT action )
455 LB_ITEMDATA *item = NULL;
456 if (index < descr->nb_items) item = &descr->items[index];
458 if (IS_OWNERDRAW(descr))
460 DRAWITEMSTRUCT dis;
461 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
463 if (!item)
465 if (action == ODA_FOCUS)
466 DrawFocusRect( hdc, rect );
467 else
468 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
469 return;
471 dis.CtlType = ODT_LISTBOX;
472 dis.CtlID = id;
473 dis.hwndItem = wnd->hwndSelf;
474 dis.itemAction = action;
475 dis.hDC = hdc;
476 dis.itemID = index;
477 dis.itemState = 0;
478 if (item && item->selected) dis.itemState |= ODS_SELECTED;
479 if ((descr->focus_item == index) &&
480 (descr->caret_on) &&
481 (GetFocus() == wnd->hwndSelf)) dis.itemState |= ODS_FOCUS;
482 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
483 dis.itemData = item ? item->data : 0;
484 dis.rcItem = *rect;
485 TRACE("[%04x]: drawitem %d (%s) action=%02x "
486 "state=%02x rect=%d,%d-%d,%d\n",
487 wnd->hwndSelf, index, item ? item->str : "", action,
488 dis.itemState, rect->left, rect->top,
489 rect->right, rect->bottom );
490 SendMessageA(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
492 else
494 COLORREF oldText = 0, oldBk = 0;
496 if (action == ODA_FOCUS)
498 DrawFocusRect( hdc, rect );
499 return;
501 if (item && item->selected)
503 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
504 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
507 TRACE("[%04x]: painting %d (%s) action=%02x "
508 "rect=%d,%d-%d,%d\n",
509 wnd->hwndSelf, index, item ? item->str : "", action,
510 rect->left, rect->top, rect->right, rect->bottom );
511 if (!item)
512 ExtTextOutA( hdc, rect->left + 1, rect->top + 1,
513 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
514 else if (!(descr->style & LBS_USETABSTOPS))
515 ExtTextOutA( hdc, rect->left + 1, rect->top + 1,
516 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
517 strlen(item->str), NULL );
518 else
520 /* Output empty string to paint background in the full width. */
521 ExtTextOutA( hdc, rect->left + 1, rect->top + 1,
522 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
523 TabbedTextOutA( hdc, rect->left + 1 , rect->top + 1,
524 item->str, strlen(item->str),
525 descr->nb_tabs, descr->tabs, 0);
527 if (item && item->selected)
529 SetBkColor( hdc, oldBk );
530 SetTextColor( hdc, oldText );
532 if ((descr->focus_item == index) &&
533 (descr->caret_on) &&
534 (GetFocus() == wnd->hwndSelf)) DrawFocusRect( hdc, rect );
539 /***********************************************************************
540 * LISTBOX_SetRedraw
542 * Change the redraw flag.
544 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL on )
546 if (on)
548 if (!(descr->style & LBS_NOREDRAW)) return;
549 descr->style &= ~LBS_NOREDRAW;
550 LISTBOX_UpdateScroll( wnd, descr );
552 else descr->style |= LBS_NOREDRAW;
556 /***********************************************************************
557 * LISTBOX_RepaintItem
559 * Repaint a single item synchronously.
561 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT index,
562 UINT action )
564 HDC hdc;
565 RECT rect;
566 HFONT oldFont = 0;
567 HBRUSH hbrush, oldBrush = 0;
569 /* Do not repaint the item if the item is not visible */
570 if ((descr->style & LBS_NOREDRAW) || !IsWindowVisible(wnd->hwndSelf)) return;
572 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) != 1) return;
573 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE ))) return;
574 if (descr->font) oldFont = SelectObject( hdc, descr->font );
575 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
576 hdc, (LPARAM)wnd->hwndSelf );
577 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
578 if (wnd->dwStyle & WS_DISABLED)
579 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
580 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
581 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action );
582 if (oldFont) SelectObject( hdc, oldFont );
583 if (oldBrush) SelectObject( hdc, oldBrush );
584 ReleaseDC( wnd->hwndSelf, hdc );
588 /***********************************************************************
589 * LISTBOX_InitStorage
591 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT nb_items,
592 DWORD bytes )
594 LB_ITEMDATA *item;
596 nb_items += LB_ARRAY_GRANULARITY - 1;
597 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
598 if (descr->items)
599 nb_items += HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
600 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
601 nb_items * sizeof(LB_ITEMDATA) )))
603 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
604 return LB_ERRSPACE;
606 descr->items = item;
607 return LB_OKAY;
611 /***********************************************************************
612 * LISTBOX_SetTabStops
614 static BOOL LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT count,
615 LPINT tabs, BOOL short_ints )
617 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
618 if (descr->tabs) HeapFree( descr->heap, 0, descr->tabs );
619 if (!(descr->nb_tabs = count))
621 descr->tabs = NULL;
622 return TRUE;
624 /* FIXME: count = 1 */
625 if (!(descr->tabs = (INT *)HeapAlloc( descr->heap, 0,
626 descr->nb_tabs * sizeof(INT) )))
627 return FALSE;
628 if (short_ints)
630 INT i;
631 LPINT16 p = (LPINT16)tabs;
632 dbg_decl_str(listbox, 256);
634 for (i = 0; i < descr->nb_tabs; i++) {
635 descr->tabs[i] = *p++<<1; /* FIXME */
636 if(TRACE_ON(listbox))
637 dsprintf(listbox, "%hd ", descr->tabs[i]);
639 TRACE("[%04x]: settabstops %s\n",
640 wnd->hwndSelf, dbg_str(listbox));
642 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
643 /* FIXME: repaint the window? */
644 return TRUE;
648 /***********************************************************************
649 * LISTBOX_GetText
651 static LRESULT LISTBOX_GetText( WND *wnd, LB_DESCR *descr, INT index,
652 LPSTR buffer )
654 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
655 if (HAS_STRINGS(descr))
657 if (!buffer)
658 return strlen(descr->items[index].str);
659 lstrcpyA( buffer, descr->items[index].str );
660 return strlen(buffer);
661 } else {
662 if (buffer)
663 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
664 return sizeof(DWORD);
669 /***********************************************************************
670 * LISTBOX_FindStringPos
672 * Find the nearest string located before a given string in sort order.
673 * If 'exact' is TRUE, return an error if we don't get an exact match.
675 static INT LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCSTR str,
676 BOOL exact )
678 INT index, min, max, res = -1;
680 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
681 min = 0;
682 max = descr->nb_items;
683 while (min != max)
685 index = (min + max) / 2;
686 if (HAS_STRINGS(descr))
687 res = lstrcmpiA( descr->items[index].str, str );
688 else
690 COMPAREITEMSTRUCT cis;
691 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
693 cis.CtlType = ODT_LISTBOX;
694 cis.CtlID = id;
695 cis.hwndItem = wnd->hwndSelf;
696 cis.itemID1 = index;
697 cis.itemData1 = descr->items[index].data;
698 cis.itemID2 = -1;
699 cis.itemData2 = (DWORD)str;
700 cis.dwLocaleId = descr->locale;
701 res = SendMessageA( descr->owner, WM_COMPAREITEM,
702 id, (LPARAM)&cis );
704 if (!res) return index;
705 if (res > 0) max = index;
706 else min = index + 1;
708 return exact ? -1 : max;
712 /***********************************************************************
713 * LISTBOX_FindFileStrPos
715 * Find the nearest string located before a given string in directory
716 * sort order (i.e. first files, then directories, then drives).
718 static INT LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCSTR str )
720 INT min, max, res = -1;
722 if (!HAS_STRINGS(descr))
723 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
724 min = 0;
725 max = descr->nb_items;
726 while (min != max)
728 INT index = (min + max) / 2;
729 const char *p = descr->items[index].str;
730 if (*p == '[') /* drive or directory */
732 if (*str != '[') res = -1;
733 else if (p[1] == '-') /* drive */
735 if (str[1] == '-') res = str[2] - p[2];
736 else res = -1;
738 else /* directory */
740 if (str[1] == '-') res = 1;
741 else res = lstrcmpiA( str, p );
744 else /* filename */
746 if (*str == '[') res = 1;
747 else res = lstrcmpiA( str, p );
749 if (!res) return index;
750 if (res < 0) max = index;
751 else min = index + 1;
753 return max;
757 /***********************************************************************
758 * LISTBOX_FindString
760 * Find the item beginning with a given string.
762 static INT LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT start,
763 LPCSTR str, BOOL exact )
765 INT i;
766 LB_ITEMDATA *item;
768 if (start >= descr->nb_items) start = -1;
769 item = descr->items + start + 1;
770 if (HAS_STRINGS(descr))
772 if (!str) return LB_ERR;
773 if (exact)
775 for (i = start + 1; i < descr->nb_items; i++, item++)
776 if (!lstrcmpiA( str, item->str )) return i;
777 for (i = 0, item = descr->items; i <= start; i++, item++)
778 if (!lstrcmpiA( str, item->str )) return i;
780 else
782 /* Special case for drives and directories: ignore prefix */
783 #define CHECK_DRIVE(item) \
784 if ((item)->str[0] == '[') \
786 if (!lstrncmpiA( str, (item)->str+1, len )) return i; \
787 if (((item)->str[1] == '-') && !lstrncmpiA(str,(item)->str+2,len)) \
788 return i; \
791 INT len = strlen(str);
792 for (i = start + 1; i < descr->nb_items; i++, item++)
794 if (!lstrncmpiA( str, item->str, len )) return i;
795 CHECK_DRIVE(item);
797 for (i = 0, item = descr->items; i <= start; i++, item++)
799 if (!lstrncmpiA( str, item->str, len )) return i;
800 CHECK_DRIVE(item);
802 #undef CHECK_DRIVE
805 else
807 if (exact && (descr->style & LBS_SORT))
808 /* If sorted, use a WM_COMPAREITEM binary search */
809 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
811 /* Otherwise use a linear search */
812 for (i = start + 1; i < descr->nb_items; i++, item++)
813 if (item->data == (DWORD)str) return i;
814 for (i = 0, item = descr->items; i <= start; i++, item++)
815 if (item->data == (DWORD)str) return i;
817 return LB_ERR;
821 /***********************************************************************
822 * LISTBOX_GetSelCount
824 static LRESULT LISTBOX_GetSelCount( WND *wnd, LB_DESCR *descr )
826 INT i, count;
827 LB_ITEMDATA *item = descr->items;
829 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
830 for (i = count = 0; i < descr->nb_items; i++, item++)
831 if (item->selected) count++;
832 return count;
836 /***********************************************************************
837 * LISTBOX_GetSelItems16
839 static LRESULT LISTBOX_GetSelItems16( WND *wnd, LB_DESCR *descr, INT16 max,
840 LPINT16 array )
842 INT i, count;
843 LB_ITEMDATA *item = descr->items;
845 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
846 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
847 if (item->selected) array[count++] = (INT16)i;
848 return count;
852 /***********************************************************************
853 * LISTBOX_GetSelItems32
855 static LRESULT LISTBOX_GetSelItems( WND *wnd, LB_DESCR *descr, INT max,
856 LPINT array )
858 INT i, count;
859 LB_ITEMDATA *item = descr->items;
861 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
862 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
863 if (item->selected) array[count++] = i;
864 return count;
868 /***********************************************************************
869 * LISTBOX_Paint
871 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC hdc )
873 INT i, col_pos = descr->page_size - 1;
874 RECT rect;
875 HFONT oldFont = 0;
876 HBRUSH hbrush, oldBrush = 0;
878 SetRect( &rect, 0, 0, descr->width, descr->height );
879 if (descr->style & LBS_NOREDRAW) return 0;
880 if (descr->style & LBS_MULTICOLUMN)
881 rect.right = rect.left + descr->column_width;
882 else if (descr->horz_pos)
884 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
885 rect.right += descr->horz_pos;
888 if (descr->font) oldFont = SelectObject( hdc, descr->font );
889 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
890 hdc, (LPARAM)wnd->hwndSelf );
891 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
892 if (wnd->dwStyle & WS_DISABLED)
893 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
895 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
896 (GetFocus() == wnd->hwndSelf))
898 /* Special case for empty listbox: paint focus rect */
899 rect.bottom = rect.top + descr->item_height;
900 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
901 ODA_FOCUS );
902 rect.top = rect.bottom;
905 for (i = descr->top_item; i < descr->nb_items; i++)
907 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
908 rect.bottom = rect.top + descr->item_height;
909 else
910 rect.bottom = rect.top + descr->items[i].height;
912 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE );
913 rect.top = rect.bottom;
915 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
917 if (!IS_OWNERDRAW(descr))
919 /* Clear the bottom of the column */
920 SetBkColor( hdc, GetSysColor( COLOR_WINDOW ) );
921 if (rect.top < descr->height)
923 rect.bottom = descr->height;
924 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
925 &rect, NULL, 0, NULL );
929 /* Go to the next column */
930 rect.left += descr->column_width;
931 rect.right += descr->column_width;
932 rect.top = 0;
933 col_pos = descr->page_size - 1;
935 else
937 col_pos--;
938 if (rect.top >= descr->height) break;
942 if (!IS_OWNERDRAW(descr))
944 /* Clear the remainder of the client area */
945 SetBkColor( hdc, GetSysColor( COLOR_WINDOW ) );
946 if (rect.top < descr->height)
948 rect.bottom = descr->height;
949 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
950 &rect, NULL, 0, NULL );
952 if (rect.right < descr->width)
954 rect.left = rect.right;
955 rect.right = descr->width;
956 rect.top = 0;
957 rect.bottom = descr->height;
958 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
959 &rect, NULL, 0, NULL );
962 if (oldFont) SelectObject( hdc, oldFont );
963 if (oldBrush) SelectObject( hdc, oldBrush );
964 return 0;
968 /***********************************************************************
969 * LISTBOX_InvalidateItems
971 * Invalidate all items from a given item. If the specified item is not
972 * visible, nothing happens.
974 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT index )
976 RECT rect;
978 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) == 1)
980 rect.bottom = descr->height;
981 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
982 if (descr->style & LBS_MULTICOLUMN)
984 /* Repaint the other columns */
985 rect.left = rect.right;
986 rect.right = descr->width;
987 rect.top = 0;
988 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
994 /***********************************************************************
995 * LISTBOX_GetItemHeight
997 static LRESULT LISTBOX_GetItemHeight( WND *wnd, LB_DESCR *descr, INT index )
999 if (descr->style & LBS_OWNERDRAWVARIABLE)
1001 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1002 return descr->items[index].height;
1004 else return descr->item_height;
1008 /***********************************************************************
1009 * LISTBOX_SetItemHeight
1011 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT index,
1012 UINT height )
1014 if (!height) height = 1;
1016 if (descr->style & LBS_OWNERDRAWVARIABLE)
1018 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1019 TRACE("[%04x]: item %d height = %d\n",
1020 wnd->hwndSelf, index, height );
1021 descr->items[index].height = height;
1022 LISTBOX_UpdateScroll( wnd, descr );
1023 LISTBOX_InvalidateItems( wnd, descr, index );
1025 else if (height != descr->item_height)
1027 TRACE("[%04x]: new height = %d\n",
1028 wnd->hwndSelf, height );
1029 descr->item_height = height;
1030 LISTBOX_UpdatePage( wnd, descr );
1031 LISTBOX_UpdateScroll( wnd, descr );
1032 InvalidateRect( wnd->hwndSelf, 0, TRUE );
1034 return LB_OKAY;
1038 /***********************************************************************
1039 * LISTBOX_SetHorizontalPos
1041 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT pos )
1043 INT diff;
1045 if (pos > descr->horz_extent - descr->width)
1046 pos = descr->horz_extent - descr->width;
1047 if (pos < 0) pos = 0;
1048 if (!(diff = descr->horz_pos - pos)) return;
1049 TRACE("[%04x]: new horz pos = %d\n",
1050 wnd->hwndSelf, pos );
1051 descr->horz_pos = pos;
1052 LISTBOX_UpdateScroll( wnd, descr );
1053 if (abs(diff) < descr->width)
1054 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1055 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1056 else
1057 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1061 /***********************************************************************
1062 * LISTBOX_SetHorizontalExtent
1064 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1065 UINT extent )
1067 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1068 return LB_OKAY;
1069 if (extent <= 0) extent = 1;
1070 if (extent == descr->horz_extent) return LB_OKAY;
1071 TRACE("[%04x]: new horz extent = %d\n",
1072 wnd->hwndSelf, extent );
1073 descr->horz_extent = extent;
1074 if (descr->horz_pos > extent - descr->width)
1075 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1076 else
1077 LISTBOX_UpdateScroll( wnd, descr );
1078 return LB_OKAY;
1082 /***********************************************************************
1083 * LISTBOX_SetColumnWidth
1085 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, UINT width)
1087 width += 2; /* For left and right margin */
1088 if (width == descr->column_width) return LB_OKAY;
1089 TRACE("[%04x]: new column width = %d\n",
1090 wnd->hwndSelf, width );
1091 descr->column_width = width;
1092 LISTBOX_UpdatePage( wnd, descr );
1093 return LB_OKAY;
1097 /***********************************************************************
1098 * LISTBOX_SetFont
1100 * Returns the item height.
1102 static INT LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT font )
1104 HDC hdc;
1105 HFONT oldFont = 0;
1106 TEXTMETRICA tm;
1108 descr->font = font;
1110 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE )))
1112 ERR("unable to get DC.\n" );
1113 return 16;
1115 if (font) oldFont = SelectObject( hdc, font );
1116 GetTextMetricsA( hdc, &tm );
1117 if (oldFont) SelectObject( hdc, oldFont );
1118 ReleaseDC( wnd->hwndSelf, hdc );
1119 if (!IS_OWNERDRAW(descr))
1120 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1121 return tm.tmHeight ;
1125 /***********************************************************************
1126 * LISTBOX_MakeItemVisible
1128 * Make sure that a given item is partially or fully visible.
1130 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT index,
1131 BOOL fully )
1133 INT top;
1135 if (index <= descr->top_item) top = index;
1136 else if (descr->style & LBS_MULTICOLUMN)
1138 INT cols = descr->width;
1139 if (!fully) cols += descr->column_width - 1;
1140 if (cols >= descr->column_width) cols /= descr->column_width;
1141 else cols = 1;
1142 if (index < descr->top_item + (descr->page_size * cols)) return;
1143 top = index - descr->page_size * (cols - 1);
1145 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1147 INT height = fully ? descr->items[index].height : 1;
1148 for (top = index; top > descr->top_item; top--)
1149 if ((height += descr->items[top-1].height) > descr->height) break;
1151 else
1153 if (index < descr->top_item + descr->page_size) return;
1154 if (!fully && (index == descr->top_item + descr->page_size) &&
1155 (descr->height > (descr->page_size * descr->item_height))) return;
1156 top = index - descr->page_size + 1;
1158 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1162 /***********************************************************************
1163 * LISTBOX_SelectItemRange
1165 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1167 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT first,
1168 INT last, BOOL on )
1170 INT i;
1172 /* A few sanity checks */
1174 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1175 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1176 if (last == -1) last = descr->nb_items - 1;
1177 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1178 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1179 /* selected_item reflects last selected/unselected item on multiple sel */
1180 descr->selected_item = last;
1182 if (on) /* Turn selection on */
1184 for (i = first; i <= last; i++)
1186 if (descr->items[i].selected) continue;
1187 descr->items[i].selected = TRUE;
1188 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1191 else /* Turn selection off */
1193 for (i = first; i <= last; i++)
1195 if (!descr->items[i].selected) continue;
1196 descr->items[i].selected = FALSE;
1197 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1200 return LB_OKAY;
1204 /***********************************************************************
1205 * LISTBOX_SetCaretIndex
1207 * NOTES
1208 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1211 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT index,
1212 BOOL fully_visible )
1214 INT oldfocus = descr->focus_item;
1216 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1217 if (index == oldfocus) return LB_OKAY;
1218 descr->focus_item = index;
1219 if ((oldfocus != -1) && descr->caret_on && (GetFocus() == wnd->hwndSelf))
1220 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1222 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1223 if (descr->caret_on && (GetFocus() == wnd->hwndSelf))
1224 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1226 return LB_OKAY;
1230 /***********************************************************************
1231 * LISTBOX_SetSelection
1233 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT index,
1234 BOOL on, BOOL send_notify )
1236 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1237 if (descr->style & LBS_MULTIPLESEL)
1239 if (index == -1) /* Select all items */
1240 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1241 else /* Only one item */
1242 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1244 else
1246 INT oldsel = descr->selected_item;
1247 if (index == oldsel) return LB_OKAY;
1248 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1249 if (index != -1) descr->items[index].selected = TRUE;
1250 descr->selected_item = index;
1251 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT);
1252 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1253 if (send_notify) SEND_NOTIFICATION( wnd, descr,
1254 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1255 else
1256 if( descr->lphc ) /* set selection change flag for parent combo */
1257 descr->lphc->wState |= CBF_SELCHANGE;
1259 return LB_OKAY;
1263 /***********************************************************************
1264 * LISTBOX_MoveCaret
1266 * Change the caret position and extend the selection to the new caret.
1268 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT index,
1269 BOOL fully_visible )
1271 LISTBOX_SetCaretIndex( wnd, descr, index, fully_visible );
1272 if (descr->style & LBS_EXTENDEDSEL)
1274 if (descr->anchor_item != -1)
1276 INT first = MIN( descr->focus_item, descr->anchor_item );
1277 INT last = MAX( descr->focus_item, descr->anchor_item );
1278 if (first > 0)
1279 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1280 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1281 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1284 else if (!(descr->style & LBS_MULTIPLESEL) && (descr->selected_item != -1))
1286 /* Set selection to new caret item */
1287 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1292 /***********************************************************************
1293 * LISTBOX_InsertItem
1295 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT index,
1296 LPSTR str, DWORD data )
1298 LB_ITEMDATA *item;
1299 INT max_items;
1301 if (index == -1) index = descr->nb_items;
1302 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1303 if (!descr->items) max_items = 0;
1304 else max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
1305 if (descr->nb_items == max_items)
1307 /* We need to grow the array */
1308 max_items += LB_ARRAY_GRANULARITY;
1309 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
1310 max_items * sizeof(LB_ITEMDATA) )))
1312 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1313 return LB_ERRSPACE;
1315 descr->items = item;
1318 /* Insert the item structure */
1320 item = &descr->items[index];
1321 if (index < descr->nb_items)
1322 RtlMoveMemory( item + 1, item,
1323 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1324 item->str = str;
1325 item->data = data;
1326 item->height = 0;
1327 item->selected = FALSE;
1328 descr->nb_items++;
1330 /* Get item height */
1332 if (descr->style & LBS_OWNERDRAWVARIABLE)
1334 MEASUREITEMSTRUCT mis;
1335 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1337 mis.CtlType = ODT_LISTBOX;
1338 mis.CtlID = id;
1339 mis.itemID = index;
1340 mis.itemData = descr->items[index].data;
1341 mis.itemHeight = descr->item_height;
1342 SendMessageA( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1343 item->height = mis.itemHeight ? mis.itemHeight : 1;
1344 TRACE("[%04x]: measure item %d (%s) = %d\n",
1345 wnd->hwndSelf, index, str ? str : "", item->height );
1348 /* Repaint the items */
1350 LISTBOX_UpdateScroll( wnd, descr );
1351 LISTBOX_InvalidateItems( wnd, descr, index );
1353 /* Move selection and focused item */
1355 if (index <= descr->selected_item) descr->selected_item++;
1356 if (index <= descr->focus_item)
1358 descr->focus_item++;
1359 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1362 /* If listbox was empty, set focus to the first item */
1364 if (descr->nb_items == 1) LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1365 return LB_OKAY;
1369 /***********************************************************************
1370 * LISTBOX_InsertString
1372 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT index,
1373 LPCSTR str )
1375 LPSTR new_str = NULL;
1376 DWORD data = 0;
1377 LRESULT ret;
1379 if (HAS_STRINGS(descr))
1381 if (!(new_str = HEAP_strdupA( descr->heap, 0, str )))
1383 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1384 return LB_ERRSPACE;
1387 else data = (DWORD)str;
1389 if (index == -1) index = descr->nb_items;
1390 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1392 if (new_str) HeapFree( descr->heap, 0, new_str );
1393 return ret;
1396 TRACE("[%04x]: added item %d '%s'\n",
1397 wnd->hwndSelf, index, HAS_STRINGS(descr) ? new_str : "" );
1398 return index;
1402 /***********************************************************************
1403 * LISTBOX_DeleteItem
1405 * Delete the content of an item. 'index' must be a valid index.
1407 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT index )
1409 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1410 * while Win95 sends it for all items with user data.
1411 * It's probably better to send it too often than not
1412 * often enough, so this is what we do here.
1414 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1416 DELETEITEMSTRUCT dis;
1417 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1419 dis.CtlType = ODT_LISTBOX;
1420 dis.CtlID = id;
1421 dis.itemID = index;
1422 dis.hwndItem = wnd->hwndSelf;
1423 dis.itemData = descr->items[index].data;
1424 SendMessageA( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1426 if (HAS_STRINGS(descr) && descr->items[index].str)
1427 HeapFree( descr->heap, 0, descr->items[index].str );
1431 /***********************************************************************
1432 * LISTBOX_RemoveItem
1434 * Remove an item from the listbox and delete its content.
1436 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT index )
1438 LB_ITEMDATA *item;
1439 INT max_items;
1441 if (index == -1) index = descr->nb_items - 1;
1442 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1443 LISTBOX_DeleteItem( wnd, descr, index );
1445 /* Remove the item */
1447 item = &descr->items[index];
1448 if (index < descr->nb_items-1)
1449 RtlMoveMemory( item, item + 1,
1450 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1451 descr->nb_items--;
1452 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1454 /* Shrink the item array if possible */
1456 max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(LB_ITEMDATA);
1457 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1459 max_items -= LB_ARRAY_GRANULARITY;
1460 item = HeapReAlloc( descr->heap, 0, descr->items,
1461 max_items * sizeof(LB_ITEMDATA) );
1462 if (item) descr->items = item;
1465 /* Repaint the items */
1467 LISTBOX_UpdateScroll( wnd, descr );
1468 LISTBOX_InvalidateItems( wnd, descr, index );
1470 /* Move selection and focused item */
1472 if (index <= descr->selected_item) descr->selected_item--;
1473 if (index <= descr->focus_item)
1475 descr->focus_item--;
1476 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1478 return LB_OKAY;
1482 /***********************************************************************
1483 * LISTBOX_ResetContent
1485 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1487 INT i;
1489 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1490 if (descr->items) HeapFree( descr->heap, 0, descr->items );
1491 descr->nb_items = 0;
1492 descr->top_item = 0;
1493 descr->selected_item = -1;
1494 descr->focus_item = 0;
1495 descr->anchor_item = -1;
1496 descr->items = NULL;
1497 LISTBOX_UpdateScroll( wnd, descr );
1498 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1502 /***********************************************************************
1503 * LISTBOX_SetCount
1505 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1507 LRESULT ret;
1509 if (HAS_STRINGS(descr)) return LB_ERR;
1510 /* FIXME: this is far from optimal... */
1511 if (count > descr->nb_items)
1513 while (count > descr->nb_items)
1514 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1515 return ret;
1517 else if (count < descr->nb_items)
1519 while (count < descr->nb_items)
1520 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1521 return ret;
1523 return LB_OKAY;
1527 /***********************************************************************
1528 * LISTBOX_Directory
1530 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1531 LPCSTR filespec, BOOL long_names )
1533 HANDLE handle;
1534 LRESULT ret = LB_OKAY;
1535 WIN32_FIND_DATAA entry;
1536 int pos;
1538 if ((handle = FindFirstFileA(filespec,&entry)) == INVALID_HANDLE_VALUE)
1540 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1542 else
1546 char buffer[270];
1547 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1549 if (!(attrib & DDL_DIRECTORY) ||
1550 !strcmp( entry.cAlternateFileName, "." )) continue;
1551 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1552 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1554 else /* not a directory */
1556 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1557 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1559 if ((attrib & DDL_EXCLUSIVE) &&
1560 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1561 continue;
1562 #undef ATTRIBS
1563 if (long_names) strcpy( buffer, entry.cFileName );
1564 else strcpy( buffer, entry.cAlternateFileName );
1566 if (!long_names) CharLowerA( buffer );
1567 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1568 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1569 break;
1570 } while (FindNextFileA( handle, &entry ));
1571 FindClose( handle );
1574 if ((ret >= 0) && (attrib & DDL_DRIVES))
1576 char buffer[] = "[-a-]";
1577 int drive;
1578 for (drive = 0; drive < MAX_DOS_DRIVES; drive++, buffer[2]++)
1580 if (!DRIVE_IsValid(drive)) continue;
1581 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1582 break;
1585 return ret;
1589 /***********************************************************************
1590 * LISTBOX_HandleVScroll
1592 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1593 WPARAM wParam, LPARAM lParam )
1595 SCROLLINFO info;
1597 if (descr->style & LBS_MULTICOLUMN) return 0;
1598 switch(LOWORD(wParam))
1600 case SB_LINEUP:
1601 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1602 break;
1603 case SB_LINEDOWN:
1604 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1605 break;
1606 case SB_PAGEUP:
1607 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1608 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1609 break;
1610 case SB_PAGEDOWN:
1611 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1612 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1613 break;
1614 case SB_THUMBPOSITION:
1615 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1616 break;
1617 case SB_THUMBTRACK:
1618 info.cbSize = sizeof(info);
1619 info.fMask = SIF_TRACKPOS;
1620 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1621 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1622 break;
1623 case SB_TOP:
1624 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1625 break;
1626 case SB_BOTTOM:
1627 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1628 break;
1630 return 0;
1634 /***********************************************************************
1635 * LISTBOX_HandleHScroll
1637 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1638 WPARAM wParam, LPARAM lParam )
1640 SCROLLINFO info;
1641 INT page;
1643 if (descr->style & LBS_MULTICOLUMN)
1645 switch(LOWORD(wParam))
1647 case SB_LINELEFT:
1648 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1649 TRUE );
1650 break;
1651 case SB_LINERIGHT:
1652 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1653 TRUE );
1654 break;
1655 case SB_PAGELEFT:
1656 page = descr->width / descr->column_width;
1657 if (page < 1) page = 1;
1658 LISTBOX_SetTopItem( wnd, descr,
1659 descr->top_item - page * descr->page_size, TRUE );
1660 break;
1661 case SB_PAGERIGHT:
1662 page = descr->width / descr->column_width;
1663 if (page < 1) page = 1;
1664 LISTBOX_SetTopItem( wnd, descr,
1665 descr->top_item + page * descr->page_size, TRUE );
1666 break;
1667 case SB_THUMBPOSITION:
1668 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1669 TRUE );
1670 break;
1671 case SB_THUMBTRACK:
1672 info.cbSize = sizeof(info);
1673 info.fMask = SIF_TRACKPOS;
1674 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1675 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1676 TRUE );
1677 break;
1678 case SB_LEFT:
1679 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1680 break;
1681 case SB_RIGHT:
1682 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1683 break;
1686 else if (descr->horz_extent)
1688 switch(LOWORD(wParam))
1690 case SB_LINELEFT:
1691 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1692 break;
1693 case SB_LINERIGHT:
1694 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1695 break;
1696 case SB_PAGELEFT:
1697 LISTBOX_SetHorizontalPos( wnd, descr,
1698 descr->horz_pos - descr->width );
1699 break;
1700 case SB_PAGERIGHT:
1701 LISTBOX_SetHorizontalPos( wnd, descr,
1702 descr->horz_pos + descr->width );
1703 break;
1704 case SB_THUMBPOSITION:
1705 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1706 break;
1707 case SB_THUMBTRACK:
1708 info.cbSize = sizeof(info);
1709 info.fMask = SIF_TRACKPOS;
1710 GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1711 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1712 break;
1713 case SB_LEFT:
1714 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1715 break;
1716 case SB_RIGHT:
1717 LISTBOX_SetHorizontalPos( wnd, descr,
1718 descr->horz_extent - descr->width );
1719 break;
1722 return 0;
1726 /***********************************************************************
1727 * LISTBOX_HandleLButtonDown
1729 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1730 WPARAM wParam, INT x, INT y )
1732 INT index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1733 TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1734 wnd->hwndSelf, x, y, index );
1735 if (!descr->caret_on && (GetFocus() == wnd->hwndSelf)) return 0;
1736 if (index != -1)
1738 if (descr->style & LBS_EXTENDEDSEL)
1740 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1741 if (wParam & MK_CONTROL)
1743 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1744 LISTBOX_SetSelection( wnd, descr, index,
1745 !descr->items[index].selected, FALSE );
1747 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1749 else
1751 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1752 LISTBOX_SetSelection( wnd, descr, index,
1753 (!(descr->style & LBS_MULTIPLESEL) ||
1754 !descr->items[index].selected), FALSE );
1758 if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1759 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1760 : descr->lphc->self->hwndSelf ) ;
1762 descr->captured = TRUE;
1763 SetCapture( wnd->hwndSelf );
1764 if (index != -1 && !descr->lphc)
1766 if (descr->style & LBS_NOTIFY )
1767 SendMessageA( descr->owner, WM_LBTRACKPOINT, index,
1768 MAKELPARAM( x, y ) );
1769 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1771 POINT pt;
1773 pt.x = x;
1774 pt.y = y;
1776 if (DragDetect( wnd->hwndSelf, pt ))
1777 SendMessageA( descr->owner, WM_BEGINDRAG, 0, 0 );
1780 return 0;
1784 /***********************************************************************
1785 * LISTBOX_HandleLButtonUp
1787 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
1789 if (LISTBOX_Timer != LB_TIMER_NONE)
1790 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
1791 LISTBOX_Timer = LB_TIMER_NONE;
1792 if (descr->captured)
1794 descr->captured = FALSE;
1795 if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
1796 if (descr->style & LBS_NOTIFY)
1797 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
1799 return 0;
1803 /***********************************************************************
1804 * LISTBOX_HandleTimer
1806 * Handle scrolling upon a timer event.
1807 * Return TRUE if scrolling should continue.
1809 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
1810 INT index, TIMER_DIRECTION dir )
1812 switch(dir)
1814 case LB_TIMER_UP:
1815 if (descr->top_item) index = descr->top_item - 1;
1816 else index = 0;
1817 break;
1818 case LB_TIMER_LEFT:
1819 if (descr->top_item) index -= descr->page_size;
1820 break;
1821 case LB_TIMER_DOWN:
1822 index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
1823 if (index == descr->focus_item) index++;
1824 if (index >= descr->nb_items) index = descr->nb_items - 1;
1825 break;
1826 case LB_TIMER_RIGHT:
1827 if (index + descr->page_size < descr->nb_items)
1828 index += descr->page_size;
1829 break;
1830 case LB_TIMER_NONE:
1831 break;
1833 if (index == descr->focus_item) return FALSE;
1834 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1835 return TRUE;
1839 /***********************************************************************
1840 * LISTBOX_HandleSystemTimer
1842 * WM_SYSTIMER handler.
1844 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
1846 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
1848 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
1849 LISTBOX_Timer = LB_TIMER_NONE;
1851 return 0;
1855 /***********************************************************************
1856 * LISTBOX_HandleMouseMove
1858 * WM_MOUSEMOVE handler.
1860 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
1861 INT x, INT y )
1863 INT index;
1864 TIMER_DIRECTION dir;
1866 if (!descr->captured) return;
1868 if (descr->style & LBS_MULTICOLUMN)
1870 if (y < 0) y = 0;
1871 else if (y >= descr->item_height * descr->page_size)
1872 y = descr->item_height * descr->page_size - 1;
1874 if (x < 0)
1876 dir = LB_TIMER_LEFT;
1877 x = 0;
1879 else if (x >= descr->width)
1881 dir = LB_TIMER_RIGHT;
1882 x = descr->width - 1;
1884 else dir = LB_TIMER_NONE; /* inside */
1886 else
1888 if (y < 0) dir = LB_TIMER_UP; /* above */
1889 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
1890 else dir = LB_TIMER_NONE; /* inside */
1893 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1894 if (index == -1) index = descr->focus_item;
1895 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
1897 /* Start/stop the system timer */
1899 if (dir != LB_TIMER_NONE)
1900 SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
1901 else if (LISTBOX_Timer != LB_TIMER_NONE)
1902 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
1903 LISTBOX_Timer = dir;
1907 /***********************************************************************
1908 * LISTBOX_HandleKeyDown
1910 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
1912 INT caret = -1;
1913 if (descr->style & LBS_WANTKEYBOARDINPUT)
1915 caret = SendMessageA( descr->owner, WM_VKEYTOITEM,
1916 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
1917 wnd->hwndSelf );
1918 if (caret == -2) return 0;
1920 if (caret == -1) switch(wParam)
1922 case VK_LEFT:
1923 if (descr->style & LBS_MULTICOLUMN)
1925 if (descr->focus_item >= descr->page_size)
1926 caret = descr->focus_item - descr->page_size;
1927 break;
1929 /* fall through */
1930 case VK_UP:
1931 caret = descr->focus_item - 1;
1932 if (caret < 0) caret = 0;
1933 break;
1934 case VK_RIGHT:
1935 if (descr->style & LBS_MULTICOLUMN)
1937 if (descr->focus_item + descr->page_size < descr->nb_items)
1938 caret = descr->focus_item + descr->page_size;
1939 break;
1941 /* fall through */
1942 case VK_DOWN:
1943 caret = descr->focus_item + 1;
1944 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1945 break;
1946 case VK_PRIOR:
1947 if (descr->style & LBS_MULTICOLUMN)
1949 INT page = descr->width / descr->column_width;
1950 if (page < 1) page = 1;
1951 caret = descr->focus_item - (page * descr->page_size) + 1;
1953 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
1954 if (caret < 0) caret = 0;
1955 break;
1956 case VK_NEXT:
1957 if (descr->style & LBS_MULTICOLUMN)
1959 INT page = descr->width / descr->column_width;
1960 if (page < 1) page = 1;
1961 caret = descr->focus_item + (page * descr->page_size) - 1;
1963 else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
1964 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1965 break;
1966 case VK_HOME:
1967 caret = 0;
1968 break;
1969 case VK_END:
1970 caret = descr->nb_items - 1;
1971 break;
1972 case VK_SPACE:
1973 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
1974 else if (descr->style & LBS_MULTIPLESEL)
1976 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
1977 !descr->items[descr->focus_item].selected,
1978 (descr->style & LBS_NOTIFY) != 0 );
1980 else if (descr->selected_item == -1)
1982 LISTBOX_SetSelection( wnd, descr, descr->focus_item, TRUE,
1983 (descr->style & LBS_NOTIFY) != 0 );
1985 break;
1987 if (caret >= 0)
1989 if ((descr->style & LBS_EXTENDEDSEL) &&
1990 !(GetKeyState( VK_SHIFT ) & 0x8000))
1991 descr->anchor_item = caret;
1992 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
1993 if (descr->style & LBS_NOTIFY)
1995 if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
1997 /* make sure that combo parent doesn't hide us */
1998 descr->lphc->wState |= CBF_NOROLLUP;
2000 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2003 return 0;
2007 /***********************************************************************
2008 * LISTBOX_HandleChar
2010 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
2011 WPARAM wParam )
2013 INT caret = -1;
2014 char str[2];
2016 str[0] = wParam & 0xff;
2017 str[1] = '\0';
2019 if (descr->style & LBS_WANTKEYBOARDINPUT)
2021 caret = SendMessageA( descr->owner, WM_CHARTOITEM,
2022 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2023 wnd->hwndSelf );
2024 if (caret == -2) return 0;
2026 if (caret == -1)
2027 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2028 if (caret != -1)
2030 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2031 if (descr->style & LBS_NOTIFY)
2032 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2034 return 0;
2038 /***********************************************************************
2039 * LISTBOX_Create
2041 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2043 LB_DESCR *descr;
2044 MEASUREITEMSTRUCT mis;
2045 RECT rect;
2047 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2048 return FALSE;
2049 if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2051 HeapFree( GetProcessHeap(), 0, descr );
2052 return FALSE;
2054 GetClientRect( wnd->hwndSelf, &rect );
2055 descr->owner = GetParent( wnd->hwndSelf );
2056 descr->style = wnd->dwStyle;
2057 descr->width = rect.right - rect.left;
2058 descr->height = rect.bottom - rect.top;
2059 descr->items = NULL;
2060 descr->nb_items = 0;
2061 descr->top_item = 0;
2062 descr->selected_item = -1;
2063 descr->focus_item = 0;
2064 descr->anchor_item = -1;
2065 descr->item_height = 1;
2066 descr->page_size = 1;
2067 descr->column_width = 150;
2068 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2069 descr->horz_pos = 0;
2070 descr->nb_tabs = 0;
2071 descr->tabs = NULL;
2072 descr->caret_on = TRUE;
2073 descr->captured = FALSE;
2074 descr->font = 0;
2075 descr->locale = 0; /* FIXME */
2076 descr->lphc = lphc;
2078 if( lphc )
2080 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2081 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2082 descr->owner = lphc->self->hwndSelf;
2085 *(LB_DESCR **)wnd->wExtra = descr;
2087 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2089 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2090 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2091 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2092 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2094 if (descr->style & LBS_OWNERDRAWFIXED)
2096 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2098 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2099 descr->item_height = lphc->fixedOwnerDrawHeight;
2101 else
2103 UINT id = (descr->lphc ) ? ID_CB_LISTBOX : wnd->wIDmenu;
2105 mis.CtlType = ODT_LISTBOX;
2106 mis.CtlID = id;
2107 mis.itemID = -1;
2108 mis.itemWidth = 0;
2109 mis.itemData = 0;
2110 mis.itemHeight = descr->item_height;
2111 SendMessageA( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2112 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2116 return TRUE;
2120 /***********************************************************************
2121 * LISTBOX_Destroy
2123 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2125 LISTBOX_ResetContent( wnd, descr );
2126 HeapDestroy( descr->heap );
2127 HeapFree( GetProcessHeap(), 0, descr );
2128 wnd->wExtra[0] = 0;
2129 return TRUE;
2133 /***********************************************************************
2134 * ListBoxWndProc
2136 static inline LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2137 WPARAM wParam, LPARAM lParam )
2139 LRESULT ret;
2140 LB_DESCR *descr;
2141 HWND hwnd = wnd->hwndSelf;
2143 if (!wnd) return 0;
2144 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2146 if (msg == WM_CREATE)
2148 if (!LISTBOX_Create( wnd, NULL ))
2149 return -1;
2150 TRACE("creating wnd=%04x descr=%p\n",
2151 hwnd, *(LB_DESCR **)wnd->wExtra );
2152 return 0;
2154 /* Ignore all other messages before we get a WM_CREATE */
2155 return DefWindowProcA( hwnd, msg, wParam, lParam );
2158 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2159 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2160 switch(msg)
2162 case LB_RESETCONTENT16:
2163 case LB_RESETCONTENT:
2164 LISTBOX_ResetContent( wnd, descr );
2165 return 0;
2167 case LB_ADDSTRING16:
2168 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2169 /* fall through */
2170 case LB_ADDSTRING:
2171 wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2172 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2174 case LB_INSERTSTRING16:
2175 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2176 wParam = (INT)(INT16)wParam;
2177 /* fall through */
2178 case LB_INSERTSTRING:
2179 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2181 case LB_ADDFILE16:
2182 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2183 /* fall through */
2184 case LB_ADDFILE:
2185 wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2186 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2188 case LB_DELETESTRING16:
2189 case LB_DELETESTRING:
2190 return LISTBOX_RemoveItem( wnd, descr, wParam );
2192 case LB_GETITEMDATA16:
2193 case LB_GETITEMDATA:
2194 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2195 return LB_ERR;
2196 return descr->items[wParam].data;
2198 case LB_SETITEMDATA16:
2199 case LB_SETITEMDATA:
2200 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2201 return LB_ERR;
2202 descr->items[wParam].data = (DWORD)lParam;
2203 return LB_OKAY;
2205 case LB_GETCOUNT16:
2206 case LB_GETCOUNT:
2207 return descr->nb_items;
2209 case LB_GETTEXT16:
2210 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2211 /* fall through */
2212 case LB_GETTEXT:
2213 return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2215 case LB_GETTEXTLEN16:
2216 /* fall through */
2217 case LB_GETTEXTLEN:
2218 if (wParam >= descr->nb_items)
2219 return LB_ERR;
2220 return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2221 : sizeof(DWORD));
2223 case LB_GETCURSEL16:
2224 case LB_GETCURSEL:
2225 if (descr->nb_items==0)
2226 return LB_ERR;
2227 /* else */
2228 if (descr->selected_item!=-1)
2229 return descr->selected_item;
2230 /* else */
2231 return descr->focus_item;
2232 /* otherwise, if the user tries to move the selection with the */
2233 /* arrow keys, we will give the application something to choke on */
2234 case LB_GETTOPINDEX16:
2235 case LB_GETTOPINDEX:
2236 return descr->top_item;
2238 case LB_GETITEMHEIGHT16:
2239 case LB_GETITEMHEIGHT:
2240 return LISTBOX_GetItemHeight( wnd, descr, wParam );
2242 case LB_SETITEMHEIGHT16:
2243 lParam = LOWORD(lParam);
2244 /* fall through */
2245 case LB_SETITEMHEIGHT:
2246 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2248 case LB_ITEMFROMPOINT:
2250 POINT pt;
2251 RECT rect;
2253 pt.x = LOWORD(lParam);
2254 pt.y = HIWORD(lParam);
2255 rect.left = 0;
2256 rect.top = 0;
2257 rect.right = descr->width;
2258 rect.bottom = descr->height;
2260 return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2261 !PtInRect( &rect, pt ) );
2264 case LB_SETCARETINDEX16:
2265 case LB_SETCARETINDEX:
2266 return LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam );
2268 case LB_GETCARETINDEX16:
2269 case LB_GETCARETINDEX:
2270 return descr->focus_item;
2272 case LB_SETTOPINDEX16:
2273 case LB_SETTOPINDEX:
2274 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2276 case LB_SETCOLUMNWIDTH16:
2277 case LB_SETCOLUMNWIDTH:
2278 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2280 case LB_GETITEMRECT16:
2282 RECT rect;
2283 ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2284 CONV_RECT32TO16( &rect, (RECT16 *)PTR_SEG_TO_LIN(lParam) );
2286 return ret;
2288 case LB_GETITEMRECT:
2289 return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT *)lParam );
2291 case LB_FINDSTRING16:
2292 wParam = (INT)(INT16)wParam;
2293 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2294 /* fall through */
2295 case LB_FINDSTRING:
2296 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2298 case LB_FINDSTRINGEXACT16:
2299 wParam = (INT)(INT16)wParam;
2300 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2301 /* fall through */
2302 case LB_FINDSTRINGEXACT:
2303 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2305 case LB_SELECTSTRING16:
2306 wParam = (INT)(INT16)wParam;
2307 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2308 /* fall through */
2309 case LB_SELECTSTRING:
2311 INT index = LISTBOX_FindString( wnd, descr, wParam,
2312 (LPCSTR)lParam, FALSE );
2313 if (index == LB_ERR)
2314 return LB_ERR;
2315 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2316 return index;
2319 case LB_GETSEL16:
2320 wParam = (INT)(INT16)wParam;
2321 /* fall through */
2322 case LB_GETSEL:
2323 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2324 return LB_ERR;
2325 return descr->items[wParam].selected;
2327 case LB_SETSEL16:
2328 lParam = (INT)(INT16)lParam;
2329 /* fall through */
2330 case LB_SETSEL:
2331 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2333 case LB_SETCURSEL16:
2334 wParam = (INT)(INT16)wParam;
2335 /* fall through */
2336 case LB_SETCURSEL:
2337 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2338 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2340 case LB_GETSELCOUNT16:
2341 case LB_GETSELCOUNT:
2342 return LISTBOX_GetSelCount( wnd, descr );
2344 case LB_GETSELITEMS16:
2345 return LISTBOX_GetSelItems16( wnd, descr, wParam,
2346 (LPINT16)PTR_SEG_TO_LIN(lParam) );
2348 case LB_GETSELITEMS:
2349 return LISTBOX_GetSelItems( wnd, descr, wParam, (LPINT)lParam );
2351 case LB_SELITEMRANGE16:
2352 case LB_SELITEMRANGE:
2353 if (LOWORD(lParam) <= HIWORD(lParam))
2354 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2355 HIWORD(lParam), wParam );
2356 else
2357 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2358 LOWORD(lParam), wParam );
2360 case LB_SELITEMRANGEEX16:
2361 case LB_SELITEMRANGEEX:
2362 if ((INT)lParam >= (INT)wParam)
2363 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2364 else
2365 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2367 case LB_GETHORIZONTALEXTENT16:
2368 case LB_GETHORIZONTALEXTENT:
2369 return descr->horz_extent;
2371 case LB_SETHORIZONTALEXTENT16:
2372 case LB_SETHORIZONTALEXTENT:
2373 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2375 case LB_GETANCHORINDEX16:
2376 case LB_GETANCHORINDEX:
2377 return descr->anchor_item;
2379 case LB_SETANCHORINDEX16:
2380 wParam = (INT)(INT16)wParam;
2381 /* fall through */
2382 case LB_SETANCHORINDEX:
2383 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2384 return LB_ERR;
2385 descr->anchor_item = (INT)wParam;
2386 return LB_OKAY;
2388 case LB_DIR16:
2389 return LISTBOX_Directory( wnd, descr, wParam,
2390 (LPCSTR)PTR_SEG_TO_LIN(lParam), FALSE );
2392 case LB_DIR:
2393 return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2395 case LB_GETLOCALE:
2396 return descr->locale;
2398 case LB_SETLOCALE:
2399 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2400 return LB_OKAY;
2402 case LB_INITSTORAGE:
2403 return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2405 case LB_SETCOUNT:
2406 return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2408 case LB_SETTABSTOPS16:
2409 return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam,
2410 (LPINT)PTR_SEG_TO_LIN(lParam), TRUE );
2412 case LB_SETTABSTOPS:
2413 return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2415 case LB_CARETON16:
2416 case LB_CARETON:
2417 if (descr->caret_on)
2418 return LB_OKAY;
2419 descr->caret_on = TRUE;
2420 if ((descr->focus_item != -1) && (GetFocus() == wnd->hwndSelf))
2421 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2422 return LB_OKAY;
2424 case LB_CARETOFF16:
2425 case LB_CARETOFF:
2426 if (!descr->caret_on)
2427 return LB_OKAY;
2428 descr->caret_on = FALSE;
2429 if ((descr->focus_item != -1) && (GetFocus() == wnd->hwndSelf))
2430 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2431 return LB_OKAY;
2433 case WM_DESTROY:
2434 return LISTBOX_Destroy( wnd, descr );
2436 case WM_ENABLE:
2437 InvalidateRect( hwnd, NULL, TRUE );
2438 return 0;
2440 case WM_SETREDRAW:
2441 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2442 return 0;
2444 case WM_GETDLGCODE:
2445 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2447 case WM_PAINT:
2449 PAINTSTRUCT ps;
2450 HDC hdc = ( wParam ) ? ((HDC)wParam)
2451 : BeginPaint( hwnd, &ps );
2452 ret = LISTBOX_Paint( wnd, descr, hdc );
2453 if( !wParam ) EndPaint( hwnd, &ps );
2455 return ret;
2456 case WM_SIZE:
2457 LISTBOX_UpdateSize( wnd, descr );
2458 return 0;
2459 case WM_GETFONT:
2460 return descr->font;
2461 case WM_SETFONT:
2462 LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2463 if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2464 return 0;
2465 case WM_SETFOCUS:
2466 descr->caret_on = TRUE;
2467 if (descr->focus_item != -1)
2468 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2469 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2470 return 0;
2471 case WM_KILLFOCUS:
2472 if ((descr->focus_item != -1) && descr->caret_on)
2473 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2474 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2475 return 0;
2476 case WM_HSCROLL:
2477 return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2478 case WM_VSCROLL:
2479 return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2480 case WM_LBUTTONDOWN:
2481 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2482 (INT16)LOWORD(lParam),
2483 (INT16)HIWORD(lParam) );
2484 case WM_LBUTTONDBLCLK:
2485 if (descr->style & LBS_NOTIFY)
2486 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2487 return 0;
2488 case WM_MOUSEMOVE:
2489 if (GetCapture() == hwnd)
2490 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2491 (INT16)HIWORD(lParam) );
2492 return 0;
2493 case WM_LBUTTONUP:
2494 return LISTBOX_HandleLButtonUp( wnd, descr );
2495 case WM_KEYDOWN:
2496 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2497 case WM_CHAR:
2498 return LISTBOX_HandleChar( wnd, descr, wParam );
2499 case WM_SYSTIMER:
2500 return LISTBOX_HandleSystemTimer( wnd, descr );
2501 case WM_ERASEBKGND:
2502 if (IS_OWNERDRAW(descr))
2504 RECT rect;
2505 HBRUSH hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
2506 wParam, (LPARAM)wnd->hwndSelf );
2507 GetClientRect(hwnd, &rect);
2508 if (hbrush) FillRect( (HDC)wParam, &rect, hbrush );
2510 return 1;
2511 case WM_DROPFILES:
2512 if( !descr->lphc )
2513 return SendMessageA( descr->owner, msg, wParam, lParam );
2514 break;
2516 case WM_DROPOBJECT:
2517 case WM_QUERYDROPOBJECT:
2518 case WM_DRAGSELECT:
2519 case WM_DRAGMOVE:
2520 if( !descr->lphc )
2522 LPDRAGINFO dragInfo = (LPDRAGINFO)PTR_SEG_TO_LIN( (SEGPTR)lParam );
2523 dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2524 dragInfo->pt.y );
2525 return SendMessageA( descr->owner, msg, wParam, lParam );
2527 break;
2529 case WM_NCCREATE:
2530 if (TWEAK_WineLook > WIN31_LOOK)
2531 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2532 return DefWindowProcA( hwnd, msg, wParam, lParam );
2533 default:
2534 if ((msg >= WM_USER) && (msg < 0xc000))
2535 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2536 hwnd, msg, wParam, lParam );
2537 return DefWindowProcA( hwnd, msg, wParam, lParam );
2539 return 0;
2542 /***********************************************************************
2543 * ListBoxWndProc
2545 * This is just a wrapper for the real wndproc, it only does window locking
2546 * and unlocking.
2548 LRESULT WINAPI ListBoxWndProc( HWND hwnd, UINT msg,
2549 WPARAM wParam, LPARAM lParam )
2551 WND* wndPtr = WIN_FindWndPtr( hwnd );
2552 LRESULT res = ListBoxWndProc_locked(wndPtr,msg,wParam,lParam);
2554 WIN_ReleaseWndPtr(wndPtr);
2555 return res;
2558 /***********************************************************************
2559 * COMBO_Directory
2561 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT attrib, LPSTR dir, BOOL bLong)
2563 WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2565 if( wnd )
2567 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2568 if( descr )
2570 LRESULT lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2572 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
2573 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2574 WIN_ReleaseWndPtr(wnd);
2575 return lRet;
2577 WIN_ReleaseWndPtr(wnd);
2579 return CB_ERR;
2582 /***********************************************************************
2583 * ComboLBWndProc_locked
2585 * The real combo listbox wndproc, but called with locked WND struct.
2587 static inline LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
2588 WPARAM wParam, LPARAM lParam )
2590 LRESULT lRet = 0;
2591 HWND hwnd = wnd->hwndSelf;
2593 if (wnd)
2595 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2597 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
2598 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2600 if( descr || msg == WM_CREATE )
2602 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2604 switch( msg )
2606 case WM_CREATE:
2607 #define lpcs ((LPCREATESTRUCTA)lParam)
2608 TRACE_(combo)("\tpassed parent handle = 0x%08x\n",
2609 (UINT)lpcs->lpCreateParams);
2611 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2612 #undef lpcs
2613 return LISTBOX_Create( wnd, lphc );
2614 case WM_LBUTTONDOWN:
2615 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2616 (INT16)LOWORD(lParam), (INT16)HIWORD(lParam));
2617 /* avoid activation at all costs */
2619 case WM_MOUSEACTIVATE:
2620 return MA_NOACTIVATE;
2621 case WM_NCACTIVATE:
2622 return FALSE;
2623 case WM_KEYDOWN:
2624 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2626 /* for some reason(?) Windows makes it possible to
2627 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2629 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2630 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2631 && (wParam == VK_DOWN || wParam == VK_UP)) )
2633 COMBO_FlipListbox( lphc, FALSE );
2634 return 0;
2637 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2639 case LB_SETCURSEL16:
2640 case LB_SETCURSEL:
2641 lRet = ListBoxWndProc( hwnd, msg, wParam, lParam );
2642 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
2643 return lRet;
2644 case WM_NCDESTROY:
2645 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2646 lphc->hWndLBox = 0;
2647 /* fall through */
2649 default:
2650 return ListBoxWndProc( hwnd, msg, wParam, lParam );
2653 lRet = DefWindowProcA( hwnd, msg, wParam, lParam );
2655 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
2657 return lRet;
2660 /***********************************************************************
2661 * ComboLBWndProc
2663 * NOTE: in Windows, winproc address of the ComboLBox is the same
2664 * as that of the Listbox.
2666 * This is just a wrapper for the real wndproc, it only does window locking
2667 * and unlocking.
2669 LRESULT WINAPI ComboLBWndProc( HWND hwnd, UINT msg,
2670 WPARAM wParam, LPARAM lParam )
2672 WND *wnd = WIN_FindWndPtr( hwnd );
2673 LRESULT res = ComboLBWndProc_locked(wnd,msg,wParam,lParam);
2675 WIN_ReleaseWndPtr(wnd);
2676 return res;