/******************************************************************************* * Copyright (c) 2007 IBM Corporation. * 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 * * Contributors: * Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation *******************************************************************************/ package com.redhat.ceylon.eclipse.code.outline; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.overloads; import static com.redhat.ceylon.eclipse.code.editor.Navigation.gotoDeclaration; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.PARAMS_IN_OUTLINES; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.PLUGIN_ID; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.imageRegistry; import static com.redhat.ceylon.eclipse.ui.CeylonResources.CEYLON_OUTLINE; import static com.redhat.ceylon.eclipse.ui.CeylonResources.CONFIG_LABELS; import static com.redhat.ceylon.eclipse.ui.CeylonResources.HIDE_PRIVATE; import static com.redhat.ceylon.eclipse.ui.CeylonResources.SORT_ALPHA; import static com.redhat.ceylon.eclipse.util.EditorUtil.triggersBinding; import static org.eclipse.ui.dialogs.PreferencesUtil.createPreferenceDialogOn; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; 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.Item; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.Widget; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ClassOrInterface; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompilationUnit; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ObjectDefinition; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.code.preferences.CeylonFiltersPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonOutlinesPreferencePage; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.util.EditorUtil; import com.redhat.ceylon.eclipse.util.Nodes; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.DeclarationWithProximity; import com.redhat.ceylon.model.typechecker.model.Referenceable; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.Unit; public class OutlinePopup extends TreeViewPopup { private static final Image OUTLINE = imageRegistry().get(CEYLON_OUTLINE); private static final Image SORT = imageRegistry().get(SORT_ALPHA); private static final Image PUBLIC = imageRegistry().get(HIDE_PRIVATE); private CeylonOutlineContentProvider outlineContentProvider; private OutlineSorter outlineSorter; private CeylonOutlineLabelProvider labelProvider; private LexicalSortingAction lexicalSortingAction; private HideNonSharedAction hideNonSharedAction; protected static final Object[] NO_CHILDREN = new Object[0]; private ToolItem sortButton; private ToolItem hideButton; private boolean mode; private final class ContentProvider extends CeylonOutlineContentProvider { @Override public Object getParent(Object element) { if (element instanceof CeylonOutlineNode) { return super.getParent(element); } else { return null; //TODO!!! } } @Override public Object[] getChildren(Object element) { if (element instanceof CeylonOutlineNode) { if (mode) { boolean includeParameters = !CeylonPlugin.getPreferences() .getBoolean(PARAMS_IN_OUTLINES); CeylonOutlineNode node = (CeylonOutlineNode) element; CompilationUnit rootNode = getEditor().getParseController() .getLastCompilationUnit(); Node treeNode = Nodes.findNode(rootNode, node.getStartOffset(), node.getEndOffset()); TypeDeclaration td; if (treeNode instanceof ClassOrInterface) { ClassOrInterface ci = (ClassOrInterface) treeNode; td = ci.getDeclarationModel(); } else if (treeNode instanceof ObjectDefinition) { ObjectDefinition od = (ObjectDefinition) treeNode; td = od.getDeclarationModel() .getTypeDeclaration(); } else { return super.getChildren(element); } List<Declaration> list = new ArrayList<Declaration>(); String filter = getFilterText().getText(); for (int i=0; i<filter.length(); i++) { char ch = filter.charAt(i); if (ch=='*' || i>0 && Character.isUpperCase(ch)) { filter = filter.substring(0, i); break; } } Collection<DeclarationWithProximity> members = td.getMatchingMemberDeclarations( rootNode.getUnit(), td, filter, 0, null).values(); for (DeclarationWithProximity dwp: members) { for (Declaration dec: overloads(dwp.getDeclaration())) { if (!(dec instanceof TypeParameter)) { if (includeParameters || !dec.isParameter()) { list.add(dec); } } } } if (!lexicalSortingAction.isChecked()) { Collections.sort(list, new Comparator<Declaration>() { public int compare(Declaration x, Declaration y) { String xn = x.getContainer() .getQualifiedNameString(); String yn = y.getContainer() .getQualifiedNameString(); return xn.compareTo(yn); } }); } return list.toArray(); } else { return super.getChildren(element); } } else { return null; } } } private final class ChangeViewListener implements KeyListener { @Override public void keyReleased(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { if (triggersBinding(e, getCommandBinding())) { mode = !mode; modeButton.setSelection(mode); updateStatusFieldText(); getTreeViewer().refresh(); getTreeViewer().expandToLevel(getDefaultLevel()); e.doit=false; } } } private class OutlineTreeViewer extends TreeViewer { // private boolean fIsFiltering = false; private OutlineTreeViewer(Tree tree) { super(tree); } @Override protected Object[] getFilteredChildren(Object parent) { Object[] result = getRawChildren(parent); // int unfilteredChildren = result.length; ViewerFilter[] filters = getFilters(); if (filters != null) { for(int i=0; i<filters.length; i++) result = filters[i].filter(this, parent, result); } // fIsFiltering = unfilteredChildren != result.length; return result; } @Override protected void internalExpandToLevel(Widget w, int level) { if (//!(fIsFiltering && !getFilterText().getText().isEmpty()) && getFilterText().getText().isEmpty() && w instanceof Item) { Item i = (Item) w; if (i.getData() instanceof CeylonOutlineNode) { CeylonOutlineNode node = (CeylonOutlineNode) i.getData(); int cat = node.getCategory(); //TODO: leave unshared declarations collapsed? if (cat==CeylonOutlineNode.IMPORT_LIST_CATEGORY) { setExpanded(i, false); return; } } } super.internalExpandToLevel(w, level); } } private class OutlineSorter extends ViewerSorter { private static final int OTHER = 1; @Override public void sort(Viewer viewer, Object[] elements) { if (lexicalSortingAction.isChecked()) { super.sort(viewer, elements); } } @Override public int compare(Viewer viewer, Object e1, Object e2) { if (e1 instanceof CeylonOutlineNode && e2 instanceof CeylonOutlineNode) { CeylonOutlineNode n1 = (CeylonOutlineNode) e1; CeylonOutlineNode n2 = (CeylonOutlineNode) e2; int cat = n1.getCategory()-n2.getCategory(); if (cat!=0) return cat; String n1n = n1.getName(); String n2n = n2.getName(); if (n1n==n2n) return 0; if (n1n==null) return -1; if (n2n==null) return 1; return n1n.compareTo(n2n); } else if (e1 instanceof Declaration && e2 instanceof Declaration) { Unit unit = getEditor().getParseController() .getLastCompilationUnit().getUnit(); String e1n = ((Declaration) e1).getName(unit); String e2n = ((Declaration) e2).getName(unit); if (e1n==e2n) return 0; if (e1n==null) return -1; if (e2n==null) return 1; return e1n.compareTo(e2n); } else { return 0; } } @Override public int category(Object element) { return OTHER; } } private class LexicalSortingAction extends Action { private TreeViewer treeViewer; private LexicalSortingAction(TreeViewer viewer) { super("Sort by Name", IAction.AS_CHECK_BOX); setToolTipText("Sort by name"); setDescription("Sort entries lexically by name"); setImageDescriptor(imageRegistry.getDescriptor(SORT_ALPHA)); treeViewer = viewer; boolean checked = getDialogSettings() .getBoolean("sort"); setChecked(checked); } @Override public void run() { boolean on = isChecked(); setChecked(on); Display display = treeViewer.getControl() .getDisplay(); BusyIndicator.showWhile(display, new Runnable() { @Override public void run() { treeViewer.refresh(false); } }); if (true) { getDialogSettings().put("sort", on); } sortButton.setSelection(on); } } private static final ViewerFilter filter = new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { if (element instanceof CeylonOutlineNode) { CeylonOutlineNode node = (CeylonOutlineNode) element; return node.isShared(); } else if (element instanceof Declaration) { Declaration dec = (Declaration) element; return dec.isShared(); } else { return true; } } }; private ToolItem modeButton; private class HideNonSharedAction extends Action { private TreeViewer treeViewer; private HideNonSharedAction(TreeViewer viewer) { treeViewer = viewer; setText("Hide Unshared"); setToolTipText("Hide Unhared Declarations"); setDescription("Hide unshared declarations"); setImageDescriptor(imageRegistry.getDescriptor(HIDE_PRIVATE)); boolean checked = getDialogSettings() .getBoolean("hideNonShared"); valueChanged(checked, false); } @Override public void run() { boolean on = isChecked(); valueChanged(on, true); hideButton.setSelection(on); } private void valueChanged(final boolean on, boolean store) { setChecked(on); Display display = treeViewer.getControl() .getDisplay(); BusyIndicator.showWhile(display, new Runnable() { @Override public void run() { if (on) { treeViewer.addFilter(filter); } else { treeViewer.removeFilter(filter); } } }); if (store) { getDialogSettings().put("hideNonShared", on); } } } public OutlinePopup(CeylonEditor editor, Shell shell, int shellStyle) { super(shell, shellStyle, PLUGIN_ID + ".editor.showOutline", editor); setTitleText("Quick Outline \u2014 " + editor.getEditorInput().getName()); } @Override protected TreeViewer createTreeViewer(Composite parent) { Tree tree = new Tree(parent, SWT.SINGLE); GridData gd = new GridData(GridData.FILL_BOTH); gd.heightHint = tree.getItemHeight() * 12; tree.setLayoutData(gd); final TreeViewer treeViewer = new OutlineTreeViewer(tree); lexicalSortingAction = new LexicalSortingAction(treeViewer); hideNonSharedAction = new HideNonSharedAction(treeViewer); outlineContentProvider = new ContentProvider(); labelProvider = new CeylonOutlineLabelProvider() { Font getFont() { return getTreeViewer().getControl().getFont(); } String getPrefix() { return getFilterText().getText(); } }; treeViewer.setLabelProvider(labelProvider); treeViewer.addFilter(new OutlineNamePatternFilter(getFilterText())); // fSortByDefiningTypeAction= new SortByDefiningTypeAction(treeViewer); // fShowOnlyMainTypeAction= new ShowOnlyMainTypeAction(treeViewer); treeViewer.setContentProvider(outlineContentProvider); outlineSorter = new OutlineSorter(); treeViewer.setSorter(outlineSorter); treeViewer.setAutoExpandLevel(getDefaultLevel()); tree.addKeyListener(new ChangeViewListener()); tree.setFont(CeylonPlugin.getOutlineFont()); return treeViewer; } @Override protected String getStatusFieldText() { String selectHint = EditorUtil.getEnterBinding() + " to open"; TriggerSequence binding = getCommandBinding(); if (binding==null) { return selectHint; } String action = mode ? " to hide " : " to show "; return binding.format() + action + "inherited members of classes and interfaces" + " \u00b7 " + selectHint; } @Override protected String getId() { return CeylonPlugin.PLUGIN_ID + ".QuickOutline"; } @Override protected Control createTitleControl(Composite parent) { getPopupLayout().copy() .numColumns(4) .spacing(6, 6) .applyTo(parent); Label label = new Label(parent, SWT.NONE); label.setImage(OUTLINE); super.createTitleControl(parent); createToolBar(parent); return null; } private void createToolBar(Composite parent) { ToolBar toolBar = new ToolBar(parent, SWT.FLAT); modeButton = new ToolItem(toolBar, SWT.CHECK); modeButton.setImage(HierarchyView.INHERITED_IMAGE); modeButton.setToolTipText("Show Inherited Members"); modeButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { mode = !mode; getTreeViewer().refresh(); getTreeViewer().expandToLevel(getDefaultLevel()); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); sortButton = new ToolItem(toolBar, SWT.CHECK); sortButton.setImage(SORT); sortButton.setToolTipText("Sort by Name"); boolean sortChecker = getDialogSettings().getBoolean("sort"); sortButton.setSelection(sortChecker); sortButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { lexicalSortingAction.setChecked(sortButton.getSelection()); lexicalSortingAction.run(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); hideButton = new ToolItem(toolBar, SWT.CHECK); hideButton.setImage(PUBLIC); hideButton.setToolTipText("Hide Unshared Declarations"); boolean hideChecked = getDialogSettings().getBoolean("hideNonShared"); hideButton.setSelection(hideChecked); hideButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { hideNonSharedAction.setChecked(hideButton.getSelection()); hideNonSharedAction.run(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); } @Override public void setInput(Object information) { CeylonParseController controller = getEditor().getParseController(); CeylonOutlineNode info = new CeylonOutlineBuilder() .buildTree(controller); inputChanged(info, info); } @Override protected Text createFilterText(Composite parent) { Text result = super.createFilterText(parent); result.addKeyListener(new ChangeViewListener()); return result; } @Override protected void fillViewMenu(IMenuManager viewMenu) { super.fillViewMenu(viewMenu); viewMenu.add(new Separator("Sorters")); if (lexicalSortingAction != null) { viewMenu.add(lexicalSortingAction); } if (hideNonSharedAction != null) { viewMenu.add(hideNonSharedAction); } viewMenu.add(new Separator()); Action configureAction = new Action("Configure Labels...", CeylonPlugin.imageRegistry() .getDescriptor(CONFIG_LABELS)) { @Override public void run() { createPreferenceDialogOn(getShell(), CeylonOutlinesPreferencePage.ID, new String[] { CeylonOutlinesPreferencePage.ID, CeylonPlugin.COLORS_AND_FONTS_PAGE_ID, CeylonFiltersPreferencePage.ID }, null).open(); getTreeViewer() .getTree() .setFont(CeylonPlugin.getOutlineFont()); getTreeViewer() .refresh(); } }; viewMenu.add(configureAction); } @Override protected void gotoSelectedElement() { Object object = getSelectedElement(); if (object instanceof CeylonOutlineNode) { dispose(); CeylonOutlineNode on = (CeylonOutlineNode) object; int startOffset = on.getStartOffset(); int endOffset = on.getEndOffset(); getEditor().selectAndReveal(startOffset, endOffset-startOffset); } else if (object instanceof Referenceable) { dispose(); gotoDeclaration((Referenceable) object); } } }