在上一篇中,我们详细阐述了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 。
本博客中的所有文章、随笔除了标题中含有引用或者转载字样的,其他均为原创。转载请注明出处,谢谢!