XListBox.cpp
上传用户:hmc_gdtv
上传日期:2013-08-04
资源大小:798k
文件大小:25k
源码类别:

Windows Mobile

开发平台:

Visual C++

  1. /*
  2.  * Copyright (c) 2001,2002,2003 Mike Matsnev.  All Rights Reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice immediately at the beginning of the file, without modification,
  10.  *    this list of conditions, and the following disclaimer.
  11.  * 2. Redistributions in binary form must reproduce the above copyright
  12.  *    notice, this list of conditions and the following disclaimer in the
  13.  *    documentation and/or other materials provided with the distribution.
  14.  * 3. Absolutely no warranty of function or purpose is made by the author
  15.  *    Mike Matsnev.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  18.  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  19.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  20.  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  21.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  22.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  * 
  28.  * $Id: XListBox.cpp,v 1.1.2.33 2004/10/21 14:53:27 mike Exp $
  29.  * 
  30.  */
  31. #include <afxwin.h>
  32. #include <afxcmn.h>
  33. #include "resource.h"
  34. #include "config.h"
  35. #include "XListBox.h"
  36. #define DEFAULT_FONT_SIZE     11
  37. #define UICON_PAD       1
  38. #define COLOR0       RGB(0,0,0)
  39. #define COLOR1       RGB(255,255,255)
  40. #define COLOR2       RGB(251,242,233)
  41. #define COLOR3       RGB(128,128,128)
  42. #define COLOR4       RGB(255,255,255)
  43. #define COLOR5       RGB(16,32,255)
  44. #define INDENT_PER_LEVEL      5
  45. // string compare
  46. #define CmpI(s1,s2) 
  47.     (::CompareString(LOCALE_USER_DEFAULT,NORM_IGNORECASE, 
  48.     (s1),-1,(s2),-1)-2)
  49. struct XLBItem {
  50.   TCHAR   *text1;
  51.   TCHAR   *text2;
  52.   int   icon_index;
  53.   int   level;
  54.   int   flags;
  55.   LONG    user_data;
  56. };
  57. #define XIF_COLLAPSED 1
  58. struct XLB {
  59.   HWND   hWnd;
  60.   int   num_items;
  61.   int   max_items;
  62.   XLBItem   *items;
  63.   int   visible_items;
  64.   XLBItem   **vitems;
  65.   int   selection; // selection is I
  66.   int   top_offset;
  67.   int   scroll_page;
  68.   int   item_height;
  69.   int   line_height;
  70.   HIMAGELIST   icons;
  71.   bool   icons_shared;
  72.   HFONT   font;
  73.   int   dots_width;
  74.   HIMAGELIST   tree_icons;
  75.   int   flags;
  76.   XLB_GetText   gtf;
  77.   void   *gtdata;
  78.   int   uicon_w;
  79.   int   ticon_w;
  80. };
  81. #define XLF_TREE  1
  82. static bool   XLB_GrowList(XLB *x) {
  83.   int incr=x->num_items;
  84.   if (incr<16)
  85.     incr=16;
  86.   if (incr>4096)
  87.     incr=4096;
  88.   int newsize=x->num_items+incr;
  89.   XLBItem   *newitems=(XLBItem*)realloc(x->items,newsize*sizeof(XLBItem));
  90.   if (newitems==NULL)
  91.     return false;
  92.   x->items=newitems;
  93.   XLBItem   **newvitems=(XLBItem**)realloc(x->vitems,newsize*sizeof(XLBItem*));
  94.   if (newvitems==NULL)
  95.     return false;
  96.   x->vitems=newvitems;
  97.   x->max_items=newsize;
  98.   return true;
  99. }
  100. static void  XLB_UpdateVisibleItems(XLB *x) {
  101.   XLBItem *ii=x->items;
  102.   XLBItem *jj=ii+x->num_items;
  103.   XLBItem **kk=x->vitems;
  104.   int   level = 0;
  105.   int   flags = 0;
  106.   while (ii<jj) {
  107.     if (ii->level > level)
  108.       flags=XLF_TREE;
  109.     level = ii->level;
  110.     *kk++=ii;
  111.     if (ii++->flags & XIF_COLLAPSED) {
  112.       // skip all following children
  113.       while (ii<jj && ii->level>level) {
  114. ++ii;
  115. flags=XLF_TREE;
  116.       }
  117.     }
  118.   }
  119.   x->visible_items = kk - x->vitems;
  120.   x->flags |= flags;
  121. }
  122. static int  XLB_GetIFromV(XLB *x,int v) {
  123.   return x->vitems[v] - x->items;
  124. }
  125. static int  XLB_GetVFromI(XLB *x,int i) {
  126.   int     top=x->visible_items-1;
  127.   int     bottom=0;
  128.   XLBItem   *ii=&x->items[i];
  129.   XLBItem   **kk=x->vitems;
  130.   while (bottom<=top) {
  131.     int   middle=(bottom+top)>>1;
  132.     if (kk[middle]==ii)
  133.       return middle;
  134.     if (kk[middle]>ii)
  135.       top=middle-1;
  136.     else
  137.       bottom=middle+1;
  138.   }
  139.   return -1;
  140. }
  141. static int  XLB_GetParent(XLB *x,int i) {
  142.   XLBItem   *jj=x->items;
  143.   XLBItem   *ii=jj+i;
  144.   int     level=ii->level;
  145.   while (ii-->jj)
  146.     if (ii->level < level)
  147.       return ii - jj;
  148.   return -1;
  149. }
  150. static void XLB_UpdateScrollbar(XLB *x);
  151. static void XLB_Restore(XLB *x,int item) {
  152.   bool changed=false;
  153.   for (;;) {
  154.     item=XLB_GetParent(x,item);
  155.     if (item<0)
  156.       break;
  157.     if (x->items[item].flags & XIF_COLLAPSED) {
  158.       x->items[item].flags &= ~XIF_COLLAPSED;
  159.       changed=true;
  160.     }
  161.   }
  162.   if (changed)
  163.     XLB_UpdateVisibleItems(x);
  164. }
  165. static bool XLB_HasChildren(XLB *x,int item) {
  166.    return item < x->num_items-1 && x->items[item+1].level > x->items[item].level;
  167. }
  168. #define SPC(x) ((x)==_T(' ') || (x)==_T('t') || (x)==_T('r') || (x)==_T('n'))
  169. static void XLB_PaintItem(XLB *x,int item,int vitem,HDC hDC,RECT& rc) {
  170.   // cache an item pointer
  171.   XLBItem   *ii=&x->items[item];
  172.   // query text1 and text2 if they are NULL
  173.   const TCHAR   *text1=ii->text1;
  174.   const TCHAR   *text2=ii->text2;
  175.   if (text1==NULL && x->gtf) {
  176.     CString t(x->gtf(x->gtdata,0,item,ii->user_data));
  177.     text1=ii->text1=_tcsdup(t);
  178.   }
  179.   if (text2==NULL && x->gtf) {
  180.     CString t(x->gtf(x->gtdata,1,item,ii->user_data));
  181.     text2=ii->text2=_tcsdup(t);
  182.   }
  183.   // set text and bg colors
  184.   COLORREF  text1_color,text2_color,bk_color;
  185.   if (item==x->selection)
  186.     text1_color=text2_color=COLOR4;
  187.   else {
  188.     text1_color=COLOR0;
  189.     text2_color=COLOR3;
  190.   }
  191.   bk_color=
  192.     item==x->selection ? COLOR5 :
  193.       vitem&1 ? COLOR1 : COLOR2;
  194.   ::SetBkColor(hDC,bk_color);
  195.   // calculate indentation and paint it
  196.   int     indent=ii->level*INDENT_PER_LEVEL;
  197.   if (indent > (rc.right-rc.left)/2)
  198.     indent=(rc.right-rc.left)/2;
  199.   RECT rci=rc;
  200.   rci.right=rci.left;
  201.   if (indent)
  202.     rci.right+=indent;
  203.   if (x->flags & XLF_TREE)
  204.     rci.right+=x->ticon_w;
  205.   if (ii->icon_index>=0)
  206.     rci.right+=x->uicon_w;
  207.   if (rci.right!=rci.left)
  208.     ::ExtTextOut(hDC,rci.left,rci.top,ETO_OPAQUE,&rci,NULL,0,NULL);
  209.   // top and bottom text rectangles
  210.   RECT     rt,rb;
  211.   rt=rc;
  212.   rt.bottom=rt.top+x->line_height;
  213.   rt.left+=indent;
  214.   rb=rt;
  215.   rb.top+=x->line_height;
  216.   rb.bottom+=x->line_height;
  217.   // paint a tree icon
  218.   if (x->flags & XLF_TREE) { // tree mode
  219.     // reduce text width
  220.     rci=rc;
  221.     rci.left=rt.left;
  222.     rci.right=rci.left+x->ticon_w;
  223.     rt.left+=x->ticon_w;
  224.     rb.left+=x->ticon_w;
  225.     // paint the icon itself
  226.     ::ImageList_DrawEx(x->tree_icons,
  227.       XLB_HasChildren(x,item) ? (ii->flags & XIF_COLLAPSED ? 0 : 1) : 2,
  228.       hDC,
  229.       rci.left,rci.top,0,0,
  230.       bk_color,CLR_NONE,
  231.       ILD_NORMAL);
  232.   }
  233.   // paint an icon
  234.   if (ii->icon_index>=0) {
  235.     // calc icon rectangle
  236.     rci=rc;
  237.     rci.left+=rt.left;
  238.     rci.right=rci.left+x->uicon_w;
  239.     // reduce text width by icon size
  240.     rt.left+=x->uicon_w;
  241.     rb.left+=x->uicon_w;
  242.     // paint the icon itself
  243.     ::ImageList_DrawEx(x->icons,
  244.       ii->icon_index,
  245.       hDC,
  246.       rci.left+UICON_PAD,rci.top+UICON_PAD,0,0,
  247.       bk_color,CLR_NONE,
  248.       ILD_NORMAL);
  249.   }
  250.   // paint text2
  251.   if (text2) {
  252.     int   tl=_tcslen(text2);
  253.     // get text2 width
  254.     SIZE    sz;
  255.     ::GetTextExtentPoint(hDC,text2,tl,&sz);
  256.     // calc text box
  257.     rci=rb;
  258.     // adjust bottom rectangle
  259.     if (sz.cx<rb.right-rb.left)
  260.       rb.right-=sz.cx;
  261.     else
  262.       rb.right=rb.left;
  263.     rb.right-=5;
  264.     if (rb.right<rb.left)
  265.       rb.right=rb.left;
  266.     rci.left=rb.right;
  267.     // paint text
  268.     ::SetTextColor(hDC,text2_color);
  269.     ::ExtTextOut(hDC,rci.left+3,rci.top,ETO_OPAQUE|ETO_CLIPPED,&rci,text2,tl,NULL);
  270.   }
  271.   int tl=text1 ? _tcslen(text1) : 0;
  272.   // get size of top part
  273.   SIZE sz;
  274.   int top_len=0;
  275.   ::GetTextExtentExPoint(hDC,text1,tl,rt.right-rt.left,&top_len,NULL,&sz);
  276.   // do some simple word wrapping
  277.   int i;
  278.   for (i=0;i<top_len;++i)
  279.     if (text1[i]==_T('n') || text1[i]==_T('r'))
  280.       break;
  281.   top_len=i;
  282.   if (top_len!=tl && !SPC(text1[top_len])) {
  283.     while (i>0 && !SPC(text1[i-1]))
  284.       --i;
  285.     if (i>0)
  286.       top_len=i;
  287.   }
  288.   // back off until spaces end
  289.   while (top_len>0 && SPC(text1[top_len-1]))
  290.     --top_len;
  291.   // set text1 color
  292.   ::SetTextColor(hDC,text1_color);
  293.   // paint top part
  294.   ::ExtTextOut(hDC,rt.left,rt.top,ETO_OPAQUE|ETO_CLIPPED,&rt,text1,top_len,NULL);
  295.   // skip spaces here
  296.   while (top_len<tl && SPC(text1[top_len]))
  297.     ++top_len;
  298.   // paint bottom part
  299.   if (rb.right>rb.left) {
  300.     // check size
  301.     ::GetTextExtentExPoint(hDC,text1+top_len,tl-top_len,rb.right-rb.left,&i,NULL,&sz);
  302.     if (i!=tl-top_len) { // overflow, will need to append "..."
  303.       ::GetTextExtentExPoint(hDC,text1+top_len,tl-top_len,rb.right-rb.left-x->dots_width,&i,NULL,&sz);
  304.       ::GetTextExtentPoint(hDC,text1+top_len,i,&sz);
  305.     }
  306.     ::ExtTextOut(hDC,rb.left,rb.top,ETO_OPAQUE|ETO_CLIPPED,&rb,text1+top_len,i,NULL);
  307.     if (i!=tl-top_len)
  308.       ::ExtTextOut(hDC,rb.left+sz.cx,rb.top,0,NULL,_T("..."),3,NULL);
  309.   }
  310. }
  311. static void XLB_PaintAll(XLB *x,HDC hDC) {
  312.   // load tree icons if we are in tree mode
  313.   if (x->flags & XLF_TREE && !x->tree_icons) {
  314.     x->tree_icons=::ImageList_LoadBitmap(
  315.       ::AfxGetResourceHandle(),
  316.       MAKEINTRESOURCE(IDB_TREE),
  317.       13, // XXX hardcoded
  318.       0,
  319.       RGB(255,0,255));
  320.     int   dummy;
  321.     ::ImageList_GetIconSize(x->tree_icons,&x->ticon_w,&dummy);
  322.   }
  323.   // select our font
  324.   HGDIOBJ   obj=NULL;
  325.   if (x->font)
  326.     obj=::SelectObject(hDC,x->font);
  327.   // get client rect
  328.   RECT cli;
  329.   ::GetClientRect(x->hWnd,&cli);
  330.   // get update rect
  331.   RECT dirty;
  332.   if (!::GetUpdateRect(x->hWnd,&dirty,FALSE))
  333.     dirty=cli;
  334.   // shift update rect to (0,0)
  335.   dirty.top-=cli.top;
  336.   dirty.bottom-=cli.top;
  337.   dirty.left-=cli.left;
  338.   dirty.right-=cli.left;
  339.   // repaint all items from top to bottom
  340.   int top=(x->top_offset+dirty.top)/x->item_height;
  341.   int bottom=(x->top_offset+dirty.bottom-1)/x->item_height;
  342.   cli.top+=top*x->item_height-x->top_offset;
  343.   for (int i=top;i<=bottom;++i,cli.top+=x->item_height) {
  344.     cli.bottom=cli.top+x->item_height;
  345.     if (i>=0 && i<x->visible_items)
  346.       XLB_PaintItem(x,XLB_GetIFromV(x,i),i,hDC,cli);
  347.     else {
  348.       // draw a blank rectangle
  349.       ::SetBkColor(hDC,COLOR1);
  350.       ::ExtTextOut(hDC,cli.left,cli.top,ETO_OPAQUE,&cli,NULL,0,NULL);
  351.     }
  352.   }
  353.   // restore font
  354.   if (obj)
  355.     ::SelectObject(hDC,obj);
  356. }
  357. static void   XLB_InitFont(XLB *x,HDC hDC) {
  358.   LOGFONT lf;
  359.   memset(&lf,0,sizeof(lf));
  360.   lf.lfHeight=-(DEFAULT_FONT_SIZE*GetDeviceCaps(hDC,LOGPIXELSY))/96;
  361.   lf.lfWeight=FW_BOLD;
  362.   lf.lfCharSet=ANSI_CHARSET;
  363.   lf.lfOutPrecision=OUT_DEFAULT_PRECIS;
  364.   lf.lfClipPrecision=CLIP_DEFAULT_PRECIS;
  365. #ifdef _WIN32_WCE
  366.   lf.lfQuality=CLEARTYPE_QUALITY;
  367. #else
  368.   lf.lfQuality=DEFAULT_QUALITY;
  369. #endif
  370.   _tcscpy(lf.lfFaceName,_T("Tahoma"));
  371.   x->font=::CreateFontIndirect(&lf);
  372.   TEXTMETRIC tm;
  373.   HGDIOBJ obj=::SelectObject(hDC,x->font);
  374.   ::GetTextMetrics(hDC,&tm);
  375.   SIZE sz;
  376.   ::GetTextExtentPoint(hDC,_T("..."),3,&sz);
  377.   ::SelectObject(hDC,obj);
  378.   x->line_height=tm.tmAscent+tm.tmDescent;
  379.   x->item_height=x->line_height*2;
  380.   x->dots_width=sz.cx;
  381. }
  382. static void XLB_ScrollTo(XLB *x,int top) {
  383.   int delta=x->top_offset - top;
  384.   x->top_offset=top;
  385.   ::SetScrollPos(x->hWnd,SB_VERT,top,TRUE);
  386.   ::ScrollWindowEx(x->hWnd,0,delta,NULL,NULL,NULL,NULL,SW_INVALIDATE);
  387. }
  388. static void XLB_EnsureVisible2(XLB *x,int item) {
  389.   int v=XLB_GetVFromI(x,item);
  390.   int   cvis=x->scroll_page/x->item_height; // completely visible items
  391.   if (cvis==0)
  392.     cvis=1;
  393.   int item_offset=v*x->item_height-x->top_offset;
  394.   // if the item is not fully visible, then
  395.   // scroll and repaint completely
  396.   if (item_offset<0) {
  397.     x->top_offset=v*x->item_height;
  398.     ::SetScrollPos(x->hWnd,SB_VERT,x->top_offset,TRUE);
  399.   } else if (item_offset+x->item_height>x->scroll_page) {
  400.     x->top_offset=(v-cvis+1)*x->item_height;
  401.     ::SetScrollPos(x->hWnd,SB_VERT,x->top_offset,TRUE);
  402.   }
  403. }
  404. static LRESULT CALLBACK XLB_WndProc(HWND hWnd,UINT uMsg,
  405.     WPARAM wParam,LPARAM lParam)
  406. {
  407.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  408.   int i,j;
  409.   HDC hDC;
  410.   switch (uMsg) {
  411.   case WM_CREATE:
  412.     x=(XLB*)malloc(sizeof(*x));
  413.     if (x==NULL)
  414.       return -1;
  415.     memset(x,0,sizeof(*x));
  416.     ::SetWindowLong(hWnd,0,(LONG)x);
  417.     x->hWnd=hWnd;
  418.     x->selection=-1;
  419.     hDC=::GetDC(hWnd);
  420.     XLB_InitFont(x,hDC);
  421.     ::ReleaseDC(hWnd,hDC);
  422.     break;
  423.   case WM_DESTROY:
  424.     if (x->icons && !x->icons_shared)
  425.       ::ImageList_Destroy(x->icons);
  426.     if (x->tree_icons)
  427.       ::ImageList_Destroy(x->tree_icons);
  428.     if (x->font)
  429.       ::DeleteObject(x->font);
  430.     for (i=0;i<x->num_items;++i) {
  431.       free(x->items[i].text1);
  432.       free(x->items[i].text2);
  433.     }
  434.     free(x->items);
  435.     free(x->vitems);
  436.     free(x);
  437.     break;
  438.   case WM_PAINT: {
  439.     PAINTSTRUCT ps;
  440.     hDC=::BeginPaint(hWnd,&ps);
  441.     XLB_PaintAll(x,hDC);
  442.     ::EndPaint(hWnd,&ps);
  443.     break; }
  444.   case WM_VSCROLL:
  445.     i=-1;
  446.     j=x->visible_items*x->item_height-x->scroll_page;
  447.     switch (LOWORD(wParam)) {
  448.     case SB_BOTTOM:
  449.       i=j;
  450.       break;
  451.     case SB_LINEDOWN:
  452.       i=min(x->top_offset+x->item_height,j);
  453.       break;
  454.     case SB_LINEUP:
  455.       i=max(x->top_offset-x->item_height,0);
  456.       break;
  457.     case SB_PAGEDOWN:
  458.       i=min(x->top_offset+x->scroll_page,j);
  459.       break;
  460.     case SB_PAGEUP:
  461.       i=max(x->top_offset-x->scroll_page,0);
  462.       break;
  463.     case SB_THUMBPOSITION:
  464.     case SB_THUMBTRACK: {
  465.       SCROLLINFO si;
  466.       si.cbSize=sizeof(si);
  467.       si.fMask=SIF_TRACKPOS;
  468.       ::GetScrollInfo(hWnd,SB_VERT,&si);
  469.       i=min(si.nTrackPos,j);
  470.       break; }
  471.     case SB_TOP:
  472.       i=0;
  473.       break;
  474.     }
  475.     if (i>=0 && i<=j && i!=x->top_offset)
  476.       XLB_ScrollTo(x,i);
  477.     break;
  478.   case WM_KEYDOWN:
  479.     i=-1;
  480.     j=XLB_GetVFromI(x,x->selection);
  481.     switch (wParam) {
  482.     case VK_RIGHT: case VK_ADD:
  483.       // expand item
  484.       if (x->flags & XLF_TREE && x->selection>=0 &&
  485. x->items[x->selection].flags & XIF_COLLAPSED &&
  486. XLB_HasChildren(x,x->selection))
  487.       {
  488. x->items[x->selection].flags &= ~XIF_COLLAPSED;
  489. XLB_UpdateVisibleItems(x);
  490. XLB_UpdateScrollbar(x);
  491. ::InvalidateRect(x->hWnd,NULL,FALSE);
  492.       }
  493.       return 0;
  494.     case VK_LEFT: case VK_SUBTRACT:
  495.       // collapse item
  496.       if (x->flags & XLF_TREE && x->selection>=0 &&
  497. !(x->items[x->selection].flags & XIF_COLLAPSED) &&
  498. XLB_HasChildren(x,x->selection))
  499.       {
  500. x->items[x->selection].flags |= XIF_COLLAPSED;
  501. XLB_UpdateVisibleItems(x);
  502. XLB_UpdateScrollbar(x);
  503. ::InvalidateRect(x->hWnd,NULL,FALSE);
  504.       }
  505.       return 0;
  506.     case VK_UP:
  507.       i=j-1;
  508.       break;
  509.     case VK_DOWN:
  510.       i=j+1;
  511.       break;
  512.     case VK_PRIOR:
  513.       i=max(j-(x->scroll_page+x->item_height-1)/x->item_height,0);
  514.       break;
  515.     case VK_NEXT:
  516.       i=min(j+(x->scroll_page+x->item_height-1)/x->item_height,x->visible_items-1);
  517.       break;
  518.     case VK_HOME:
  519.       i=0;
  520.       break;
  521.     case VK_END:
  522.       i=x->visible_items-1;
  523.       break;
  524.     }
  525.     if (i>=0 && i<x->visible_items && i!=j) {
  526.       j=i*x->item_height-x->top_offset;
  527.       // if the item is not fully visible, then
  528.       // scroll and repaint completely
  529.       if (j<0 || j+x->item_height>x->scroll_page) {
  530. x->selection=XLB_GetIFromV(x,i);
  531. XLB_EnsureVisible2(x,x->selection);
  532. ::InvalidateRect(x->hWnd,NULL,FALSE);
  533.       } else
  534. XLB_SetSelection(hWnd,XLB_GetIFromV(x,i));
  535.     }
  536.     break;
  537.   case WM_LBUTTONDOWN:
  538.     i=(HIWORD(lParam)+x->top_offset)/x->item_height;
  539.     if (i>=0 && i<x->visible_items) {
  540.       j=XLB_GetIFromV(x,i);
  541.       XLB_SetSelection(hWnd,j);
  542.       // if we are in tree mode and hit a control icon,
  543.       // then collapse/uncollapse an item
  544.       if (x->flags & XLF_TREE && x->tree_icons) {
  545. int   iw,ih;
  546. ::ImageList_GetIconSize(x->tree_icons,&iw,&ih);
  547. if (LOWORD(lParam) < x->items[j].level*INDENT_PER_LEVEL + iw &&
  548.     XLB_HasChildren(x,j))
  549. {
  550.   x->items[j].flags ^= XIF_COLLAPSED;
  551.   XLB_UpdateVisibleItems(x);
  552.   XLB_UpdateScrollbar(x);
  553.   ::InvalidateRect(x->hWnd,NULL,FALSE);
  554. }
  555.       }
  556. #if POCKETPC
  557.       SHRGINFO shrg;
  558.       memset(&shrg,0,sizeof(shrg));
  559.       shrg.cbSize=sizeof(shrg);
  560.       shrg.hwndClient=hWnd;
  561.       shrg.ptDown.x=LOWORD(lParam);
  562.       shrg.ptDown.y=HIWORD(lParam);
  563.       shrg.dwFlags=SHRG_RETURNCMD;
  564.       if (SHRecognizeGesture(&shrg)==GN_CONTEXTMENU)
  565. ::SendMessage(::GetParent(hWnd),XLM_CONTEXTMENU,lParam,(LPARAM)hWnd);
  566. #endif
  567.     }
  568.     break;
  569.   case WM_LBUTTONUP:
  570.     i=(HIWORD(lParam)+x->top_offset)/x->item_height;
  571.     if (i>=0 && i<x->visible_items && x->selection>=0)
  572.       ::SendMessage(::GetParent(hWnd),XLM_CLICK,lParam,(LPARAM)hWnd);
  573.     break;
  574.   case WM_RBUTTONDOWN:
  575.     i=(HIWORD(lParam)+x->top_offset)/x->item_height;
  576.     if (i>=0 && i<x->visible_items) {
  577.       XLB_SetSelection(hWnd,XLB_GetIFromV(x,i));
  578.       ::SendMessage(::GetParent(hWnd),XLM_CONTEXTMENU,lParam,(LPARAM)hWnd);
  579.     }
  580.     break;
  581.   case WM_LBUTTONDBLCLK:
  582.     i=(HIWORD(lParam)+x->top_offset)/x->item_height;
  583.     if (i>=0 && i<x->visible_items)
  584.       ::SendMessage(::GetParent(hWnd),XLM_DBLCLK,lParam,(LPARAM)hWnd);
  585.     break;
  586.   case WM_GETDLGCODE:
  587.     return DLGC_WANTARROWS | DLGC_WANTCHARS;
  588. #if defined(WM_MOUSEWHEEL) && defined(SPI_GETWHEELSCROLLLINES) && defined(WHEEL_DELTA)
  589.   case WM_MOUSEWHEEL: {
  590.     UINT    lines=3;
  591.     ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0,&lines,0);
  592.     int     scrl=(short)HIWORD(wParam)*(int)lines*x->item_height;
  593.     scrl/=WHEEL_DELTA;
  594.     j=x->visible_items*x->item_height-x->scroll_page;
  595.     i=max(min(x->top_offset-scrl,j),0);
  596.     if (i!=x->top_offset)
  597.       XLB_ScrollTo(x,i);
  598.     break; }
  599. #endif
  600.   default:
  601.     return ::DefWindowProc(hWnd,uMsg,wParam,lParam);
  602.   }
  603.   return 0;
  604. }
  605. void  XLB_SetImageList(HWND hWnd,HIMAGELIST hIml,bool shared) {
  606.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  607.   if (!x)
  608.     return;
  609.   if (x->icons && !x->icons_shared)
  610.     ::ImageList_Destroy(x->icons);
  611.   x->icons=hIml;
  612.   x->icons_shared=shared;
  613.   if (hIml) {
  614.     int     dummy;
  615.     ::ImageList_GetIconSize(hIml,&x->uicon_w,&dummy);
  616.     x->uicon_w+=UICON_PAD*2;
  617.   } else
  618.     x->uicon_w=0;
  619. }
  620. void  XLB_UpdateScrollbar(XLB *x) {
  621.   RECT   rc;
  622.   ::GetClientRect(x->hWnd,&rc);
  623.   x->scroll_page=rc.bottom-rc.top;
  624.   SCROLLINFO  si;
  625.   memset(&si,0,sizeof(si));
  626.   si.cbSize=sizeof(si);
  627.   si.fMask=SIF_PAGE|SIF_RANGE;
  628.   si.nMin=0;
  629.   si.nMax=x->visible_items*x->item_height-1;
  630.   if (si.nMax<x->scroll_page) {
  631.     si.nMax=0;
  632.     si.nPage=0;
  633.   } else
  634.     si.nPage=x->scroll_page;
  635.   ::SetScrollInfo(x->hWnd,SB_VERT,&si,TRUE);
  636. }
  637. struct XLB_Handle *XLB_GetHandle(HWND hWnd) {
  638.   return (XLB_Handle *)::GetWindowLong(hWnd,0);
  639. }
  640. bool  XLB_AppendItem(struct XLB_Handle *handle,
  641.      const TCHAR *text1,const TCHAR *text2,
  642.      int icon,int level,
  643.      LONG user_data)
  644. {
  645.   XLB *x=(XLB*)handle;
  646.   if (!x)
  647.     return false;
  648.   if (x->num_items >= x->max_items && !XLB_GrowList(x))
  649.     return false;
  650.   XLBItem *ii=&x->items[x->num_items];
  651.   ii->text1=ii->text2=NULL;
  652.   if (text1) {
  653.     ii->text1=_tcsdup(text1);
  654.     if (ii->text1==NULL)
  655.       return false;
  656.   }
  657.   if (text2) {
  658.     ii->text2=_tcsdup(text2);
  659.     if (ii->text2==NULL) {
  660.       free(ii->text1);
  661.       return false;
  662.     }
  663.   }
  664.   ii->icon_index=icon;
  665.   ii->level=level;
  666.   ii->user_data=user_data;
  667.   ii->flags=0;
  668.   x->num_items++;
  669.   return true;
  670. }
  671. int XLB_GetSelection(HWND hWnd) {
  672.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  673.   if (!x)
  674.     return -1;
  675.   return x->selection;
  676. }
  677. static void XLB_GetItemRect(XLB *x,int item,RECT& r) {
  678.   memset(&r,0,sizeof(r));
  679.   if (item<0 || item>=x->num_items)
  680.     return;
  681.   item=XLB_GetVFromI(x,item);
  682.   if (item<0)
  683.     return;
  684.   ::GetClientRect(x->hWnd,&r);
  685.   r.top+=x->item_height*item;
  686.   r.top-=x->top_offset;
  687.   r.bottom=r.top+x->item_height;
  688. }
  689. static void XLB_InvalidateItem(XLB *x,int item) {
  690.   RECT   ri;
  691.   XLB_GetItemRect(x,item,ri);
  692.   ::InvalidateRect(x->hWnd,&ri,FALSE);
  693. }
  694. void XLB_SetSelection(HWND hWnd,int sel) {
  695.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  696.   if (!x || sel<0 || sel>=x->num_items || sel==x->selection)
  697.     return;
  698.   if (XLB_GetVFromI(x,sel)<0) {
  699.     XLB_Restore(x,sel);
  700.     XLB_UpdateScrollbar(x);
  701.   }
  702.   if (x->selection!=-1)
  703.     XLB_InvalidateItem(x,x->selection);
  704.   if (sel!=-1)
  705.     XLB_InvalidateItem(x,sel);
  706.   x->selection=sel;
  707. }
  708. int XLB_GetItemCount(HWND hWnd) {
  709.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  710.   if (!x)
  711.     return 0;
  712.   return x->num_items;
  713. }
  714. LONG XLB_GetData(HWND hWnd,int item) {
  715.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  716.   if (!x || item<0 || item>=x->num_items)
  717.     return 0;
  718.   return x->items[item].user_data;
  719. }
  720. void XLB_EnsureVisible(HWND hWnd,int item,bool middle) {
  721.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  722.   if (!x || item<0 || item>=x->num_items)
  723.     return;
  724.   int v=XLB_GetVFromI(x,item);
  725.   if (v<0) { // item is not currently visble
  726.     XLB_Restore(x,item);
  727.     XLB_UpdateScrollbar(x);
  728.     v=XLB_GetVFromI(x,item);
  729.   }
  730.   int   cvis=x->scroll_page/x->item_height; // completely visible items
  731.   if (cvis==0)
  732.     cvis=1;
  733.   if (middle) { // scroll the view so the item is in the middle
  734.     int   new_offset=(v-cvis/2)*x->item_height;
  735.     if (new_offset+x->scroll_page > x->visible_items*x->item_height)
  736.       new_offset=x->visible_items*x->item_height - x->scroll_page;
  737.     if (new_offset<0)
  738.       new_offset=0;
  739.     if (x->top_offset != new_offset)
  740.       XLB_ScrollTo(x,new_offset);
  741.     return;
  742.   }
  743.   int item_offset=v*x->item_height-x->top_offset;
  744.   // if the item is not fully visible, then
  745.   // scroll and repaint completely
  746.   if (item_offset<0)
  747.     XLB_ScrollTo(x,v*x->item_height);
  748.   else if (item_offset+x->item_height>x->scroll_page)
  749.     XLB_ScrollTo(x,(v-cvis+1)*x->item_height);
  750. }
  751. // TODO: avoid unneeded redraws
  752. void XLB_DeleteItem(HWND hWnd,int item) {
  753.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  754.   if (!x || item<0 || item>=x->num_items)
  755.     return;
  756.   free(x->items[item].text1);
  757.   free(x->items[item].text2);
  758.   memmove(&x->items[item],&x->items[item+1],sizeof(XLBItem)*(x->num_items-item-1));
  759.   x->num_items--;
  760.   if (x->selection>=x->num_items)
  761.     x->selection=x->num_items-1;
  762.   XLB_UpdateVisibleItems(x);
  763.   XLB_EnsureVisible(hWnd,x->selection);
  764.   ::InvalidateRect(hWnd,NULL,FALSE);
  765.   XLB_UpdateScrollbar(x);
  766. }
  767. void XLB_SetItemText1(HWND hWnd,int item,const TCHAR *text) {
  768.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  769.   if (!x || item<0 || item>=x->num_items)
  770.     return;
  771.   free(x->items[item].text1);
  772.   x->items[item].text1=_tcsdup(text);
  773.   XLB_InvalidateItem(x,item);
  774. }
  775. void XLB_DeleteAllItems(HWND hWnd) {
  776.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  777.   if (!x)
  778.     return;
  779.   for (int i=0;i<x->num_items;++i) {
  780.     free(x->items[i].text1);
  781.     free(x->items[i].text2);
  782.   }
  783.   x->num_items=0;
  784.   x->visible_items=0;
  785.   x->selection=-1;
  786.   x->top_offset=0;
  787.   ::SetScrollPos(hWnd,SB_VERT,0,TRUE);
  788.   XLB_UpdateScrollbar(x);
  789. }
  790. void  XLB_UpdateState(HWND hWnd) {
  791.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  792.   if (!x)
  793.     return;
  794.   XLB_UpdateVisibleItems(x);
  795.   XLB_UpdateScrollbar(x);
  796. }
  797. void  XLB_CollapseLevel(HWND hWnd,int level) {
  798.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  799.   if (!x)
  800.     return;
  801.   if (level<0)
  802.     level = 0x7fffffff; // XXX should be maxint
  803.   XLBItem *ii=x->items;
  804.   XLBItem *jj=ii+x->num_items;
  805.   while (ii<jj) {
  806.     if (ii->level >= level)
  807.       ii->flags |= XIF_COLLAPSED;
  808.     else
  809.       ii->flags &= ~XIF_COLLAPSED;
  810.     ++ii;
  811.   }
  812.   if (x->top_offset >= x->visible_items)
  813.     x->top_offset = 0; // XXX can do something better
  814.   XLB_UpdateState(hWnd);
  815.   ::InvalidateRect(hWnd,NULL,FALSE); // XXX can be avoided sometimes
  816. }
  817. // custom sort function
  818. static int  compare_items(const void *v1,const void *v2) {
  819.   const XLBItem *i1=(XLBItem*)v1;
  820.   const XLBItem *i2=(XLBItem*)v2;
  821.   int   i=i1->user_data-i2->user_data;
  822.   if (i!=0)
  823.     return i;
  824.   bool e1=!i1->text1 || !*i1->text1;
  825.   bool e2=!i2->text1 || !*i2->text1;
  826.   if (e1)
  827.     return e2 ? 0 : -1;
  828.   if (e2)
  829.     return 1;
  830.   return CmpI(i1->text1,i2->text1);
  831. }
  832. void XLB_SortItems(HWND hWnd) {
  833.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  834.   if (!x)
  835.     return;
  836.   qsort(x->items,x->num_items,sizeof(XLBItem),compare_items);
  837. }
  838. const TCHAR *XLB_GetItemText1(HWND hWnd,int item) {
  839.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  840.   if (!x || item<0 || item>=x->num_items)
  841.     return NULL;
  842.   return x->items[item].text1;
  843. }
  844. void XLB_SetGTFunc(HWND hWnd,XLB_GetText fn,void *ugtdata) {
  845.   XLB *x=(XLB*)::GetWindowLong(hWnd,0);
  846.   if (!x)
  847.     return;
  848.   x->gtf=fn;
  849.   x->gtdata=ugtdata;
  850. }
  851. void   XLB_Init() {
  852.   WNDCLASS    wc;
  853.   memset(&wc,0,sizeof(wc));
  854.   wc.style=CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;
  855.   wc.lpfnWndProc=XLB_WndProc;
  856.   wc.cbWndExtra=sizeof(XLB*);
  857.   wc.hInstance=::GetModuleHandle(NULL);
  858.   // assume the SDK always defines this via macro
  859. #if defined(LoadCursor)
  860.   wc.hCursor=::LoadCursor(NULL,IDC_ARROW);
  861. #endif
  862.   wc.lpszClassName=_T("XListBox");
  863.   ATOM cls=::RegisterClass(&wc);
  864. }