/* * Copyright (c) 2014, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.internal.text.functions; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.internal.text.editor.DartEditor; import com.google.dart.tools.ui.internal.text.editor.DartOutlinePage_NEW; import com.google.dart.tools.ui.internal.text.editor.LightNodeElements; import com.google.dart.tools.ui.internal.util.GridDataFactory; import org.dartlang.analysis.server.protocol.ElementKind; import org.dartlang.analysis.server.protocol.Outline; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.PopupDialog; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; import org.eclipse.jface.viewers.IStructuredSelection; 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.swt.SWT; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusListener; 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.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; 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.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.texteditor.ITextEditor; import java.util.List; /** * Show outline in light-weight control. */ public class DartOutlineInformationControl_NEW extends PopupDialog implements IInformationControl { /** * {@link ViewerFilter} that allows only branches with outlines satisfying {@link #matcher}. */ private class NameFilter extends ViewerFilter { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { Outline outline = (Outline) element; // no filter if (matcher == null) { return true; } // maybe "outline" matches String name = outline.getElement().getName(); if (name != null && matcher.match(name)) { return true; } // ...or has matching children return hasMatchingChild(outline); } private boolean hasMatchingChild(Outline outline) { for (Outline child : outline.getChildren()) { if (select(viewer, outline, child)) { return true; } } return false; } } private class OutlineTreeViewer extends TreeViewer { private OutlineTreeViewer(Tree tree) { super(tree); setUseHashlookup(true); } @Override protected Widget internalGetWidgetToSelect(Object elementOrTreePath) { if (elementOrTreePath instanceof TreeItem) { return (TreeItem) elementOrTreePath; } return super.internalGetWidgetToSelect(elementOrTreePath); } TreeItem findItem2(Object o) { return (TreeItem) super.findItem(o); } } /** * @return the deepest {@link Outline} enclosing given offset, may be <code>null</code>. */ private static Outline findOutlineEnclosingOffset(List<Outline> outlines, int offset) { for (Outline outline : outlines) { if (outline.containsInclusive(offset)) { Outline deeperOutline = findOutlineEnclosingOffset(outline.getChildren(), offset); if (deeperOutline != null) { return deeperOutline; } return outline; } } return null; } private final DartEditor editor; private Text filterText; private OutlineTreeViewer viewer; private DartElementPrefixPatternMatcher matcher; public DartOutlineInformationControl_NEW(Shell parent, int shellStyle, ITextEditor textEditor) { super(parent, shellStyle, true, true, true, true, true, "titleText", "infoText"); editor = textEditor instanceof DartEditor ? (DartEditor) textEditor : null; create(); } @Override public void addDisposeListener(DisposeListener listener) { getShell().addDisposeListener(listener); } @Override public void addFocusListener(FocusListener listener) { getShell().addFocusListener(listener); } @Override public Point computeSizeHint() { return getShell().getSize(); } @Override public void dispose() { close(); } @Override public boolean isFocusControl() { return viewer.getControl().isFocusControl() || filterText.isFocusControl(); } @Override public void removeDisposeListener(DisposeListener listener) { getShell().removeDisposeListener(listener); } @Override public void removeFocusListener(FocusListener listener) { getShell().removeFocusListener(listener); } @Override public void setBackgroundColor(Color background) { applyBackgroundColor(background, getContents()); } @Override public void setFocus() { getShell().forceFocus(); filterText.setFocus(); } @Override public void setForegroundColor(Color foreground) { applyForegroundColor(foreground, getContents()); } @Override public void setInformation(String information) { // ignore } @Override public void setLocation(Point location) { if (!getPersistSize() || getDialogSettings() == null) { getShell().setLocation(location); } } @Override public void setSize(int width, int height) { getShell().setSize(width, height); } @Override public void setSizeConstraints(int maxWidth, int maxHeight) { // ignore } @Override public void setVisible(boolean visible) { if (visible) { open(); } else { saveDialogBounds(getShell()); getShell().setVisible(false); } } @Override protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText("Outline"); } @Override protected Control createDialogArea(Composite parent) { final Tree tree = new Tree(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION); GridDataFactory.create(tree).hintHeightChars(20).grab().fill(); if (editor == null) { return tree; } // create TreeViewer viewer = new OutlineTreeViewer(tree); viewer.addFilter(new NameFilter()); viewer.setComparer(DartOutlinePage_NEW.OUTLINE_COMPARER); viewer.setContentProvider(new DartOutlinePage_NEW.OutlineContentProvider()); viewer.setLabelProvider(new DelegatingStyledCellLabelProvider( new DartOutlinePage_NEW.OutlineLabelProvider())); viewer.setInput(editor.getOutline()); selectOutlineEnclosingEditorSelection(); // close on ESC tree.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.character == 0x1B) { dispose(); } } }); // goto on Enter tree.addSelectionListener(new SelectionAdapter() { @Override public void widgetDefaultSelected(SelectionEvent e) { gotoSelectedOutline(); } }); // select item under mouse tree.addMouseMoveListener(new MouseMoveListener() { TreeItem lastItem = null; @Override public void mouseMove(MouseEvent e) { if (tree.equals(e.getSource())) { Object o = tree.getItem(new Point(e.x, e.y)); if (o instanceof TreeItem) { if (!o.equals(lastItem)) { lastItem = (TreeItem) o; tree.setSelection(new TreeItem[] {lastItem}); } } } } }); // goto after click tree.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { // has selection if (tree.getSelectionCount() < 1) { return; } // first (usually left) button if (e.button != 1) { return; } // select element under mouse Object targetItem = tree.getItem(new Point(e.x, e.y)); if (targetItem != null) { gotoSelectedOutline(); } } }); // done return tree; } protected void createHorizontalSeparator(Composite parent) { Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.LINE_DOT); separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); } @Override protected Control createTitleControl(Composite parent) { filterText = new Text(parent, SWT.NONE); GridDataFactory.create(filterText).grabHorizontal().fillHorizontal().alignVerticalMiddle(); Dialog.applyDialogFont(filterText); filterText.setText(""); // navigation filterText.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == 0x0D) { gotoSelectedOutline(); } if (e.keyCode == SWT.ARROW_DOWN) { viewer.getTree().setFocus(); } if (e.keyCode == SWT.ARROW_UP) { viewer.getTree().setFocus(); } if (e.character == 0x1B) { dispose(); } } }); // filter change filterText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { String text = filterText.getText(); setMatcherString(text); } }); return filterText; } @Override protected IDialogSettings getDialogSettings() { String sectionName = "com.google.dart.tools.ui.functions.QuickOutline"; IDialogSettings pluginSettings = DartToolsPlugin.getDefault().getDialogSettings(); IDialogSettings settings = pluginSettings.getSection(sectionName); if (settings == null) { settings = pluginSettings.addNewSection(sectionName); } return settings; } @Override protected boolean hasInfoArea() { return false; } /** * @return the first {@link TreeItem} matching {@link #matcher}. */ private TreeItem findFirstMatchingItem(TreeItem[] items) { for (TreeItem item : items) { // no filter - first is good if (matcher == null) { return item; } // check "outline" for filter { String label = item.getText(); if (matcher.match(label)) { return item; } } // not "outline", but may be one of its children item = findFirstMatchingItem(item.getItems()); if (item != null) { return item; } } // no matching outlines return null; } /** * Opens selected {@link Outline} in the {@link #editor}. */ private void gotoSelectedOutline() { if (viewer != null) { IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); Outline outline = (Outline) selection.getFirstElement(); editor.setSelection_NEW(outline, true); dispose(); } } /** * Selects the first {@link TreeItem} which matches the {@link #matcher}. */ private void selectFirstMatch() { Tree tree = viewer.getTree(); TreeItem item = findFirstMatchingItem(tree.getItems()); if (item != null) { viewer.setSelection(new StructuredSelection(item), true); } else { viewer.setSelection(StructuredSelection.EMPTY); } } /** * Attempts to find {@link Outline} corresponding to the selection in {@link #editor} and select * it in {@link #viewer}. */ private void selectOutlineEnclosingEditorSelection() { // may be small unit, expand as possible LightNodeElements.expandTreeItemsTimeBoxed(viewer, 50L * 1000000L); // prepare "outline" to select final Outline outline; { Outline input = (Outline) viewer.getInput(); List<Outline> outlineList = input.getChildren(); ITextSelection editorSelection = (ITextSelection) editor.getSelectionProvider().getSelection(); int editorOffset = editorSelection.getOffset(); outline = findOutlineEnclosingOffset(outlineList, editorOffset); } // select "outline" if (outline != null) { // select "outline" to expect its parents viewer.setSelection(new StructuredSelection(outline), true); // make root of "outline" top item { Outline parent = outline.getParent(); while (parent != null && !parent.getElement().getKind().equals(ElementKind.COMPILATION_UNIT)) { if (parent.getParent() == null) { TreeItem parentItem = viewer.findItem2(parent); if (parentItem != null) { viewer.getTree().setTopItem(parentItem); } break; } parent = parent.getParent(); } } // schedule "outline" selection again for case when parent of "outline" has many children Display.getCurrent().asyncExec(new Runnable() { @Override public void run() { viewer.setSelection(new StructuredSelection(outline), true); } }); } } /** * Sets the patterns to filter out {@link #viewer}. * <p> * The following characters have special meaning: ? => any character * => any string */ private void setMatcherString(String pattern) { // update "stringMatcher" if (pattern.length() == 0) { matcher = null; } else { matcher = new DartElementPrefixPatternMatcher(pattern); } // refresh "viewer" viewer.getControl().setRedraw(false); try { viewer.collapseAll(); viewer.refresh(false); LightNodeElements.expandTreeItemsTimeBoxed(viewer, 75L * 1000000L); selectFirstMatch(); } finally { viewer.getControl().setRedraw(true); } } }