/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.python.pydev.actions; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.events.ShellListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.UIJob; import org.python.pydev.core.CorePlugin; import org.python.pydev.core.IModule; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.log.Log; import org.python.pydev.core.uiutils.DialogMemento; import org.python.pydev.editor.PyEdit; import org.python.pydev.editor.actions.PyOpenAction; import org.python.pydev.editor.actions.refactoring.PyRefactorAction; import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule; import org.python.pydev.editor.model.ItemPointer; import org.python.pydev.editor.model.Location; import org.python.pydev.editor.refactoring.AbstractPyRefactoring; import org.python.pydev.editor.refactoring.IPyRefactoring; import org.python.pydev.editor.refactoring.RefactoringRequest; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.visitors.NodeUtils; import org.python.pydev.parser.visitors.scope.ASTEntry; import org.python.pydev.parser.visitors.scope.DefinitionsASTIteratorVisitor; import org.python.pydev.ui.dialogs.TreeSelectionDialog; import com.aptana.shared_core.structure.Tuple; import com.python.pydev.refactoring.IPyRefactoring2; import com.python.pydev.ui.hierarchy.HierarchyNodeModel; import com.python.pydev.ui.hierarchy.TreeNode; import com.python.pydev.ui.hierarchy.TreeNodeContentProvider; /** * @author fabioz * */ public final class PyOutlineSelectionDialog extends TreeSelectionDialog { /** * Should we show the parents or not? */ private boolean showParentHierarchy; /** * Label indicating what are we showing. */ private Label labelCtrlO; /** * May be null (in which case the ast and nodeToModel should be used). */ private PyEdit pyEdit; /** * May be null (in which case the pyedit should be used to calculate it). */ private HashMap<SimpleNode, HierarchyNodeModel> nodeToModel; /** * May be null (in which case the pyedit should be used to calculate it). */ private SimpleNode ast; /** * Structure without parents. */ private TreeNode<OutlineEntry> root; /** * Structure with the parent methods. */ private TreeNode<OutlineEntry> rootWithParents; /** * Helper to save/restore geometry. */ private final DialogMemento memento; /** * Listener to handle the 2nd ctrl+O */ private KeyListener ctrlOlistener; /** * The first line selected (starts at 1) */ private int startLineIndex = -1; private TreeNode<OutlineEntry> initialSelection; private final UIJob uiJobSetRootWithParentsInput = new UIJob("Set input") { @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (!monitor.isCanceled()) { getTreeViewer().setInput(rootWithParents); } else { //Will be recalculated if asked again! rootWithParents = null; } return Status.OK_STATUS; } }; private final Job jobCalculateParents = new Job("Calculate parents") { @Override public IStatus run(IProgressMonitor monitor) { rootWithParents = root.createCopy(null); if (nodeToModel == null) { //Step 2: create mapping: classdef to hierarchy model. nodeToModel = new HashMap<SimpleNode, HierarchyNodeModel>(); List<Tuple<ClassDef, TreeNode<OutlineEntry>>> gathered = new ArrayList<Tuple<ClassDef, TreeNode<OutlineEntry>>>(); gatherClasses(rootWithParents, monitor, gathered); monitor.beginTask("Calculate parents", gathered.size() + 1); IPyRefactoring pyRefactoring = AbstractPyRefactoring.getPyRefactoring(); IPyRefactoring2 r2 = (IPyRefactoring2) pyRefactoring; for (Tuple<ClassDef, TreeNode<OutlineEntry>> t : gathered) { SubProgressMonitor subProgressMonitor = new SubProgressMonitor(monitor, 1); try { ClassDef classDef = t.o1; PySelection ps = new PySelection(pyEdit.getDocument(), classDef.name.beginLine - 1, classDef.name.beginColumn - 1); try { RefactoringRequest refactoringRequest = PyRefactorAction.createRefactoringRequest( subProgressMonitor, pyEdit, ps); HierarchyNodeModel model = r2.findClassHierarchy(refactoringRequest, true); nodeToModel.put(t.o2.data.node, model); } catch (MisconfigurationException e) { Log.log(e); } } finally { subProgressMonitor.done(); } } } if (!monitor.isCanceled()) { fillHierarchy(rootWithParents); } if (!monitor.isCanceled()) { uiJobSetRootWithParentsInput.setPriority(Job.INTERACTIVE); uiJobSetRootWithParentsInput.schedule(); } else { //Will be recalculated if asked again! rootWithParents = null; } monitor.done(); return Status.OK_STATUS; } }; private PyOutlineSelectionDialog(Shell shell) { super(shell, createLabelProvider(), new TreeNodeContentProvider()); setShellStyle(getShellStyle() & ~SWT.APPLICATION_MODAL); //Not modal because then the user may cancel the progress. if (CorePlugin.getDefault() != null) { memento = new DialogMemento(getShell(), "com.python.pydev.actions.PyShowOutline"); } else { memento = null; } setMessage("Filter (press enter to go to selected element)"); setTitle("PyDev: Quick Outline"); setAllowMultiple(false); this.showParentHierarchy = false; } /** * Handle the creation for earlier versions of Eclipse. */ protected static ILabelProvider createLabelProvider() { try { return new LabelProviderWithDecoration(new ShowOutlineLabelProvider(), PlatformUI.getWorkbench() .getDecoratorManager().getLabelDecorator(), null); } catch (Throwable e) { return new ShowOutlineLabelProvider(); } } /** * Constructor to be used if the pyedit is not available (info must be pre-calculated) */ public PyOutlineSelectionDialog(Shell shell, SimpleNode ast, HashMap<SimpleNode, HierarchyNodeModel> nodeToModel) { this(shell); this.ast = ast; this.nodeToModel = nodeToModel; calculateHierarchy(); setInput(root); } /** * Constructor to be used if the pyedit is available (in which case the info will be calculated on demand) */ public PyOutlineSelectionDialog(Shell shell, PyEdit pyEdit) { this(shell); this.pyEdit = pyEdit; PySelection ps = this.pyEdit.createPySelection(); startLineIndex = ps.getStartLineIndex() + 1; //+1 because the ast starts at 1 calculateHierarchy(); setInput(root); //After creating the tree viewer (and setting the input), let's set the initial selection! if (initialSelection != null) { this.setInitialSelections(new Object[] { initialSelection }); } } public boolean close() { if (memento != null) { memento.writeSettings(getShell()); } return super.close(); } protected Point getInitialSize() { if (memento != null) { return memento.getInitialSize(super.getInitialSize(), getShell()); } return new Point(640, 480); } protected Point getInitialLocation(Point initialSize) { if (memento != null) { return memento.getInitialLocation(initialSize, super.getInitialLocation(initialSize), getShell()); } return new Point(250, 250); } public Control createDialogArea(Composite parent) { if (memento != null) { memento.readSettings(); } Control ret = super.createDialogArea(parent); ctrlOlistener = new KeyListener() { public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { if ((e.keyCode == 'o' || e.keyCode == 'O') && e.stateMask == SWT.CTRL) { toggleShowParentHierarchy(); } } }; this.text.addKeyListener(ctrlOlistener); this.text.addKeyListener(new KeyListener() { public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.CR || e.keyCode == SWT.LF || e.keyCode == SWT.KEYPAD_CR) { okPressed(); } } }); this.getTreeViewer().getTree().addKeyListener(ctrlOlistener); return ret; } protected void updateShowParentHierarchyMessage() { if (showParentHierarchy) { labelCtrlO.setText("Press Ctrl+O to hide parent hierarchy."); } else { labelCtrlO.setText("Press Ctrl+O to show parent hierarchy."); } } @Override protected int getDefaultMargins() { return 0; } @Override protected int getDefaultSpacing() { return 0; } /* (non-Javadoc) * @see org.eclipse.ui.dialogs.SelectionStatusDialog#createButtonBar(org.eclipse.swt.widgets.Composite) */ @Override protected Control createButtonBar(Composite parent) { labelCtrlO = new Label(parent, SWT.NONE); this.labelCtrlO.addKeyListener(ctrlOlistener); updateShowParentHierarchyMessage(); return labelCtrlO; } protected void toggleShowParentHierarchy() { showParentHierarchy = !showParentHierarchy; updateShowParentHierarchyMessage(); TreeViewer treeViewer = this.getTreeViewer(); if (showParentHierarchy) { //Create the TreeNode structure if it's still not created... calculateHierarchyWithParents(); } else { calculateHierarchy(); treeViewer.setInput(root); } } private void calculateHierarchy() { if (root != null) { return; } if (this.ast == null && pyEdit != null) { this.ast = pyEdit.getAST(); } if (ast == null) { return; } DefinitionsASTIteratorVisitor visitor = DefinitionsASTIteratorVisitor.create(ast); if (visitor == null) { return; } Map<ASTEntry, TreeNode<OutlineEntry>> entryToTreeNode = new HashMap<ASTEntry, TreeNode<OutlineEntry>>(); //Step 1: create 'regular' tree structure from the nodes. TreeNode<OutlineEntry> root = new TreeNode<OutlineEntry>(null, null, null); for (Iterator<ASTEntry> it = visitor.getOutline(); it.hasNext();) { ASTEntry next = it.next(); TreeNode<OutlineEntry> n; if (next.parent != null) { TreeNode<OutlineEntry> parent = entryToTreeNode.get(next.parent); if (parent == null) { Log.log("Unexpected condition: child found before parent!"); parent = root; } n = new TreeNode<OutlineEntry>(parent, new OutlineEntry(next), null); } else { n = new TreeNode<OutlineEntry>(root, new OutlineEntry(next), null); } if (n.data.node.beginLine <= startLineIndex) { initialSelection = n; } entryToTreeNode.put(next, n); } this.root = root; } private void calculateHierarchyWithParents() { if (rootWithParents != null) { uiJobSetRootWithParentsInput.setPriority(Job.INTERACTIVE); uiJobSetRootWithParentsInput.schedule(); return; } calculateHierarchy(); //make sure the root is OK if (root == null) { return; } jobCalculateParents.setPriority(Job.INTERACTIVE); jobCalculateParents.schedule(); } private void fillHierarchy(TreeNode<OutlineEntry> entry) { ArrayList<TreeNode<OutlineEntry>> copy = new ArrayList<TreeNode<OutlineEntry>>(entry.children); for (TreeNode<OutlineEntry> nextEntry : copy) { HierarchyNodeModel model = this.nodeToModel.get(nextEntry.data.node); addMethods(nextEntry, model); fillHierarchy(nextEntry); } } private void addMethods(TreeNode<OutlineEntry> nextEntry, HierarchyNodeModel model) { if (model == null || model.parents == null) { return; } for (HierarchyNodeModel parent : model.parents) { DefinitionsASTIteratorVisitor visitor = DefinitionsASTIteratorVisitor.createForChildren(parent.ast); if (visitor == null) { continue; } Iterator<ASTEntry> outline = visitor.getOutline(); while (outline.hasNext()) { ASTEntry entry = outline.next(); if (entry.parent == null) { //only direct children... new TreeNode<OutlineEntry>(nextEntry, new OutlineEntry(entry, parent), null); } } addMethods(nextEntry, parent); } } private void gatherClasses(TreeNode<OutlineEntry> entry, IProgressMonitor monitor, List<Tuple<ClassDef, TreeNode<OutlineEntry>>> gathered) { if (entry.children.size() == 0) { return; } //Iterate in a copy, since we may change the original... for (TreeNode<OutlineEntry> nextEntry : entry.children) { if (nextEntry.data.node instanceof ClassDef) { ClassDef classDef = (ClassDef) nextEntry.data.node; gathered.add(new Tuple<ClassDef, TreeNode<OutlineEntry>>(classDef, nextEntry)); } //Enter the leaf to fill it too. gatherClasses(nextEntry, monitor, gathered); } } @Override protected void configureShell(final Shell shell) { super.configureShell(shell); //Whenever the shell is deactivated, we want to go on and close it (i.e.: work as a popup dialog) shell.addShellListener(new ShellListener() { public void shellIconified(ShellEvent e) { } public void shellDeiconified(ShellEvent e) { } public void shellDeactivated(ShellEvent e) { shell.close(); } public void shellClosed(ShellEvent e) { } public void shellActivated(ShellEvent e) { } }); } /* (non-Javadoc) * @see org.eclipse.ui.dialogs.ElementTreeSelectionDialog#open() */ @Override public int open() { int ret = super.open(); if (ret == OK) { Object[] result = getResult(); if (result != null && result.length > 0) { @SuppressWarnings("unchecked") TreeNode<OutlineEntry> n = (TreeNode<OutlineEntry>) result[0]; OutlineEntry outlineEntry = n.data; if (outlineEntry.model == null) { Location location = new Location(NodeUtils.getNameLineDefinition(outlineEntry.node) - 1, NodeUtils.getNameColDefinition(outlineEntry.node) - 1); new PyOpenAction().showInEditor(pyEdit, location, location); } else { PyOpenAction pyOpenAction = new PyOpenAction(); IModule m = outlineEntry.model.module; if (m instanceof SourceModule) { SourceModule sourceModule = (SourceModule) m; File file = sourceModule.getFile(); if (file != null) { ItemPointer p = new ItemPointer(file, outlineEntry.node); pyOpenAction.run(p); } } } } } return ret; } }