本文主要記錄怎么給代碼編輯器實(shí)際語法高亮顯示的功能,先來張效果圖吧:
當(dāng)JEditorPane被創(chuàng)建時(shí),它會(huì)把createDefaultEditorKit()方法(javax.swing.text.EditorKit的子類對象)的返回值作為默認(rèn)的編輯器工具包,然后將文本的編輯與顯示工作交給這個(gè)工具包。其原型為:
1
protected EditorKit createDefaultEditorKit()
2

{
3
return new PlainEditorKit();
4
}
這個(gè)方法默認(rèn)是返回一個(gè)PlainEditorKit對象,也就是一個(gè)純文本的編輯器工具包,所以JEditorPane默認(rèn)并沒有格式化與彩色顯示等功能,看來我們先要定制一個(gè)支持彩色顯示的EditorKit,然后把它作為createDefaultEditorKit()的返回值。
EditorKit基本上什么也沒有做,只是提供了很多抽象方法給它的子類去實(shí)現(xiàn),Swing默認(rèn)已經(jīng)給它添加了一個(gè)子類DefaultEditorKit(Swing常用的一招,就是給抽象類前面加個(gè)Default進(jìn)行最基本的實(shí)現(xiàn)),既然是Default,那它所提供的功能肯定和一個(gè)記事本沒有多大區(qū)別,這要是繼承下來,有多少方法需要覆蓋啊,別慌,查看一下Swing的源碼,你會(huì)發(fā)現(xiàn)Swing還提供了一個(gè)繼承自DefaultEditorKit的類StyledEditorKit,顧名思義,這個(gè)類肯定為我們提供了很多支持格式化顯示的方法,又是一個(gè)巨人,快,趕緊拉過來往肩上爬。
接下來就是覆蓋StyledEditorKit中的相關(guān)方法了,其實(shí)有很多方法都可以覆蓋,但是意義不是很大,比如
public String getContentType();
這個(gè)方法是獲得此工具包聲明支持的數(shù)據(jù)的 MIME 類型,默認(rèn)是text/plain,也就是文本文檔,Java文件說白了也是文本文檔,不過可以讓它返回 text/java 以唯一標(biāo)識(shí)編輯器所支持的MIME類型。
EditorKit中有兩個(gè)重要的方法實(shí)現(xiàn)對文檔的管理與顯示:
public abstract Document createDefaultDocument();
創(chuàng)建一個(gè)適合此編輯器類型文本存儲(chǔ)模型。EditorKit把對文本文檔的管理功能交給了這個(gè)方法的返回值。
public abstract ViewFactory getViewFactory();
獲取適合生成此工具包生成的任何模型視圖的工廠。EditorKit把編輯器的顯示功能交給了這個(gè)方法的返回值,比如什么字符顯示成什么樣子,什么顏色等。我們必須覆蓋這兩個(gè)方法以實(shí)現(xiàn)自定義編輯器的功能。
因?yàn)槲覀兊木庉嬈骱蚃EditorPane唯一不同的可能就是代碼怎么來顯示,所以createDefaultDocument()可以返回一個(gè)默認(rèn)的javax.swing.text.DefaultStyledDocument 就行,對于getViewFactory,我們需要定制一個(gè)ViewFactory視圖來實(shí)現(xiàn)編輯器獨(dú)有的各種顯示功能。
ViewFactory在Java中被定義為一個(gè)接口,里面提供了唯一的一個(gè)方法:
public View create(Element elem);
這個(gè)方法根據(jù)給定的文檔的結(jié)構(gòu)化元素創(chuàng)建一個(gè)視圖。在這個(gè)方法中,我們只需要返回一個(gè)繼承自View的視圖即可,真正的顯示任務(wù)是交給這個(gè)視圖的。因此,我們的ViewFactory類很簡單:
1
public class JavaViewFactory implements ViewFactory
2

{
3
/**//*
4
* (non-Javadoc)
5
*
6
* @see javax.swing.text.ViewFactory#create(javax.swing.text.Element)
7
*/
8
public View create(Element element)
9
{
10
return new JavaEditorView(element);
11
}
12
}
接下來的重點(diǎn)就是這個(gè)JavaEditorView了,所有的語法高亮等顯示功能都是交給它來完成的
View是一個(gè)抽象類,Swing默認(rèn)給我們提供了多個(gè)它的子類,AsyncBoxView, ComponentView, CompositeView, GlyphView, IconView, ImageView, PlainView 以實(shí)現(xiàn)對不同文檔類型的顯示,當(dāng)中只有PlainView是與文本文檔相關(guān)的,它實(shí)現(xiàn)簡單的多行文本視圖的 View 接口,該文本視圖的文本只有一種字體和顏色,沒錯(cuò),我們的JavaEditorView需要繼承自PlainView。
PlainView提供了很多方法進(jìn)行文本文檔的視圖顯示,要實(shí)現(xiàn)高亮顯示,我們關(guān)心的有兩個(gè)方法:
protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException
一看名字就知道這個(gè)方法是控制選中狀態(tài)下的顯示方式,由于本文只討論非選中狀態(tài)。所以重點(diǎn)看一下另外一個(gè)方法:
protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException
這個(gè)方法將模型中給定的范圍呈現(xiàn)為正常的未選定文本。使用前景色或指定的顏色顯示文本。
參數(shù):
g - 圖形上下文(做Swing的人再熟悉不過了,文本也是畫出來的)
x - 起始 X 坐標(biāo),該值 >= 0
y - 起始 Y 坐標(biāo),該值 >= 0
p0 - 模型中的起始位置,該值 >= 0
p1 - 模型中的結(jié)束位置,該值 >= 0
下面是覆蓋后的實(shí)現(xiàn):
1
protected int drawUnselectedText(Graphics g, int x, int y, int startOffset, int endOffset)
2
throws BadLocationException
3

{
4
int docLength = getDocument().getLength();
5
int length = (endOffset < docLength ? endOffset : docLength) - startOffset;
6
7
return scanParagraph(g, x, y, startOffset, length);
8
}
先是得到從起始位置到結(jié)束位置的長度,然后再交由scanParagraph去處理指定長度的文本,其實(shí)也就是怎么把它畫出來。
對于一個(gè)Java代碼編輯器,要考慮類名,運(yùn)算符,數(shù)字,關(guān)鍵字等的顯示方式,所以scanParagraph要做的事情很多,本文只以怎么么高亮顯示類名為例來說明:
1
private int scanParagraph(Graphics g, int x, int y, int startOffset, int length) throws BadLocationException
2

{
3
Segment seg = new Segment();
4
//得到編輯器組件
5
JavaCodeEditor editor = (JavaCodeEditor) getContainer();
6
//得到startOffset,位置開始的length個(gè)長度的字符串,其實(shí)也就是我們要處理的字符串
7
getDocument().getText(startOffset, length, seg);
8
for (int wordIndex = 0; wordIndex < seg.length();)
9
{
10
char currentChar = seg.charAt(wordIndex);
11
if (Character.isJavaIdentifierStart(currentChar))
12
{
13
//下面我默認(rèn)用Object說明,實(shí)際中要處理seg中的內(nèi)容。
14
String identifier = "Object";
15
int len = identifier.length();
16
17
//比如說以紅色顯示類名
18
Segment text = getLineBuffer();
19
getDocument().getText(startOffset + wordIndex, len, text);
20
//還有其它樣式的話只管給g加
21
g.setColor(color);
22
23
Utilities.drawTabbedText(text, x, y, g, this, startOffset + wordIndex);
24
25
//下面的代碼略
26

.
27
}
28
}
29
//下面的代碼略
30

.
31
}
我只是以類名進(jìn)行示范,實(shí)際中可能還要考慮此類名是否在注釋當(dāng)中等等...