[PATCH 2/2] comctl32/listbox: Implement LBS_NODATA

Gabriel Ivăncescu gabrielopcode at gmail.com
Mon Nov 19 07:42:36 CST 2018


The LBS_NODATA style's purpose is to drastically improve performance and
memory usage on very large lists, since they should function as virtual
lists. We don't store any data for single-selection listboxes, while for
multi-selection listboxes, only 1 bit per item is stored, which is very
significant for performance on such large lists as this allows us to process
32 (or 64) items at a time (so it's not just useful for lower memory usage).

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=32374
Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

I'm aware that the patch is large, but it is hard to split it up more
than this without causing breakage somewhere, and it should be implemented
properly for the purpose of the style (not really to add any extra features,
but just massive performance & memory improvements). So no new tests are
needed, but it should pass all existing tests, obviously.

Actually it is a bit larger than it seems. For example, most of the changes
in InsertItem and RemoveItem are just due to indentation changes, the actual
changes are small there (just adding LBS_NODATA case).

Note that every detail about the LBS_NODATA internal storage is kept within
the NODATA helper functions so it doesn't leak outside. I did it this way
to encapsulate the implementation details so that the rest of the code
doesn't have to concern itself with how LBS_NODATA is actually stored,
the only rule is to never dereference descr->items since there's no such
normal data available (NULL for single-selection in fact). This means fewer
changes outside of LBS_NODATA helpers and should be easier for maintenance.

I tried hard to avoid changing much of the previous code (i.e. outside of
the new helper functions) and I couldn't really split this patch more than
this without breaking something in the process.

 dlls/comctl32/listbox.c | 585 +++++++++++++++++++++++++++++++++-------
 1 file changed, 491 insertions(+), 94 deletions(-)

diff --git a/dlls/comctl32/listbox.c b/dlls/comctl32/listbox.c
index dbc7b4a..cb531ab 100644
--- a/dlls/comctl32/listbox.c
+++ b/dlls/comctl32/listbox.c
@@ -18,14 +18,13 @@
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * TODO:
- *    - LBS_NODATA
  */
 
 #include <string.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <limits.h>
 #include "windef.h"
 #include "winbase.h"
 #include "wingdi.h"
@@ -70,7 +69,7 @@ typedef struct
     UINT        style;          /* Window style */
     INT         width;          /* Window width */
     INT         height;         /* Window height */
-    LB_ITEMDATA  *items;        /* Array of items */
+    LB_ITEMDATA  *items;        /* Array of items; DO NOT dereference if LBS_NODATA */
     INT         nb_items;       /* Number of items */
     INT         top_item;       /* Top visible item */
     INT         selected_item;  /* Selected item */
@@ -125,6 +124,326 @@ static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
 
 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
 
+/***********************************************************************
+ *           Helper functions for LBS_NODATA listboxes
+ *
+ * Since LBS_NODATA listboxes can be extremely large, we need to store
+ * them with minimal overhead, both for performance and memory usage.
+ *
+ * In all cases, it is important to note that descr->items must never be
+ * dereferenced directly with LBS_NODATA, outside of these helpers.
+ *
+ * For single-selection listboxes, we store literally no data for items,
+ * and thus descr->items will always be NULL in this case.
+ *
+ * For multi-selection listboxes, we store an array of bits, 1 bit for
+ * each item, to store the selection state of each item.
+ *
+ * Operations through the array will be done in chunks for efficiency.
+ * There's no need to worry about reading past the end of the array
+ * when reading an entire chunk, because our allocation granularity
+ * and the alignment of the heap itself is more than enough below.
+ *
+ */
+static unsigned int NODATA_ctz(ULONG_PTR x)
+{
+#ifdef HAVE___BUILTIN_CTZ
+    return (sizeof(x) <= sizeof(int))  ? __builtin_ctz(x)  :
+           (sizeof(x) <= sizeof(long)) ? __builtin_ctzl(x) :
+                                         __builtin_ctzll(x);
+#else
+    static const BYTE ctz[] =
+    {
+           0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
+    };
+    unsigned int res;
+    if (x == 0) return ~0;
+    for (res = 0; !(x & 0xff); x >>= 8) res += 8;
+    return res + ctz[(x & 0xff) - 1];
+#endif
+}
+
+/* Granularity in bytes of the multi-selection bit array; must be a power of 2 */
+enum { NODATA_granularity = 4 * sizeof(void*) };
+
+static BOOL NODATA_insert_item(LB_DESCR *descr, UINT index)
+{
+    /* We have to shift the bit array by 1 to the left after the index */
+    typedef ULONG_PTR chunkT;
+    chunkT bit, mask, *p, *last;
+    UINT orig_num = descr->nb_items;
+
+    if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
+        return TRUE;
+    p = (chunkT*)descr->items;
+
+    /* See if the insertion will span a new byte boundary */
+    if (orig_num % CHAR_BIT == 0)
+    {
+        UINT sz = orig_num / CHAR_BIT + 1;
+        if (!p || sz > HeapSize(GetProcessHeap(), 0, p))
+        {
+            sz = (sz + NODATA_granularity - 1) & ~(NODATA_granularity - 1);
+            if (!p) p = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sz);
+            else  p = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, p, sz);
+            if (!p) return FALSE;
+            descr->items = (void*)p;
+        }
+    }
+
+    last = p + orig_num / (sizeof(chunkT) * CHAR_BIT);
+
+    /* Mask of bits before the index, but on the same chunk */
+    mask = ( (chunkT)1 << index % (sizeof(chunkT) * CHAR_BIT) ) - 1;
+
+    /* The position of the chunk where the index is located */
+    p += index / (sizeof(chunkT) * CHAR_BIT);
+
+    /* The top bit that will be shifted out */
+    bit = *p >> (sizeof(chunkT) * CHAR_BIT - 1);
+
+    *p = (*p & mask) | ((*p & ~mask) << 1);
+    while (p < last)
+    {
+        chunkT cur = *(++p);
+        *p = *p << 1 | bit;
+        bit = cur >> (sizeof(chunkT) * CHAR_BIT - 1);
+    }
+    return TRUE;
+}
+
+static void NODATA_remove_item(LB_DESCR *descr, UINT index)
+{
+    /* We have to shift the bit array by 1 to the right after the index
+       NOTE: descr->nb_items was already decremented by this point */
+    typedef ULONG_PTR chunkT;
+    chunkT chunk, mask, *p, *last;
+    UINT num = descr->nb_items;
+
+    if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
+        return;
+    p = (chunkT*)descr->items;
+
+    last = p + num / (sizeof(chunkT) * CHAR_BIT);
+
+    /* Mask of bits before the index, but on the same chunk */
+    mask = ( (chunkT)1 << index % (sizeof(chunkT) * CHAR_BIT) ) - 1;
+
+    /* The position of the chunk where the index is located */
+    p += index / (sizeof(chunkT) * CHAR_BIT);
+
+    chunk = (*p & mask) | ((*p >> 1) & ~mask);
+    for (; p < last; p++)
+    {
+        *p = chunk | (p[1] << (sizeof(chunkT) * CHAR_BIT - 1));
+        chunk = p[1] >> 1;
+    }
+
+    /* Store the last chunk as there's no next chunk anymore */
+    *p = chunk;
+
+    /* Shrink the array if needed */
+    if (num % CHAR_BIT == 0)
+    {
+        UINT sz = num / CHAR_BIT;
+        p = (chunkT*)descr->items;
+
+        if (sz + NODATA_granularity * 2 < HeapSize(GetProcessHeap(), 0, p))
+        {
+            sz = (sz + NODATA_granularity - 1) & ~(NODATA_granularity - 1);
+            if ((p = HeapReAlloc(GetProcessHeap(), 0, p, sz)))
+                descr->items = (void*)p;
+        }
+    }
+}
+
+static BOOL NODATA_multisel_is_sel(const LB_DESCR *descr, UINT index)
+{
+    BYTE *p = (BYTE*)descr->items;
+    return (p[index / CHAR_BIT] & (1 << index % CHAR_BIT)) != 0;
+}
+
+static BOOL NODATA_is_sel(const LB_DESCR *descr, UINT index)
+{
+    if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
+        return NODATA_multisel_is_sel(descr, index);
+    return index == descr->selected_item;
+}
+
+static BOOL NODATA_multisel_expand(LB_DESCR *descr, UINT num)
+{
+    void *p = descr->items;
+    UINT sz = (num + CHAR_BIT - 1) / CHAR_BIT;
+
+    if (!p || sz > HeapSize(GetProcessHeap(), 0, p))
+    {
+        sz = (sz + NODATA_granularity - 1) & ~(NODATA_granularity - 1);
+        if (!p) p = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sz);
+        else  p = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, p, sz);
+        if (!p) return FALSE;
+        descr->items = p;
+    }
+    return TRUE;
+}
+
+static void NODATA_multisel_shrink(LB_DESCR *descr, UINT orig_num)
+{
+    BYTE *p = (BYTE*)descr->items;
+    UINT num = descr->nb_items;
+    UINT sz = (num + CHAR_BIT - 1) / CHAR_BIT;
+    UINT orig_sz = (orig_num + CHAR_BIT - 1) / CHAR_BIT;
+
+    /* Mask out trailing bits beyond the new end index (if any) */
+    p[sz - 1] &= ((num % CHAR_BIT != 0) << num % CHAR_BIT) - 1;
+
+    /* Shrink the array if needed */
+    if (sz + NODATA_granularity * 2 < HeapSize(GetProcessHeap(), 0, p))
+    {
+        UINT rnd_sz = (sz + NODATA_granularity - 1) & ~(NODATA_granularity - 1);
+        if ((p = HeapReAlloc(GetProcessHeap(), 0, p, rnd_sz)))
+        {
+            descr->items = (void*)p;
+            orig_sz = rnd_sz;
+        }
+    }
+    memset(&p[sz], 0, orig_sz - sz);
+}
+
+static LRESULT NODATA_init_storage(LB_DESCR *descr, UINT num)
+{
+    if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
+    {
+        if (!NODATA_multisel_expand(descr, descr->nb_items + num))
+        {
+            SEND_NOTIFICATION(descr, LBN_ERRSPACE);
+            return LB_ERRSPACE;
+        }
+    }
+    return LB_OKAY;
+}
+
+static UINT NODATA_multisel_count(const LB_DESCR *descr)
+{
+    /* Use population count on the entire bit array to count selections */
+    typedef ULONG_PTR chunkT;
+    chunkT *end, *p = (chunkT*)descr->items;
+    UINT count = 0;
+
+    end = p + (descr->nb_items + sizeof(chunkT) * CHAR_BIT - 1) / (sizeof(chunkT) * CHAR_BIT);
+    while (p < end)
+    {
+        chunkT x = *p++;
+
+#ifdef HAVE___BUILTIN_POPCOUNT
+        count += (sizeof(x) <= sizeof(int))  ? __builtin_popcount(x)  :
+                 (sizeof(x) <= sizeof(long)) ? __builtin_popcountl(x) :
+                                               __builtin_popcountll(x);
+#else
+        x -= x >> 1 & (chunkT)0x5555555555555555;
+        x = (x & (chunkT)0x3333333333333333) + (x >> 2 & (chunkT)0x3333333333333333);
+        x = (x + (x >> 4)) & (chunkT)0x0f0f0f0f0f0f0f0f;
+        count += x * (chunkT)0x0101010101010101 >> (sizeof(x) - 1) * CHAR_BIT;
+#endif
+    }
+    return count;
+}
+
+static UINT NODATA_multisel_items(const LB_DESCR *descr, INT *array, INT max)
+{
+    /* For each chunk find the lowest set bit and clear it in a loop */
+#ifdef HAVE___BUILTIN_CTZ
+    typedef ULONG_PTR chunkT;
+#else
+    typedef BYTE chunkT;
+#endif
+    chunkT *end, *p = (chunkT*)descr->items;
+    UINT count = 0, i = 0;
+    if (max <= 0) return 0;
+
+    end = p + (descr->nb_items + sizeof(chunkT) * CHAR_BIT - 1) / (sizeof(chunkT) * CHAR_BIT);
+    while (p < end)
+    {
+        chunkT x = *p++;
+        while (x)
+        {
+            array[count++] = i + NODATA_ctz(x);
+            if (count == max) return count;
+
+            /* Clear the lowest set bit */
+            x &= x - 1;
+        }
+        i += sizeof(chunkT) * CHAR_BIT;
+    }
+    return count;
+}
+
+static void NODATA_multisel_range(LB_DESCR *descr, UINT first, UINT last, BOOL select)
+{
+    /* (De)select a range of items, invalidating changed ones if needed */
+    typedef ULONG_PTR chunkT;
+    chunkT chunk, fill, mask, *p = (chunkT*)descr->items;
+    UINT last_chunk_item = last - last % (sizeof(chunkT) * CHAR_BIT);
+    UINT last_visible, cur = first - first % (sizeof(chunkT) * CHAR_BIT);
+
+    /* Calculate the last possibly visible item (top_item is always the first visible) */
+    last_visible = descr->top_item;
+    last_visible += (descr->style & LBS_MULTICOLUMN)
+                    ? (descr->width + descr->column_width - 1) / descr->column_width * descr->page_size
+                    : (descr->height + descr->item_height - 1) / descr->item_height;
+
+    /* Mask of bits for the first chunk */
+    mask = ~0 << first % (sizeof(chunkT) * CHAR_BIT);
+
+    p += first / (sizeof(chunkT) * CHAR_BIT);
+    for (;;)
+    {
+        /* Remove the mask's bits after last if we're at the last chunk */
+        if (cur == last_chunk_item)
+            mask &= ~( (chunkT)~1 << last % (sizeof(chunkT) * CHAR_BIT) );
+
+        chunk = select ? (*p | mask) : (*p & ~mask);
+        fill = select ? ~0 : 0;
+        do
+        {
+            chunkT orig = *p;
+            *p++ = chunk;
+
+            /* If there are visible items in the chunk, we might have to invalidate them */
+            if (cur <= last_visible && cur + sizeof(chunkT) * CHAR_BIT >= descr->top_item)
+            {
+                /* Get the bits that were changed and invalidate their items */
+                chunkT x = orig ^ chunk;
+                while (x)
+                {
+                    RECT rect;
+                    if (LISTBOX_GetItemRect(descr, cur + NODATA_ctz(x), &rect) == 1)
+                        InvalidateRect(descr->self, &rect, TRUE);
+
+                    /* Clear the lowest set bit */
+                    x &= x - 1;
+                }
+            }
+            chunk = fill;
+            cur += sizeof(chunkT) * CHAR_BIT;
+        } while (cur < last_chunk_item);
+
+        /* If we're past the last chunk then we're done */
+        if (cur != last_chunk_item) break;
+
+        /* Do one more iteration for the last chunk with its mask */
+        mask = ~0;
+    }
+}
+
+
+
 /***********************************************************************
  *           LISTBOX_GetCurrentPageSize
  *
@@ -494,7 +813,7 @@ static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
         RECT r;
         HRGN hrgn;
 
-	if (!item)
+	if (index >= descr->nb_items)
 	{
 	    if (action == ODA_FOCUS)
 		DrawFocusRect( hdc, rect );
@@ -517,16 +836,19 @@ static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
         dis.hDC          = hdc;
         dis.itemID       = index;
         dis.itemState    = 0;
-        if (item->selected) dis.itemState |= ODS_SELECTED;
+        if ( ( (descr->style & LBS_NODATA) && NODATA_is_sel(descr, index)) ||
+             (!(descr->style & LBS_NODATA) && item->selected) )
+            dis.itemState |= ODS_SELECTED;
+
         if (!ignoreFocus && (descr->focus_item == index) &&
             (descr->caret_on) &&
             (descr->in_focus)) dis.itemState |= ODS_FOCUS;
         if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
-        dis.itemData     = item->data;
+        dis.itemData     = (descr->style & LBS_NODATA) ? 0 : item->data;
         dis.rcItem       = *rect;
         TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
-              descr->self, index, debugstr_w(item->str), action,
-              dis.itemState, wine_dbgstr_rect(rect) );
+              descr->self, index, debugstr_w((descr->style & LBS_NODATA) ? NULL : item->str),
+              action, dis.itemState, wine_dbgstr_rect(rect) );
         SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
         SelectClipRgn( hdc, hrgn );
         if (hrgn) DeleteObject( hrgn );
@@ -673,6 +995,9 @@ static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
 {
     LB_ITEMDATA *item;
 
+    if (descr->style & LBS_NODATA)
+        return NODATA_init_storage(descr, nb_items);
+
     nb_items += LB_ARRAY_GRANULARITY - 1;
     nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
     if (descr->items) {
@@ -946,6 +1271,10 @@ static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
     if (!(descr->style & LBS_MULTIPLESEL) ||
         (descr->style & LBS_NOSEL))
       return LB_ERR;
+
+    if (descr->style & LBS_NODATA)
+        return NODATA_multisel_count(descr);
+
     for (i = count = 0; i < descr->nb_items; i++, item++)
         if (item->selected) count++;
     return count;
@@ -961,6 +1290,9 @@ static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array
     const LB_ITEMDATA *item = descr->items;
 
     if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
+    if (descr->style & LBS_NODATA)
+        return NODATA_multisel_items(descr, array, max);
+
     for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
         if (item->selected) array[count++] = i;
     return count;
@@ -1393,6 +1725,12 @@ static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
     if (first < 0) first = 0;
     if (last < first) return LB_OKAY;
 
+    if (descr->style & LBS_NODATA)
+    {
+        NODATA_multisel_range(descr, first, last, on);
+        return LB_OKAY;
+    }
+
     if (on)  /* Turn selection on */
     {
         for (i = first; i <= last; i++)
@@ -1440,10 +1778,13 @@ static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
     {
         INT oldsel = descr->selected_item;
         if (index == oldsel) return LB_OKAY;
-        if (oldsel != -1) descr->items[oldsel].selected = FALSE;
-        if (index != -1) descr->items[index].selected = TRUE;
-        if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
+        if (!(descr->style & LBS_NODATA))
+        {
+            if (oldsel != -1) descr->items[oldsel].selected = FALSE;
+            if (index != -1) descr->items[index].selected = TRUE;
+        }
         descr->selected_item = index;
+        if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
         if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
         if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
                                (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
@@ -1516,54 +1857,64 @@ static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
 
     if (index == -1) index = descr->nb_items;
     else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
-    if (!descr->items) max_items = 0;
-    else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
-    if (descr->nb_items == max_items)
-    {
-        /* We need to grow the array */
-        max_items += LB_ARRAY_GRANULARITY;
-	if (descr->items)
-	    item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
-                                  max_items * sizeof(LB_ITEMDATA) );
-	else
-	    item = HeapAlloc( GetProcessHeap(), 0,
-                                  max_items * sizeof(LB_ITEMDATA) );
-        if (!item)
+    if (descr->style & LBS_NODATA)
+    {
+        if (!NODATA_insert_item(descr, index))
         {
             SEND_NOTIFICATION( descr, LBN_ERRSPACE );
             return LB_ERRSPACE;
         }
-        descr->items = item;
+        descr->nb_items++;
     }
+    else
+    {
+        if (!descr->items) max_items = 0;
+        else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
+        if (descr->nb_items == max_items)
+        {
+            /* We need to grow the array */
+            max_items += LB_ARRAY_GRANULARITY;
+            if (descr->items)
+                item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
+                                      max_items * sizeof(LB_ITEMDATA) );
+            else
+                item = HeapAlloc( GetProcessHeap(), 0,
+                                      max_items * sizeof(LB_ITEMDATA) );
+            if (!item)
+            {
+                SEND_NOTIFICATION( descr, LBN_ERRSPACE );
+                return LB_ERRSPACE;
+            }
+            descr->items = item;
+        }
 
-    /* Insert the item structure */
-
-    item = &descr->items[index];
-    if (index < descr->nb_items)
-        RtlMoveMemory( item + 1, item,
-                       (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
-    item->str      = str;
-    item->data     = HAS_STRINGS(descr) ? 0 : data;
-    item->height   = 0;
-    item->selected = FALSE;
-    descr->nb_items++;
-
-    /* Get item height */
+        /* Insert the item structure */
+        item = &descr->items[index];
+        if (index < descr->nb_items)
+            RtlMoveMemory( item + 1, item,
+                           (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
+        item->str      = str;
+        item->data     = HAS_STRINGS(descr) ? 0 : data;
+        item->height   = 0;
+        item->selected = FALSE;
+        descr->nb_items++;
 
-    if (descr->style & LBS_OWNERDRAWVARIABLE)
-    {
-        MEASUREITEMSTRUCT mis;
-        UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
+        /* Get item height */
+        if (descr->style & LBS_OWNERDRAWVARIABLE)
+        {
+            MEASUREITEMSTRUCT mis;
+            UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
 
-        mis.CtlType    = ODT_LISTBOX;
-        mis.CtlID      = id;
-        mis.itemID     = index;
-        mis.itemData   = data;
-        mis.itemHeight = descr->item_height;
-        SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
-        item->height = mis.itemHeight ? mis.itemHeight : 1;
-        TRACE("[%p]: measure item %d (%s) = %d\n",
-              descr->self, index, str ? debugstr_w(str) : "", item->height );
+            mis.CtlType    = ODT_LISTBOX;
+            mis.CtlID      = id;
+            mis.itemID     = index;
+            mis.itemData   = data;
+            mis.itemHeight = descr->item_height;
+            SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
+            item->height = mis.itemHeight ? mis.itemHeight : 1;
+            TRACE("[%p]: measure item %d (%s) = %d\n",
+                  descr->self, index, str ? debugstr_w(str) : "", item->height );
+        }
     }
 
     /* Repaint the items */
@@ -1678,28 +2029,39 @@ static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
     LISTBOX_InvalidateItems( descr, index );
 
     descr->nb_items--;
-    LISTBOX_DeleteItem( descr, index );
-
-    if (!descr->nb_items) return LB_OKAY;
-
-    /* Remove the item */
+    if (descr->style & LBS_NODATA)
+    {
+        if (!descr->nb_items)
+        {
+            SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
+            return LB_OKAY;
+        }
+        NODATA_remove_item(descr, index);
+    }
+    else
+    {
+        LISTBOX_DeleteItem( descr, index );
 
-    item = &descr->items[index];
-    if (index < descr->nb_items)
-        RtlMoveMemory( item, item + 1,
-                       (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
-    if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
+        if (!descr->nb_items) return LB_OKAY;
 
-    /* Shrink the item array if possible */
+        /* Remove the item */
+        item = &descr->items[index];
+        if (index < descr->nb_items)
+            RtlMoveMemory( item, item + 1,
+                           (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
 
-    max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
-    if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
-    {
-        max_items -= LB_ARRAY_GRANULARITY;
-        item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
-                            max_items * sizeof(LB_ITEMDATA) );
-        if (item) descr->items = item;
+        /* Shrink the item array if possible */
+        max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
+        if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
+        {
+            max_items -= LB_ARRAY_GRANULARITY;
+            item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
+                                max_items * sizeof(LB_ITEMDATA) );
+            if (item) descr->items = item;
+        }
     }
+    descr->anchor_item = min(descr->anchor_item, descr->nb_items - 1);
+
     /* Repaint the items */
 
     LISTBOX_UpdateScroll( descr );
@@ -1737,7 +2099,8 @@ static void LISTBOX_ResetContent( LB_DESCR *descr )
 {
     INT i;
 
-    for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
+    if (!(descr->style & LBS_NODATA))
+        for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
     HeapFree( GetProcessHeap(), 0, descr->items );
     descr->nb_items      = 0;
     descr->top_item      = 0;
@@ -1753,22 +2116,52 @@ static void LISTBOX_ResetContent( LB_DESCR *descr )
  */
 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
 {
-    LRESULT ret;
+    INT orig_num;
 
     if (!(descr->style & LBS_NODATA)) return LB_ERR;
+    count = max(count, 0);
 
-    /* FIXME: this is far from optimal... */
     if (count > descr->nb_items)
     {
-        while (count > descr->nb_items)
-            if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
-                return ret;
+        if ((descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)) &&
+            !NODATA_multisel_expand(descr, count))
+        {
+            SEND_NOTIFICATION(descr, LBN_ERRSPACE);
+            return LB_ERRSPACE;
+        }
+        orig_num = descr->nb_items;
+        descr->nb_items = count;
+
+        LISTBOX_UpdateScroll(descr);
+        LISTBOX_InvalidateItems(descr, orig_num);
+
+        /* If listbox was empty, set focus to the first item */
+        if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
     }
     else if (count < descr->nb_items)
     {
-        while (count < descr->nb_items)
-            if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
-                return ret;
+        LISTBOX_InvalidateItems(descr, count);
+        orig_num = descr->nb_items;
+        descr->nb_items = count;
+
+        if (count == 0) SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
+        else
+        {
+            descr->anchor_item = min(descr->anchor_item, count - 1);
+
+            if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
+                NODATA_multisel_shrink(descr, orig_num);
+            else if (descr->selected_item >= descr->nb_items)
+                descr->selected_item = -1;
+
+            LISTBOX_UpdateScroll(descr);
+
+            /* If we removed the scrollbar, reset the top of the list */
+            if (descr->nb_items <= descr->page_size && orig_num > descr->page_size)
+                LISTBOX_SetTopItem(descr, 0, TRUE);
+
+            descr->focus_item = min(descr->focus_item, descr->nb_items - 1);
+        }
     }
 
     InvalidateRect( descr->self, NULL, TRUE );
@@ -2053,31 +2446,30 @@ static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, IN
            if (!(keys & (MK_SHIFT|MK_CONTROL)))
            LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
         */
+        BOOL selected;
 
         if (!(keys & MK_SHIFT)) descr->anchor_item = index;
         if (keys & MK_CONTROL)
         {
             LISTBOX_SetCaretIndex( descr, index, FALSE );
-            LISTBOX_SetSelection( descr, index,
-                                  !descr->items[index].selected,
+            selected = (descr->style & LBS_NODATA)
+                       ? NODATA_multisel_is_sel(descr, index)
+                       : descr->items[index].selected;
+
+            LISTBOX_SetSelection( descr, index, !selected,
                                   (descr->style & LBS_NOTIFY) != 0);
         }
         else
         {
             LISTBOX_MoveCaret( descr, index, FALSE );
+            selected = (descr->style & LBS_NODATA)
+                       ? NODATA_multisel_is_sel(descr, index)
+                       : descr->items[index].selected;
 
-            if (descr->style & LBS_EXTENDEDSEL)
-            {
-                LISTBOX_SetSelection( descr, index,
-                               descr->items[index].selected,
-                              (descr->style & LBS_NOTIFY) != 0 );
-            }
-            else
-            {
-                LISTBOX_SetSelection( descr, index,
-                               !descr->items[index].selected,
-                              (descr->style & LBS_NOTIFY) != 0 );
-            }
+            if (!(descr->style & LBS_EXTENDEDSEL))
+                selected = !selected;
+            LISTBOX_SetSelection( descr, index, selected,
+                                  (descr->style & LBS_NOTIFY) != 0 );
         }
     }
     else
@@ -2394,8 +2786,11 @@ static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
         if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
         else if (descr->style & LBS_MULTIPLESEL)
         {
-            LISTBOX_SetSelection( descr, descr->focus_item,
-                                  !descr->items[descr->focus_item].selected,
+            BOOL selected = (descr->style & LBS_NODATA)
+                            ? NODATA_multisel_is_sel(descr, descr->focus_item)
+                            : descr->items[descr->focus_item].selected;
+
+            LISTBOX_SetSelection( descr, descr->focus_item, !selected,
                                   (descr->style & LBS_NOTIFY) != 0 );
         }
         break;
@@ -2763,6 +3158,8 @@ static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam,
     case LB_GETSEL:
         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
             return LB_ERR;
+        if (descr->style & LBS_NODATA)
+            return NODATA_is_sel(descr, wParam);
         return descr->items[wParam].selected;
 
     case LB_SETSEL:
-- 
2.19.1




More information about the wine-devel mailing list