JAVA.SWT/JFace: JFace篇之文本处理

 

 

《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

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值