📄 qtree.java
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.widgets.qstyle;
import static java.lang.Math.max;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Text;
import edu.tsinghua.lumaqq.widgets.BaseComposite;
/**
* 实现一个QQ那样的树型控件
*
* @author luma
*/
public class QTree extends BaseComposite {
// 动画效果的runnable
private Runnable animateRunnable = new Runnable() {
public void run() {
synchronized(animateList) {
int size = animateList.size();
for(int i = 0; i < size; i++)
updateItem(animateList.get(i));
frame++;
if(size > 0)
getDisplay().timerExec(ANIMATION_INTERVAL, this);
}
}
};
static final QItem[] EMPTY_ITEMS = new QItem[0];
static final int MAX_DEEPTH = 16;
static final int ITEM_TOP_MARGIN = 3;
static final int ITEM_BOTTOM_MARGIN = 3;
static final int ITEM_IMAGE_TEXT_SPACING = 3;
static final int ITEM_LEFT_MARING = 2;
static final int ITEM_RIGHT_MARGIN = 2;
static final int PREFIX_ICON_SPACING = 2;
static final char TAB = '\t';
// 汉字声母与GB2312编码对照表
private static char[][] pyTable = new char[][] {
{0xB0A1, 0xB0C4, 'a'},
{0xB0C5, 0xB2C0, 'b'},
{0xB2C1, 0xB4ED, 'c'},
{0xB4EE, 0xB6E9, 'd'},
{0xB6EA, 0xB7A1, 'e'},
{0xB7A2, 0xB8C0, 'f'},
{0xB8C1, 0xB9FD, 'g'},
{0xB9FE, 0xBBF6, 'h'},
{0xBBF7, 0xBFA5, 'j'},
{0xBFA6, 0xC0AB, 'k'},
{0xC0AC, 0xC2E7, 'l'},
{0xC2E8, 0xC4C2, 'm'},
{0xC4C3, 0xC5B5, 'n'},
{0xC5B6, 0xC5BD, 'o'},
{0xC5BE, 0xC6D9, 'p'},
{0xC6DA, 0xC8BA, 'q'},
{0xC8BB, 0xC8F5, 'r'},
{0xC8F6, 0xCBF9, 's'},
{0xCBFA, 0xCDD9, 't'},
{0xCDDA, 0xCEF3, 'w'},
{0xCEF4, 0xD1B8, 'x'},
{0xD1B9, 0xD4D0, 'y'},
{0xD4D1, 0xD7F9, 'z'}
};
private static final int ANIMATION_INTERVAL = 500;
private QItem[] items;
private int itemCount;
private List<QItem> expandCache;
// item的图标文字布局和图标大小数组,按照层次关系保存,顶层的由itemLayouts[0]指定,等等
private ItemLayout[] itemLayouts;
private int[] itemImageSizes;
int verticalOffset;
int fontHeight;
private int clientAreaHeight;
private int clientAreaWidth;
private int levelIndent;
private int deferredHeight;
// 事件监听器
private Listener listener;
private List<IQTreeListener> qtreeListeners;
private QItem itemUnderMouse;
private boolean enableIconHover;
private int frame;
List<QItem> animateList;
private boolean editable;
private QTreeEditor editor;
// 快速查找结果缓冲
private List<QItem> findResult;
private int nextResult;
// 上一次快速查找所用的字符
private char lastChar;
// 用什么键打开/收起item
private int itemOpenOnButton;
private boolean expandItemOnSingleClick;
// 按键处理的映射表,key是按键值,value是动作的id
private Map<Integer, Integer> keyActionMap;
// 用户自定义按键的映射表,key是按键值,vlaue是Runnable对象
private Map<Integer, Runnable> userActionMap;
/** 用户自定义按键 */
public static final int CUSTOM_ACTION = 1;
/** 下翻页 */
public static final int PAGE_DOWN = 2;
/** 上翻页 */
public static final int PAGE_UP = 3;
/** 到最顶 */
public static final int GO_TOP = 4;
/** 到最底 */
public static final int GO_BOTTOM = 5;
// 是否是Mac
private static final boolean IS_CARBON;
static {
// 如果不是Mac,底层也不是gtk,则做双缓冲
String platform = SWT.getPlatform();
IS_CARBON = "carbon".equals(platform);
}
/**
* 创建一个QTree对象
*
* @param parent
* 父容器
*/
public QTree(Composite parent) {
super(parent, SWT.V_SCROLL | SWT.DOUBLE_BUFFERED);
itemCount = 0;
items = new QItem[16];
itemLayouts = new ItemLayout[MAX_DEEPTH];
itemImageSizes = new int[MAX_DEEPTH];
verticalOffset = 0;
fontHeight = calculateFontHeight();
clientAreaHeight = clientAreaWidth = 0;
deferredHeight = 0;
enableIconHover = true;
itemUnderMouse = null;
animateList = new ArrayList<QItem>();
findResult = new ArrayList<QItem>();
lastChar = 0;
nextResult = 0;
levelIndent = 16;
keyActionMap = new HashMap<Integer, Integer>();
userActionMap = new HashMap<Integer, Runnable>();
itemOpenOnButton = SWT.BUTTON1;
expandItemOnSingleClick = true;
qtreeListeners = new ArrayList<IQTreeListener>();
editable = true;
expandCache = new ArrayList<QItem>();
for(int i = 0; i < MAX_DEEPTH; i++) {
itemLayouts[i] = ItemLayout.HORIZONTAL;
itemImageSizes[i] = 16;
}
installListeners();
createKeyBindings();
editor = new QTreeEditor(this);
}
/**
* 装载事件监听器
*/
private void installListeners() {
listener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose:
handleDispose(event);
break;
case SWT.KeyDown:
handleKeyDown(event);
break;
case SWT.MouseDown:
handleMouseDown(event);
break;
case SWT.MouseUp:
handleMouseUp(event);
break;
case SWT.MouseDoubleClick:
handleMouseDoubleClick(event);
break;
case SWT.MouseMove:
handleMouseMove(event);
break;
case SWT.MouseExit:
handleMouseExit(event);
break;
case SWT.Paint:
handlePaint(event);
break;
case SWT.Resize:
handleResize(event);
break;
case SWT.Traverse:
handleTraverse(event);
break;
}
}
};
ScrollBar verticalBar = getVerticalBar();
if (verticalBar != null) {
verticalBar.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
handleVerticalScroll(event);
}
});
}
addListener(SWT.Dispose, listener);
addListener(SWT.KeyDown, listener);
addListener(SWT.MouseDown, listener);
addListener(SWT.MouseUp, listener);
addListener(SWT.MouseDoubleClick, listener);
addListener(SWT.MouseMove, listener);
addListener(SWT.MouseExit, listener);
addListener(SWT.Paint, listener);
addListener(SWT.Resize, listener);
addListener(SWT.Traverse, listener);
}
/**
* 处理鼠标按下事件
*
* @param event
*/
protected void handleMouseDown(Event event) {
if(editor.getEditor() != null) {
Text t = (Text)editor.getEditor();
editor.getItem().setText(t.getText());
}
disposeEditor();
setFocus();
}
/**
* 如果有编辑器打开,关闭它
*/
private void disposeEditor() {
if(editor.getEditor() != null)
editor.getEditor().dispose();
editor.setEditor(null);
editor.setItem(null);
}
/**
* 处理鼠标移出事件
*
* @param event
*/
protected void handleMouseExit(Event event) {
// 恢复旧item的状态
if(itemUnderMouse != null) {
itemUnderMouse.clearHitTestFlag();
itemUnderMouse = null;
}
}
/**
* 处理垂直滚动事件
*
* @param event
*/
protected void handleVerticalScroll(Event event) {
// 检查是否和现有值一样
ScrollBar verticalBar = getVerticalBar();
if(verticalBar == null)
return;
int newOffset = verticalBar.getSelection();
if(newOffset == verticalOffset)
return;
setVerticalOffset(newOffset, false);
}
/**
* 设置垂直位移
*
* @param newOffset
* 新的位移值
*/
private void setVerticalOffset(int newOffset, boolean adjustVerticalBar) {
// 设置新位置
ScrollBar bar = getVerticalBar();
if(bar == null)
return;
if(adjustVerticalBar)
bar.setSelection(newOffset);
// 校验范围
if(newOffset > bar.getMaximum() - bar.getThumb())
newOffset = bar.getMaximum() - bar.getThumb();
if(newOffset < 0)
newOffset = 0;
// 滚动
int delta = newOffset - verticalOffset;
int srcY = (delta > 0) ? delta : 0;
int destY = (delta > 0) ? 0 : -delta;
delta = Math.abs(delta);
int copyHeight = clientAreaHeight - delta;
scroll(0, destY, 0, srcY, clientAreaWidth, copyHeight);
int gap = (delta << 1) - clientAreaHeight;
if(gap > 0)
redraw(0, copyHeight, clientAreaWidth, gap, true);
verticalOffset = newOffset;
if(verticalOffset > 0 && !bar.getVisible()) {
bar.setMaximum(getTotalHeight() + clientAreaHeight - getItemHeight(0));
bar.setPageIncrement(clientAreaHeight);
bar.setThumb(clientAreaHeight);
bar.setIncrement(10);
bar.setVisible(true);
} else if(verticalOffset == 0 && bar.getVisible() && getTotalHeight() <= clientAreaHeight) {
bar.setVisible(false);
}
}
/**
* 滚动客户区
*
* @param destX
* @param destY
* @param srcX
* @param srcY
* @param width
* @param height
*/
private void scroll(int destX, int destY, int srcX, int srcY, int width, int height) {
GC gc = new GC(this);
gc.copyArea(srcX, srcY, width, height, destX, destY);
gc.dispose();
if(clientAreaHeight - height >= height)
redraw(srcX, srcY, width, height, true);
else {
int h = clientAreaHeight - height;
int oldDeferredHeight = deferredHeight;
deferredHeight += h;
redraw(srcX, (srcY >= destY) ? (height - oldDeferredHeight) : srcY, width, h + oldDeferredHeight, true);
}
}
/**
* 处理按钮遍历事件
*
* @param event
*/
protected void handleTraverse(Event event) {
switch (event.detail) {
case SWT.TRAVERSE_PAGE_NEXT:
case SWT.TRAVERSE_PAGE_PREVIOUS:
event.doit = false;
break;
default:
event.doit = true;
break;
}
}
/**
* 处理resize事件
*
* @param event
*/
protected void handleResize(Event event) {
Rectangle clientArea = getClientArea();
int oldHeight = clientAreaHeight;
clientAreaHeight = clientArea.height;
clientAreaWidth = clientArea.width;
if(oldHeight != clientAreaHeight)
setScrollBar();
}
/**
* 处理重画事件
*
* @param event
*/
protected void handlePaint(Event event) {
// Check if there is work to do
Rectangle clientArea = getClientArea();
if (event.height <= 0)
return;
if (clientArea.width == 0 || clientArea.height == 0)
return;
// get gc and other info
QItem item = getItemAtY(event.y + verticalOffset);
int paintY = (item == null) ? event.y : (getYAtItem(item) - verticalOffset);
int endY = event.y + event.height;
GC gc = event.gc;
// 如果gtk设置颜色时出现错误,则先用背景色重画所有部分
if(isGTKColorError()) {
gc.setBackground(getBackground());
gc.fillRectangle(0, paintY, clientArea.width, endY - paintY);
}
// draw item
while(paintY <= endY && item != null) {
item.onPaint(gc, paintY, frame);
paintY += getItemHeight(item.getLevel());
item = nextItem(item);
}
if(!isGTKColorError() && paintY < endY) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -