Java tutorial
/******************************************************************************* * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.eclipse.xtext.ui.editor.outline.quickoutline; import java.util.List; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.bindings.keys.KeySequence; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.bindings.keys.SWTKeySupport; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.PopupDialog; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.ui.editor.XtextEditor; import org.eclipse.xtext.ui.editor.contentassist.PrefixMatcher; import org.eclipse.xtext.ui.editor.model.IXtextDocument; import org.eclipse.xtext.ui.editor.outline.IOutlineNode; import org.eclipse.xtext.ui.editor.outline.IOutlineTreeProvider; import org.eclipse.xtext.ui.editor.outline.impl.OutlineFilterAndSorter; import org.eclipse.xtext.ui.editor.outline.impl.OutlineMode; import org.eclipse.xtext.ui.editor.outline.impl.OutlineNodeContentProvider; import org.eclipse.xtext.ui.editor.outline.impl.OutlineNodeElementOpener; import org.eclipse.xtext.ui.editor.outline.impl.OutlineNodeLabelProvider; import org.eclipse.xtext.ui.internal.Activator; import org.eclipse.xtext.util.concurrent.IUnitOfWork; import com.google.inject.Inject; /** * @author Peter Friese - Initial contribution and API * @author Jan Koehnlein - Adaption to new outline architecture */ public class QuickOutlinePopup extends PopupDialog implements DisposeListener { protected class NamePatternFilter extends ViewerFilter { public NamePatternFilter() { } @Override public boolean select(Viewer viewer, Object parentElement, Object element) { StringMatcher matcher = getMatcher(); if (matcher == null || !(viewer instanceof TreeViewer)) return true; TreeViewer treeViewer = (TreeViewer) viewer; String matchName = ((ILabelProvider) treeViewer.getLabelProvider()).getText(element); matchName = TextProcessor.deprocess(matchName); if (matchName != null && matcher.match(matchName)) return true; return hasUnfilteredChild(treeViewer, element); } /** * @since 2.1 protected */ protected boolean hasUnfilteredChild(TreeViewer viewer, Object element) { Object[] children = ((ITreeContentProvider) viewer.getContentProvider()).getChildren(element); for (int i = 0; i < children.length; i++) if (select(viewer, element, children[i])) return true; return false; } } @Inject private IOutlineTreeProvider treeProvider; @Inject private OutlineNodeLabelProvider labelProvider; @Inject private OutlineNodeContentProvider contentProvider; @Inject private OutlineFilterAndSorter.IComparator comparator; @Inject private QuickOutlineFilterAndSorter filterAndSorter; @Inject private PrefixMatcher prefixMatcher; @Inject private OutlineNodeElementOpener elementOpener; @Inject private IQuickOutlineContribution.Composite contributions; private int TREESTYLE = SWT.V_SCROLL | SWT.H_SCROLL; private TreeViewer treeViewer; private IXtextDocument document; private XtextEditor xtextEditor; private Text filterText; private PrefixMatcherOutlineAdapter prefixMatcherOutlineAdapter; private KeyStroke invokingKeystroke; private String invokingKeystrokeFormatted; public QuickOutlinePopup() { this(null); } public QuickOutlinePopup(Shell parent) { super(parent, SWT.RESIZE, true, false, true, true, true, null, Messages.QuickOutlinePopup_pressESC); } @Override protected Control createTitleControl(Composite parent) { filterText = createFilterText(parent); return filterText; } @Override protected Control createDialogArea(Composite parent) { treeViewer = createTreeViewer(parent, TREESTYLE); scheduleRefresh(); final Tree tree = treeViewer.getTree(); tree.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.character == 0x1B) { // ESC dispose(); } else { if (e.keyCode == invokingKeystroke.getNaturalKey() && e.stateMask == invokingKeystroke.getModifierKeys()) { changeOutlineMode(); e.doit = false; } } } }); tree.addSelectionListener(new SelectionAdapter() { @Override public void widgetDefaultSelected(SelectionEvent e) { gotoSelectedElement(); } }); installFilter(); setInfoText(); addDisposeListener(this); return treeViewer.getControl(); } /** * @since 2.2 */ protected void setInfoText() { if (treeProvider instanceof IOutlineTreeProvider.ModeAware) { setInfoText("Press '" + invokingKeystrokeFormatted + "' to " + ((IOutlineTreeProvider.ModeAware) treeProvider).getNextMode().getDescription()); } else { setInfoText(Messages.QuickOutlinePopup_pressESC); } } protected TreeViewer createTreeViewer(Composite parent, int style) { Tree tree = new Tree(parent, SWT.SINGLE | (style & ~SWT.MULTI)); GridData gd = new GridData(GridData.FILL_BOTH); gd.heightHint = tree.getItemHeight() * 12; tree.setLayoutData(gd); final TreeViewer treeViewer = new TreeViewer(tree); treeViewer.addFilter(new NamePatternFilter()); treeViewer.setContentProvider(contentProvider); filterAndSorter.setComparator(comparator); contentProvider.setFilterAndSorter(filterAndSorter); treeViewer.setLabelProvider(labelProvider); treeViewer.setAutoExpandLevel(2); return treeViewer; } /** * @since 2.7 */ public void scheduleRefresh() { if (getTreeViewer() != null) { IOutlineNode rootNode = document.priorityReadOnly(new IUnitOfWork<IOutlineNode, XtextResource>() { @Override public IOutlineNode exec(XtextResource state) throws Exception { IOutlineNode rootNode = treeProvider.createRoot(document); createChildrenRecursively(rootNode.getChildren()); return rootNode; } protected void createChildrenRecursively(List<IOutlineNode> nodes) { for (IOutlineNode node : nodes) { createChildrenRecursively(node.getChildren()); } } }); getTreeViewer().setInput(rootNode); } } protected Text createFilterText(Composite parent) { filterText = new Text(parent, SWT.NONE); Dialog.applyDialogFont(filterText); GridData data = new GridData(GridData.FILL_HORIZONTAL); data.horizontalAlignment = GridData.FILL; data.verticalAlignment = GridData.CENTER; filterText.setLayoutData(data); filterText.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == 0x0D) // return gotoSelectedElement(); if (e.keyCode == SWT.ARROW_DOWN) treeViewer.getTree().setFocus(); if (e.keyCode == SWT.ARROW_UP) treeViewer.getTree().setFocus(); if (e.character == 0x1B) // ESC dispose(); if (e.keyCode == invokingKeystroke.getNaturalKey() && e.stateMask == invokingKeystroke.getModifierKeys()) { changeOutlineMode(); e.doit = false; } } }); return filterText; } @Override protected Control getFocusControl() { return filterText; } /** * @since 2.1 protected */ protected void installFilter() { filterText.setText(""); //$NON-NLS-1$ filterText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { String text = ((Text) e.widget).getText(); setMatcherString(text, true); } }); } protected StringMatcher getMatcher() { return prefixMatcherOutlineAdapter; } protected boolean hasMatcher() { return prefixMatcherOutlineAdapter != null; } protected void setMatcherString(String pattern, boolean update) { if (pattern.length() == 0) { prefixMatcherOutlineAdapter = null; } else { prefixMatcherOutlineAdapter = new PrefixMatcherOutlineAdapter(pattern, prefixMatcher); } if (update) stringMatcherUpdated(); } protected void stringMatcherUpdated() { // refresh viewer to re-filter treeViewer.getControl().setRedraw(false); treeViewer.refresh(); treeViewer.expandAll(); selectFirstMatch(); treeViewer.getControl().setRedraw(true); } protected void selectFirstMatch() { Object[] rootElements = contentProvider.getElements(treeViewer.getInput()); Object matchingElement = findMatchingElement(rootElements); ISelection selection = StructuredSelection.EMPTY; if (matchingElement != null) { selection = new StructuredSelection(matchingElement); } treeViewer.setSelection(selection); } protected Object findMatchingElement(Object[] elements) { if (hasMatcher()) { for (Object element : elements) { String text = labelProvider.getStyledStringProvider().getStyledText(element).getString(); if (getMatcher().match(text)) { return element; } Object[] children = contentProvider.getChildren(element); Object matchingChild = findMatchingElement(children); if (matchingChild != null) { return matchingChild; } } } return null; } protected Object getSelectedElement() { if (treeViewer == null) { return null; } return ((IStructuredSelection) treeViewer.getSelection()).getFirstElement(); } /** * @since 2.1 protected */ protected void gotoSelectedElement() { Object selectedElement = getSelectedElement(); if (selectedElement != null) { dispose(); if (selectedElement instanceof IOutlineNode) { elementOpener.open((IOutlineNode) selectedElement, xtextEditor.getInternalSourceViewer()); } } } public final void dispose() { close(); } public void addDisposeListener(DisposeListener listener) { getShell().addDisposeListener(listener); } public void removeDisposeListener(DisposeListener listener) { getShell().removeDisposeListener(listener); } @Override public void widgetDisposed(DisposeEvent event) { treeViewer = null; filterText = null; } public void setInput(IXtextDocument document) { this.document = document; scheduleRefresh(); } @Override protected Point getDefaultLocation(Point initialSize) { Control textWidget = (Control) xtextEditor.getAdapter(Control.class); Point size = textWidget.getSize(); Point popupLocation = new Point((size.x / 2) - (initialSize.x / 2), (size.y / 2) - (initialSize.y / 2)); return textWidget.toDisplay(popupLocation); } @Override protected IDialogSettings getDialogSettings() { String sectionName = "xtext.quickoutline"; //$NON-NLS-1$ IDialogSettings settings = Activator.getDefault().getDialogSettings().getSection(sectionName); if (settings == null) { settings = Activator.getDefault().getDialogSettings().addNewSection(sectionName); } return settings; } public void setEditor(XtextEditor xtextEditor) { this.xtextEditor = xtextEditor; } /** * @since 2.2 */ public void setEvent(Event event) { this.invokingKeystrokeFormatted = KeySequence .getInstance(SWTKeySupport .convertAcceleratorToKeyStroke(SWTKeySupport.convertEventToUnmodifiedAccelerator(event))) .format(); this.invokingKeystroke = KeyStroke.getInstance(event.stateMask, event.keyCode); } /** * @since 2.2 */ protected void changeOutlineMode() { if (treeProvider instanceof IOutlineTreeProvider.ModeAware) { IOutlineTreeProvider.ModeAware modeTreeProvider = (IOutlineTreeProvider.ModeAware) treeProvider; OutlineMode nextMode = modeTreeProvider.getNextMode(); modeTreeProvider.setCurrentMode(nextMode); setInfoText(); setInput(document); } } /** * @since 2.7 */ public TreeViewer getTreeViewer() { return treeViewer; } @Override public boolean close() { contributions.deregister(this); return super.close(); } @Override protected void fillDialogMenu(IMenuManager dialogMenu) { super.fillDialogMenu(dialogMenu); dialogMenu.add(new Separator("XtextContributions")); contributions.register(this, dialogMenu); } }