Add a few more 32->16 Escape conversions.
[wine.git] / controls / listbox.c
blobdbcf862d6c9642df8f8d775bfc045303d792dbd5
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 ((page_size = descr->height / descr->item_height) < 1) page_size = 1;
301 if (page_size == descr->page_size) return;
302 descr->page_size = page_size;
303 if (descr->style & LBS_MULTICOLUMN)
304 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
305 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
309 /***********************************************************************
310 * LISTBOX_UpdateSize
312 * Update the size of the listbox. Should be called when the size of
313 * the client area changes.
315 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
317 RECT rect;
319 GetClientRect( wnd->hwndSelf, &rect );
320 descr->width = rect.right - rect.left;
321 descr->height = rect.bottom - rect.top;
322 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !IS_OWNERDRAW(descr))
324 if ((descr->height > descr->item_height) &&
325 (descr->height % descr->item_height))
327 TRACE("[%04x]: changing height %d -> %d\n",
328 wnd->hwndSelf, descr->height,
329 descr->height - descr->height%descr->item_height );
330 SetWindowPos( wnd->hwndSelf, 0, 0, 0,
331 wnd->rectWindow.right - wnd->rectWindow.left,
332 wnd->rectWindow.bottom - wnd->rectWindow.top -
333 (descr->height % descr->item_height),
334 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
335 return;
338 TRACE("[%04x]: new size = %d,%d\n",
339 wnd->hwndSelf, descr->width, descr->height );
340 LISTBOX_UpdatePage( wnd, descr );
341 LISTBOX_UpdateScroll( wnd, descr );
345 /***********************************************************************
346 * LISTBOX_GetItemRect
348 * Get the rectangle enclosing an item, in listbox client coordinates.
349 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
351 static LRESULT LISTBOX_GetItemRect( WND *wnd, LB_DESCR *descr, INT index,
352 RECT *rect )
354 /* Index <= 0 is legal even on empty listboxes */
355 if (index && (index >= descr->nb_items)) return -1;
356 SetRect( rect, 0, 0, descr->width, descr->height );
357 if (descr->style & LBS_MULTICOLUMN)
359 INT col = (index / descr->page_size) -
360 (descr->top_item / descr->page_size);
361 rect->left += col * descr->column_width;
362 rect->right = rect->left + descr->column_width;
363 rect->top += (index % descr->page_size) * descr->item_height;
364 rect->bottom = rect->top + descr->item_height;
366 else if (descr->style & LBS_OWNERDRAWVARIABLE)
368 INT i;
369 rect->right += descr->horz_pos;
370 if ((index >= 0) && (index < descr->nb_items))
372 if (index < descr->top_item)
374 for (i = descr->top_item-1; i >= index; i--)
375 rect->top -= descr->items[i].height;
377 else
379 for (i = descr->top_item; i < index; i++)
380 rect->top += descr->items[i].height;
382 rect->bottom = rect->top + descr->items[index].height;
386 else
388 rect->top += (index - descr->top_item) * descr->item_height;
389 rect->bottom = rect->top + descr->item_height;
390 rect->right += descr->horz_pos;
393 return ((rect->left < descr->width) && (rect->right > 0) &&
394 (rect->top < descr->height) && (rect->bottom > 0));
398 /***********************************************************************
399 * LISTBOX_GetItemFromPoint
401 * Return the item nearest from point (x,y) (in client coordinates).
403 static INT LISTBOX_GetItemFromPoint( WND *wnd, LB_DESCR *descr,
404 INT x, INT y )
406 INT index = descr->top_item;
408 if (!descr->nb_items) return -1; /* No items */
409 if (descr->style & LBS_OWNERDRAWVARIABLE)
411 INT pos = 0;
412 if (y >= 0)
414 while (index < descr->nb_items)
416 if ((pos += descr->items[index].height) > y) break;
417 index++;
420 else
422 while (index > 0)
424 index--;
425 if ((pos -= descr->items[index].height) <= y) break;
429 else if (descr->style & LBS_MULTICOLUMN)
431 if (y >= descr->item_height * descr->page_size) return -1;
432 if (y >= 0) index += y / descr->item_height;
433 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
434 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
436 else
438 index += (y / descr->item_height);
440 if (index < 0) return 0;
441 if (index >= descr->nb_items) return -1;
442 return index;
446 /***********************************************************************
447 * LISTBOX_PaintItem
449 * Paint an item.
451 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC hdc,
452 const RECT *rect, INT index, UINT action )
454 LB_ITEMDATA *item = NULL;
455 if (index < descr->nb_items) item = &descr->items[index];
457 if (IS_OWNERDRAW(descr))
459 DRAWITEMSTRUCT dis;
460 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
462 if (!item)
464 if (action == ODA_FOCUS)
465 DrawFocusRect( hdc, rect );
466 else
467 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
468 return;
470 dis.CtlType = ODT_LISTBOX;
471 dis.CtlID = id;
472 dis.hwndItem = wnd->hwndSelf;
473 dis.itemAction = action;
474 dis.hDC = hdc;
475 dis.itemID = index;
476 dis.itemState = 0;
477 if (item && item->selected) dis.itemState |= ODS_SELECTED;
478 if ((descr->focus_item == index) &&
479 (descr->caret_on) &&
480 (GetFocus() == wnd->hwndSelf)) dis.itemState |= ODS_FOCUS;
481 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
482 dis.itemData = item ? item->data : 0;
483 dis.rcItem = *rect;
484 TRACE("[%04x]: drawitem %d (%s) action=%02x "
485 "state=%02x rect=%d,%d-%d,%d\n",
486 wnd->hwndSelf, index, item ? item->str : "", action,
487 dis.itemState, rect->left, rect->top,
488 rect->right, rect->bottom );
489 SendMessageA(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
491 else
493 COLORREF oldText = 0, oldBk = 0;
495 if (action == ODA_FOCUS)
497 DrawFocusRect( hdc, rect );
498 return;
500 if (item && item->selected)
502 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
503 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
506 TRACE("[%04x]: painting %d (%s) action=%02x "
507 "rect=%d,%d-%d,%d\n",
508 wnd->hwndSelf, index, item ? item->str : "", action,
509 rect->left, rect->top, rect->right, rect->bottom );
510 if (!item)
511 ExtTextOutA( hdc, rect->left + 1, rect->top + 1,
512 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
513 else if (!(descr->style & LBS_USETABSTOPS))
514 ExtTextOutA( hdc, rect->left + 1, rect->top + 1,
515 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
516 strlen(item->str), NULL );
517 else
519 /* Output empty string to paint background in the full width. */
520 ExtTextOutA( hdc, rect->left + 1, rect->top + 1,
521 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
522 TabbedTextOutA( hdc, rect->left + 1 , rect->top + 1,
523 item->str, strlen(item->str),
524 descr->nb_tabs, descr->tabs, 0);
526 if (item && item->selected)
528 SetBkColor( hdc, oldBk );
529 SetTextColor( hdc, oldText );
531 if ((descr->focus_item == index) &&
532 (descr->caret_on) &&
533 (GetFocus() == wnd->hwndSelf)) DrawFocusRect( hdc, rect );
538 /***********************************************************************
539 * LISTBOX_SetRedraw
541 * Change the redraw flag.
543 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL on )
545 if (on)
547 if (!(descr->style & LBS_NOREDRAW)) return;
548 descr->style &= ~LBS_NOREDRAW;
549 LISTBOX_UpdateScroll( wnd, descr );
551 else descr->style |= LBS_NOREDRAW;
555 /***********************************************************************
556 * LISTBOX_RepaintItem
558 * Repaint a single item synchronously.
560 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT index,
561 UINT action )
563 HDC hdc;
564 RECT rect;
565 HFONT oldFont = 0;
566 HBRUSH hbrush, oldBrush = 0;
568 /* Do not repaint the item if the item is not visible */
569 if ((descr->style & LBS_NOREDRAW) || !IsWindowVisible(wnd->hwndSelf)) return;
571 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) != 1) return;
572 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE ))) return;
573 if (descr->font) oldFont = SelectObject( hdc, descr->font );
574 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
575 hdc, (LPARAM)wnd->hwndSelf );
576 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
577 if (wnd->dwStyle & WS_DISABLED)
578 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
579 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
580 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action );
581 if (oldFont) SelectObject( hdc, oldFont );
582 if (oldBrush) SelectObject( hdc, oldBrush );
583 ReleaseDC( wnd->hwndSelf, hdc );
587 /***********************************************************************
588 * LISTBOX_InitStorage
590 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT nb_items,
591 DWORD bytes )
593 LB_ITEMDATA *item;
595 nb_items += LB_ARRAY_GRANULARITY - 1;
596 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
597 if (descr->items)
598 nb_items += HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
599 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
600 nb_items * sizeof(LB_ITEMDATA) )))
602 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
603 return LB_ERRSPACE;
605 descr->items = item;
606 return LB_OKAY;
610 /***********************************************************************
611 * LISTBOX_SetTabStops
613 static BOOL LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT count,
614 LPINT tabs, BOOL short_ints )
616 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
617 if (descr->tabs) HeapFree( descr->heap, 0, descr->tabs );
618 if (!(descr->nb_tabs = count))
620 descr->tabs = NULL;
621 return TRUE;
623 /* FIXME: count = 1 */
624 if (!(descr->tabs = (INT *)HeapAlloc( descr->heap, 0,
625 descr->nb_tabs * sizeof(INT) )))
626 return FALSE;
627 if (short_ints)
629 INT i;
630 LPINT16 p = (LPINT16)tabs;
631 dbg_decl_str(listbox, 256);
633 for (i = 0; i < descr->nb_tabs; i++) {
634 descr->tabs[i] = *p++<<1; /* FIXME */
635 if(TRACE_ON(listbox))
636 dsprintf(listbox, "%hd ", descr->tabs[i]);
638 TRACE("[%04x]: settabstops %s\n",
639 wnd->hwndSelf, dbg_str(listbox));
641 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
642 /* FIXME: repaint the window? */
643 return TRUE;
647 /***********************************************************************
648 * LISTBOX_GetText
650 static LRESULT LISTBOX_GetText( WND *wnd, LB_DESCR *descr, INT index,
651 LPSTR buffer )
653 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
654 if (HAS_STRINGS(descr))
656 if (!buffer)
657 return strlen(descr->items[index].str);
658 lstrcpyA( buffer, descr->items[index].str );
659 return strlen(buffer);
660 } else {
661 if (buffer)
662 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
663 return sizeof(DWORD);
668 /***********************************************************************
669 * LISTBOX_FindStringPos
671 * Find the nearest string located before a given string in sort order.
672 * If 'exact' is TRUE, return an error if we don't get an exact match.
674 static INT LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCSTR str,
675 BOOL exact )
677 INT index, min, max, res = -1;
679 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
680 min = 0;
681 max = descr->nb_items;
682 while (min != max)
684 index = (min + max) / 2;
685 if (HAS_STRINGS(descr))
686 res = lstrcmpiA( descr->items[index].str, str );
687 else
689 COMPAREITEMSTRUCT cis;
690 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
692 cis.CtlType = ODT_LISTBOX;
693 cis.CtlID = id;
694 cis.hwndItem = wnd->hwndSelf;
695 cis.itemID1 = index;
696 cis.itemData1 = descr->items[index].data;
697 cis.itemID2 = -1;
698 cis.itemData2 = (DWORD)str;
699 cis.dwLocaleId = descr->locale;
700 res = SendMessageA( descr->owner, WM_COMPAREITEM,
701 id, (LPARAM)&cis );
703 if (!res) return index;
704 if (res > 0) max = index;
705 else min = index + 1;
707 return exact ? -1 : max;
711 /***********************************************************************
712 * LISTBOX_FindFileStrPos
714 * Find the nearest string located before a given string in directory
715 * sort order (i.e. first files, then directories, then drives).
717 static INT LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCSTR str )
719 INT min, max, res = -1;
721 if (!HAS_STRINGS(descr))
722 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
723 min = 0;
724 max = descr->nb_items;
725 while (min != max)
727 INT index = (min + max) / 2;
728 const char *p = descr->items[index].str;
729 if (*p == '[') /* drive or directory */
731 if (*str != '[') res = -1;
732 else if (p[1] == '-') /* drive */
734 if (str[1] == '-') res = str[2] - p[2];
735 else res = -1;
737 else /* directory */
739 if (str[1] == '-') res = 1;
740 else res = lstrcmpiA( str, p );
743 else /* filename */
745 if (*str == '[') res = 1;
746 else res = lstrcmpiA( str, p );
748 if (!res) return index;
749 if (res < 0) max = index;
750 else min = index + 1;
752 return max;
756 /***********************************************************************
757 * LISTBOX_FindString
759 * Find the item beginning with a given string.
761 static INT LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT start,
762 LPCSTR str, BOOL exact )
764 INT i;
765 LB_ITEMDATA *item;
767 if (start >= descr->nb_items) start = -1;
768 item = descr->items + start + 1;
769 if (HAS_STRINGS(descr))
771 if (!str) return LB_ERR;
772 if (exact)
774 for (i = start + 1; i < descr->nb_items; i++, item++)
775 if (!lstrcmpiA( str, item->str )) return i;
776 for (i = 0, item = descr->items; i <= start; i++, item++)
777 if (!lstrcmpiA( str, item->str )) return i;
779 else
781 /* Special case for drives and directories: ignore prefix */
782 #define CHECK_DRIVE(item) \
783 if ((item)->str[0] == '[') \
785 if (!lstrncmpiA( str, (item)->str+1, len )) return i; \
786 if (((item)->str[1] == '-') && !lstrncmpiA(str,(item)->str+2,len)) \
787 return i; \
790 INT len = strlen(str);
791 for (i = start + 1; i < descr->nb_items; i++, item++)
793 if (!lstrncmpiA( str, item->str, len )) return i;
794 CHECK_DRIVE(item);
796 for (i = 0, item = descr->items; i <= start; i++, item++)
798 if (!lstrncmpiA( str, item->str, len )) return i;
799 CHECK_DRIVE(item);
801 #undef CHECK_DRIVE
804 else
806 if (exact && (descr->style & LBS_SORT))
807 /* If sorted, use a WM_COMPAREITEM binary search */
808 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
810 /* Otherwise use a linear search */
811 for (i = start + 1; i < descr->nb_items; i++, item++)
812 if (item->data == (DWORD)str) return i;
813 for (i = 0, item = descr->items; i <= start; i++, item++)
814 if (item->data == (DWORD)str) return i;
816 return LB_ERR;
820 /***********************************************************************
821 * LISTBOX_GetSelCount
823 static LRESULT LISTBOX_GetSelCount( WND *wnd, LB_DESCR *descr )
825 INT 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; i++, item++)
830 if (item->selected) count++;
831 return count;
835 /***********************************************************************
836 * LISTBOX_GetSelItems16
838 static LRESULT LISTBOX_GetSelItems16( WND *wnd, LB_DESCR *descr, INT16 max,
839 LPINT16 array )
841 INT 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++] = (INT16)i;
847 return count;
851 /***********************************************************************
852 * LISTBOX_GetSelItems32
854 static LRESULT LISTBOX_GetSelItems( WND *wnd, LB_DESCR *descr, INT max,
855 LPINT array )
857 INT i, count;
858 LB_ITEMDATA *item = descr->items;
860 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
861 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
862 if (item->selected) array[count++] = i;
863 return count;
867 /***********************************************************************
868 * LISTBOX_Paint
870 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC hdc )
872 INT i, col_pos = descr->page_size - 1;
873 RECT rect;
874 HFONT oldFont = 0;
875 HBRUSH hbrush, oldBrush = 0;
877 SetRect( &rect, 0, 0, descr->width, descr->height );
878 if (descr->style & LBS_NOREDRAW) return 0;
879 if (descr->style & LBS_MULTICOLUMN)
880 rect.right = rect.left + descr->column_width;
881 else if (descr->horz_pos)
883 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
884 rect.right += descr->horz_pos;
887 if (descr->font) oldFont = SelectObject( hdc, descr->font );
888 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
889 hdc, (LPARAM)wnd->hwndSelf );
890 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
891 if (wnd->dwStyle & WS_DISABLED)
892 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
894 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
895 (GetFocus() == wnd->hwndSelf))
897 /* Special case for empty listbox: paint focus rect */
898 rect.bottom = rect.top + descr->item_height;
899 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
900 ODA_FOCUS );
901 rect.top = rect.bottom;
904 for (i = descr->top_item; i < descr->nb_items; i++)
906 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
907 rect.bottom = rect.top + descr->item_height;
908 else
909 rect.bottom = rect.top + descr->items[i].height;
911 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE );
912 rect.top = rect.bottom;
914 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
916 if (!IS_OWNERDRAW(descr))
918 /* Clear the bottom of the column */
919 SetBkColor( hdc, GetSysColor( COLOR_WINDOW ) );
920 if (rect.top < descr->height)
922 rect.bottom = descr->height;
923 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
924 &rect, NULL, 0, NULL );
928 /* Go to the next column */
929 rect.left += descr->column_width;
930 rect.right += descr->column_width;
931 rect.top = 0;
932 col_pos = descr->page_size - 1;
934 else
936 col_pos--;
937 if (rect.top >= descr->height) break;
941 if (!IS_OWNERDRAW(descr))
943 /* Clear the remainder of the client area */
944 SetBkColor( hdc, GetSysColor( COLOR_WINDOW ) );
945 if (rect.top < descr->height)
947 rect.bottom = descr->height;
948 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
949 &rect, NULL, 0, NULL );
951 if (rect.right < descr->width)
953 rect.left = rect.right;
954 rect.right = descr->width;
955 rect.top = 0;
956 rect.bottom = descr->height;
957 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
958 &rect, NULL, 0, NULL );
961 if (oldFont) SelectObject( hdc, oldFont );
962 if (oldBrush) SelectObject( hdc, oldBrush );
963 return 0;
967 /***********************************************************************
968 * LISTBOX_InvalidateItems
970 * Invalidate all items from a given item. If the specified item is not
971 * visible, nothing happens.
973 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT index )
975 RECT rect;
977 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) == 1)
979 rect.bottom = descr->height;
980 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
981 if (descr->style & LBS_MULTICOLUMN)
983 /* Repaint the other columns */
984 rect.left = rect.right;
985 rect.right = descr->width;
986 rect.top = 0;
987 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
993 /***********************************************************************
994 * LISTBOX_GetItemHeight
996 static LRESULT LISTBOX_GetItemHeight( WND *wnd, LB_DESCR *descr, INT index )
998 if (descr->style & LBS_OWNERDRAWVARIABLE)
1000 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1001 return descr->items[index].height;
1003 else return descr->item_height;
1007 /***********************************************************************
1008 * LISTBOX_SetItemHeight
1010 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT index,
1011 UINT height )
1013 if (!height) height = 1;
1015 if (descr->style & LBS_OWNERDRAWVARIABLE)
1017 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1018 TRACE("[%04x]: item %d height = %d\n",
1019 wnd->hwndSelf, index, height );
1020 descr->items[index].height = height;
1021 LISTBOX_UpdateScroll( wnd, descr );
1022 LISTBOX_InvalidateItems( wnd, descr, index );
1024 else if (height != descr->item_height)
1026 TRACE("[%04x]: new height = %d\n",
1027 wnd->hwndSelf, height );
1028 descr->item_height = height;
1029 LISTBOX_UpdatePage( wnd, descr );
1030 LISTBOX_UpdateScroll( wnd, descr );
1031 InvalidateRect( wnd->hwndSelf, 0, TRUE );
1033 return LB_OKAY;
1037 /***********************************************************************
1038 * LISTBOX_SetHorizontalPos
1040 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT pos )
1042 INT diff;
1044 if (pos > descr->horz_extent - descr->width)
1045 pos = descr->horz_extent - descr->width;
1046 if (pos < 0) pos = 0;
1047 if (!(diff = descr->horz_pos - pos)) return;
1048 TRACE("[%04x]: new horz pos = %d\n",
1049 wnd->hwndSelf, pos );
1050 descr->horz_pos = pos;
1051 LISTBOX_UpdateScroll( wnd, descr );
1052 if (abs(diff) < descr->width)
1053 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1054 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1055 else
1056 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1060 /***********************************************************************
1061 * LISTBOX_SetHorizontalExtent
1063 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1064 UINT extent )
1066 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1067 return LB_OKAY;
1068 if (extent <= 0) extent = 1;
1069 if (extent == descr->horz_extent) return LB_OKAY;
1070 TRACE("[%04x]: new horz extent = %d\n",
1071 wnd->hwndSelf, extent );
1072 descr->horz_extent = extent;
1073 if (descr->horz_pos > extent - descr->width)
1074 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1075 else
1076 LISTBOX_UpdateScroll( wnd, descr );
1077 return LB_OKAY;
1081 /***********************************************************************
1082 * LISTBOX_SetColumnWidth
1084 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, UINT width)
1086 width += 2; /* For left and right margin */
1087 if (width == descr->column_width) return LB_OKAY;
1088 TRACE("[%04x]: new column width = %d\n",
1089 wnd->hwndSelf, width );
1090 descr->column_width = width;
1091 LISTBOX_UpdatePage( wnd, descr );
1092 return LB_OKAY;
1096 /***********************************************************************
1097 * LISTBOX_SetFont
1099 * Returns the item height.
1101 static INT LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT font )
1103 HDC hdc;
1104 HFONT oldFont = 0;
1105 TEXTMETRICA tm;
1107 descr->font = font;
1109 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE )))
1111 ERR("unable to get DC.\n" );
1112 return 16;
1114 if (font) oldFont = SelectObject( hdc, font );
1115 GetTextMetricsA( hdc, &tm );
1116 if (oldFont) SelectObject( hdc, oldFont );
1117 ReleaseDC( wnd->hwndSelf, hdc );
1118 if (!IS_OWNERDRAW(descr))
1119 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1120 return tm.tmHeight ;
1124 /***********************************************************************
1125 * LISTBOX_MakeItemVisible
1127 * Make sure that a given item is partially or fully visible.
1129 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT index,
1130 BOOL fully )
1132 INT top;
1134 if (index <= descr->top_item) top = index;
1135 else if (descr->style & LBS_MULTICOLUMN)
1137 INT cols = descr->width;
1138 if (!fully) cols += descr->column_width - 1;
1139 if (cols >= descr->column_width) cols /= descr->column_width;
1140 else cols = 1;
1141 if (index < descr->top_item + (descr->page_size * cols)) return;
1142 top = index - descr->page_size * (cols - 1);
1144 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1146 INT height = fully ? descr->items[index].height : 1;
1147 for (top = index; top > descr->top_item; top--)
1148 if ((height += descr->items[top-1].height) > descr->height) break;
1150 else
1152 if (index < descr->top_item + descr->page_size) return;
1153 if (!fully && (index == descr->top_item + descr->page_size) &&
1154 (descr->height > (descr->page_size * descr->item_height))) return;
1155 top = index - descr->page_size + 1;
1157 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1161 /***********************************************************************
1162 * LISTBOX_SelectItemRange
1164 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1166 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT first,
1167 INT last, BOOL on )
1169 INT i;
1171 /* A few sanity checks */
1173 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1174 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1175 if (last == -1) last = descr->nb_items - 1;
1176 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1177 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1178 /* selected_item reflects last selected/unselected item on multiple sel */
1179 descr->selected_item = last;
1181 if (on) /* Turn selection on */
1183 for (i = first; i <= last; i++)
1185 if (descr->items[i].selected) continue;
1186 descr->items[i].selected = TRUE;
1187 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1190 else /* Turn selection off */
1192 for (i = first; i <= last; i++)
1194 if (!descr->items[i].selected) continue;
1195 descr->items[i].selected = FALSE;
1196 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1199 return LB_OKAY;
1203 /***********************************************************************
1204 * LISTBOX_SetCaretIndex
1206 * NOTES
1207 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1210 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT index,
1211 BOOL fully_visible )
1213 INT oldfocus = descr->focus_item;
1215 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1216 if (index == oldfocus) return LB_OKAY;
1217 descr->focus_item = index;
1218 if ((oldfocus != -1) && descr->caret_on && (GetFocus() == wnd->hwndSelf))
1219 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1221 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1222 if (descr->caret_on && (GetFocus() == wnd->hwndSelf))
1223 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1225 return LB_OKAY;
1229 /***********************************************************************
1230 * LISTBOX_SetSelection
1232 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT index,
1233 BOOL on, BOOL send_notify )
1235 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1236 if (descr->style & LBS_MULTIPLESEL)
1238 if (index == -1) /* Select all items */
1239 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1240 else /* Only one item */
1241 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1243 else
1245 INT oldsel = descr->selected_item;
1246 if (index == oldsel) return LB_OKAY;
1247 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1248 if (index != -1) descr->items[index].selected = TRUE;
1249 descr->selected_item = index;
1250 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT);
1251 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1252 if (send_notify) SEND_NOTIFICATION( wnd, descr,
1253 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1254 else
1255 if( descr->lphc ) /* set selection change flag for parent combo */
1256 descr->lphc->wState |= CBF_SELCHANGE;
1258 return LB_OKAY;
1262 /***********************************************************************
1263 * LISTBOX_MoveCaret
1265 * Change the caret position and extend the selection to the new caret.
1267 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT index,
1268 BOOL fully_visible )
1270 LISTBOX_SetCaretIndex( wnd, descr, index, fully_visible );
1271 if (descr->style & LBS_EXTENDEDSEL)
1273 if (descr->anchor_item != -1)
1275 INT first = MIN( descr->focus_item, descr->anchor_item );
1276 INT last = MAX( descr->focus_item, descr->anchor_item );
1277 if (first > 0)
1278 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1279 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1280 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1283 else if (!(descr->style & LBS_MULTIPLESEL) && (descr->selected_item != -1))
1285 /* Set selection to new caret item */
1286 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1291 /***********************************************************************
1292 * LISTBOX_InsertItem
1294 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT index,
1295 LPSTR str, DWORD data )
1297 LB_ITEMDATA *item;
1298 INT max_items;
1300 if (index == -1) index = descr->nb_items;
1301 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1302 if (!descr->items) max_items = 0;
1303 else max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
1304 if (descr->nb_items == max_items)
1306 /* We need to grow the array */
1307 max_items += LB_ARRAY_GRANULARITY;
1308 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
1309 max_items * sizeof(LB_ITEMDATA) )))
1311 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1312 return LB_ERRSPACE;
1314 descr->items = item;
1317 /* Insert the item structure */
1319 item = &descr->items[index];
1320 if (index < descr->nb_items)
1321 RtlMoveMemory( item + 1, item,
1322 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1323 item->str = str;
1324 item->data = data;
1325 item->height = 0;
1326 item->selected = FALSE;
1327 descr->nb_items++;
1329 /* Get item height */
1331 if (descr->style & LBS_OWNERDRAWVARIABLE)
1333 MEASUREITEMSTRUCT mis;
1334 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1336 mis.CtlType = ODT_LISTBOX;
1337 mis.CtlID = id;
1338 mis.itemID = index;
1339 mis.itemData = descr->items[index].data;
1340 mis.itemHeight = descr->item_height;
1341 SendMessageA( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1342 item->height = mis.itemHeight ? mis.itemHeight : 1;
1343 TRACE("[%04x]: measure item %d (%s) = %d\n",
1344 wnd->hwndSelf, index, str ? str : "", item->height );
1347 /* Repaint the items */
1349 LISTBOX_UpdateScroll( wnd, descr );
1350 LISTBOX_InvalidateItems( wnd, descr, index );
1352 /* Move selection and focused item */
1354 if (index <= descr->selected_item) descr->selected_item++;
1355 if (index <= descr->focus_item)
1357 descr->focus_item++;
1358 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1361 /* If listbox was empty, set focus to the first item */
1363 if (descr->nb_items == 1) LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1364 return LB_OKAY;
1368 /***********************************************************************
1369 * LISTBOX_InsertString
1371 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT index,
1372 LPCSTR str )
1374 LPSTR new_str = NULL;
1375 DWORD data = 0;
1376 LRESULT ret;
1378 if (HAS_STRINGS(descr))
1380 if (!(new_str = HEAP_strdupA( descr->heap, 0, str )))
1382 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1383 return LB_ERRSPACE;
1386 else data = (DWORD)str;
1388 if (index == -1) index = descr->nb_items;
1389 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1391 if (new_str) HeapFree( descr->heap, 0, new_str );
1392 return ret;
1395 TRACE("[%04x]: added item %d '%s'\n",
1396 wnd->hwndSelf, index, HAS_STRINGS(descr) ? new_str : "" );
1397 return index;
1401 /***********************************************************************
1402 * LISTBOX_DeleteItem
1404 * Delete the content of an item. 'index' must be a valid index.
1406 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT index )
1408 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1409 * while Win95 sends it for all items with user data.
1410 * It's probably better to send it too often than not
1411 * often enough, so this is what we do here.
1413 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1415 DELETEITEMSTRUCT dis;
1416 UINT id = (descr->lphc) ? ID_CB_LISTBOX : wnd->wIDmenu;
1418 dis.CtlType = ODT_LISTBOX;
1419 dis.CtlID = id;
1420 dis.itemID = index;
1421 dis.hwndItem = wnd->hwndSelf;
1422 dis.itemData = descr->items[index].data;
1423 SendMessageA( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1425 if (HAS_STRINGS(descr) && descr->items[index].str)
1426 HeapFree( descr->heap, 0, descr->items[index].str );
1430 /***********************************************************************
1431 * LISTBOX_RemoveItem
1433 * Remove an item from the listbox and delete its content.
1435 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT index )
1437 LB_ITEMDATA *item;
1438 INT max_items;
1440 if (index == -1) index = descr->nb_items - 1;
1441 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1442 LISTBOX_DeleteItem( wnd, descr, index );
1444 /* Remove the item */
1446 item = &descr->items[index];
1447 if (index < descr->nb_items-1)
1448 RtlMoveMemory( item, item + 1,
1449 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1450 descr->nb_items--;
1451 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1453 /* Shrink the item array if possible */
1455 max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(LB_ITEMDATA);
1456 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1458 max_items -= LB_ARRAY_GRANULARITY;
1459 item = HeapReAlloc( descr->heap, 0, descr->items,
1460 max_items * sizeof(LB_ITEMDATA) );
1461 if (item) descr->items = item;
1464 /* Repaint the items */
1466 LISTBOX_UpdateScroll( wnd, descr );
1467 LISTBOX_InvalidateItems( wnd, descr, index );
1469 /* Move selection and focused item */
1471 if (index <= descr->selected_item) descr->selected_item--;
1472 if (index <= descr->focus_item)
1474 descr->focus_item--;
1475 LISTBOX_MoveCaret( wnd, descr, descr->focus_item, FALSE );
1477 return LB_OKAY;
1481 /***********************************************************************
1482 * LISTBOX_ResetContent
1484 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1486 INT i;
1488 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1489 if (descr->items) HeapFree( descr->heap, 0, descr->items );
1490 descr->nb_items = 0;
1491 descr->top_item = 0;
1492 descr->selected_item = -1;
1493 descr->focus_item = 0;
1494 descr->anchor_item = -1;
1495 descr->items = NULL;
1496 LISTBOX_UpdateScroll( wnd, descr );
1497 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1501 /***********************************************************************
1502 * LISTBOX_SetCount
1504 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1506 LRESULT ret;
1508 if (HAS_STRINGS(descr)) return LB_ERR;
1509 /* FIXME: this is far from optimal... */
1510 if (count > descr->nb_items)
1512 while (count > descr->nb_items)
1513 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1514 return ret;
1516 else if (count < descr->nb_items)
1518 while (count < descr->nb_items)
1519 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1520 return ret;
1522 return LB_OKAY;
1526 /***********************************************************************
1527 * LISTBOX_Directory
1529 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1530 LPCSTR filespec, BOOL long_names )
1532 HANDLE handle;
1533 LRESULT ret = LB_OKAY;
1534 WIN32_FIND_DATAA entry;
1535 int pos;
1537 if ((handle = FindFirstFileA(filespec,&entry)) == INVALID_HANDLE_VALUE)
1539 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1541 else
1545 char buffer[270];
1546 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1548 if (!(attrib & DDL_DIRECTORY) ||
1549 !strcmp( entry.cAlternateFileName, "." )) continue;
1550 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1551 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1553 else /* not a directory */
1555 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1556 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1558 if ((attrib & DDL_EXCLUSIVE) &&
1559 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1560 continue;
1561 #undef ATTRIBS
1562 if (long_names) strcpy( buffer, entry.cFileName );
1563 else strcpy( buffer, entry.cAlternateFileName );
1565 if (!long_names) CharLowerA( buffer );
1566 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1567 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1568 break;
1569 } while (FindNextFileA( handle, &entry ));
1570 FindClose( handle );
1573 if ((ret >= 0) && (attrib & DDL_DRIVES))
1575 char buffer[] = "[-a-]";
1576 int drive;
1577 for (drive = 0; drive < MAX_DOS_DRIVES; drive++, buffer[2]++)
1579 if (!DRIVE_IsValid(drive)) continue;
1580 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1581 break;
1584 return ret;
1588 /***********************************************************************
1589 * LISTBOX_HandleVScroll
1591 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1592 WPARAM wParam, LPARAM lParam )
1594 SCROLLINFO info;
1596 if (descr->style & LBS_MULTICOLUMN) return 0;
1597 switch(LOWORD(wParam))
1599 case SB_LINEUP:
1600 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1601 break;
1602 case SB_LINEDOWN:
1603 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1604 break;
1605 case SB_PAGEUP:
1606 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1607 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1608 break;
1609 case SB_PAGEDOWN:
1610 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1611 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1612 break;
1613 case SB_THUMBPOSITION:
1614 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1615 break;
1616 case SB_THUMBTRACK:
1617 info.cbSize = sizeof(info);
1618 info.fMask = SIF_TRACKPOS;
1619 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1620 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1621 break;
1622 case SB_TOP:
1623 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1624 break;
1625 case SB_BOTTOM:
1626 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1627 break;
1629 return 0;
1633 /***********************************************************************
1634 * LISTBOX_HandleHScroll
1636 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1637 WPARAM wParam, LPARAM lParam )
1639 SCROLLINFO info;
1640 INT page;
1642 if (descr->style & LBS_MULTICOLUMN)
1644 switch(LOWORD(wParam))
1646 case SB_LINELEFT:
1647 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1648 TRUE );
1649 break;
1650 case SB_LINERIGHT:
1651 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1652 TRUE );
1653 break;
1654 case SB_PAGELEFT:
1655 page = descr->width / descr->column_width;
1656 if (page < 1) page = 1;
1657 LISTBOX_SetTopItem( wnd, descr,
1658 descr->top_item - page * descr->page_size, TRUE );
1659 break;
1660 case SB_PAGERIGHT:
1661 page = descr->width / descr->column_width;
1662 if (page < 1) page = 1;
1663 LISTBOX_SetTopItem( wnd, descr,
1664 descr->top_item + page * descr->page_size, TRUE );
1665 break;
1666 case SB_THUMBPOSITION:
1667 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1668 TRUE );
1669 break;
1670 case SB_THUMBTRACK:
1671 info.cbSize = sizeof(info);
1672 info.fMask = SIF_TRACKPOS;
1673 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1674 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1675 TRUE );
1676 break;
1677 case SB_LEFT:
1678 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1679 break;
1680 case SB_RIGHT:
1681 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1682 break;
1685 else if (descr->horz_extent)
1687 switch(LOWORD(wParam))
1689 case SB_LINELEFT:
1690 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1691 break;
1692 case SB_LINERIGHT:
1693 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1694 break;
1695 case SB_PAGELEFT:
1696 LISTBOX_SetHorizontalPos( wnd, descr,
1697 descr->horz_pos - descr->width );
1698 break;
1699 case SB_PAGERIGHT:
1700 LISTBOX_SetHorizontalPos( wnd, descr,
1701 descr->horz_pos + descr->width );
1702 break;
1703 case SB_THUMBPOSITION:
1704 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1705 break;
1706 case SB_THUMBTRACK:
1707 info.cbSize = sizeof(info);
1708 info.fMask = SIF_TRACKPOS;
1709 GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1710 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1711 break;
1712 case SB_LEFT:
1713 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1714 break;
1715 case SB_RIGHT:
1716 LISTBOX_SetHorizontalPos( wnd, descr,
1717 descr->horz_extent - descr->width );
1718 break;
1721 return 0;
1725 /***********************************************************************
1726 * LISTBOX_HandleLButtonDown
1728 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1729 WPARAM wParam, INT x, INT y )
1731 INT index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1732 TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1733 wnd->hwndSelf, x, y, index );
1734 if (!descr->caret_on && (GetFocus() == wnd->hwndSelf)) return 0;
1735 if (index != -1)
1737 if (descr->style & LBS_EXTENDEDSEL)
1739 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1740 if (wParam & MK_CONTROL)
1742 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1743 LISTBOX_SetSelection( wnd, descr, index,
1744 !descr->items[index].selected, FALSE );
1746 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1748 else
1750 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1751 LISTBOX_SetSelection( wnd, descr, index,
1752 (!(descr->style & LBS_MULTIPLESEL) ||
1753 !descr->items[index].selected), FALSE );
1757 if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1758 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1759 : descr->lphc->self->hwndSelf ) ;
1761 descr->captured = TRUE;
1762 SetCapture( wnd->hwndSelf );
1763 if (index != -1 && !descr->lphc)
1765 if (descr->style & LBS_NOTIFY )
1766 SendMessageA( descr->owner, WM_LBTRACKPOINT, index,
1767 MAKELPARAM( x, y ) );
1768 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1770 POINT pt;
1772 pt.x = x;
1773 pt.y = y;
1775 if (DragDetect( wnd->hwndSelf, pt ))
1776 SendMessageA( descr->owner, WM_BEGINDRAG, 0, 0 );
1779 return 0;
1783 /***********************************************************************
1784 * LISTBOX_HandleLButtonUp
1786 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
1788 if (LISTBOX_Timer != LB_TIMER_NONE)
1789 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
1790 LISTBOX_Timer = LB_TIMER_NONE;
1791 if (descr->captured)
1793 descr->captured = FALSE;
1794 if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
1795 if (descr->style & LBS_NOTIFY)
1796 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
1798 return 0;
1802 /***********************************************************************
1803 * LISTBOX_HandleTimer
1805 * Handle scrolling upon a timer event.
1806 * Return TRUE if scrolling should continue.
1808 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
1809 INT index, TIMER_DIRECTION dir )
1811 switch(dir)
1813 case LB_TIMER_UP:
1814 if (descr->top_item) index = descr->top_item - 1;
1815 else index = 0;
1816 break;
1817 case LB_TIMER_LEFT:
1818 if (descr->top_item) index -= descr->page_size;
1819 break;
1820 case LB_TIMER_DOWN:
1821 index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
1822 if (index == descr->focus_item) index++;
1823 if (index >= descr->nb_items) index = descr->nb_items - 1;
1824 break;
1825 case LB_TIMER_RIGHT:
1826 if (index + descr->page_size < descr->nb_items)
1827 index += descr->page_size;
1828 break;
1829 case LB_TIMER_NONE:
1830 break;
1832 if (index == descr->focus_item) return FALSE;
1833 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1834 return TRUE;
1838 /***********************************************************************
1839 * LISTBOX_HandleSystemTimer
1841 * WM_SYSTIMER handler.
1843 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
1845 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
1847 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
1848 LISTBOX_Timer = LB_TIMER_NONE;
1850 return 0;
1854 /***********************************************************************
1855 * LISTBOX_HandleMouseMove
1857 * WM_MOUSEMOVE handler.
1859 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
1860 INT x, INT y )
1862 INT index;
1863 TIMER_DIRECTION dir;
1865 if (!descr->captured) return;
1867 if (descr->style & LBS_MULTICOLUMN)
1869 if (y < 0) y = 0;
1870 else if (y >= descr->item_height * descr->page_size)
1871 y = descr->item_height * descr->page_size - 1;
1873 if (x < 0)
1875 dir = LB_TIMER_LEFT;
1876 x = 0;
1878 else if (x >= descr->width)
1880 dir = LB_TIMER_RIGHT;
1881 x = descr->width - 1;
1883 else dir = LB_TIMER_NONE; /* inside */
1885 else
1887 if (y < 0) dir = LB_TIMER_UP; /* above */
1888 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
1889 else dir = LB_TIMER_NONE; /* inside */
1892 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1893 if (index == -1) index = descr->focus_item;
1894 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
1896 /* Start/stop the system timer */
1898 if (dir != LB_TIMER_NONE)
1899 SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
1900 else if (LISTBOX_Timer != LB_TIMER_NONE)
1901 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
1902 LISTBOX_Timer = dir;
1906 /***********************************************************************
1907 * LISTBOX_HandleKeyDown
1909 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
1911 INT caret = -1;
1912 if (descr->style & LBS_WANTKEYBOARDINPUT)
1914 caret = SendMessageA( descr->owner, WM_VKEYTOITEM,
1915 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
1916 wnd->hwndSelf );
1917 if (caret == -2) return 0;
1919 if (caret == -1) switch(wParam)
1921 case VK_LEFT:
1922 if (descr->style & LBS_MULTICOLUMN)
1924 if (descr->focus_item >= descr->page_size)
1925 caret = descr->focus_item - descr->page_size;
1926 break;
1928 /* fall through */
1929 case VK_UP:
1930 caret = descr->focus_item - 1;
1931 if (caret < 0) caret = 0;
1932 break;
1933 case VK_RIGHT:
1934 if (descr->style & LBS_MULTICOLUMN)
1936 if (descr->focus_item + descr->page_size < descr->nb_items)
1937 caret = descr->focus_item + descr->page_size;
1938 break;
1940 /* fall through */
1941 case VK_DOWN:
1942 caret = descr->focus_item + 1;
1943 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1944 break;
1945 case VK_PRIOR:
1946 if (descr->style & LBS_MULTICOLUMN)
1948 INT page = descr->width / descr->column_width;
1949 if (page < 1) page = 1;
1950 caret = descr->focus_item - (page * descr->page_size) + 1;
1952 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
1953 if (caret < 0) caret = 0;
1954 break;
1955 case VK_NEXT:
1956 if (descr->style & LBS_MULTICOLUMN)
1958 INT page = descr->width / descr->column_width;
1959 if (page < 1) page = 1;
1960 caret = descr->focus_item + (page * descr->page_size) - 1;
1962 else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
1963 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
1964 break;
1965 case VK_HOME:
1966 caret = 0;
1967 break;
1968 case VK_END:
1969 caret = descr->nb_items - 1;
1970 break;
1971 case VK_SPACE:
1972 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
1973 else if (descr->style & LBS_MULTIPLESEL)
1975 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
1976 !descr->items[descr->focus_item].selected,
1977 (descr->style & LBS_NOTIFY) != 0 );
1979 else if (descr->selected_item == -1)
1981 LISTBOX_SetSelection( wnd, descr, descr->focus_item, TRUE,
1982 (descr->style & LBS_NOTIFY) != 0 );
1984 break;
1986 if (caret >= 0)
1988 if ((descr->style & LBS_EXTENDEDSEL) &&
1989 !(GetKeyState( VK_SHIFT ) & 0x8000))
1990 descr->anchor_item = caret;
1991 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
1992 if (descr->style & LBS_NOTIFY)
1994 if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
1996 /* make sure that combo parent doesn't hide us */
1997 descr->lphc->wState |= CBF_NOROLLUP;
1999 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2002 return 0;
2006 /***********************************************************************
2007 * LISTBOX_HandleChar
2009 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
2010 WPARAM wParam )
2012 INT caret = -1;
2013 char str[2];
2015 str[0] = wParam & 0xff;
2016 str[1] = '\0';
2018 if (descr->style & LBS_WANTKEYBOARDINPUT)
2020 caret = SendMessageA( descr->owner, WM_CHARTOITEM,
2021 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2022 wnd->hwndSelf );
2023 if (caret == -2) return 0;
2025 if (caret == -1)
2026 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2027 if (caret != -1)
2029 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2030 if (descr->style & LBS_NOTIFY)
2031 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2033 return 0;
2037 /***********************************************************************
2038 * LISTBOX_Create
2040 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2042 LB_DESCR *descr;
2043 MEASUREITEMSTRUCT mis;
2044 RECT rect;
2046 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2047 return FALSE;
2048 if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2050 HeapFree( GetProcessHeap(), 0, descr );
2051 return FALSE;
2053 GetClientRect( wnd->hwndSelf, &rect );
2054 descr->owner = GetParent( wnd->hwndSelf );
2055 descr->style = wnd->dwStyle;
2056 descr->width = rect.right - rect.left;
2057 descr->height = rect.bottom - rect.top;
2058 descr->items = NULL;
2059 descr->nb_items = 0;
2060 descr->top_item = 0;
2061 descr->selected_item = -1;
2062 descr->focus_item = 0;
2063 descr->anchor_item = -1;
2064 descr->item_height = 1;
2065 descr->page_size = 1;
2066 descr->column_width = 150;
2067 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2068 descr->horz_pos = 0;
2069 descr->nb_tabs = 0;
2070 descr->tabs = NULL;
2071 descr->caret_on = TRUE;
2072 descr->captured = FALSE;
2073 descr->font = 0;
2074 descr->locale = 0; /* FIXME */
2075 descr->lphc = lphc;
2077 if( lphc )
2079 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2080 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2081 descr->owner = lphc->self->hwndSelf;
2084 *(LB_DESCR **)wnd->wExtra = descr;
2086 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2088 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2089 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2090 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2091 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2093 if (descr->style & LBS_OWNERDRAWFIXED)
2095 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2097 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2098 descr->item_height = lphc->fixedOwnerDrawHeight;
2100 else
2102 UINT id = (descr->lphc ) ? ID_CB_LISTBOX : wnd->wIDmenu;
2104 mis.CtlType = ODT_LISTBOX;
2105 mis.CtlID = id;
2106 mis.itemID = -1;
2107 mis.itemWidth = 0;
2108 mis.itemData = 0;
2109 mis.itemHeight = descr->item_height;
2110 SendMessageA( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2111 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2115 return TRUE;
2119 /***********************************************************************
2120 * LISTBOX_Destroy
2122 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2124 LISTBOX_ResetContent( wnd, descr );
2125 HeapDestroy( descr->heap );
2126 HeapFree( GetProcessHeap(), 0, descr );
2127 wnd->wExtra[0] = 0;
2128 return TRUE;
2132 /***********************************************************************
2133 * ListBoxWndProc
2135 static inline LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2136 WPARAM wParam, LPARAM lParam )
2138 LRESULT ret;
2139 LB_DESCR *descr;
2140 HWND hwnd = wnd->hwndSelf;
2142 if (!wnd) return 0;
2143 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2145 if (msg == WM_CREATE)
2147 if (!LISTBOX_Create( wnd, NULL ))
2148 return -1;
2149 TRACE("creating wnd=%04x descr=%p\n",
2150 hwnd, *(LB_DESCR **)wnd->wExtra );
2151 return 0;
2153 /* Ignore all other messages before we get a WM_CREATE */
2154 return DefWindowProcA( hwnd, msg, wParam, lParam );
2157 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2158 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2159 switch(msg)
2161 case LB_RESETCONTENT16:
2162 case LB_RESETCONTENT:
2163 LISTBOX_ResetContent( wnd, descr );
2164 return 0;
2166 case LB_ADDSTRING16:
2167 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2168 /* fall through */
2169 case LB_ADDSTRING:
2170 wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2171 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2173 case LB_INSERTSTRING16:
2174 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2175 wParam = (INT)(INT16)wParam;
2176 /* fall through */
2177 case LB_INSERTSTRING:
2178 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2180 case LB_ADDFILE16:
2181 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2182 /* fall through */
2183 case LB_ADDFILE:
2184 wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2185 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2187 case LB_DELETESTRING16:
2188 case LB_DELETESTRING:
2189 return LISTBOX_RemoveItem( wnd, descr, wParam );
2191 case LB_GETITEMDATA16:
2192 case LB_GETITEMDATA:
2193 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2194 return LB_ERR;
2195 return descr->items[wParam].data;
2197 case LB_SETITEMDATA16:
2198 case LB_SETITEMDATA:
2199 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2200 return LB_ERR;
2201 descr->items[wParam].data = (DWORD)lParam;
2202 return LB_OKAY;
2204 case LB_GETCOUNT16:
2205 case LB_GETCOUNT:
2206 return descr->nb_items;
2208 case LB_GETTEXT16:
2209 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2210 /* fall through */
2211 case LB_GETTEXT:
2212 return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2214 case LB_GETTEXTLEN16:
2215 /* fall through */
2216 case LB_GETTEXTLEN:
2217 if (wParam >= descr->nb_items)
2218 return LB_ERR;
2219 return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2220 : sizeof(DWORD));
2222 case LB_GETCURSEL16:
2223 case LB_GETCURSEL:
2224 if (descr->nb_items==0)
2225 return LB_ERR;
2226 /* else */
2227 if (descr->selected_item!=-1)
2228 return descr->selected_item;
2229 /* else */
2230 return descr->focus_item;
2231 /* otherwise, if the user tries to move the selection with the */
2232 /* arrow keys, we will give the application something to choke on */
2233 case LB_GETTOPINDEX16:
2234 case LB_GETTOPINDEX:
2235 return descr->top_item;
2237 case LB_GETITEMHEIGHT16:
2238 case LB_GETITEMHEIGHT:
2239 return LISTBOX_GetItemHeight( wnd, descr, wParam );
2241 case LB_SETITEMHEIGHT16:
2242 lParam = LOWORD(lParam);
2243 /* fall through */
2244 case LB_SETITEMHEIGHT:
2245 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2247 case LB_ITEMFROMPOINT:
2249 POINT pt;
2250 RECT rect;
2252 pt.x = LOWORD(lParam);
2253 pt.y = HIWORD(lParam);
2254 rect.left = 0;
2255 rect.top = 0;
2256 rect.right = descr->width;
2257 rect.bottom = descr->height;
2259 return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2260 !PtInRect( &rect, pt ) );
2263 case LB_SETCARETINDEX16:
2264 case LB_SETCARETINDEX:
2265 return LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam );
2267 case LB_GETCARETINDEX16:
2268 case LB_GETCARETINDEX:
2269 return descr->focus_item;
2271 case LB_SETTOPINDEX16:
2272 case LB_SETTOPINDEX:
2273 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2275 case LB_SETCOLUMNWIDTH16:
2276 case LB_SETCOLUMNWIDTH:
2277 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2279 case LB_GETITEMRECT16:
2281 RECT rect;
2282 ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2283 CONV_RECT32TO16( &rect, (RECT16 *)PTR_SEG_TO_LIN(lParam) );
2285 return ret;
2287 case LB_GETITEMRECT:
2288 return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT *)lParam );
2290 case LB_FINDSTRING16:
2291 wParam = (INT)(INT16)wParam;
2292 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2293 /* fall through */
2294 case LB_FINDSTRING:
2295 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2297 case LB_FINDSTRINGEXACT16:
2298 wParam = (INT)(INT16)wParam;
2299 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2300 /* fall through */
2301 case LB_FINDSTRINGEXACT:
2302 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2304 case LB_SELECTSTRING16:
2305 wParam = (INT)(INT16)wParam;
2306 if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2307 /* fall through */
2308 case LB_SELECTSTRING:
2310 INT index = LISTBOX_FindString( wnd, descr, wParam,
2311 (LPCSTR)lParam, FALSE );
2312 if (index == LB_ERR)
2313 return LB_ERR;
2314 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2315 return index;
2318 case LB_GETSEL16:
2319 wParam = (INT)(INT16)wParam;
2320 /* fall through */
2321 case LB_GETSEL:
2322 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2323 return LB_ERR;
2324 return descr->items[wParam].selected;
2326 case LB_SETSEL16:
2327 lParam = (INT)(INT16)lParam;
2328 /* fall through */
2329 case LB_SETSEL:
2330 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2332 case LB_SETCURSEL16:
2333 wParam = (INT)(INT16)wParam;
2334 /* fall through */
2335 case LB_SETCURSEL:
2336 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2337 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2339 case LB_GETSELCOUNT16:
2340 case LB_GETSELCOUNT:
2341 return LISTBOX_GetSelCount( wnd, descr );
2343 case LB_GETSELITEMS16:
2344 return LISTBOX_GetSelItems16( wnd, descr, wParam,
2345 (LPINT16)PTR_SEG_TO_LIN(lParam) );
2347 case LB_GETSELITEMS:
2348 return LISTBOX_GetSelItems( wnd, descr, wParam, (LPINT)lParam );
2350 case LB_SELITEMRANGE16:
2351 case LB_SELITEMRANGE:
2352 if (LOWORD(lParam) <= HIWORD(lParam))
2353 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2354 HIWORD(lParam), wParam );
2355 else
2356 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2357 LOWORD(lParam), wParam );
2359 case LB_SELITEMRANGEEX16:
2360 case LB_SELITEMRANGEEX:
2361 if ((INT)lParam >= (INT)wParam)
2362 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2363 else
2364 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2366 case LB_GETHORIZONTALEXTENT16:
2367 case LB_GETHORIZONTALEXTENT:
2368 return descr->horz_extent;
2370 case LB_SETHORIZONTALEXTENT16:
2371 case LB_SETHORIZONTALEXTENT:
2372 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2374 case LB_GETANCHORINDEX16:
2375 case LB_GETANCHORINDEX:
2376 return descr->anchor_item;
2378 case LB_SETANCHORINDEX16:
2379 wParam = (INT)(INT16)wParam;
2380 /* fall through */
2381 case LB_SETANCHORINDEX:
2382 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2383 return LB_ERR;
2384 descr->anchor_item = (INT)wParam;
2385 return LB_OKAY;
2387 case LB_DIR16:
2388 return LISTBOX_Directory( wnd, descr, wParam,
2389 (LPCSTR)PTR_SEG_TO_LIN(lParam), FALSE );
2391 case LB_DIR:
2392 return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2394 case LB_GETLOCALE:
2395 return descr->locale;
2397 case LB_SETLOCALE:
2398 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2399 return LB_OKAY;
2401 case LB_INITSTORAGE:
2402 return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2404 case LB_SETCOUNT:
2405 return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2407 case LB_SETTABSTOPS16:
2408 return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam,
2409 (LPINT)PTR_SEG_TO_LIN(lParam), TRUE );
2411 case LB_SETTABSTOPS:
2412 return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2414 case LB_CARETON16:
2415 case LB_CARETON:
2416 if (descr->caret_on)
2417 return LB_OKAY;
2418 descr->caret_on = TRUE;
2419 if ((descr->focus_item != -1) && (GetFocus() == wnd->hwndSelf))
2420 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2421 return LB_OKAY;
2423 case LB_CARETOFF16:
2424 case LB_CARETOFF:
2425 if (!descr->caret_on)
2426 return LB_OKAY;
2427 descr->caret_on = FALSE;
2428 if ((descr->focus_item != -1) && (GetFocus() == wnd->hwndSelf))
2429 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2430 return LB_OKAY;
2432 case WM_DESTROY:
2433 return LISTBOX_Destroy( wnd, descr );
2435 case WM_ENABLE:
2436 InvalidateRect( hwnd, NULL, TRUE );
2437 return 0;
2439 case WM_SETREDRAW:
2440 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2441 return 0;
2443 case WM_GETDLGCODE:
2444 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2446 case WM_PAINT:
2448 PAINTSTRUCT ps;
2449 HDC hdc = ( wParam ) ? ((HDC)wParam)
2450 : BeginPaint( hwnd, &ps );
2451 ret = LISTBOX_Paint( wnd, descr, hdc );
2452 if( !wParam ) EndPaint( hwnd, &ps );
2454 return ret;
2455 case WM_SIZE:
2456 LISTBOX_UpdateSize( wnd, descr );
2457 return 0;
2458 case WM_GETFONT:
2459 return descr->font;
2460 case WM_SETFONT:
2461 LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2462 if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2463 return 0;
2464 case WM_SETFOCUS:
2465 descr->caret_on = TRUE;
2466 if (descr->focus_item != -1)
2467 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2468 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2469 return 0;
2470 case WM_KILLFOCUS:
2471 if ((descr->focus_item != -1) && descr->caret_on)
2472 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2473 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2474 return 0;
2475 case WM_HSCROLL:
2476 return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2477 case WM_VSCROLL:
2478 return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2479 case WM_LBUTTONDOWN:
2480 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2481 (INT16)LOWORD(lParam),
2482 (INT16)HIWORD(lParam) );
2483 case WM_LBUTTONDBLCLK:
2484 if (descr->style & LBS_NOTIFY)
2485 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2486 return 0;
2487 case WM_MOUSEMOVE:
2488 if (GetCapture() == hwnd)
2489 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2490 (INT16)HIWORD(lParam) );
2491 return 0;
2492 case WM_LBUTTONUP:
2493 return LISTBOX_HandleLButtonUp( wnd, descr );
2494 case WM_KEYDOWN:
2495 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2496 case WM_CHAR:
2497 return LISTBOX_HandleChar( wnd, descr, wParam );
2498 case WM_SYSTIMER:
2499 return LISTBOX_HandleSystemTimer( wnd, descr );
2500 case WM_ERASEBKGND:
2501 if (IS_OWNERDRAW(descr))
2503 RECT rect;
2504 HBRUSH hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
2505 wParam, (LPARAM)wnd->hwndSelf );
2506 GetClientRect(hwnd, &rect);
2507 if (hbrush) FillRect( (HDC)wParam, &rect, hbrush );
2509 return 1;
2510 case WM_DROPFILES:
2511 if( !descr->lphc )
2512 return SendMessageA( descr->owner, msg, wParam, lParam );
2513 break;
2515 case WM_DROPOBJECT:
2516 case WM_QUERYDROPOBJECT:
2517 case WM_DRAGSELECT:
2518 case WM_DRAGMOVE:
2519 if( !descr->lphc )
2521 LPDRAGINFO dragInfo = (LPDRAGINFO)PTR_SEG_TO_LIN( (SEGPTR)lParam );
2522 dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2523 dragInfo->pt.y );
2524 return SendMessageA( descr->owner, msg, wParam, lParam );
2526 break;
2528 case WM_NCCREATE:
2529 if (TWEAK_WineLook > WIN31_LOOK)
2530 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2531 return DefWindowProcA( hwnd, msg, wParam, lParam );
2532 default:
2533 if ((msg >= WM_USER) && (msg < 0xc000))
2534 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2535 hwnd, msg, wParam, lParam );
2536 return DefWindowProcA( hwnd, msg, wParam, lParam );
2538 return 0;
2541 /***********************************************************************
2542 * ListBoxWndProc
2544 * This is just a wrapper for the real wndproc, it only does window locking
2545 * and unlocking.
2547 LRESULT WINAPI ListBoxWndProc( HWND hwnd, UINT msg,
2548 WPARAM wParam, LPARAM lParam )
2550 WND* wndPtr = WIN_FindWndPtr( hwnd );
2551 LRESULT res = ListBoxWndProc_locked(wndPtr,msg,wParam,lParam);
2553 WIN_ReleaseWndPtr(wndPtr);
2554 return res;
2557 /***********************************************************************
2558 * COMBO_Directory
2560 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT attrib, LPSTR dir, BOOL bLong)
2562 WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2564 if( wnd )
2566 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2567 if( descr )
2569 LRESULT lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2571 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
2572 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2573 WIN_ReleaseWndPtr(wnd);
2574 return lRet;
2576 WIN_ReleaseWndPtr(wnd);
2578 return CB_ERR;
2581 /***********************************************************************
2582 * ComboLBWndProc_locked
2584 * The real combo listbox wndproc, but called with locked WND struct.
2586 static inline LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
2587 WPARAM wParam, LPARAM lParam )
2589 LRESULT lRet = 0;
2590 HWND hwnd = wnd->hwndSelf;
2592 if (wnd)
2594 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2596 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
2597 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2599 if( descr || msg == WM_CREATE )
2601 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2603 switch( msg )
2605 case WM_CREATE:
2606 #define lpcs ((LPCREATESTRUCTA)lParam)
2607 TRACE_(combo)("\tpassed parent handle = 0x%08x\n",
2608 (UINT)lpcs->lpCreateParams);
2610 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2611 #undef lpcs
2612 return LISTBOX_Create( wnd, lphc );
2613 case WM_LBUTTONDOWN:
2614 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2615 (INT16)LOWORD(lParam), (INT16)HIWORD(lParam));
2616 /* avoid activation at all costs */
2618 case WM_MOUSEACTIVATE:
2619 return MA_NOACTIVATE;
2620 case WM_NCACTIVATE:
2621 return FALSE;
2622 case WM_KEYDOWN:
2623 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2625 /* for some reason(?) Windows makes it possible to
2626 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2628 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2629 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2630 && (wParam == VK_DOWN || wParam == VK_UP)) )
2632 COMBO_FlipListbox( lphc, FALSE );
2633 return 0;
2636 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2638 case LB_SETCURSEL16:
2639 case LB_SETCURSEL:
2640 lRet = ListBoxWndProc( hwnd, msg, wParam, lParam );
2641 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
2642 return lRet;
2643 case WM_NCDESTROY:
2644 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2645 lphc->hWndLBox = 0;
2646 /* fall through */
2648 default:
2649 return ListBoxWndProc( hwnd, msg, wParam, lParam );
2652 lRet = DefWindowProcA( hwnd, msg, wParam, lParam );
2654 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
2656 return lRet;
2659 /***********************************************************************
2660 * ComboLBWndProc
2662 * NOTE: in Windows, winproc address of the ComboLBox is the same
2663 * as that of the Listbox.
2665 * This is just a wrapper for the real wndproc, it only does window locking
2666 * and unlocking.
2668 LRESULT WINAPI ComboLBWndProc( HWND hwnd, UINT msg,
2669 WPARAM wParam, LPARAM lParam )
2671 WND *wnd = WIN_FindWndPtr( hwnd );
2672 LRESULT res = ComboLBWndProc_locked(wnd,msg,wParam,lParam);
2674 WIN_ReleaseWndPtr(wnd);
2675 return res;