📄 xlistbox.cpp
字号:
/*
* Copyright (c) 2001,2002,2003 Mike Matsnev. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Absolutely no warranty of function or purpose is made by the author
* Mike Matsnev.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: XListBox.cpp,v 1.1.2.33 2004/10/21 14:53:27 mike Exp $
*
*/
#include <afxwin.h>
#include <afxcmn.h>
#include "resource.h"
#include "config.h"
#include "XListBox.h"
#define DEFAULT_FONT_SIZE 11
#define UICON_PAD 1
#define COLOR0 RGB(0,0,0)
#define COLOR1 RGB(255,255,255)
#define COLOR2 RGB(251,242,233)
#define COLOR3 RGB(128,128,128)
#define COLOR4 RGB(255,255,255)
#define COLOR5 RGB(16,32,255)
#define INDENT_PER_LEVEL 5
// string compare
#define CmpI(s1,s2) \
(::CompareString(LOCALE_USER_DEFAULT,NORM_IGNORECASE, \
(s1),-1,(s2),-1)-2)
struct XLBItem {
TCHAR *text1;
TCHAR *text2;
int icon_index;
int level;
int flags;
LONG user_data;
};
#define XIF_COLLAPSED 1
struct XLB {
HWND hWnd;
int num_items;
int max_items;
XLBItem *items;
int visible_items;
XLBItem **vitems;
int selection; // selection is I
int top_offset;
int scroll_page;
int item_height;
int line_height;
HIMAGELIST icons;
bool icons_shared;
HFONT font;
int dots_width;
HIMAGELIST tree_icons;
int flags;
XLB_GetText gtf;
void *gtdata;
int uicon_w;
int ticon_w;
};
#define XLF_TREE 1
static bool XLB_GrowList(XLB *x) {
int incr=x->num_items;
if (incr<16)
incr=16;
if (incr>4096)
incr=4096;
int newsize=x->num_items+incr;
XLBItem *newitems=(XLBItem*)realloc(x->items,newsize*sizeof(XLBItem));
if (newitems==NULL)
return false;
x->items=newitems;
XLBItem **newvitems=(XLBItem**)realloc(x->vitems,newsize*sizeof(XLBItem*));
if (newvitems==NULL)
return false;
x->vitems=newvitems;
x->max_items=newsize;
return true;
}
static void XLB_UpdateVisibleItems(XLB *x) {
XLBItem *ii=x->items;
XLBItem *jj=ii+x->num_items;
XLBItem **kk=x->vitems;
int level = 0;
int flags = 0;
while (ii<jj) {
if (ii->level > level)
flags=XLF_TREE;
level = ii->level;
*kk++=ii;
if (ii++->flags & XIF_COLLAPSED) {
// skip all following children
while (ii<jj && ii->level>level) {
++ii;
flags=XLF_TREE;
}
}
}
x->visible_items = kk - x->vitems;
x->flags |= flags;
}
static int XLB_GetIFromV(XLB *x,int v) {
return x->vitems[v] - x->items;
}
static int XLB_GetVFromI(XLB *x,int i) {
int top=x->visible_items-1;
int bottom=0;
XLBItem *ii=&x->items[i];
XLBItem **kk=x->vitems;
while (bottom<=top) {
int middle=(bottom+top)>>1;
if (kk[middle]==ii)
return middle;
if (kk[middle]>ii)
top=middle-1;
else
bottom=middle+1;
}
return -1;
}
static int XLB_GetParent(XLB *x,int i) {
XLBItem *jj=x->items;
XLBItem *ii=jj+i;
int level=ii->level;
while (ii-->jj)
if (ii->level < level)
return ii - jj;
return -1;
}
static void XLB_UpdateScrollbar(XLB *x);
static void XLB_Restore(XLB *x,int item) {
bool changed=false;
for (;;) {
item=XLB_GetParent(x,item);
if (item<0)
break;
if (x->items[item].flags & XIF_COLLAPSED) {
x->items[item].flags &= ~XIF_COLLAPSED;
changed=true;
}
}
if (changed)
XLB_UpdateVisibleItems(x);
}
static bool XLB_HasChildren(XLB *x,int item) {
return item < x->num_items-1 && x->items[item+1].level > x->items[item].level;
}
#define SPC(x) ((x)==_T(' ') || (x)==_T('\t') || (x)==_T('\r') || (x)==_T('\n'))
static void XLB_PaintItem(XLB *x,int item,int vitem,HDC hDC,RECT& rc) {
// cache an item pointer
XLBItem *ii=&x->items[item];
// query text1 and text2 if they are NULL
const TCHAR *text1=ii->text1;
const TCHAR *text2=ii->text2;
if (text1==NULL && x->gtf) {
CString t(x->gtf(x->gtdata,0,item,ii->user_data));
text1=ii->text1=_tcsdup(t);
}
if (text2==NULL && x->gtf) {
CString t(x->gtf(x->gtdata,1,item,ii->user_data));
text2=ii->text2=_tcsdup(t);
}
// set text and bg colors
COLORREF text1_color,text2_color,bk_color;
if (item==x->selection)
text1_color=text2_color=COLOR4;
else {
text1_color=COLOR0;
text2_color=COLOR3;
}
bk_color=
item==x->selection ? COLOR5 :
vitem&1 ? COLOR1 : COLOR2;
::SetBkColor(hDC,bk_color);
// calculate indentation and paint it
int indent=ii->level*INDENT_PER_LEVEL;
if (indent > (rc.right-rc.left)/2)
indent=(rc.right-rc.left)/2;
RECT rci=rc;
rci.right=rci.left;
if (indent)
rci.right+=indent;
if (x->flags & XLF_TREE)
rci.right+=x->ticon_w;
if (ii->icon_index>=0)
rci.right+=x->uicon_w;
if (rci.right!=rci.left)
::ExtTextOut(hDC,rci.left,rci.top,ETO_OPAQUE,&rci,NULL,0,NULL);
// top and bottom text rectangles
RECT rt,rb;
rt=rc;
rt.bottom=rt.top+x->line_height;
rt.left+=indent;
rb=rt;
rb.top+=x->line_height;
rb.bottom+=x->line_height;
// paint a tree icon
if (x->flags & XLF_TREE) { // tree mode
// reduce text width
rci=rc;
rci.left=rt.left;
rci.right=rci.left+x->ticon_w;
rt.left+=x->ticon_w;
rb.left+=x->ticon_w;
// paint the icon itself
::ImageList_DrawEx(x->tree_icons,
XLB_HasChildren(x,item) ? (ii->flags & XIF_COLLAPSED ? 0 : 1) : 2,
hDC,
rci.left,rci.top,0,0,
bk_color,CLR_NONE,
ILD_NORMAL);
}
// paint an icon
if (ii->icon_index>=0) {
// calc icon rectangle
rci=rc;
rci.left+=rt.left;
rci.right=rci.left+x->uicon_w;
// reduce text width by icon size
rt.left+=x->uicon_w;
rb.left+=x->uicon_w;
// paint the icon itself
::ImageList_DrawEx(x->icons,
ii->icon_index,
hDC,
rci.left+UICON_PAD,rci.top+UICON_PAD,0,0,
bk_color,CLR_NONE,
ILD_NORMAL);
}
// paint text2
if (text2) {
int tl=_tcslen(text2);
// get text2 width
SIZE sz;
::GetTextExtentPoint(hDC,text2,tl,&sz);
// calc text box
rci=rb;
// adjust bottom rectangle
if (sz.cx<rb.right-rb.left)
rb.right-=sz.cx;
else
rb.right=rb.left;
rb.right-=5;
if (rb.right<rb.left)
rb.right=rb.left;
rci.left=rb.right;
// paint text
::SetTextColor(hDC,text2_color);
::ExtTextOut(hDC,rci.left+3,rci.top,ETO_OPAQUE|ETO_CLIPPED,&rci,text2,tl,NULL);
}
int tl=text1 ? _tcslen(text1) : 0;
// get size of top part
SIZE sz;
int top_len=0;
::GetTextExtentExPoint(hDC,text1,tl,rt.right-rt.left,&top_len,NULL,&sz);
// do some simple word wrapping
int i;
for (i=0;i<top_len;++i)
if (text1[i]==_T('\n') || text1[i]==_T('\r'))
break;
top_len=i;
if (top_len!=tl && !SPC(text1[top_len])) {
while (i>0 && !SPC(text1[i-1]))
--i;
if (i>0)
top_len=i;
}
// back off until spaces end
while (top_len>0 && SPC(text1[top_len-1]))
--top_len;
// set text1 color
::SetTextColor(hDC,text1_color);
// paint top part
::ExtTextOut(hDC,rt.left,rt.top,ETO_OPAQUE|ETO_CLIPPED,&rt,text1,top_len,NULL);
// skip spaces here
while (top_len<tl && SPC(text1[top_len]))
++top_len;
// paint bottom part
if (rb.right>rb.left) {
// check size
::GetTextExtentExPoint(hDC,text1+top_len,tl-top_len,rb.right-rb.left,&i,NULL,&sz);
if (i!=tl-top_len) { // overflow, will need to append "..."
::GetTextExtentExPoint(hDC,text1+top_len,tl-top_len,rb.right-rb.left-x->dots_width,&i,NULL,&sz);
::GetTextExtentPoint(hDC,text1+top_len,i,&sz);
}
::ExtTextOut(hDC,rb.left,rb.top,ETO_OPAQUE|ETO_CLIPPED,&rb,text1+top_len,i,NULL);
if (i!=tl-top_len)
::ExtTextOut(hDC,rb.left+sz.cx,rb.top,0,NULL,_T("..."),3,NULL);
}
}
static void XLB_PaintAll(XLB *x,HDC hDC) {
// load tree icons if we are in tree mode
if (x->flags & XLF_TREE && !x->tree_icons) {
x->tree_icons=::ImageList_LoadBitmap(
::AfxGetResourceHandle(),
MAKEINTRESOURCE(IDB_TREE),
13, // XXX hardcoded
0,
RGB(255,0,255));
int dummy;
::ImageList_GetIconSize(x->tree_icons,&x->ticon_w,&dummy);
}
// select our font
HGDIOBJ obj=NULL;
if (x->font)
obj=::SelectObject(hDC,x->font);
// get client rect
RECT cli;
::GetClientRect(x->hWnd,&cli);
// get update rect
RECT dirty;
if (!::GetUpdateRect(x->hWnd,&dirty,FALSE))
dirty=cli;
// shift update rect to (0,0)
dirty.top-=cli.top;
dirty.bottom-=cli.top;
dirty.left-=cli.left;
dirty.right-=cli.left;
// repaint all items from top to bottom
int top=(x->top_offset+dirty.top)/x->item_height;
int bottom=(x->top_offset+dirty.bottom-1)/x->item_height;
cli.top+=top*x->item_height-x->top_offset;
for (int i=top;i<=bottom;++i,cli.top+=x->item_height) {
cli.bottom=cli.top+x->item_height;
if (i>=0 && i<x->visible_items)
XLB_PaintItem(x,XLB_GetIFromV(x,i),i,hDC,cli);
else {
// draw a blank rectangle
::SetBkColor(hDC,COLOR1);
::ExtTextOut(hDC,cli.left,cli.top,ETO_OPAQUE,&cli,NULL,0,NULL);
}
}
// restore font
if (obj)
::SelectObject(hDC,obj);
}
static void XLB_InitFont(XLB *x,HDC hDC) {
LOGFONT lf;
memset(&lf,0,sizeof(lf));
lf.lfHeight=-(DEFAULT_FONT_SIZE*GetDeviceCaps(hDC,LOGPIXELSY))/96;
lf.lfWeight=FW_BOLD;
lf.lfCharSet=ANSI_CHARSET;
lf.lfOutPrecision=OUT_DEFAULT_PRECIS;
lf.lfClipPrecision=CLIP_DEFAULT_PRECIS;
#ifdef _WIN32_WCE
lf.lfQuality=CLEARTYPE_QUALITY;
#else
lf.lfQuality=DEFAULT_QUALITY;
#endif
_tcscpy(lf.lfFaceName,_T("Tahoma"));
x->font=::CreateFontIndirect(&lf);
TEXTMETRIC tm;
HGDIOBJ obj=::SelectObject(hDC,x->font);
::GetTextMetrics(hDC,&tm);
SIZE sz;
::GetTextExtentPoint(hDC,_T("..."),3,&sz);
::SelectObject(hDC,obj);
x->line_height=tm.tmAscent+tm.tmDescent;
x->item_height=x->line_height*2;
x->dots_width=sz.cx;
}
static void XLB_ScrollTo(XLB *x,int top) {
int delta=x->top_offset - top;
x->top_offset=top;
::SetScrollPos(x->hWnd,SB_VERT,top,TRUE);
::ScrollWindowEx(x->hWnd,0,delta,NULL,NULL,NULL,NULL,SW_INVALIDATE);
}
static void XLB_EnsureVisible2(XLB *x,int item) {
int v=XLB_GetVFromI(x,item);
int cvis=x->scroll_page/x->item_height; // completely visible items
if (cvis==0)
cvis=1;
int item_offset=v*x->item_height-x->top_offset;
// if the item is not fully visible, then
// scroll and repaint completely
if (item_offset<0) {
x->top_offset=v*x->item_height;
::SetScrollPos(x->hWnd,SB_VERT,x->top_offset,TRUE);
} else if (item_offset+x->item_height>x->scroll_page) {
x->top_offset=(v-cvis+1)*x->item_height;
::SetScrollPos(x->hWnd,SB_VERT,x->top_offset,TRUE);
}
}
static LRESULT CALLBACK XLB_WndProc(HWND hWnd,UINT uMsg,
WPARAM wParam,LPARAM lParam)
{
XLB *x=(XLB*)::GetWindowLong(hWnd,0);
int i,j;
HDC hDC;
switch (uMsg) {
case WM_CREATE:
x=(XLB*)malloc(sizeof(*x));
if (x==NULL)
return -1;
memset(x,0,sizeof(*x));
::SetWindowLong(hWnd,0,(LONG)x);
x->hWnd=hWnd;
x->selection=-1;
hDC=::GetDC(hWnd);
XLB_InitFont(x,hDC);
::ReleaseDC(hWnd,hDC);
break;
case WM_DESTROY:
if (x->icons && !x->icons_shared)
::ImageList_Destroy(x->icons);
if (x->tree_icons)
::ImageList_Destroy(x->tree_icons);
if (x->font)
::DeleteObject(x->font);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -