📄 chatbox.java
字号:
}
}
// 光标
private Cursor beamCursor;
private Caret caret;
// 当前光标所在字符数
private int caretChar;
// 当前光标所在行数
private int caretLine;
// 客户区域大小
private Rectangle clientArea;
private Display display;
// 当前鼠标是否按下,和按下时的坐标
private boolean down;
private int downX, downY;
// 当前是否有区域被选择
private boolean selected;
// 当前字体的描述信息
private FontMetrics fontMetrics;
// 用户输入缓冲区对象数组
private List lineBufs;
// 当前总行数
private int lines;
private int margin = 5;
// 最大字符数限制
private int maxChar;
private ScrollBar vBar;
// view port看到的第一行的行号和最后一行的行号
private int viewBeginLine;
private int viewEndLine;
// 当前被影响的行
private int affectedBeginLine, affectedEndLine;
// 拷贝缓冲区,不带标签占位符
private String regularCopy;
// 拷贝缓冲区,带表情,可以用parseText方法解析出若干linecache
private String extendCopy;
// 剪贴板
private Clipboard clipboard;
// IconHolder实例
private IconHolder icons = IconHolder.getInstance();
// 当前是否enable,对于disable的,只响应鼠标消息和ctrl + a, ctrl + c事件
private boolean enabled;
// disable时的背景色
private Color color;
// 右键菜单,有两个,一个在enable时显示,一个在disable时显示,也可以搞一个,我觉得搞两个好些
private Menu enableMenu, disableMenu;
// 表示换行是用Enter还是用Ctrl + Enter,不用的一方将忽略不做处理
// 注意这个useEnter和OptionUtils中的useEnter并不是一个意思,这个是用来
// 换行的,option中的是用来发送消息的,他们两个在实际中将会是相反的值
private boolean useEnter;
/**
* @param parent
* @param style
*/
public ChatBox(Composite parent, int style) {
super(parent, SWT.V_SCROLL);
initVariable();
initMenu();
}
/**
* 初始化菜单项
*/
private void initMenu() {
// enable时的弹出菜单
enableMenu = new Menu(this);
// 剪切菜单
MenuItem mi = new MenuItem(enableMenu, SWT.PUSH);
mi.setText(LumaQQ.getResourceString("chatbox.menu.cut"));
mi.addSelectionListener(
new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleCtrlX();
}
}
);
// 复制菜单
mi = new MenuItem(enableMenu, SWT.PUSH);
mi.setText(LumaQQ.getResourceString("chatbox.menu.copy"));
mi.addSelectionListener(
new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleCtrlC();
}
}
);
// 粘贴菜单
mi = new MenuItem(enableMenu, SWT.PUSH);
mi.setText(LumaQQ.getResourceString("chatbox.menu.paste"));
mi.addSelectionListener(
new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleCtrlV();
}
}
);
// 添加菜单显示监听器
enableMenu.addMenuListener(
new MenuAdapter() {
public void menuShown(MenuEvent e) {
enableMenu.getItem(0).setEnabled(selected);
enableMenu.getItem(1).setEnabled(selected);
}
}
);
// disable时的菜单
disableMenu = new Menu(this);
// 复制菜单
mi = new MenuItem(disableMenu, SWT.PUSH);
mi.setText(LumaQQ.getResourceString("chatbox.menu.copy"));
mi.addSelectionListener(
new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleCtrlC();
}
}
);
// 全选菜单
mi = new MenuItem(disableMenu, SWT.PUSH);
mi.setText(LumaQQ.getResourceString("chatbox.menu.select.all"));
mi.addSelectionListener(
new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
selectAll();
}
}
);
// 添加菜单显示监听器
disableMenu.addMenuListener(
new MenuAdapter() {
public void menuShown(MenuEvent e) {
disableMenu.getItem(0).setEnabled(selected);
}
}
);
}
/**
* 在当前光标位置添加一个表情
* @param smileyId
*/
public void addSmiley(int smileyId) {
handleCharacter((char)0, smileyId);
}
// 调整从lineCache开始的一系列软分行行
private int adjustLine(GC gc, LineCache lineCache) {
// 调整过程中新增了多少行
int newLine = 0;
// 当前行
int curLine = lineBufs.indexOf(lineCache);
// 得到当前行宽
int width = lineCache.getWidth(gc);
// 检查是否超出边界
recalculateClientArea();
if(clientArea.width == 0) return 0;
width -= clientArea.width - 2 * margin;
while(width > 0 || width <= 0 && !lineCache.hardLine) {
if(width > 0) {
// 得到下一行,如果没有且当前行是硬分行,则创建一行
LineCache nextLine = null;
if(lineCache.hardLine) {
nextLine = new LineCache();
lineBufs.add(curLine + 1, nextLine);
newLine++;
lineCache.hardLine = false;
} else
nextLine = (LineCache)lineBufs.get(curLine + 1);
// 得到维持宽度不超出所需要删掉的字符数
int index = lineCache.getEndCharIndexLargerThanWidth(gc, width);
nextLine.insert(0, lineCache.delete(index));
nextLine.insertSmiley(0, lineCache.exceedSmiley);
// 置下一轮循环条件
lineCache = nextLine;
curLine++;
} else {
LineCache nextLine = (LineCache)lineBufs.get(curLine + 1);
lineBufs.remove(curLine + 1);
lineCache.append(nextLine.getChars());
lineCache.appendSmiley(nextLine.getSmiley());
lineCache.hardLine = nextLine.hardLine;
newLine--;
}
width = lineCache.getWidth(gc) - clientArea.width + 2 * margin;
}
return newLine;
}
// 调整垂直卷滚条的参数
private void adjustVerticalBar() {
vBar.setMaximum(lines);
vBar.setThumb(viewEndLine - viewBeginLine + 1);
vBar.setSelection(viewBeginLine);
if(viewBeginLine > 0 || viewEndLine < lines - 1)
vBar.setVisible(true);
else
vBar.setVisible(false);
}
// 根据鼠标位置计算光标所在字符
private int calcCaretChar(GC gc, int x, int line) {
LineCache lineCache = (LineCache)lineBufs.get(line);
return lineCache.getBeginCharIndexLessThanWidth(gc, x);
}
// 根据鼠标位置计算光标所在行
private int calcCaretLine(int y) {
y -= margin;
int cl, i = 0;
while(y > 0)
y -= getLineHeight(i++) + margin;
cl = viewBeginLine + i - 1;
if(cl < viewBeginLine)
cl = viewBeginLine;
else if(cl > viewEndLine)
cl = viewEndLine;
return cl;
}
// 画一个Bevel矩形
private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright)
{
Color old = gc.getForeground();
gc.setForeground(bottomright);
gc.drawLine(x + w, y, x + w, y + h);
gc.drawLine(x, y + h, x + w, y + h);
gc.setForeground(topleft);
gc.drawLine(x, y, (x + w) - 1, y);
gc.drawLine(x, y, x, (y + h) - 1);
gc.setForeground(old);
}
// 给定一个结束行,根据客户区的大小判断开始行的行号
private int getBeginLine(int end) {
int h = margin;
int begin = end + 1;
while(h < clientArea.height && begin >= 0)
h += getLineHeight(--begin) + margin;
return begin + 1;
}
// 给定一个开始行,根据客户区的大小判断结束行的行号
private int getEndLine(int begin) {
int h = margin;
int end = begin - 1;
while(h < clientArea.height && end <= lines - 1)
h += getLineHeight(++end) + margin;
return end - 1;
}
// 计算某行的高度
private int getLineHeight(int line) {
LineCache lineCache;
if(line < 0 || line >= lineBufs.size()) return 0;
else lineCache = (LineCache)lineBufs.get(line);
if(lineCache.hasSmiley)
return Math.max(fontMetrics.getHeight(), 20);
else
return fontMetrics.getHeight();
}
// 返回给定行号的Y坐标,这个坐标是相对于视口的坐标,行号也是一个相对行号
private int getLineY(int line) {
if(line < viewBeginLine) return -100;
int y = margin;
for(int i = viewBeginLine; i < line; i++)
y += getLineHeight(i) + margin;
return y;
}
// 处理back space按键
private void handleBackSpaceKey() {
GC gc = new GC(this);
LineCache lineCache = (LineCache)lineBufs.get(caretLine);
// 如果当前有选择部分则删除
if(selected) {
handleCharacter(lineCache, (char)0, 0, null, true, false);
return;
}
// 如果光标处于最前,则直接返回
if(caretChar == 0 && caretLine == 0) return;
if(lineCache.hardLine && caretChar > 0) {
lineCache.delete(caretChar - 1, caretChar);
caretChar--;
setCaretLine(caretLine);
} else {
if(caretChar == 0 && caretLine > 0) {
lineCache = (LineCache)lineBufs.get(caretLine - 1);
caretLine--;
caretChar = lineCache.size();
if(caretLine < viewBeginLine)
viewBeginLine--;
}
// 删除一个字符
if(!lineCache.hardLine) {
lineCache.delete(caretChar - 1, caretChar);
caretChar--;
}
// 删除下一行
LineCache nextLine = (LineCache)lineBufs.get(caretLine + 1);
lineBufs.remove(caretLine + 1);
lineCache.append(nextLine.getChars());
lineCache.appendSmiley(nextLine.getSmiley());
lineCache.hardLine = nextLine.hardLine;
// 调整行
int i = adjustLine(gc, lineCache) - 1;
lines += i;
if(i < 0)
viewEndLine = getEndLine(viewBeginLine);
// 调整卷滚条
adjustVerticalBar();
setCaretLine(caretLine);
}
gc.dispose();
redraw();
}
// 在一行中插入一个字符,可以选择不插入,如果有选择部分,何以选择保存选择部分
// 方法返回删除的行数
private int insertChar(LineCache lineCache, char c, int smileyId, LineCache insertLine, boolean noInsert, boolean saveDelete) {
int lineDeleted = 0;
// 如果有选择部分,删除
if(selected) {
if(saveDelete) {
LineCache copyCache = deleteSelected();
regularCopy = copyCache.getString();
clipboard.setContents(new Object[] { regularCopy }, new Transfer[] { TextTransfer.getInstance()});
} else
deleteSelected();
for(int i = affectedEndLine - 1; i > affectedBeginLine; i--) {
LineCache line = (LineCache)lineBufs.get(i);
if(line.size() == 0) {
lineBufs.remove(i);
lineDeleted--;
}
}
}
// 插入这个字符
if(!noInsert) {
if(insertLine != null) {
lineCache.insert(caretChar, insertLine.getChars());
lineCache.insertSmiley(caretChar, insertLine.getSmiley());
} else if(c == 0)
lineCache.insertSmiley(caretChar, smileyId);
else
lineCache.insert(caretChar, c);
}
// 把最后一行选择的行的内容附加到当前行后,只有一种情况不需要附加
// 就是选择的行只有一行(就是当前行),且此行为硬分行
if(selected) {
selected = false;
if(!(affectedEndLine == affectedBeginLine && lineCache.hardLine)) {
if(caretLine + 1 < lineBufs.size()) {
LineCache line = null;
for(line = (LineCache)lineBufs.get(caretLine + 1); !line.hardLine; line = (LineCache)lineBufs.get(caretLine + 1)) {
lineCache.append(line.getChars());
lineCache.appendSmiley(line.getSmiley());
lineBufs.remove(caretLine + 1);
lineDeleted--;
}
lineCache.append(line.getChars());
lineCache.appendSmiley(line.getSmiley());
lineBufs.remove(caretLine + 1);
lineCache.hardLine = true;
lineDeleted--;
} else
lineCache.hardLine = true;
}
}
return lineDeleted;
}
/**
* 处理字符输入事件,但是也可以用来处理删除选择内容,copy cut等等
* @param lineCache 这是当前行
* @param c 这是要插入的字符
* @param smileyId 要插入的表情
* @param insertLine 要插入的行
* @param noInsert 是否插入
* @param saveDelete 是否保存删除的内容到剪贴板
*/
private void handleCharacter(LineCache lineCache, char c, int smileyId, LineCache insertLine, boolean noInsert, boolean saveDelete) {
GC gc = new GC(this);
// 调整行
int newLine = insertChar(lineCache, c, smileyId, insertLine, noInsert, saveDelete) + adjustLine(gc, lineCache);
// 如果光标本来是在最后,则下移一行,要注意判断粘贴的情况
if(!noInsert) {
if(insertLine != null) {
int t = caretChar + insertLine.size();
t -= lineCache.size();
int i = 0;
while(t > 0) {
i++;
t -= ((LineCache)lineBufs.get(caretLine + i)).size();
}
setCaretLine(caretLine + i);
caretChar = t + ((LineCache)lineBufs.get(caretLine)).size();
} else if(caretChar + 1 > lineCache.size()) {
caretChar = 1;
caretLine++;
} else {
setCaretLine(caretLine);
caretChar++;
}
}
// 如果调整过程中产生了新行,则调整总行数和视口
if(newLine != 0) {
lines += newLine;
if(caretLine < viewBeginLine) {
viewBeginLine = caretLine;
viewEndLine = getEndLine(viewBeginLine);
} else if(caretLine > viewEndLine) {
viewEndLine = caretLine;
viewBeginLine = getBeginLine(viewEndLine);
} else
viewEndLine = getEndLine(viewBeginLine);
adjustVerticalBar();
}
gc.dispose();
redraw();
}
// 处理普通输入事件
private void handleCharacter(char c, int smileyId) {
LineCache lineCache = (LineCache)lineBufs.get(caretLine);
handleCharacter(lineCache, c, smileyId, null, false, false);
}
// 处理delete按键
private void handleDeleteKey() {
// 如果光标处于最末尾,则直接返回
LineCache lineCache = (LineCache)lineBufs.get(caretLine);
if(caretChar == lineCache.size() && caretLine == lines - 1) return;
// 如果当前有选择的内容,则删除选择内容
if(selected) {
handleCharacter(lineCache, (char)0, 0, null, true, false);
return;
}
int i;
if(lineCache.hardLine && caretChar < lineCache.size()) {
lineCache.delete(caretChar, caretChar + 1);
} else {
GC gc = new GC(this);
// 得到下一行
LineCache nextLine = (LineCache)lineBufs.get(caretLine + 1);
// 如果是软分行,删除这个字符,硬分行不删,因为光标在末尾,等于删掉了回车
// 如果是软分行且在末尾,则删掉下一行的第一个
if(!lineCache.hardLine) {
if(caretChar < lineCache.size())
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -