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