内容大纲
之所以要给编辑器提供内容大纲主要基于两点:一是让用户更好的总览文档全局,二个是能更方便的定位到指定的文档位置
如下图所示
内容大纲能做的三件事:
1.对结构性文档内容的解析,在XML编辑器中,我们需要显示节点的名称,属性名与值,但是不显示节点文档内容
2.能通过内容大纲进行更新,在内容大纲中直接对编辑器内容进行修改,这里我们不做实现
3.导航功能的支持,当在内容大纲中选中了一个节点,那么在编辑器中对应的部分应该立即高亮显示
首先我们要做的工作就是把内容大纲与编辑器关联起来,Eclipse提供了一个接口IContentOutlinePage, 但是我们最简单的做法是直接从ContentOutlinePage继承.而通过编辑器的getAdapter方法可以将其与内容大纲关联起来,如下代码所示:
- public Object getAdapter(Class required)
- {
- if (IContentOutlinePage.class.equals(required))
- {
- if (outlinePage == null)
- {
- outlinePage = new XMLContentOutlinePage(this);
- ...
- }
- return outlinePage;
- }
- return super.getAdapter(required);
- }
在OutlineContentHandler类中来对文档进行解析,该类主要实现了SAX ContentHandler接口,它的工作就是根据XML文档构造一棵XML树,在这棵树上包含了内容大纲所需要的节点名,属性名,属性值以及在节点在文档中的位置(行号和列号),这些内容我们将通过XMLTree, XMLElement, XMLAttribute类来处理, 在我们的实现类XMLContentOutlinePage 中,通过一个ITreeViewer类将树状内容展示给用户,因此我们必须同时实现ITreeContentProvider 和ITreeLabelProvider 接口来提供内容,以及告诉TreeViewer如何展示内容,这里我们的实现类分别是OutlineContentProvider 和OutlineLabelProvider,OutlineContentProvider部分代码实现如下:
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
- {
- ...
- input = (IEditorInput) newInput;
- if (newInput != null)
- {
- IDocument document = documentProvider.getDocument(newInput);
- if (document != null)
- {
- ...
- XMLElement rootElement = parseRootElement(document);
- if (rootElement != null)
- {
- root = rootElement;
- }
- }
- }
- }
从上面的代码中,我们可以看出OutlineContentHandler的工作就是将XML文档内容转换成XMLTree给OutlineContentProvider使用
大纲视图的更新
使用视图大纲来更新编辑内容主要有两种策略:一种就是在大纲视图中做出修改之后能实时更新文档,一种是在保存文档的时候进行更新,为了简单起见,这里我们采用后者
前面我们已经看到当调用OutlineContentProvider.inputChanged()方法时,内容大纲将被更新,根据我们的使用策略,该方法的调用只会出现在两个地方,一个是文档加载时,一个是文档保存时
前面我们已经知道,编辑器在加载文档的时候,它会通过getAdapter()方法来获得内容大纲对象,代码如下所示:
- public Object getAdapter(Class required)
- {
- if (IContentOutlinePage.class.equals(required))
- {
- if (outlinePage == null)
- {
- outlinePage = new XMLContentOutlinePage(this);
- if (getEditorInput() != null)
- outlinePage.setInput(getEditorInput());
- }
- return outlinePage;
- }
- return super.getAdapter(required);
- }
这里我们将当前编辑的内容通过getInput()方法传递给内容大纲,此时将导致内容大纲的update()方法被调用,其实现代码如下:
- public void update()
- {
- TreeViewer viewer = getTreeViewer();
- if (viewer != null)
- {
- Control control = viewer.getControl();
- if (control != null && !control.isDisposed())
- {
- control.setRedraw(false);
- viewer.setInput(input);
- viewer.expandAll();
- control.setRedraw(true);
- }
- }
- }
当编辑器保存时对内容大纲进行更新只需要复写编辑器的editorSaved()方法即可,代码如下:
- if (outlinePage != null)
- outlinePage.update();
内容的导航
为了对编辑的文档一目了然,我们将文档内容使用XMLTree进行封装,为了进行导航,一方面我们需要将当前编辑器中的位置映射到内容大纲的相应节点,另一方面就是将我们在内容大纲中选中的节点的同时让编辑器中相应的文本也被选中
为了得到文档的行列信息, 在SAX ContentHandler中我们使用了DocumentLocator类来构建XMLTree结构
- private XMLElement parseRootElement(IDocument document)
- {
- String text = document.get();
- XMLParser xmlParser = new XMLParser();
- OutlineContentHandler contentHandler = new OutlineContentHandler();
- contentHandler.setDocument(document);
- ...
- contentHandler.setDocumentLocator(new LocatorImpl());
- xmlParser.setContentHandler(contentHandler);
- xmlParser.doParse(text);
- XMLElement root = contentHandler.getRootElement();
- return root;
- }
为了让对内容大纲选择更迭的处理反应到编辑器中,我们使用了下面的代码:
- public void selectionChanged(SelectionChangedEvent event)
- {
- super.selectionChanged(event);
- // find out which item in tree viewer we have selected, and set
- // highlight range accordingly
- ISelection selection = event.getSelection();
- if (selection.isEmpty())
- editor.resetHighlightRange();
- else
- {
- IStructuredSelection sel = (IStructuredSelection) selection;
- XMLElement element = (XMLElement) sel.getFirstElement();
- int start = element.getPosition().getOffset();
- int length = element.getPosition().getLength();
- try
- {
- editor.setHighlightRange(start, length, true);
- }
- catch (IllegalArgumentException x)
- {
- editor.resetHighlightRange();
- }
- }
- }
通过从选择节点中取得位置信息,然后对编辑器中的高亮显示区域重新定位.