《Eclipse SWT/JFACE 核心应用》 清华大学出版社 21 文本处理
JFace提供的文本处理功能非常强大,提醒在Eclipse平台中编辑器的部分。例如代码的折叠、内容提示、代码的格式化等,这些都是基于JFace的文本处理框架的。
21.1 文本处理概述
JFace中有关文本处理的部分分别打包在以下两个包中:
◆ org.eclipse.jface.text_3.6.0.v20100526-0800.jar
◆ org.eclipse.text_3.5.0.v20100601-1300.jar
文本的处理在各种语言的编辑器中经常使用,功能主要有语法着色、语法检查、内容辅助提示等,JFace的文本处理框架的功能远不止这些,功能非常强大。
21.2 项目实战:JavaScript编辑器
该编辑器实现的主要功能如下:
◇ 文件的打开、保存和打印。
◇ 可自定义不同关键字的颜色,如注释的颜色、字符串的颜色、JavaScript关键字的颜色和一些JavaScript内置对象的颜色。
◇ 可自定义显示文本的字体格式。
◇ 对文本进行撤销和重复操作。
◇ 可对文本进行查找和替换功能。
◇ 当输入代码的过程中,可以在提示JavaScript语法中内置对象的内容提示功能。
本项目的文件结果:
◆ 本项目包中的类:
◇ Constants.java:保存了程序中使用的一些字符串常量。
◇ EventManager.java:集中处理事件的类。
◇ JSCodeScanner.java:搜索代码规则的类。
◇ JSEditor.java:程序的主窗口类。
◇ JSEditorConfiguration.java:编辑器配置类。
◇ JSEditorTestDrive.java:程序的入口类。
◇ JSPreferencePage.java:设置首选项页面。
◇ ObjectContentAssistant.java:内容助手类。
◇ ObjectDetector.java:搜索内置对象字符类。
◇ KeywordDetector.java:搜索关键字对象字符类。
◇ PersistentDocument.java:持久化文档类。
◇ ResourceManager.java:程序中涉及的一些资源的管理。
◆ 本项目action包中的类:
◇ HelpAction.java:帮助菜单项。
◇ OpenAction.java:打开文件菜单项。
◇ PreferenceAction.java:打开首选项对话框项。
◇ PrintAction.java:打印菜单项。
◇ RedoAction.java:撤销菜单项。
◇ SaveAction:保存菜单项。
◇ SearchAction.java:查找替换菜单项。
◇ UndoAction.java:重复此次操作菜单项。
◆ 本项目dialog包中的类:
◇ AboutDialog.java:关于对话框。
◇ FindAndReplace.java:查找替换对话框。
21.3 主窗口模块
主窗口模块是本程序的主界面,继承自ApplicationWindow,这是一个应用程序窗口,窗口中还添加了菜单栏和工具栏。
该主窗口程序中显示代码的控件是SourceViewer对象。代码如下:
package www.swt.com.ch21;
import org.eclipse.jface.action.*;
public class JSEditor extends ApplicationWindow implements IPropertyChangeListener {
private PersistentDocument document;//数据文档对象
private SourceViewer viewer;//显示文本控件对象
private EventManager eventManager;//事件管理器
private PreferenceStore preference;//保存首选项设置的对象
private IUndoManager undoManager;//撤销与重复管理器
private JSEditorConfiguration configuration;//文本控件的配置对象
public JSEditor() {
super(null);
eventManager = new EventManager(this);//初始化事件管理器
this.addMenuBar();//添加菜单
this.addToolBar( SWT.FLAT );//添加工具栏
}
protected void configureShell(Shell shell) {
super.configureShell(shell);
shell.setText("JavaScript 编辑器");
shell.setSize(600, 400);
}
protected Control createContents(Composite parent) {
Composite top = new Composite(parent,SWT.NONE);
top.setLayout( new FillLayout());
//初始化文档对象
document = new PersistentDocument();
//初始化源文件显示控件对象
viewer = new SourceViewer(top, new VerticalRuler(10), SWT.V_SCROLL | SWT.H_SCROLL);
//初始化文档的配置对象
configuration = new JSEditorConfiguration(this);
viewer.configure(configuration );//设置文档配置
viewer.setDocument(document);//设置文档
undoManager = new DefaultUndoManager(100);//初始化撤销管理器对象,默认可撤销100次
undoManager.connect(viewer);//将该撤销管理器应用于文档
//初始化代码中的字体
initCodeFont();
return parent;
}
//初始化代码的字体
private void initCodeFont() {
// 定义一个默认的字体
FontData defaultFont = new FontData("Courier New", 10, SWT.NORMAL);
// 如果从首选项读出的字体有异常,则使用默认的字体
FontData data = StringConverter.asFontData(ResourceManager
.getPreferenceStore().getString(Constants.CODE_FONT),
defaultFont);
// 创建字体
Font font = new Font(this.getShell().getDisplay(), data);
viewer.getTextWidget().setFont(font);// 设置字体
}
//初始化菜单项
protected MenuManager createMenuManager() {
MenuManager top = new MenuManager();
MenuManager fileMenu = new MenuManager("文件(&F)");
MenuManager editMenu = new MenuManager("编辑(&E)");
MenuManager helpMenu = new MenuManager("帮助(&H)");
fileMenu.add( new OpenAction(this) );
fileMenu.add( new SaveAction(this) );
fileMenu.add( new Separator());
fileMenu.add( new PrintAction(this) );
editMenu.add( new UndoAction(this));
editMenu.add( new RedoAction(this));
editMenu.add( new Separator());
editMenu.add( new SearchAction(this));
editMenu.add( new Separator());
editMenu.add( new PreferenceAction(this));
helpMenu.add( new HelpAction(this));
top.add( fileMenu );
top.add( editMenu ) ;
top.add(helpMenu);
return top;
}
//初始化工具栏
protected ToolBarManager createToolBarManager(int style) {
ToolBarManager toolBar = new ToolBarManager(style);
toolBar.add(new OpenAction(this));
toolBar.add( new SaveAction(this) );
toolBar.add( new Separator());
toolBar.add( new PrintAction(this) );
toolBar.add( new UndoAction(this));
toolBar.add( new RedoAction(this));
toolBar.add( new Separator());
toolBar.add( new SearchAction(this));
toolBar.add( new Separator());
toolBar.add( new PreferenceAction(this));
toolBar.add( new HelpAction(this));
return toolBar;
}
public PersistentDocument getDocument() {
return document;
}
public SourceViewer getViewer() {
return viewer;
}
public EventManager getEventManager() {
return eventManager;
}
public PreferenceStore getPreference() {
return preference;
}
public void setPreference(PreferenceStore preference) {
this.preference = preference;
}
public IUndoManager getUndoManager() {
return undoManager;
}
public JSEditorConfiguration getConfiguration() {
return configuration;
}
//为IPropertyChangeListener接口中的方法,当设置首选项的字体时
//重新设置编辑器的字体
public void propertyChange(PropertyChangeEvent event) {
if (event.getNewValue()==null)
return;
if (event.getProperty().equals(Constants.CODE_FONT))
eventManager.setCodeFont( (FontData[]) event.getNewValue());
}
}
主窗口程序代码分析:
◆ TextViewer对象:
TextViewer对象可以理解为是文本处理的MVC对象,TreeView底层封装了Tree控件,使树的数据和视图分开,而TextViewer底层封装的是StyledText对象,用于显示文本,文本的数据部分则是由实现了IDocument接口的对象提供的。创建一个简单的文本编辑器如下:
TextViewer viewer = new TextViewer(shell, SWT.V_SCROLL|SWT.H_SCROLL);
viewer.setDocument(new Document());
在本程序中使用的是SourceViewer对象,该类继承自TextViewer,除了一般的编辑文本的功能外,增加了编辑源代码的功能,适用作代码编辑器。另外,该类还有一个子类ProjectionViewer类,比SourceViewer功能更加丰富,比如说可以提供代码折叠的效果等。
◆ IDocument接口:
处理文本另一个重要的概念就是IDocument接口,它提供了文本的数据,包括文本的修改、字符的位置、行数等属性和操作。实现该接口常用的有Document类和ProjectionDocument类,后者主要是与ProjectionViewer一起使用,通常情况下使用Document类就可以了。
本程序中使用的PersistentDocument类继承自Document类,为文档增加了打开和保存数据的功能。代码如下:
package www.swt.com.ch21;
import java.io.*;
import org.eclipse.jface.text.*;
public class PersistentDocument extends Document implements IDocumentListener {
private String fileName;//打开的文件名
private boolean dirty;//文件是否修改过
public PersistentDocument() {
this.addDocumentListener(this);//为该文档注册文档监听器
}
//保存到指定的文件
public void save() throws IOException {
if (fileName == null)
throw new IllegalStateException("文件名不为空!");
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(fileName));
out.write(get());
dirty = false;
} finally {
try {
if (out != null)
out.close();
} catch (IOException e) {
}
}
}
//打开文件的方法
public void open() throws IOException {
if (fileName == null)
throw new IllegalStateException("文件名不为空!");
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(fileName));
StringBuffer buf = new StringBuffer();
int n;
while ((n = in.read()) != -1) {
buf.append((char) n);
}
set(buf.toString());
dirty = false;
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
}
}
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
//接口中的方法,空实现
public void documentAboutToBeChanged(DocumentEvent arg0) {
}
//接口中的方法,当文档修改后,设置已修改状态
public void documentChanged(DocumentEvent arg0) {
dirty = true;
}
}
对文档中的字符的获取是通过父类的get()方法获得的,而设置文档字符的方法是通过父类中的set()方法设置的。
启动主窗口程序:
在启动主窗口程序之前,首先加载首选项文件,因为代码着色部分要使用首选项保存文件中的对各种颜色的设置。
该启动程序的代码实现如下:
package www.swt.com.ch21;
import java.io.IOException;
public class JSEditorTestDrive {
public static void main(String[] args) {
//创建主窗口对象
JSEditor jsApp = new JSEditor();
//为主窗口对象设置首选项对象
//首选项对象通过ResourceManager中的静态方法获得
jsApp.setPreference(ResourceManager.getPreferenceStore() );
//并为首选项保存对象注册选项改变监听器
ResourceManager.getPreferenceStore().addPropertyChangeListener(jsApp);
jsApp.setBlockOnOpen(true);
jsApp.open();
Display.getCurrent().dispose();
//窗口关闭后保存设置的首选项
try {
ResourceManager.getPreferenceStore().save();
} catch (IOException e) {
e.printStackTrace();
}
}
}
21.4 代码着色
对代码着色要遵循一定的规则,比如说要区别注释和关键字,也要区别声明的字符和内置的一些对象等。
◆ 源代码配置类(SourceViewerConfiguration)
要实现对代码的着色,主要是通过设置SourceView对象中的configure方法来实现的:
viewer.configure(configuration );//设置文档配置
其中,configuration是JSEditorConfiguration配置对象,对代码实现的着色和内容辅助的功能都是通过该对象来设置的。JSEditorConfiguration类继承自SourceViewerConfiguration类,通过覆盖父类中的方法可以实现对代码进行着色和内容辅助等功能。代码如下:
package www.swt.com.ch21;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.*;
import org.eclipse.jface.text.presentation.*;
import org.eclipse.jface.text.rules.*;
import org.eclipse.jface.text.source.*;
public class JSEditorConfiguration extends SourceViewerConfiguration {
private JSEditor editor ;
public JSEditorConfiguration( JSEditor editor ){
this.editor=editor;
}
//覆盖父类中的方法,主要提供代码着色功能
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
PresentationReconciler reconciler = new PresentationReconciler();
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(new JSCodeScanner());
reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
return reconciler;
}
//覆盖父类中的方法,主要提供内容辅助功能
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
//创建内容助手对象
ContentAssistant contentAssistant = new ContentAssistant();
//设置提示的内容
contentAssistant.setContentAssistProcessor(new ObjectContentAssistant(),IDocument.DEFAULT_CONTENT_TYPE);
//设置自动激活提示
contentAssistant.enableAutoActivation(true);
//设置自动激活提示的时间为500毫秒
contentAssistant.setAutoActivationDelay(500);
return contentAssistant;
}
}
覆盖父类中的getPresentationReconciler方法,可以设置当输入文本时可处理的一些事件。IPresentationReconciler接口负责处理文件修改的过程,PresentationReconciler实现了该接口。当文本修改时,可以任务当前的文本不再正确。此时,会调用IPresentationDamager对象计算被修改文档所在的区域,而IPresentationRepairer对象将利用新的文本属性来进行修改,所以要使用setDamager和setRepairer方法。
另外DefaultDamagerRepairer对象实现了IPresentationDamager和IPresentationRepairer接口。该对象可以通过设定一下规则进行代码扫描,当扫描的代码符合规则时,就会按照规则指定代码样式修改。
◆ 基于规则的代码扫描器类(RuleBasedScanner)
具体设置代码修复的规则如下:
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(new JSCodeScanner());
在以上的代码中创建了一个JSCodeScanner对象。该对象即为扫描代码的规则对象。代码如下:
package www.swt.com.ch21;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.*;
import org.eclipse.swt.SWT;
public class JSCodeScanner extends RuleBasedScanner {
private TextAttribute keywords ;//关键字的文本属性
private TextAttribute string ;//字符串的文本属性
private TextAttribute object ;//内置对象的文本属性
private TextAttribute comment ;//注释部分的文本属性
public JSCodeScanner(){
//获得首选项中所设置的颜色,初始化各文本属性
keywords = new TextAttribute (ResourceManager.getColor(Constants.COLOR_KEYWORD),null,SWT.BOLD);
string = new TextAttribute (ResourceManager.getColor(Constants.COLOR_STRING));
object = new TextAttribute (ResourceManager.getColor(Constants.COLOR_OBJECT));
comment = new TextAttribute (ResourceManager.getColor(Constants.COLOR_COMMENT),null,SWT.ITALIC);
//设置代码的规则
setupRules();
}
private void setupRules() {
//用一个List集合对象保存所有的规则
List<IRule> rules = new ArrayList<IRule>();
//字符串的规则
rules.add(new SingleLineRule("\"", "\"",new Token( string), '\\'));
rules.add(new SingleLineRule("'", "'", new Token( string), '\\'));
//注释的规则
rules.add(new SingleLineRule("/*", "*/", new Token( comment), '\\'));
rules.add(new EndOfLineRule("//", new Token( comment),'\\'));
//空格的规则
rules.add(new WhitespaceRule(new IWhitespaceDetector() {
public boolean isWhitespace(char c) {
return Character.isWhitespace(c);
}
}));
//关键字的规则
WordRule keywordRule = new WordRule(new KeywordDetector());
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
keywordRule.addWord(Constants.JS_SYNTAX_KEYWORD[i], new Token( keywords ));
rules.add(keywordRule);
//内置对象的规则
WordRule objectRule = new WordRule(new ObjectDetector());
for (int i = 0, n = Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i < n; i++)
objectRule.addWord(Constants.JS_SYNTAX_BUILDIB_OBJECT[i], new Token( object ));
rules.add(objectRule);
//集合类中保存的规则转化为数组
IRule[] result = new IRule[rules.size()];
rules.toArray(result);
//调用父类中的方法,设置规则
//此方法非常重要
setRules(result);
}
}
使用该类方法的主要目的是,为扫描代码提供多个规则。可以这样理解,当修改文本时,程序会自动搜索所有设置的规则,如果当前的文本符合设置的某一个规则时,就按照这个规则所设定的文字样式重新显示文本。所以,对规则的设定直接影响着如何对不同的代码进行着色。
该类JSCodeScanner继承自RuleBasedScanner,这样就可以使用设置的规则来对代码进行扫描了。
◆ 设置代码扫描规则
所有实现了IRule接口的规则如下:
EndOfLineRule, MultiLineRule, NumberRule, PatternRule, SingleLineRule, WhitespaceRule, WordPatternRule, WordRule
该程序中使用的规则类:
● SingleLineRule:单行规则类
SingleLineRule可以设置指定两个字符间的规则。它的构造方法有:
⊙ SingleLineRule(String startSequence, String endSequence, IToken token) :startSequence表示起始的字符,endSequence表示终止的字符,token为符合该规则时所应用的代码样式IToken对象。
⊙ SingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter) :escapeCharacter表示转义符。
⊙ SingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter, boolean breaksOnEOF) :breaksOnEOF表示是否在文件末尾终止该规则。
⊙ SingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter, boolean breaksOnEOF, boolean escapeContinuesLine) :escapeContinuesLine表示是否在该行结束后使用转义符。
rules.add(new SingleLineRule("/*", "*/", new Token( comment), '\\'));
表示该规则是在两个字符“/*”和“*/”之间应用,也就是本例程序中注释部分的规则。
● EndOfLineRule:没有终止字符的规则类
EndOfLineRule对象可以设置只有初始字符相同后的规则。
rules.add(new EndOfLineRule("//", new Token( comment),'\\'));
表示带有“//”标记后的都可以应用此规则。
● WordRule:单词规则
WordRule对象应用于对某些特定的字符设置的规则,一般可以用来设置关键字的规则,当输入的字符串为规则中所指定的一些字符串时,就可以使用该对象。
构造方法如下:
⊙ WordRule(IWordDetector detector) :其中IWordDetector为接口,创建WordRule对象时要使用实现这个接口的对象。
⊙ WordRule(IWordDetector detector, IToken defaultToken) :也可以设置默认的IToken代码格式对象。
本程序中对关键字设置的规则使用的就是WordRule规则:
WordRule keywordRule = new WordRule(new KeywordDetector());
其中KeywordDetector对象是实现了IWordDetector的对象,代码如下:
package www.swt.com.ch21;
import org.eclipse.jface.text.rules.IWordDetector;
public class KeywordDetector implements IWordDetector {
// 接口中的方法,字符是否是单词的开始
public boolean isWordStart(char c) {
//循环所有的关键字
//如果有关键字中的第一个字符匹配该字符,则返回true
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
if (c == ((String) Constants.JS_SYNTAX_KEYWORD[i]).charAt(0))
return true;
return false;
}
// 接口中的方法,字符是否是单词中的一部分
public boolean isWordPart(char c) {
//循环所有的关键字
//如果关键字的字符中有该字符,则返回true
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
if (((String) Constants.JS_SYNTAX_KEYWORD[i]).indexOf(c) != -1)
return true;
return false;
}
}
另外,创建WordRule对象后,还要使用以下方法将该规则所应用的字符添加到该对象中:
void addWord(String word, IToken token)
word为该规则应用的字符串,IToken为规则匹配后所应用的代码样式。例如本程序中循环所有关键字字符,然后添加到对象中:
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
keywordRule.addWord(Constants.JS_SYNTAX_KEYWORD[i], new Token( keywords ));
同理,对JavaScript内置对象也是通过此规则来设定的。代码如下:
package www.swt.com.ch21;
import org.eclipse.jface.text.rules.IWordDetector;
public class ObjectDetector implements IWordDetector {
public boolean isWordStart(char c) {
for (int i = 0, n = Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i < n; i++)
if (c == ((String) Constants.JS_SYNTAX_BUILDIB_OBJECT[i]).charAt(0))
return true;
return false;
}
public boolean isWordPart(char c) {
for (int i = 0, n = Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i < n; i++)
if (((String) Constants.JS_SYNTAX_BUILDIB_OBJECT[i]).indexOf(c) != -1)
return true;
return false;
}
}
疑问:这样的匹配方式是否严格?
举例:如果定义了关键字“abstract”、“super”,输入“auper”时开始字符与“abstract”匹配,后面的部分与“super”匹配,则“auper”是否会被认为是关键字?
答案:不会。
原因:WordRule在最后还会进行全词匹配???
◆ 提取类(Token)和文本属性类(TextAttribute)
IToken为一个接口,实现该接口的类是Token,在规则中使用Token对象可以将符合该规则字符转换成对应Token中设置的文本格式。对于文本格式的设置使用的是TextAttribute类。
TextAttribute类的构造方法:
⊙ TextAttribute(Color foreground) :foreground为字符显示的前景色。
⊙ TextAttribute(Color foreground, Color background, int style) :background为字符显示背景色,style表示样式常量,SWT.BOLD表示加粗,SWT.ITALIC表示倾斜。
最后,本程序中规则的设置的字体的颜色是从首选项获得的,参考ResourceManager类相关的代码部分。
21.5 内容辅助
对于本例中的JavaScript编辑器,希望在编码时输入“.”时,能够根据当前输入的对象来提供一些内容的提示,效果如下:
◆ 配置编辑器的内容助手
JSEditorConfiguration.java中设置助手类的代码:
//创建内容助手对象
ContentAssistant contentAssistant = new ContentAssistant();
//设置提示的内容
contentAssistant.setContentAssistProcessor(new ObjectContentAssistant(),IDocument.DEFAULT_CONTENT_TYPE);
//设置自动激活提示
contentAssistant.enableAutoActivation(true);
//设置自动激活提示的时间为500毫秒
contentAssistant.setAutoActivationDelay(500);
其中,ObjectContentAssistant类是需要设置的内容显示的过程器,该类要实现IContentAssistProcessor接口,所以说,设置辅助内容的过程主要是在ObjectContentAssistent类中。
◆ 内容辅助类
内容辅助类ObjectContentAssistant.java的代码如下:
package www.swt.com.ch21;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.contentassist.*;
public class ObjectContentAssistant implements IContentAssistProcessor {
// 接口中的方法,获得内容的提示数组
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
int offset) {
IDocument doc = viewer.getDocument();
//获得提示列表中的内容
List list = computObject(getObjectName(doc, offset), offset);
return (CompletionProposal[]) list.toArray(new CompletionProposal[list
.size()]);
}
// 接口中的方法,空实现
public IContextInformation[] computeContextInformation(ITextViewer viewer,
int offset) {
return null;
}
// 接口中的方法,设置何时激活提示,这里当输入.时激活内容提示
public char[] getCompletionProposalAutoActivationCharacters() {
return new char[] { '.' };
}
// 接口中的方法
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
// 接口中的方法
public String getErrorMessage() {
return null;
}
// 接口中的方法
public IContextInformationValidator getContextInformationValidator() {
return null;
}
//获得获得提示时,之前输入的字符
public String getObjectName(IDocument doc, int offset) {
//offset为当前光标所在位置,也就是偏移量
StringBuffer buf = new StringBuffer();
offset--;
//依次从当前位置查找,直到字符串为空格或者是"." 时停止,
//然后将字符串反转
while (true) {
try {
char c = doc.getChar(--offset);//获得前一个字符
if (Character.isWhitespace(c))//如果为空格,跳出
break;
if (c=='.')//如果为“.”则跳出
break;
buf.append(c);
} catch (BadLocationException e) {
break;
}
}
return buf.reverse().toString();//最后将字符反转
}
// 设置内容提示的中的内容
public List computObject(String objName, int offset) {
//objName首先看objName是否为内置的对象
List list = new ArrayList();
boolean bFind = false;
for (int i = 0; i < Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i++) {
String tempString = Constants.JS_SYNTAX_BUILDIB_OBJECT[i];
if (objName.equals(tempString))
{
bFind = true;
break;
}
}
//如果是内置对象,则将所有的对象的字符添加到内容提示列表中
if (bFind) {
for (int i = 0; i < Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i++) {
String insert = objName + "."+ Constants.JS_SYNTAX_BUILDIB_OBJECT[i];
//CompletionProposal对象
CompletionProposal proposal = new CompletionProposal(insert,
offset - objName.length() - 1, objName.length()+1, insert.length()+1,null,null,null,"aaa"
);
list.add(proposal);
//list.
}
}
return list;
}
}
ObjectContentAssistant实现了接口IContentAssistProcessor,本程序中实现了其中的getCompletionProposalAutoActivationCharacters和computeCompletionProposals两个方法。前者设置当输入何字符串时激活内容辅助的方法;后者返回的是一个ICompletionProposal数组列表,也就是显示内容辅助中的列表内容。
◆ 辅助建议类(CompletionProposal)
ICompletionProposal为一个接口,下面几个类实现了该接口:CompletionProposal, TemplateProposal
本例中使用CompletionProposal类来返回辅助内容列表。
一般情况下,并不是只要输入“.”就出现内容提示,通常输入“.”时要获得该对象的属性,所以要获得当前输入的“.”之前的字符串信息,本程序中获得输入“.”之前的字符串的方法是getObjectName。该方法从当前的位置向前循环,当遇到空格或是“.”时,结束循环,获得输入“.”位置到结束位置的字符。
例如输入“document.form.”后,输入“.”此时通过getObjectName方法获得的字符串为“form”,这样就可以判断是否是内置的对象了。
判断是否为内置对象字符的功能在computeObject方法中实现。该方法首先循环所有的内置对象的字符,查找是否存在通过getObjectName获得属性。如果存在,则将内置对象的字符添加到CompletionProposal对象中,最后返回一个集合对象。
CompletionProposal类构造方法:
⊙ CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition) :replacementString为选择提示内容的字符替换的字符串;replacementOffset为替换文本所在文档中的位置,也就是偏移量;replacementLength为替换文本的字符长度,插入提示内容后鼠标所在位置。
⊙ CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo) :image为内容提示中显示的图标;displayString为内容提示中显示的文本,但并不是选中后替换的文本;contextInformation为当光标放在提示列表中的一项时所出现的具体的提示信息;additionalProposalInfo为附件的提示信息。
当然,本程序的内容辅助功能还非常简单,并未实现动态的获得辅助的内容。
《Eclipse SWT/JFACE 核心应用》 清华大学出版社 21 文本处理
21.6 文档的撤销与重复
JFace文本处理框架中,使用IUndoManager对象来管理文档的撤销与恢复,IUndoManager为一个接口,实现该接口的类为DefaultUndoManager。
◆ 文档管理器对象(DefaultUndoManager)
以下代码为文本编辑对象创建了文档撤销管理器的方法:
undoManager = new DefaultUndoManager(100);//初始化撤销管理器对象,默认可撤销100次
undoManager.connect(viewer);//将该撤销管理器应用于文档
将管理器应用到TextViewer对象后,就可以使用undoManager.redo()方法来恢复操作,使用undoManager.undo()撤销操作。
◆ 撤销操作的实现
撤销和重做的功能是通过UndoAction和RedoAction两个类实现的。
撤销操作UndoAction类实现的代码如下:
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.ResourceManager;
public class UndoAction extends Action {
private JSEditor editor;
public UndoAction(JSEditor editor) {
super("撤销@Ctrl+Z");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\undo.gif"));
this.editor = editor;
}
public void run() {
editor.getUndoManager().undo();
}
}
◆ 恢复操作的实现
恢复操作RedoAction类实现的代码如下:
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.ResourceManager;
public class RedoAction extends Action {
private JSEditor editor;
public RedoAction(JSEditor editor) {
super("重做@Ctrl+Y");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\redo.gif"));
this.editor = editor;
}
public void run() {
editor.getUndoManager().redo();
}
}
以上两个程序需要注意的是,创建对象时都要将JSEditor作为构造参数传入,这样可以方便的获得主窗口对象中的属性。
21.7 查找和替换窗口
对于Document对象,已经封装了对文本查找和替换的常用操作,要实现查找和替换功能,只需使用它内置的一些方法即可。
◆ 窗口的界面设计
该对话框整体的布局是网格式布局,并且为两列。中间两个为Group分组面板,布局也是网格式布局。
package www.swt.com.ch21.dialog;
import org.eclipse.jface.dialogs.Dialog;
public class FindAndReplace extends Dialog {
private JSEditor editor;
private Button btFind;//查找按钮
private Button btReplace;//替换按钮
private Button btFindAndReplace;//查找与替换按钮
private Button btClose;//关闭按钮
private FindReplaceDocumentAdapter findDdapater;
public FindAndReplace(JSEditor editor ,Shell parentShell) {
super(parentShell);
this.editor = editor ;
//查找文档字符的适配器对象
findDdapater = new FindReplaceDocumentAdapter(this.editor.getDocument());
}
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("查找/替换");
newShell.setSize(200,270);
}
protected Control createContents(Composite parent) {
//创建对话框的控件
parent.setLayout(new GridLayout(2, false));
new Label( parent , SWT.LEFT).setText("查找:");
final Text findText = new Text(parent, SWT.BORDER);
findText.setLayoutData( new GridData(GridData.FILL_HORIZONTAL));
new Label( parent , SWT.LEFT).setText("替换为:");
final Text replaceText = new Text(parent, SWT.BORDER);
replaceText.setLayoutData( new GridData(GridData.FILL_HORIZONTAL));
Group group = new Group( parent, SWT.NONE);
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalSpan = 2;
group.setLayoutData(data);
group.setText("方向");
group.setLayout( new GridLayout(2,true) );
final Button forwardButton = new Button(group,SWT.RADIO);
forwardButton.setText("前进");
final Button backButton = new Button(group,SWT.RADIO);
backButton.setText("后退");
group = new Group( parent, SWT.NONE);
data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalSpan = 2;
group.setLayoutData(data);
group.setText("选项");
group.setLayout( new GridLayout(2,true) );
final Button match = new Button(group,SWT.CHECK);
match.setText("区分大小写");
final Button wholeWord = new Button(group,SWT.CHECK);
wholeWord.setText("整个字");
final Button regexp = new Button(group,SWT.CHECK);
regexp.setText("正则表达式");
Composite composite = new Composite( parent , SWT.NONE);
data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalSpan = 2;
composite.setLayoutData(data);
composite.setLayout( new GridLayout(2,true));
btFind = new Button( composite , SWT.PUSH );
btFind.setText("查找");
btFind.setLayoutData( new GridData(GridData.FILL_HORIZONTAL));
btReplace = new Button( composite , SWT.PUSH );
btReplace.setText("替换");
btReplace.setLayoutData( new GridData(GridData.FILL_HORIZONTAL));
btFindAndReplace = new Button( composite , SWT.PUSH );
btFindAndReplace.setText("查找/替换");
btFindAndReplace.setLayoutData( new GridData(GridData.FILL_HORIZONTAL));
btClose = new Button( composite , SWT.PUSH );
btClose.setText("关闭窗口");
btClose.setLayoutData( new GridData(GridData.FILL_HORIZONTAL));
//设置按钮的事件
//设置查找选项时,正则表达式与匹配整个字符不能同时使用
wholeWord.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent event) {
if ( wholeWord.getSelection() ){
regexp.setSelection( false );
regexp.setEnabled( false );
}else{
regexp.setEnabled( true );
}
}
});
regexp.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent event) {
if ( regexp.getSelection() ){
wholeWord.setSelection( false );
wholeWord.setEnabled( false );
}else{
wholeWord.setEnabled( true );
}
}
});
//为查找按钮注册事件监听器
btFind.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
boolean b = editor.getEventManager().isFind( findDdapater,
findText.getText(),forwardButton.getSelection(),match.getSelection(),
wholeWord.getSelection(),regexp.getSelection());
//如果找到匹配的字符,将替换和替换全部按钮设置为可用状态
enableReplaceButtons(b);
}
});
//为替换按钮注册事件监听器
btReplace.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
editor.getEventManager().doReplace( findDdapater , replaceText.getText());
enableReplaceButtons(false);
}
});
//为查找/按钮注册事件监听器
btFindAndReplace.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
editor.getEventManager().doReplace( findDdapater , replaceText.getText());
boolean b = editor.getEventManager().isFind( findDdapater,
findText.getText(),forwardButton.getSelection(),match.getSelection(),
wholeWord.getSelection(),regexp.getSelection());
enableReplaceButtons(b);
}
});
//为关闭按钮注册监听器
btClose.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
getShell().close();
}
});
//设置向前为默认选中状态
forwardButton.setSelection(true);
//初始化时替换和查找替换按钮不可用
enableReplaceButtons(false);
//设置焦点为查找的文本框
findText.setFocus();
return parent;
}
private void enableReplaceButtons(boolean enable) {
btReplace.setEnabled(enable);
btFindAndReplace.setEnabled(enable);
}
}
◆ 查找功能的实现
当点击查找按钮后,会调用EventManager对象的isFind方法进行查找。EventManager对象是事件管理器对象,集中处理一些事件。代码如下:
package www.swt.com.ch21;
import java.io.IOException;
public class EventManager {
private JSEditor editor;
public EventManager(JSEditor editor) {
this.editor = editor;
}
//设置字体
public void setCodeFont(FontData[] fontData) {
Font font = editor.getViewer().getTextWidget().getFont();
if (font != null)
font.dispose();
font = new Font(editor.getShell().getDisplay(), fontData);
editor.getViewer().getTextWidget().setFont(font);
}
//打开文件
public void openFile() {
FileDialog dialog = new FileDialog(editor.getShell(), SWT.OPEN);
dialog.setFilterExtensions(new String[] { "*.js", "*.html", "*.htm",
"*.*" });
String name = dialog.open();
if ((name == null) || (name.length() == 0))
return;
try {
editor.getDocument().setFileName(name);
editor.getDocument().open();
} catch (IOException e) {
e.printStackTrace();
}
}
//保存文件
public void saveFile() {
if (!editor.getDocument().isDirty())
return;
boolean b = MessageDialog.openConfirm(editor.getShell(), "确认保存",
"您确实要保存文件吗?");
if (b) {
try {
editor.getDocument().save();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//查找字符
public boolean isFind(FindReplaceDocumentAdapter adapter, String find,
boolean forward, boolean matchCase, boolean wholeWord,
boolean regexp) {
boolean bFind = false;
IRegion region = null;
try {
// 获得当前文本所在的位置,也就是偏移量
int offset = editor.getViewer().getTextWidget().getCaretOffset();
if (!forward) {
Point pt = editor.getViewer().getSelectedRange();
if (pt.x != pt.y) {
offset = pt.x - 1;
}
}
//确保没有超出adapter的范围
if (offset >= adapter.length())
offset = adapter.length() - 1;
if (offset < 0)
offset = 0;
//查找字符
region = adapter.find(offset, find, forward, matchCase, wholeWord,regexp);
//如果找到,设置匹配的字符选中,并返回true
if (region != null) {
editor.getViewer().setSelectedRange(region.getOffset(),region.getLength());
bFind = true;
}
} catch (BadLocationException e) {
;
} catch (PatternSyntaxException e) {
;
}
return bFind;
}
//替换字符
public void doReplace(FindReplaceDocumentAdapter adapter, String replaceText) {
try {
adapter.replace(replaceText, false);
} catch (BadLocationException e) {
}
}
}
◆ 替换功能的实现
使用EventManager类中的doReplace方法。
“编辑”菜单中打开“查找/替换”对话框对应的事件处理类:
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.ResourceManager;
import www.swt.com.ch21.dialog.FindAndReplace;
public class SearchAction extends Action {
private JSEditor editor;
public SearchAction(JSEditor editor) {
super("查找\\替换@Ctrl+F");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\search.gif"));
this.editor = editor;
}
public void run() {
new FindAndReplace( editor , editor.getShell()).open();
}
}
21.8 首选项的对话框
首选项页面如下:
◆ 首选项页面的代码实现
package www.swt.com.ch21;
import org.eclipse.jface.preference.ColorFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.FontFieldEditor;
public class JSPreferencePage extends FieldEditorPreferencePage{
private ColorFieldEditor keyword;
private ColorFieldEditor comment;
private ColorFieldEditor string;
private ColorFieldEditor object;
private FontFieldEditor codeFont;
public JSPreferencePage() {
super(GRID);
}
protected void createFieldEditors() {
keyword = new ColorFieldEditor(Constants.COLOR_KEYWORD,"关键字颜色:",getFieldEditorParent());
addField(keyword);
comment = new ColorFieldEditor(Constants.COLOR_COMMENT,"注释颜色:",getFieldEditorParent());
addField(comment);
string = new ColorFieldEditor(Constants.COLOR_STRING,"字符串颜色:",getFieldEditorParent());
addField(string);
object = new ColorFieldEditor(Constants.COLOR_OBJECT,"内置对象颜色:",getFieldEditorParent());
addField(object);
codeFont= new FontFieldEditor(Constants.CODE_FONT,"字体:",getFieldEditorParent());
addField(codeFont);
}
}
◆ 打开首选项页面的代码
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.jface.preference.PreferenceNode;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.JSPreferencePage;
import www.swt.com.ch21.ResourceManager;
public class PreferenceAction extends Action {
private JSEditor editor;
public PreferenceAction(JSEditor editor) {
super("首选项@Ctrl+R");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\prefs.gif"));
this.editor = editor;
}
public void run() {
PreferenceManager mgr = new PreferenceManager();
mgr.addToRoot(new PreferenceNode("edit", "编辑器", null,JSPreferencePage.class.getName()));
PreferenceDialog dlg = new PreferenceDialog(editor.getShell(), mgr);
dlg.setPreferenceStore(editor.getPreference());
dlg.open();
}
}
21.9 文件的打开、保存和打印
◆ 打开文件
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.*;
import www.swt.com.ch21.JSEditor;
public class OpenAction extends Action {
private JSEditor editor;
public OpenAction(JSEditor editor) {
super("打开@Ctrl+O");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\open.gif"));
this.editor = editor;
}
public void run() {
editor.getEventManager().openFile();
}
}
◆ 保存文件
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.ResourceManager;
public class SaveAction extends Action {
private JSEditor editor;
public SaveAction(JSEditor editor) {
super("保存@Ctrl+S");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\save.gif"));
this.editor = editor;
}
public void run() {
editor.getEventManager().saveFile();
}
}
◆ 打印文件
package www.swt.com.ch21.action;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.ResourceManager;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
public class PrintAction extends Action{
private JSEditor editor;
public PrintAction(JSEditor editor){
super("打印@Ctrl+P");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\print.gif"));
this.editor = editor;
}
public void run() {
editor.getViewer().getTextWidget().print();
}
}
StyledText对象提供了打印的方法print(),可以直接使用该方法进行打印。
21.10 帮助对话框
package www.swt.com.ch21.dialog;
import org.eclipse.jface.dialogs.Dialog;
public class AboutDialog extends Dialog {
public AboutDialog(Shell parentShell) {
super(parentShell);
}
protected Control createContents(Composite parent) {
this.getShell().setSize(200,150);
this.getShell().setText("关于我们");
parent.setLayout( new GridLayout());
new Label(parent, SWT.CENTER).setText("JS编辑器 Verson 1.0");
new Label(parent, SWT.CENTER).setText("作者:Janet");
new Label(parent, SWT.RIGHT).setText("2006.8");
return parent;
}
}
调用该窗口的事件处理类HelpAction:
package www.swt.com.ch21.action;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import www.swt.com.ch21.JSEditor;
import www.swt.com.ch21.ResourceManager;
import www.swt.com.ch21.dialog.AboutDialog;
public class HelpAction extends Action {
private JSEditor editor;
public HelpAction(JSEditor editor) {
super("帮助@Ctrl+O");
this.setImageDescriptor(ImageDescriptor.createFromFile(ResourceManager.class,"icons\\help.gif"));
this.editor = editor;
}
public void run() {
AboutDialog dlg = new AboutDialog( editor.getShell());
dlg.open();
}
}
21.11 其他的一些工具类
◆ 事件管理类
EventManager.java前面已经提供。
◆ 资源管理类
ResourceManager类使用了Singleton设计模式。
package www.swt.com.ch21;
import java.io.IOException;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.StringConverter;
import org.eclipse.swt.graphics.Color;
public class ResourceManager {
private ResourceManager(){}//设置为private,不允许创建对象
private static ColorRegistry colorRegistry;//管理颜色的对象
//获得颜色注册器对象
public static ColorRegistry getColorRegistry() {
if (colorRegistry == null) {
colorRegistry = new ColorRegistry();
initColor();
}
return colorRegistry;
}
//获得颜色注册器中的颜色对象的工具方法
public static Color getColor( String key ) {
Color color = getColorRegistry().get(key);
return color;
}
//初始话首选项文件中设置的各种代码的颜色
private static void initColor() {
colorRegistry.put(Constants.COLOR_COMMENT,StringConverter.asRGB(getPreferenceStore().getString(Constants.COLOR_COMMENT)));
colorRegistry.put(Constants.COLOR_KEYWORD,StringConverter.asRGB(getPreferenceStore().getString(Constants.COLOR_KEYWORD)));
colorRegistry.put(Constants.COLOR_STRING,StringConverter.asRGB(getPreferenceStore().getString(Constants.COLOR_STRING)));
colorRegistry.put(Constants.COLOR_OBJECT,StringConverter.asRGB(getPreferenceStore().getString(Constants.COLOR_OBJECT)));
}
//保存的首选项设置文件
private static PreferenceStore preference ;
//加载首选项文件
public static PreferenceStore getPreferenceStore() {
if (preference == null) {
preference = new PreferenceStore( "jsEditor.properties");
try {
preference.load();
} catch (IOException e) {
e.printStackTrace();
}
}
return preference;
}
}
◆ 程序中使用的常量
package www.swt.com.ch21;
public class Constants {
//颜色的key值
public static final String COLOR_COMMENT = "Comment";
public static final String COLOR_KEYWORD = "Keyword";
public static final String COLOR_STRING = "String";
public static final String COLOR_OBJECT = "Object";
//字体的key值
public static final String CODE_FONT = "Font";
//语法使用的关键字
public static final String[] JS_SYNTAX_KEYWORD = {
"abstract", "boolean","break","byte","case",
"catch","char","class","const","continue",
"default","delete","do","double","else",
"extends","false","final","finally","float",
"for","function","goto","if","implements",
"import","short","in","instanceof","int",
"interface","long","native","new","null",
"package","private","protected","public","return",
"short","static","super","switch","synchronized",
"this","throw","throws","transient","true",
"try","typeof","var","void","while",
"with","script","language"
};
//语法使用的内置对象
public static final String[] JS_SYNTAX_BUILDIB_OBJECT = {
"Anchor","anchors","Applet","applets","Area",
"Array","Button","Checkbox","Date","document",
"FileUpload","Form","forms","Frame","frames",
"Hidden","history","Image","images","Link","write"
};
//程序中使用的一些图片常量
public static final String ICON_OPEN = "icon_open";
public static final String ICON_SAVE = "icon_save";
public static final String ICON_PINT = "icon_print";
public static final String ICON_UNDO = "icon_undo";
public static final String ICON_REDO = "icon_redo";
public static final String ICON_SEARCH = "icon_search";
public static final String ICON_PREFS = "icon_prefs";
public static final String ICON_HELP = "icon_help";
}
◆ jsEditor.properties内容:
#Sat Aug 14 23:55:10 CST 2010
Keyword=0,0,0
String=128,128,64
Object=0,0,0
Comment=128,0,128