/* * Copyright (c) 2013, 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.engine.ast.CompilationUnit; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.internal.search.ui.DartSearchActionGroup_OLD; 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.actions.OpenViewActionGroup_OLD; import com.google.dart.tools.ui.actions.RefactorActionGroup_OLD; 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.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Status; 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.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; 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.custom.BusyIndicator; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; 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.progress.UIJob; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; /** * {@link IContentOutlinePage} for {@link DartEditor}. */ public class DartOutlinePage extends Page implements IContentOutlinePage { 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(final 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(LightNodeElements.NAME_COMPARATOR); } else { viewer.setComparator(LightNodeElements.POSITION_COMPARATOR); } } }); // may be remember selection if (store) { DartToolsPlugin.getDefault().getPreferenceStore().setValue( "LexicalSortingAction.isChecked", on); //$NON-NLS-1$ } } } private final ListenerList selectionChangedListeners = new ListenerList(ListenerList.IDENTITY); private final String contextMenuID; private DartEditor editor; private DartOutlineViewer viewer; private boolean ignoreSelectionChangedEvent = false; private Menu contextMenu; private CompositeActionGroup actionGroups; private CompilationUnit input; private IPreferenceStore preferences; private IPropertyChangeListener propertyChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { doPropertyChange(event); } }; private static final ViewerFilter PUBLIC_FILTER = new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object o) { if (o instanceof LightNodeElement) { LightNodeElement element = (LightNodeElement) o; return !element.isPrivate(); } return false; } }; public DartOutlinePage(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.setContentProvider(LightNodeElements.newTreeContentProvider(editor)); viewer.setLabelProvider(LightNodeElements.newLabelProvider()); SWTUtil.bindJFaceResourcesFontToControl(tree); // 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[] { new OpenViewActionGroup_OLD(site), new RefactorActionGroup_OLD(site), new DartSearchActionGroup_OLD(site)}); // 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(); // schedule update in 100ms from now, to make impression that editor opens instantaneously new UIJob("Update Outline") { @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (viewer != null) { viewer.setInput(input); //LightNodeElements.expandTreeItemsTimeBoxed(viewer, 75L * 1000000L); } return Status.OK_STATUS; } }.schedule(); } @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(final LightNodeElement element) { updateViewerWithoutDraw(new Runnable() { @Override public void run() { setSelection(new StructuredSelection(element)); viewer.reveal(element); } }); } @Override public void setFocus() { if (viewer != null) { viewer.getControl().setFocus(); } } public void setInput(final CompilationUnit input) { this.input = input; updateViewerWithoutDraw(new Runnable() { @Override public void run() { Object[] expandedElements = viewer.getExpandedElements(); ignoreSelectionChangedEvent = true; try { viewer.setInput(input); } finally { ignoreSelectionChangedEvent = false; } viewer.setExpandedElements(expandedElements); } }); } @Override public void setSelection(final ISelection newSelection) { if (!Objects.equal(viewer.getSelection(), newSelection)) { updateViewerWithoutDraw(new Runnable() { @Override public void run() { 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); } } }); } /** * Performs the given operation over {@link #viewer} while redraw is disabled. */ private void updateViewerWithoutDraw(Runnable runnable) { if (viewer != null) { final Control control = viewer.getControl().getParent(); control.setRedraw(false); try { runnable.run(); } finally { if (DartCore.isLinux()) { // By some reason on Linux we need to add a delay. // It seems like Tree operations are performed not in the UI thread. Display.getCurrent().timerExec(50, new Runnable() { @Override public void run() { control.setRedraw(true); } }); } else { control.setRedraw(true); } } } } }