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