在上一篇中,我們詳細闡述了WTP中最重要的數據模型之一IStructuredDocument(我們就稱之為WTP Document吧,和另外一個核心數據模型WTP Model----IStructuredModel對應),本節中我們將自己開發一個工具來分析IStrucutredDocument。
PS:千萬別著急,后面的文章會對WTP StructuredTextEditor進行功能特征定制的,在真正定制之前一定要搞清楚WTP Document(IStructuredDocument)和WTP Model(IStructuredModel),連核心數據模型都不熟悉,后面談何定制^_^
【WTP提供的Properteis視圖擴展】
說明:Properteis視圖是Eclipse固有的,允許用戶通過相應的類型擴展機制來定制Properties視圖中的內容,涉及到的主要知識點包括:
1、Eclipse的Adapter機制(IAdaptable、IAdapterFactory、AdapterManager),關于Eclipse中的類型適配擴展機制,博客中的另外一篇文章做了分析:
【Eclipse插件開發】Eclipse中類型擴展機制分析
2、Properties視圖相關的幾個重要接口:
org.eclipse.ui.views.properties.IPropertySource.class
org.eclipse.ui.views.properties.PropertySheetPage
...
3、WTP就是借助于Eclipse類型擴展機制,實現了對應的功能。我們看一下在WTP的StructuredTextEditor中的getAdapter方法中提供了擴展服務(IWorkbenchPart本身就是聲明為IAdaptable):
1 /*
2 * (non-Javadoc)
3 *
4 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
5 */
6 public Object getAdapter(Class required) {
7 //
8 else if (IPropertySheetPage.class.equals(required) && isEditable()) {
9 if (fPropertySheetPage == null || fPropertySheetPage.getControl() == null || fPropertySheetPage.getControl().isDisposed()) {
10 PropertySheetConfiguration cfg = createPropertySheetConfiguration();
11 if (cfg != null) {
12 ConfigurablePropertySheetPage propertySheetPage = new ConfigurablePropertySheetPage();
13 propertySheetPage.setConfiguration(cfg);
14 fPropertySheetPage = propertySheetPage;
15 }
16 }
17 result = fPropertySheetPage;
18 }
19 //
.
20 }
ConfigurablePropertySheetPage^_^(org.eclipse.wst.sse.ui.internal.properties.ConfigurablePropertySheetPage)。
WTP的properties視圖的主要功能就是,根據用戶在編輯器中光標的位置,在Properties視圖中展示對應的標簽內容,并支持用戶編輯,例如:

當前編輯器中,用戶光標位于jsp:directive.page標簽內,屬性視圖就列舉了對應的標簽內容,允許用戶編輯。
【我們自己的Stuctured Document View】
如果有一個視圖來將當前編輯器中的內容對應的IStrucuturedDocument以樹狀結構的方式展示出來,再提供一定的用戶交互,那對認清楚IStructuredDocument的本質是有作用的哈
【需求】
1、提供一個Structured Document View視圖,以樹狀方式將當前編輯器中的IStructuredDocument展示出來
2、交互(編輯器 ---> Structured Document View視圖):
激活WTP JSP編輯器(或者是我們前面自己定制的編輯器),即時更新Structured Document View視圖
當用戶光在編輯器中標移動時,自動選中Structured Document View視圖中對應的節點
當編輯器中的內容改變時,即時更新Structured Document View視圖
當前激活編輯器關閉時,清空Structured Document View視圖內容
3、交互(Structured Document View視圖 ---> 編輯器)
雙擊視圖中樹狀控件中特定節點,對應內容在編輯器中被選中
4、顯示內容:
對于IStructuredDocument,則顯示對應的具體實現類(對應于JSP類型則為JobSafeStructuredDocument)
對于IStrucutredDocumentRegion(ITextRegionCollection),則顯示實現類名稱、節點類型、位置范圍、文本等
對于葉子節點的ITextRegion,則顯示實現類名稱、節點類型、位置范圍(說明:相對于父ITextRegionCollection的相對位置,非對于整個文檔的相對位置!!!)
【效果預覽】

如上圖所示,雙擊視圖中的節點,編輯器中對應的內容被選中^_^。
【實現摘要(文章后面會附上完整代碼)】
1、
創建插件工程wtp.stuctureddocument,創建視圖,不用多說,利用擴展點org.eclipse.ui.views,對應內容如下:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <?eclipse version="3.2"?>
3 <plugin>
4 <extension
5 point="org.eclipse.ui.views">
6 <category
7 id="wtp.structureddocument.category"
8 name="Structured Document分析"/>
9 <view
10 category="wtp.structureddocument.category"
11 class="wtp.structureddocument.view.StructuredDocumentView"
12 id="wtp.structureddocument.view"
13 name="Structured Document分析"/>
14 </extension>
15 </plugin>
wtp.structureddocument.view.StructuredDocumentView為視圖對應的ViewPart實現,里面創建了一個tree viewer控件,并給其配置了對應的content provider和label provider,具體參加附件中的源碼。
2、
利用workbench中的選擇服務(seleciton service)。前面需求中說過,我們要監聽光標在編輯器中的位置選擇,所以使用此服務,所以我們的StructuredDocumentView要實現org.eclipse.ui.ISelectionListener接口。
(PS:selection service是workbench的一個重要特性,也是我們常用的,Eclipse官方網站上有一篇專題文章,看看撒兩眼^_^)
注冊selection listener:
1 /* (non-Javadoc)
2 * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite)
3 */
4 public void init(IViewSite site) throws PartInitException {
5 super.init(site);
6
7 this.getSite().getPage().getWorkbenchWindow().getSelectionService().addPostSelectionListener(this);
8 this.getSite().getPage().getWorkbenchWindow().getPartService().addPartListener(partListener);
9 }
實現selection事件處理:
1 /* (non-Javadoc)
2 * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
3 */
4 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
5 if (part instanceof TextEditor) {
6 IEditorInput editorInput = ((TextEditor)part).getEditorInput();
7 IDocument document = ((TextEditor)part).getDocumentProvider().getDocument(editorInput);
8
9 if (!(document instanceof IStructuredDocument)) {
10 this.viewer.setInput(new Object[0]);
11 return ;
12 }
13
14 IStructuredDocument structuredDocument = (IStructuredDocument)document;
15
16 if (this.needUpdateInput(document)) {
17 this.viewer.setInput(new Object[]{document});
18 this.viewer.expandAll();
19 this.viewer.collapseAll();
20
21 //set source workbench part, will be used in part listener
22 this.sourcePart = part;
23
24 //注冊監聽器,便于同步刷新tree viewer
25 structuredDocument.addDocumentListener(documentListener);
26 }
27
28 if (selection instanceof ITextSelection) {
29 int offset = ((ITextSelection)selection).getOffset();
30 IStructuredDocumentRegion structuredDocumentRegion = structuredDocument.getRegionAtCharacterOffset(offset);
31 ITextRegion textRegion = structuredDocumentRegion.getRegionAtCharacterOffset(offset);
32
33 this.viewer.collapseAll();
34 this.viewer.expandToLevel(structuredDocumentRegion, 2);
35 this.viewer.setSelection(new StructuredSelection(textRegion));
36 }
37 }
38 }
1 /**
2 * 判斷當前document和tree viewer中輸入的document是否一致
3 *
4 * @param document
5 * @return
6 */
7 private boolean needUpdateInput(IDocument document) {
8 if (this.viewer.getInput() != null) {
9 Object[] oldInput = (Object[])this.viewer.getInput();
10 if (oldInput.length == 1)
11 return oldInput[0] != document;
12 }
13
14 return true;
15 }
看的出來,我們的selection處理邏輯如下:
1、如果當前選擇事件來自TextEditor類型的編輯器中(文本編輯器的共同超類),則去獲取編輯器對應的IDocument,如果是IStrucuturedDocument則判斷是否需要更新;如果不是,則不是我們的目標(例如如果打開的是java源碼編輯器,那對應的IDocument實現肯定不是WTP的IStrucuturedDocument了哈^_^)
2、如果是IStrucuturedDocument,則確定是否需要更新(也就是判斷我們的Structured Document分析視圖中現有的input是否就是當前編輯器中內容對應的IStrucuturedDocument)。如果需要更新,則重新設置tree viewer輸入
3、我們根據光標在編輯器中的位置信息(參加org.eclipse.jface.text.ITexSelection),首先利用IStructuredDocument.getRegionAtCharacterOffset(int)來定位對應的IStructuredDocumentRegion樹枝節點,然后在利用IStructuredDocumentRegion.getRegionAtCharacterOffset(int)來定位對應的ITextRegion樹葉節點,在我們視圖的樹狀控件中選中對應的樹葉節點。
回顧:對于IStructuredDocument來說,它的孩子就是IStructuredDoucmentRegion,并沒有提供對應的接口允許用戶直接去獲取特定offset對應的具體ITextRegion。再看一下前面用到的一幅圖吧:
3、
處理視圖中tree viewer雙擊,定位編輯器中對應內容。
1 this.viewer.addDoubleClickListener(new IDoubleClickListener() {
2 public void doubleClick(DoubleClickEvent event) {
3 TreeSelection treeSelection = (TreeSelection)event.getSelection();
4 TreePath treePath = treeSelection.getPaths()[0];
5
6 if (treePath.getSegmentCount() == 1) {
7 //選擇的是IStructuredDocument
8 IStructuredDocument structuredDocument = (IStructuredDocument)treeSelection.getFirstElement();
9
10 //處理編輯器選中,選中整個文檔
11 ((StructuredTextEditor)sourcePart).getTextViewer().setSelectedRange(0, structuredDocument.getLength());
12 }
13 else if (treePath.getSegmentCount() == 2) {
14 //選擇的是IStructuredDocumentRegion
15 IStructuredDocumentRegion structuredDocumentRegion = (IStructuredDocumentRegion)treePath.getLastSegment();
16 int selectionOffset = structuredDocumentRegion.getStart();
17 int selectionLength = structuredDocumentRegion.getLength();
18
19 //處理編輯器選中,選中structured document region區域
20 ((StructuredTextEditor)sourcePart).getTextViewer().setSelectedRange(selectionOffset, selectionLength);
21 }
22 else if (treePath.getSegmentCount() == 3) {
23 //選擇的是非container的ITextRegion,其父節點為IStructuredDocumentRegion
24 IStructuredDocumentRegion structuredDocumentRegion = (IStructuredDocumentRegion)treePath.getSegment(1);
25 ITextRegion textRegion = (ITextRegion)treePath.getLastSegment();
26
27 int selectionOffset = structuredDocumentRegion.getStartOffset(textRegion);
28 int selectionLength = textRegion.getLength();
29
30 //處理編輯器選中,選中的是葉子節點的text region區域
31 ((StructuredTextEditor)sourcePart).getTextViewer().setSelectedRange(selectionOffset, selectionLength);
32 }
33 }
34 });
TreePath(org.eclipse.jface.viewers.TreePath)中的seg count信息其實確定了我們的雙擊的控件位于整個樹狀控件的位置,具體自己看吧^_^
4、
利用IDocumentListener同步更新視圖。邏輯非常簡單,如下:
1 private class StructuredDocumentListener implements IDocumentListener {
2 public void documentChanged(DocumentEvent event) {
3 viewer.refresh();
4 }
5
6 public void documentAboutToBeChanged(DocumentEvent event) {
7 // nothing to do
8 }
9 }
我們的listener實在前面handle selection的過程注冊的,再決定用一個新的IStructuredDocument作為tree viewer輸入的時候,同步注冊對應的監聽器。
注意:我們這邊并沒有wtp自己的document listener,它自己都不建議使用了^_^
5、
處理編輯器關閉行為,利用workbench的part service特性。當對應的編輯器關閉時,視圖中的tree viewer
1 private class PartListener implements IPartListener {
2 public void partActivated(IWorkbenchPart part) {
3 // TODO Auto-generated method stub
4
5 }
6
7 public void partBroughtToTop(IWorkbenchPart part) {
8 // TODO Auto-generated method stub
9
10 }
11
12 /*
13 * 如果被關閉的workbench part是提供document信息的source part,則情況tree viewer
14 *
15 * @see org.eclipse.ui.IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart)
16 */
17 public void partClosed(IWorkbenchPart part) {
18 if (sourcePart == part) {
19 sourcePart = null;
20 viewer.setInput(new Object[0]);
21 }
22 }
23
24 public void partDeactivated(IWorkbenchPart part) {
25 // TODO Auto-generated method stub
26
27 }
28
29 public void partOpened(IWorkbenchPart part) {
30 // TODO Auto-generated method stub
31 }
32 }
注意:上面代碼的source part是我們的handle selection過程中緩存的,這邊就派上用場了啊,能夠判斷當前關閉的part是否就提供當前IStructuredDocument的text eidtor 了^_^
注冊代碼再上面的init方法代碼中有,注冊了對應的selection listener和part listener
上面基本上就是我們的視圖主類wtp.structureddocument.view.StructuredDocumentView的所有代碼了,我們開發這樣一個視圖也只用了200多行代碼...
最后強調一下,我們的這個插件工程需要依賴的插件列表為:
org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.wst.sse.core,
org.eclipse.wst.xml.core,
org.eclipse.wst.sse.ui,
org.eclipse.jface.text,
org.eclipse.ui.workbench.texteditor
【源碼下載】
源碼為實際工程以Export ---> Archive File方式導出的,下載鏈接:
source.zip 。
本博客中的所有文章、隨筆除了標題中含有引用或者轉載字樣的,其他均為原創。轉載請注明出處,謝謝!