📄 chatbox.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;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Caret;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ScrollBar;
import edu.tsinghua.lumaqq.IconHolder;
import edu.tsinghua.lumaqq.LumaQQ;
/**
* 支持输入文字和图像的编辑框,并且有垂直卷滚条,主要用于聊天
*
* @author 马若劼
*/
public class ChatBox extends Canvas implements PaintListener, DisposeListener, MouseListener, KeyListener, TraverseListener, MouseMoveListener, ControlListener {
// 一行的缓冲类
private class LineCache {
// 超出的表情,如果有的话
private int[] exceedSmiley;
// 本行是否是硬分行,所谓硬分行是指用户在行的末尾输入了回车符,
// 而软分行则是因为长度太大,warp造成的
private boolean hardLine;
// 是否包含了表情
private boolean hasSmiley;
private StringBuffer sb;
// 表情序号的数组
private int[] smileyArray;
// 表情的个数
private int smileyNum;
// 当前行是否收到鼠标选择影响,以及影响的字符开始和结束范围
private boolean affected;
private int affectedBegin, affectedEnd;
private int temp;
/**
* 构造函数
*/
public LineCache() {
sb = new StringBuffer();
hasSmiley = false;
smileyNum = 0;
smileyArray = new int[10];
hardLine = true;
affected = false;
}
// 返回这一行包含的字符数,这个字符数包含了表情数,其他语言的字符也只算一个字符
public int getCharSize() {
int i = 0, j, size = 0;
while((j = sb.indexOf("\0", i)) != -1) {
String sub = sb.substring(i, j);
size += sub.length();
i = j + 1;
}
if(i < sb.length()) {
String sub = sb.substring(i);
size += sub.length();
}
size += smileyNum;
return size;
}
// 追加一个字符
public void append(char c) {
sb.append(c);
}
// 追加一个字符串
public void append(String s) {
if(s == null) return;
sb.append(s);
}
// 追加一个字符数组
public void append(char[] c) {
if(c == null) return;
sb.append(c);
}
// 追加一个表情
public void appendSmiley(int smileyId) {
sb.append((char)0);
if(smileyNum + 1 > smileyArray.length)
smileyArray = enlargeArray(smileyArray);
smileyArray[smileyNum++] = smileyId;
hasSmiley = true;
}
// 追加一串表情
public void appendSmiley(int[] smiley) {
if(smiley == null) return;
while(smileyNum + smiley.length > smileyArray.length)
smileyArray = enlargeArray(smileyArray);
System.arraycopy(smiley, 0, smileyArray, smileyNum, smiley.length);
smileyNum += smiley.length;
hasSmiley = true;
}
// 删除index之后的(包括index)的字符,并把其后的smiley置于
// exceedSmiley中
public char[] delete(int index) {
return delete(index, sb.length());
}
// 删除从begin到end的字符,不包括end
public char[] delete(int begin, int end) {
exceedSmiley = null;
if(sb.length() == 0) return null;
if(begin > sb.length()) begin = sb.length();
if(end < 0) end = 0;
if(begin >= end) return null;
char[] ret = new char[end - begin];
sb.getChars(begin, end, ret, 0);
int smileyDeleted = getSmileyNum(begin, end);
if(smileyDeleted > 0) {
int smileyBegin = getSmileyNumBefore(begin);
exceedSmiley = new int[smileyDeleted];
System.arraycopy(smileyArray, smileyBegin, exceedSmiley, 0, smileyDeleted);
System.arraycopy(smileyArray, smileyBegin + smileyDeleted, smileyArray, smileyBegin, smileyNum - smileyBegin - smileyDeleted);
}
smileyNum -= smileyDeleted;
if(smileyNum == 0) hasSmiley = false;
// 删除后面的字符
sb.delete(begin, end);
return ret;
}
// 画出这一行,起始点为x, y
public void drawLine(GC gc, int x, int y) {
// 表情和文字哪种高,另外一种的y坐标就要下沉
int textY = y, smileyY = y, textX = x;
if(hasSmiley) {
int h = gc.getFontMetrics().getHeight();
if(h >= 20)
smileyY += h - 20;
else
textY += 20 - h;
}
// 开始画
temp = 0;
if(affected) {
if(affectedBegin > 0)
textX = drawString(gc, sb.substring(0, affectedBegin), textX, textY, smileyY, false);
if(affectedEnd > affectedBegin)
textX = drawString(gc, sb.substring(affectedBegin, affectedEnd), textX, textY, smileyY, true);
if(sb.length() > affectedEnd)
drawString(gc, sb.substring(affectedEnd, sb.length()), textX, textY, smileyY, false);
} else
drawString(gc, sb.toString(), textX, textY, smileyY, false);
}
// 画一个字符串
private int drawString(GC gc, String str, int x, int y, int smileyY, boolean reverse) {
Color fg = null;
Color bg = null;
if(reverse) {
fg = gc.getForeground();
bg = gc.getBackground();
gc.setForeground(bg);
gc.setBackground(fg);
}
int i = 0, j;
while((j = str.indexOf("\0", i)) != -1) {
String sub = str.substring(i, j);
if(!sub.equals("")) {
gc.drawString(sub, x, y);
x += gc.textExtent(sub).x;
}
Image smileyImage = icons.getSmiley(smileyArray[temp++]);
if(smileyImage != null) {
gc.drawImage(smileyImage, x, smileyY);
x += 20;
}
i = j + 1;
}
// 最后一串字符
if(i < str.length()) {
String sub = str.substring(i);
if(!sub.equals("")) {
gc.drawString(sub, x, y);
x += gc.textExtent(sub).x;
}
}
if(reverse) {
gc.setForeground(fg);
gc.setBackground(bg);
}
return x;
}
// 数组扩容
private int[] enlargeArray(int[] array) {
int[] ret = new int[array.length + 10];
System.arraycopy(array, 0, ret, 0, array.length);
return ret;
}
// 从头部得到一个字符偏移,偏移之前(不包括偏移)的总宽度要刚好小于等于width
public int getBeginCharIndexLessThanWidth(GC gc, int width) {
if(width < 0) return 0;
// 我这里先暂且用一种近似的算法来计算这个索引
// 先根据width和clientArea的比值得到一个索引的相对位置
// 然后再一个字符一个字符的调整,就这样
int len = sb.length();
int init = (len * width) / (getWidth(gc, 0, sb.length()) + 1); // 加1是为了防止他等于0
if(init > len) init = len;
int w = getWidthBefore(gc, init);
if(w < width && init < len) {
while(w < width && init < len) {
init++;
w = getWidthBefore(gc, init);
}
if(w != width)
init--;
} else if(w > width) {
while(w > width) {
init--;
w = getWidthBefore(gc, init);
}
}
return init;
}
// 返回string,这个string不会包含表情的占位符
public String getString() {
return getString(0, sb.length());
}
// 返回从begin开始到end结束的不包含表情占位符号的字符串
public String getString(int begin, int end) {
StringBuffer ret = new StringBuffer();
for(int i = begin; i < end; i++) {
if(sb.charAt(i) != 0)
ret.append(sb.charAt(i));
}
return ret.toString();
}
// 返回所有的字符
public char[] getChars() {
return getChars(0, sb.length());
}
// 返回从begin到end的字符串,不包括end
public char[] getChars(int begin, int end) {
if(sb.length() == 0) return null;
if(begin < 0) begin = 0;
if(end > sb.length()) end = sb.length();
if(begin >= end) return null;
char[] c = new char[end - begin];
sb.getChars(begin, end, c, 0);
return c;
}
// 从尾部得到一个字符偏移,偏移之后(包括偏移)的总宽度要刚好大于等于width
public int getEndCharIndexLargerThanWidth(GC gc, int width) {
if(width < 0) return sb.length() - 1;
int len = sb.length();
int init = (len * width) / (getWidth(gc, 0, sb.length()) + 1); // 加1是为了防止他等于0
if(init > len) init = 0;
int w = getWidthAfter(gc, init);
if(w < width) {
while(w < width && init > 0) {
init--;
w = getWidthAfter(gc, init);
}
} else if(w > width) {
while(w > width) {
init++;
w = getWidthAfter(gc, init);
}
if(w != width)
init--;
}
return init;
}
// 返回超出长度的部分,并把这部分从这行删除
public char[] getExceed(GC gc) {
// 计算现在的总长度
int width = getWidth(gc, 0, sb.length());
// 计算超出的长度
int exceed = width + 2 * margin - clientArea.width;
// 如果超出的长度小于等于0,则返回null
if(exceed <= 0)
return null;
else { // 如果超出了,计算需要减少几个字符才能不超出
int w = 0, i = sb.length(), s = 0;
while(exceed > w) {
char c = sb.charAt(--i);
if(c == 0) { // 注意字符里面有表情的代表符号,0x0,碰到表情,算20的宽度
w += 20;
// 注意要表情数加1,为什么不直接把smileyNum减1?因为还需要得到被踢出
// 这一行的表情
s++;
}
else // 如果不是表情,则计算字符宽度
w += gc.textExtent(new String(new char[] { c })).x;
}
// 删除超出的部分
return delete(i);
}
}
// 返回所有的表情
public int[] getSmiley() {
return getSmiley(0, smileyNum);
}
// 返回第begin到end个表情,不包括第end个
public int[] getSmiley(int begin, int end) {
if(begin < 0) begin = 0;
if(end > smileyNum) end = smileyNum;
if(begin >= end) return null;
int[] ret = new int[end - begin];
System.arraycopy(smileyArray, begin, ret, 0, end - begin);
return ret;
}
// 找出从begin到end之间的表情数,不包括end
private int getSmileyNum(int begin, int end) {
int ret = 0;
while(true) {
int index = sb.indexOf("\0", begin);
if(index != -1 && index < end) {
ret++;
begin = index + 1;
} else
break;
}
return ret;
}
// 找出从index开始往后数,表情的个数,包括index
private int getSmileyNumAfter(int index) {
return getSmileyNum(index, sb.length());
}
// 找出从index开始往前数,表情的个数,不包括index
private int getSmileyNumBefore(int index) {
return getSmileyNum(0, index);
}
// 返回整个串的长度
public int getWidth(GC gc) {
return getWidth(gc, 0, sb.length());
}
// 返回从index开始到end为止的字符宽度,不包括end
public int getWidth(GC gc, int begin, int end) {
if(end > sb.length()) end = sb.length();
if(begin < 0) begin = 0;
if(begin >= end) return 0;
String str = getString(begin, end);
int x = gc.textExtent(str).x;
int s = getSmileyNum(begin, end);
return x + s * 20;
}
// 返回从index开始到结尾的字符宽度,包括index
public int getWidthAfter(GC gc, int index) {
return getWidth(gc, index, sb.length());
}
// 返回index开始之前的字符总宽度,不包括index
public int getWidthBefore(GC gc, int index) {
return getWidth(gc, 0, index);
}
// 插入一个字符
public void insert(int index, char c) {
sb.insert(index, c);
}
//插入一个字符数组
public void insert(int index, char[] c) {
if(c == null) return;
sb.insert(index, c);
}
// 插入一个表情
public void insertSmiley(int index, int smileyId) {
sb.insert(index, (char)0);
if(smileyNum + 1> smileyArray.length)
smileyArray = enlargeArray(smileyArray);
int s = getSmileyNumBefore(index);
System.arraycopy(smileyArray, s, smileyArray, s + 1, smileyNum - s);
smileyArray[s] = smileyId;
smileyNum++;
hasSmiley = true;
}
// 在index字符处,插入表情
public void insertSmiley(int index, int[] smiley) {
if(smiley == null) return;
while(smileyNum + smiley.length > smileyArray.length)
smileyArray = enlargeArray(smileyArray);
int s = getSmileyNumBefore(index);
System.arraycopy(smileyArray, s, smileyArray, s + smiley.length, smileyNum - s);
System.arraycopy(smiley, 0, smileyArray, s, smiley.length);
smileyNum += smiley.length;
hasSmiley = true;
}
// 返回字符总数
public int size() {
return sb.length();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -