⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 richbox.java

📁 lumaQQ的源文件
💻 JAVA
📖 第 1 页 / 共 5 页
字号:
/*
* 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.rich;

import static edu.tsinghua.lumaqq.resource.Messages.*;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
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.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;

import edu.tsinghua.lumaqq.widgets.menu.CMenu;
import edu.tsinghua.lumaqq.widgets.menu.CMenuItem;
import edu.tsinghua.lumaqq.widgets.menu.IMenuListener;
import edu.tsinghua.lumaqq.widgets.menu.ISelectionListener;
import edu.tsinghua.lumaqq.widgets.menu.MenuEvent;
import edu.tsinghua.lumaqq.widgets.menu.SelectionEvent;

/**
 * 新的聊天输入框,支持不同大小字体,颜色,显示任意大小图像
 * 
 * @author luma
 */
public class RichBox extends Canvas {
	// 光标
	private Cursor beamCursor;
	// 游标
	private Caret caret;
	// 只读标志
    private boolean readonly;
    // 事件监听器
    private Listener listener;
    // 垂直偏移
	private int verticalScrollOffset;
    // view顶部的行号
	private int topLine; 
	// 上次重画时的最顶行
	private int lastPaintTopLine;
	// content and other tools
	private IWrappedRichContent content;
	private ContentHelper contentHelper;
	// GC和gc引用次数
	private GC gc;
	private int gcReference;
	
	// the client area height. Needed to calculate
	// content width for new
	private int clientAreaHeight;
	// the client area width. Needed during Resize
	// callback to determine
	private int clientAreaWidth; 
	
	// margins
	private int leftMargin;
	private int topMargin;
	private int rightMargin;
	private int bottomMargin;
	
	// 光标的X偏移位置,不包括左边缘,这个变量用来保证光标上下移动时有一个基本的基准点
	private int columnX;
	
	// 选择的起始和结束偏移,不包括结束位置
	private int selectionStart, selectionEnd;
	// 选择的锚点,选择开始的参考点
	private int selectionAnchor;
	// true表示运行用双击选择行
	private boolean doubleClickEnabled;

	// 右键菜单
	private boolean enableContextMenu;
	private CMenu contextMenu;
	
	// true表示禁止动画
	private boolean forbiddenAnimation;
	
	// caret offset,注意要处理图片的情况,过一个图片我们要把offset增加2
	private int caretOffset;
	
	// 自动滚动的方向
	private int autoScrollDirection;
	
	// 通过用这个颜色在图片上画一次,使图片成为被选择状态
	private Color backXorColor;
	
	// 剪贴板
	private Clipboard clipboard;
	
	static final char TAB = '\t';
	
	// 按键处理的映射表,key是按键值,value是动作的id
	private Map<Integer, Integer> keyActionMap;
	
	// 用户自定义按键的映射表,key是按键值,vlaue是Runnable对象
	private Map<Integer, Runnable> userActionMap;
	
	// 按键动作id
	/** 对应于Home */
	public static final int LINE_START = 1;
	/** 对应于End */
	public static final int LINE_END = 2;
	/** 上箭头 */
	public static final int LINE_UP = 3;
	/** 下箭头 */
	public static final int LINE_DOWN = 4;
	/** 左箭头 */
	public static final int COLUMN_LEFT = 5;
	/** 右箭头 */
	public static final int COLUMN_RIGHT = 6;
	/** Ctrl + Home */
	public static final int TEXT_START = 7;
	/** Ctrl + End */
	public static final int TEXT_END = 8;
	/** 上翻页 */
	public static final int PAGE_UP = 9;
	/** 下翻页 */
	public static final int PAGE_DOWN = 10;
	/** 向左选择一个字符 */
	public static final int SELECT_COLUMN_LEFT = 11;
	/** 向右选择一个字符 */
	public static final int SELECT_COLUMN_RIGHT = 12;
	/** 向上选择一行 */
	public static final int SELECT_LINE_UP = 13;
	/** 向下选择一行 */
	public static final int SELECT_LINE_DOWN = 14;
	/** 剪切 */
	public static final int CUT = 15;
	/** 拷贝 */
	public static final int COPY = 16;
	/** 粘贴 */
	public static final int PASTE = 17;
	/** 删除光标前一个字符 */
	public static final int DELETE_PREVIOUS = 18;
	/** 删除光标后一个字符 */
	public static final int DELETE_NEXT = 19;
	/** 全选 */
	public static final int SELECT_ALL = 20;
	/** 用户自定义按键 */
	public static final int CUSTOM_ACTION = 21;
	/** 回车换行 */
	public static final int NEW_LINE = 22;
	/** 选择到行首 */
	public static final int SELECT_COLUMN_START = 23;
	/** 选择到行尾 */
	public static final int SELECT_COLUMN_END = 24;
	
	// 回车换行
	private static final String CRLF = System.getProperty("line.separator");
	
	// 自动滚动的间隔
	private static final int AUTO_SCROLL_INTERVAL = 100;
	
	// 是否是Mac
	private static final boolean IS_CARBON;
	// 是否做双缓冲
	private static final boolean DOUBLE_BUFFERED;
	static {
	    // 如果不是Mac,底层也不是gtk,则做双缓冲
		String platform = SWT.getPlatform();
		IS_CARBON = "carbon".equals(platform);
		DOUBLE_BUFFERED = !IS_CARBON;
	}
	
	/*
	 * 一些颜色的handle
	 * 在GTK下运行时,有时候背景颜色,前景颜色设置都无效,这个应该是和GTK的相关设置有一定关系
	 * 这里我提供一个解决办法,以便让有此问题的人也可以正常看到背景和前景色
	 */
	private Color cacheBg;
	private boolean gtkColorError;
	
	// 自动滚动的任务
	private Runnable autoScrollTimer = new Runnable() {
        public void run() {
			if (autoScrollDirection == SWT.UP) {
				doLineUp();
				getDisplay().timerExec(AUTO_SCROLL_INTERVAL, this);
			} else if (autoScrollDirection == SWT.DOWN) {
				doLineDown();
				getDisplay().timerExec(AUTO_SCROLL_INTERVAL, this);
			}
        }
	};
	
	// 事件类型定义
	static final int VerifyKey = 3005;
	
	private AnimationManager animationManager;
    private ImageResolver imageResolver;
	
    /**
     * @param parent
     * @param style
     */
    public RichBox(Composite parent) {
		super(parent, SWT.V_SCROLL | SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND);
		initialVariables();
		initialControl();
		initialContextMenu();
		installListeners();
		setScrollBars();
		createKeyBindings();
    }
    
	/**
     * 初始化右键菜单
     */
    private void initialContextMenu() {
        contextMenu = new CMenu();
        
        final CMenuItem miCopy = new CMenuItem(contextMenu, SWT.PUSH);
        miCopy.setText(richbox_menu_copy);
        miCopy.addSelectionListener(new ISelectionListener() {
            public void widgetSelected(SelectionEvent e) {
            	if(!isDisposed())
            		copy();
            }
        });
        final CMenuItem miCut = new CMenuItem(contextMenu, SWT.PUSH);
        miCut.setText(richbox_menu_cut);
        miCut.addSelectionListener(new ISelectionListener() {
            public void widgetSelected(SelectionEvent e) {
            	if(!isDisposed())
            		cut();
            }
        });
        final CMenuItem miPaste = new CMenuItem(contextMenu, SWT.PUSH);
        miPaste.setText(richbox_menu_paste);
        miPaste.addSelectionListener(new ISelectionListener() {
            public void widgetSelected(SelectionEvent e) {
            	if(!isDisposed())
            		paste();
            }
        });
        CMenuItem mi = new CMenuItem(contextMenu, SWT.SEPARATOR);
        mi = new CMenuItem(contextMenu, SWT.PUSH);
        mi.setText(richbox_menu_select_all);
        mi.addSelectionListener(new ISelectionListener() {
            public void widgetSelected(SelectionEvent e) {
            	if(!isDisposed())
            		doSelectAll();
            }
        });
        mi = new CMenuItem(contextMenu, SWT.PUSH);
        mi.setText(richbox_menu_clear);
        mi.addSelectionListener(new ISelectionListener() {
            public void widgetSelected(SelectionEvent e) {
            	if(!isDisposed())
            		clear();
            }
        });
        
        contextMenu.addMenuListener(new IMenuListener() {
            public void menuShown(MenuEvent e) {
                miCopy.setEnabled(hasSelection());
                miCut.setEnabled(!readonly & hasSelection());
                miPaste.setEnabled(!readonly);
            }
        });
    }

    /**
	 * Adds a verify key listener. A VerifyKey event is sent by the widget when
	 * a key is pressed. The widget ignores the key press if the listener sets
	 * the doit field of the event to false.
	 * <p>
	 * 
	 * @param l
	 *            the listener
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been
	 *                disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
	 *                thread that created the receiver</li>
	 *                </ul>
	 * @exception IllegalArgumentException
	 *                <ul>
	 *                <li>ERROR_NULL_ARGUMENT when listener is null</li>
	 *                </ul>
	 */
	public void addVerifyKeyListener(VerifyKeyListener l) {
		checkWidget();
		if (l == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		RichBoxListener typedListener = new RichBoxListener(l);
		addListener(VerifyKey, typedListener);
	}

    /**
     * 装载事件监听器
     */
    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.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.Paint, listener);
		addListener(SWT.Resize, listener);
		addListener(SWT.Traverse, listener);
		addDisposeListener(animationManager);
		addDisposeListener(imageResolver);
    }

	/**
     * @param event
     */
    protected void handleVerticalScroll(Event event) {
		setVerticalScrollOffset(getVerticalBar().getSelection(), false);
    }

	/**
	 * Scrolls the widget vertically.
	 * <p>
	 * 
	 * @param pixelOffset
	 *            the new vertical scroll offset
	 * @param adjustScrollBar
	 *            true= the scroll thumb will be moved to reflect the new scroll
	 *            offset. false = the scroll thumb will not be moved
	 * @return true=the widget was scrolled false=the widget was not scrolled,
	 *         the given offset is not valid.
	 */
	private boolean setVerticalScrollOffset(int pixelOffset, boolean adjustScrollBar) {
		ScrollBar verticalBar = getVerticalBar();

		if (pixelOffset == verticalScrollOffset) {
			return false;
		}
		if (verticalBar != null && adjustScrollBar) {
			verticalBar.setSelection(pixelOffset);
		}
		int h = clientAreaHeight - topMargin - bottomMargin;
		int w = clientAreaWidth - leftMargin - rightMargin;
		int delta = pixelOffset - verticalScrollOffset;
		int srcY = (delta > 0) ? delta : 0;
		int destY = (delta > 0) ? 0 : -delta;
		delta = Math.abs(delta);
		int copyHeight = h - delta;
	    scroll(leftMargin, topMargin + destY, leftMargin, topMargin + srcY, w, copyHeight, true);
	    int gap = (delta << 1) - h;
	    if(gap > 0)
	        redraw(leftMargin, copyHeight + topMargin, w, gap, true);

		verticalScrollOffset = pixelOffset;
		calculateTopLine();
		setCaretLocation();
		
		if(verticalScrollOffset == 0)
		    setScrollBars();
		return true;
	}
	
	/**
	 * 设置光标位置, 这个方法会修改光标X位置
	 */
	private void setCaretLocation() {
		int lineIndex = content.getLineAtOffset(caretOffset);
		int lineOffset = content.getLineStartOffset(lineIndex);
		int offsetInLine = caretOffset - lineOffset;
		int newCaretX = contentHelper.getXAtOffset(getGC(), lineIndex, offsetInLine);
		releaseGC();
		setCaretLocation(newCaretX, lineIndex);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -