/* * 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.editor; import com.google.common.base.Objects; import com.google.dart.tools.ui.DartPluginImages; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.actions.InstrumentedAction; import com.google.dart.tools.ui.instrumentation.UIInstrumentationBuilder; import com.google.dart.tools.ui.internal.text.DartHelpContextIds; import com.google.dart.tools.ui.internal.util.SWTUtil; import com.google.dart.tools.ui.internal.viewsupport.ColoredViewersManager; import org.dartlang.analysis.server.protocol.Element; import org.dartlang.analysis.server.protocol.Outline; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.IElementComparer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Tree; import org.eclipse.ui.IActionBars; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionContext; import org.eclipse.ui.actions.ActionGroup; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.part.Page; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import java.util.List; /** * {@link IContentOutlinePage} for {@link DartEditor}. */ public class DartOutlinePage_NEW extends Page implements IContentOutlinePage { /** * {@link ViewerComparator} for {@link Outline} names. */ public static class NameComparator extends ViewerComparator { private static final int NOT_ELEMENT = 2; private static final int PRIVATE_ELEMENT = 1; private static final int PUBLIC_ELEMENT = 0; @Override public int category(Object e) { if (!(e instanceof Outline)) { return NOT_ELEMENT; } Outline outline = (Outline) e; if (outline.getElement().isPrivate()) { return PRIVATE_ELEMENT; } return PUBLIC_ELEMENT; } @Override public int compare(Viewer viewer, Object e1, Object e2) { // compare categories int cat1 = category(e1); int cat2 = category(e2); if (cat1 != cat2) { return cat1 - cat2; } // check types if (!(e1 instanceof Outline)) { return 0; } if (!(e2 instanceof Outline)) { return 0; } // compare names String name1 = ((Outline) e1).getElement().getName(); String name2 = ((Outline) e2).getElement().getName(); if (name1 == null || name2 == null) { return 0; } return name1.compareTo(name2); } } public static class OutlineContentProvider implements ITreeContentProvider { @Override public void dispose() { } @Override public Object[] getChildren(Object parentElement) { List<Outline> outlineList = ((Outline) parentElement).getChildren(); return outlineList.toArray(new Outline[outlineList.size()]); } @Override public Object[] getElements(Object inputElement) { List<Outline> outlineList = ((Outline) inputElement).getChildren(); return outlineList.toArray(new Outline[outlineList.size()]); } @Override public Object getParent(Object element) { return ((Outline) element).getParent(); } @Override public boolean hasChildren(Object element) { return getChildren(element).length != 0; } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } public static class OutlineLabelProvider extends LabelProvider implements IStyledLabelProvider { private final ElementLabelProvider_NEW elementLabelProvider = new ElementLabelProvider_NEW(); @Override public Image getImage(Object obj) { Outline outline = (Outline) obj; Element element = outline.getElement(); return elementLabelProvider.getImage(element); } @Override public StyledString getStyledText(Object obj) { Outline outline = (Outline) obj; Element element = outline.getElement(); return elementLabelProvider.getStyledText(element); } @Override public String getText(Object obj) { Outline outline = (Outline) obj; Element element = outline.getElement(); return elementLabelProvider.getText(element); } } /** * {@link ViewerComparator} for {@link Outline} positions. */ public static class PositionComparator extends ViewerComparator { @Override public int compare(Viewer viewer, Object e1, Object e2) { if (!(e1 instanceof Outline)) { return 0; } if (!(e2 instanceof Outline)) { return 0; } int offset1 = ((Outline) e1).getElement().getLocation().getOffset(); int offset2 = ((Outline) e2).getElement().getLocation().getOffset(); return offset1 - offset2; } } private class CollapseAllAction extends InstrumentedAction { CollapseAllAction() { super("Collapse All"); //$NON-NLS-1$ setDescription("Collapse All"); //$NON-NLS-1$ setToolTipText("Collapse All"); //$NON-NLS-1$ DartPluginImages.setLocalImageDescriptors(this, "collapseall.gif"); //$NON-NLS-1$ PlatformUI.getWorkbench().getHelpSystem().setHelp( this, DartHelpContextIds.COLLAPSE_ALL_ACTION); } @Override protected void doRun(Event event, UIInstrumentationBuilder instrumentation) { viewer.collapseAll(); } } private class DartOutlineViewer extends TreeViewer { public DartOutlineViewer(Tree tree) { super(tree); setUseHashlookup(true); tree.setBackgroundMode(SWT.INHERIT_FORCE); } private void updateColors() { SWTUtil.setColors(getTree(), preferences); } } private class ExpandAllAction extends InstrumentedAction { ExpandAllAction() { super("Expand All"); //$NON-NLS-1$ setDescription("Expand All"); //$NON-NLS-1$ setToolTipText("Expand All"); //$NON-NLS-1$ DartPluginImages.setLocalImageDescriptors(this, "expandall.gif"); //$NON-NLS-1$ PlatformUI.getWorkbench().getHelpSystem().setHelp(this, DartHelpContextIds.EXPAND_ALL_ACTION); } @Override protected void doRun(Event event, UIInstrumentationBuilder instrumentation) { viewer.expandAll(); } } private class HideNonPublicAction extends InstrumentedAction { HideNonPublicAction() { super("Hide non-public members", IAction.AS_CHECK_BOX); //$NON-NLS-1$ setDescription("Hide non-public members"); //$NON-NLS-1$ setToolTipText("Hide non-public members"); //$NON-NLS-1$ DartPluginImages.setLocalImageDescriptors(this, "public_co.gif"); //$NON-NLS-1$ PlatformUI.getWorkbench().getHelpSystem().setHelp( this, DartHelpContextIds.HIDE_NON_PUBLIC_ACTION); } @Override protected void doRun(Event event, UIInstrumentationBuilder instrumentation) { if (isChecked()) { viewer.addFilter(PUBLIC_FILTER); } else { viewer.removeFilter(PUBLIC_FILTER); } } } private class LexicalSortingAction extends InstrumentedAction { public LexicalSortingAction() { PlatformUI.getWorkbench().getHelpSystem().setHelp( this, DartHelpContextIds.LEXICAL_SORTING_OUTLINE_ACTION); setText(DartEditorMessages.JavaOutlinePage_Sort_label); DartPluginImages.setLocalImageDescriptors(this, "alphab_sort_co.gif"); //$NON-NLS-1$ setToolTipText(DartEditorMessages.JavaOutlinePage_Sort_tooltip); setDescription(DartEditorMessages.JavaOutlinePage_Sort_description); // restore selection boolean checked = DartToolsPlugin.getDefault().getPreferenceStore().getBoolean( "LexicalSortingAction.isChecked"); //$NON-NLS-1$ valueChanged(checked, false); } @Override protected void doRun(Event event, UIInstrumentationBuilder instrumentation) { valueChanged(isChecked(), true); } private void valueChanged(final boolean on, boolean store) { setChecked(on); // set comparator BusyIndicator.showWhile(viewer.getControl().getDisplay(), new Runnable() { @Override public void run() { if (on) { viewer.setComparator(NAME_COMPARATOR); } else { viewer.setComparator(POSITION_COMPARATOR); } } }); // may be remember selection if (store) { DartToolsPlugin.getDefault().getPreferenceStore().setValue( "LexicalSortingAction.isChecked", on); //$NON-NLS-1$ } } } public static final IElementComparer OUTLINE_COMPARER = new IElementComparer() { @Override public boolean equals(Object a, Object b) { if (a instanceof TreePath) { a = ((TreePath) a).getLastSegment(); } if (b instanceof TreePath) { b = ((TreePath) b).getLastSegment(); } if (a == b) { return true; } if (!(a instanceof Outline) || !(b instanceof Outline)) { return false; } Outline outlineA = (Outline) a; Outline outlineB = (Outline) b; if (!equals(outlineA.getParent(), outlineB.getParent())) { return false; } String nameA = outlineA.getElement().getName(); String nameB = outlineB.getElement().getName(); return nameA.equals(nameB); } @Override public int hashCode(Object element) { return element.hashCode(); } }; private static Outline getOutlineAtOffset(Outline outline, int offset) { if (outline == null) { return null; } if (!outline.containsInclusive(offset)) { return null; } for (Outline child : outline.getChildren()) { Outline result = getOutlineAtOffset(child, offset); if (result != null) { return result; } } return outline; } /** * Checks if two given {@link Outline} objects are equal, and copies location information from "b" * to "a". */ private static boolean isEqualOutlineTree(Outline a, Outline b) { if (a == null || b == null) { return false; } // compare elements Element elementA = a.getElement(); Element elementB = b.getElement(); if (!ElementLabelProvider_NEW.areEqual(elementA, elementB)) { return false; } // compare children List<Outline> childrenA = a.getChildren(); List<Outline> childrenB = b.getChildren(); if (childrenA.size() != childrenB.size()) { return false; } for (int i = 0; i < childrenA.size(); i++) { Outline childA = childrenA.get(i); Outline childB = childrenB.get(i); if (!isEqualOutlineTree(childA, childB)) { return false; } } // OK, update "a" a.setOffset(b.getOffset()); a.setLength(b.getLength()); a.setElement(b.getElement()); return true; } private final ListenerList selectionChangedListeners = new ListenerList(ListenerList.IDENTITY); private final String contextMenuID; private DartEditor editor; private Outline input; private int offset; private DartOutlineViewer viewer; private boolean ignoreSelectionChangedEvent = false; private Menu contextMenu; private CompositeActionGroup actionGroups; private IPreferenceStore preferences; private IPropertyChangeListener propertyChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { doPropertyChange(event); } }; private static final ViewerComparator NAME_COMPARATOR = new NameComparator(); private static final ViewerComparator POSITION_COMPARATOR = new PositionComparator(); private static final ViewerFilter PUBLIC_FILTER = new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object o) { if (o instanceof Outline) { throw new IllegalStateException("com.google.dart.server.Outline not yet supported."); // return !((Outline) o).getElement().isPrivate(); } return false; } }; public DartOutlinePage_NEW(String contextMenuID, DartEditor editor) { Assert.isNotNull(editor); this.contextMenuID = contextMenuID; this.editor = editor; } @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { if (viewer != null) { viewer.addSelectionChangedListener(listener); } else { selectionChangedListeners.add(listener); } } @Override public void createControl(Composite parent) { preferences = DartToolsPlugin.getDefault().getCombinedPreferenceStore(); Tree tree = new Tree(parent, SWT.MULTI | SWT.FULL_SELECTION); // create "viewer" viewer = new DartOutlineViewer(tree); ColoredViewersManager.install(viewer); viewer.setComparer(OUTLINE_COMPARER); viewer.setContentProvider(new OutlineContentProvider()); viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new OutlineLabelProvider())); SWTUtil.bindJFaceResourcesFontToControl(tree); internalSetInput(); // install listeners added before UI creation { Object[] listeners = selectionChangedListeners.getListeners(); for (Object listener : listeners) { selectionChangedListeners.remove(listener); viewer.addSelectionChangedListener((ISelectionChangedListener) listener); } } // prepare context menu MenuManager manager = new MenuManager(contextMenuID, contextMenuID); manager.setRemoveAllWhenShown(true); manager.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(IMenuManager m) { contextMenuAboutToShow(m); } }); contextMenu = manager.createContextMenu(tree); tree.setMenu(contextMenu); // register "viewer" as selection provide for this view IPageSite site = getSite(); site.setSelectionProvider(viewer); actionGroups = new CompositeActionGroup(new ActionGroup[] {}); // configure actions { IActionBars actionBars = site.getActionBars(); { IToolBarManager toolBarManager = actionBars.getToolBarManager(); toolBarManager.add(new HideNonPublicAction()); toolBarManager.add(new LexicalSortingAction()); toolBarManager.add(new ExpandAllAction()); toolBarManager.add(new CollapseAllAction()); } actionGroups.fillActionBars(actionBars); } // handle TreeViewer actions viewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { if (ignoreSelectionChangedEvent) { return; } editor.doSelectionChanged(event.getSelection()); } }); viewer.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { toggleExpansion(event.getSelection()); } }); // update colors preferences.addPropertyChangeListener(propertyChangeListener); viewer.updateColors(); } @Override public void dispose() { // clear "editor" if (editor == null) { return; } editor.outlinePageClosed(); editor = null; // remove property listeners if (propertyChangeListener != null) { preferences.removePropertyChangeListener(propertyChangeListener); propertyChangeListener = null; } // dispose "contextMenu" if (contextMenu != null && !contextMenu.isDisposed()) { contextMenu.dispose(); contextMenu = null; } // dispose actions if (actionGroups != null) { actionGroups.dispose(); actionGroups = null; } // we are not selection provider anymore getSite().setSelectionProvider(null); // done viewer = null; input = null; super.dispose(); } @Override public Control getControl() { if (viewer != null) { return viewer.getControl(); } return null; } @Override public ISelection getSelection() { if (viewer == null) { return StructuredSelection.EMPTY; } return viewer.getSelection(); } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { if (viewer != null) { viewer.removeSelectionChangedListener(listener); } else { selectionChangedListeners.remove(listener); } } public void select(int offset) { Outline newOutline = getOutlineAtOffset(input, offset); if (newOutline == null || viewer == null) { return; } // check if the same Outline is selected IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); Outline oldOutline = (Outline) selection.getFirstElement(); if (isEqualOutlineTree(oldOutline, newOutline)) { return; } // set new selection setSelection(new StructuredSelection(newOutline)); viewer.reveal(newOutline); } @Override public void setFocus() { if (viewer != null) { viewer.getControl().setFocus(); } } public void setInput(Outline input, int offset) { if (isEqualOutlineTree(this.input, input)) { return; } this.input = input; this.offset = offset; internalSetInput(); } @Override public void setSelection(ISelection newSelection) { if (!Objects.equal(viewer.getSelection(), newSelection)) { ignoreSelectionChangedEvent = true; try { viewer.setSelection(newSelection, true); } finally { ignoreSelectionChangedEvent = false; } } } protected void toggleExpansion(ISelection selection) { if (selection instanceof IStructuredSelection) { Object sel = ((IStructuredSelection) selection).getFirstElement(); boolean expanded = viewer.getExpandedState(sel); if (expanded) { viewer.collapseToLevel(sel, 1); } else { viewer.expandToLevel(sel, 1); } } } private void contextMenuAboutToShow(IMenuManager menu) { DartToolsPlugin.createStandardGroups(menu); IStructuredSelection selection = (IStructuredSelection) getSelection(); actionGroups.setContext(new ActionContext(selection)); actionGroups.fillContextMenu(menu); } private void doPropertyChange(PropertyChangeEvent event) { SWTUtil.runUI(new Runnable() { @Override public void run() { if (viewer != null) { viewer.updateColors(); viewer.refresh(false); } } }); } private void internalSetInput() { if (viewer == null) { return; } if (input == null) { return; } Object[] expandedElements = viewer.getExpandedElements(); ignoreSelectionChangedEvent = true; try { viewer.setInput(input); } finally { ignoreSelectionChangedEvent = false; } viewer.setExpandedElements(expandedElements); select(offset); } }