/**
* Copyright (c) 2012-2013 MATSUFUJI Hideharu <matsufuji2008@gmail.com>,
* 2013 KUBO Atsuhiro <kubo@iteman.jp>,
* All rights reserved.
*
* This file is part of MakeGood.
*
* 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
*/
package com.piece_framework.makegood.ui.views;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.dltk.core.IMember;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementVisitor;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.ScriptElementImageProvider;
import org.eclipse.dltk.ui.ScriptElementLabels;
import org.eclipse.dltk.ui.viewsupport.AppearanceAwareLabelProvider;
import org.eclipse.dltk.ui.viewsupport.DecoratingModelLabelProvider;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.php.core.compiler.IPHPModifiers;
import org.eclipse.php.internal.ui.util.PHPPluginImages;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.ITextEditor;
import com.piece_framework.makegood.core.TestClass;
import com.piece_framework.makegood.core.TestMethod;
import com.piece_framework.makegood.core.TestingFramework;
import com.piece_framework.makegood.core.preference.MakeGoodProperties;
import com.piece_framework.makegood.ui.Activator;
import com.piece_framework.makegood.ui.ActiveEditor;
import com.piece_framework.makegood.ui.EditorParser;
import com.piece_framework.makegood.ui.MakeGoodContext;
import com.piece_framework.makegood.ui.Messages;
/**
* @since 2.3.0
*/
@SuppressWarnings("restriction")
public class TestOutlineView extends ViewPart {
public static final String ID = "com.piece_framework.makegood.ui.views.testOutlineView"; //$NON-NLS-1$
private static boolean isOutlineSelecting = false;
/**
* @since 2.5.0
*/
private static int layout = ToggleShowHierarchyAction.LAYOUT_HIERARCHICAL;
/**
* @since 2.5.0
*/
private static int sortingOrder = ToggleSortAction.ORDER_DEFINITION;
private TreeViewer viewer;
private List<IType> baseTestClasses;
/**
* @since 2.5.0
*/
private IAction toggleShowHierarchyAction;
/**
* @since 2.5.0
*/
private IAction toggleSortAction;
public TestOutlineView() {}
@Override
public void createPartControl(Composite parent) {
parent.setLayout(new FillLayout());
viewer = new TestOutlineTreeViewer(parent);
viewer.setLabelProvider(
new DecoratingModelLabelProvider(
new TestOutlineAppearanceAwareLabelProvider()));
TreeEventListener eventListener = new TreeEventListener();
viewer.addSelectionChangedListener(eventListener);
viewer.addDoubleClickListener(eventListener);
MenuManager contextMenuManager = new MenuManager();
contextMenuManager.setRemoveAllWhenShown(true);
Menu contextMenu = contextMenuManager.createContextMenu(viewer.getTree());
viewer.getTree().setMenu(contextMenu);
getSite().registerContextMenu(contextMenuManager, viewer);
getSite().setSelectionProvider(viewer);
toggleSortAction = new ToggleSortAction(sortingOrder);
toggleShowHierarchyAction = new ToggleShowHierarchyAction(Messages.TestOutlineView_ToggleShowHierarchyAction, layout);
registerActions();
viewer.setContentProvider(((ToggleShowHierarchyAction) toggleShowHierarchyAction).getContentProvider(layout));
updateTestOutline();
// Add the caret listener to the active PHP editor.
// (TestOutlineViewController#partActivate() is not invoked into start up Eclipse.)
ActiveEditor activeEditor = MakeGoodContext.getInstance().getActiveEditor();
if (activeEditor.isPHP()) {
StyledText text = (StyledText) activeEditor.get().getAdapter(Control.class);
text.addCaretListener(new TestOutlineViewController());
}
}
@Override
public void setFocus() {
viewer.getTree().setFocus();
}
public void updateTestOutline() {
if (viewer == null) return;
if (viewer.getContentProvider() == null) return;
if (viewer.getControl().isDisposed()) return;
viewer.setInput(null);
ActiveEditor activeEditor = MakeGoodContext.getInstance().getActiveEditor();
if (!activeEditor.isPHP()) return;
ISourceModule module = EditorParser.createActiveEditorParser().getSourceModule();
IResource activeResource = module.getResource();
if (activeResource == null) return;
if (!new MakeGoodProperties(activeResource.getProject()).exists()) return;
List<TestClass> testClasses = new ArrayList<TestClass>();
try {
for (IType type: module.getTypes()) {
TestingFramework testingFramework =
new MakeGoodProperties(type.getResource().getProject()).getTestingFramework();
if (!TestClass.isTestClass(type, testingFramework)) continue;
TestClass testClass = new TestClass(type, testingFramework);
testClasses.add(testClass);
}
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
// If the name includes "test" but test class is not found,
// add the retry listener to the DLTK job.
boolean mayBeTest = module.getElementName().toLowerCase().indexOf("test") > 0;
if (testClasses.size() == 0 && mayBeTest) {
for (Job job: UIJob.getJobManager().find(null)) {
if (job.getName().indexOf("DLTK") != -1) {
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
Job job = new UIJob("MakeGood Test Outline View Updated") {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
TestOutlineView.this.updateTestOutline();
return Status.OK_STATUS;
}
};
job.schedule();
}
});
return;
}
}
}
viewer.setInput(testClasses);
viewer.expandAll();
collectBaseTestClasses(testClasses);
selectCurrentElement();
}
public void refresh() {
viewer.refresh();
}
public void selectCurrentElement() {
if (isOutlineSelecting) return;
EditorParser parser = EditorParser.createActiveEditorParser();
if (parser == null) return;
Tree tree = (Tree) viewer.getControl();
tree.deselectAll();
TreeItem foundItem = findItem(
tree.getItems(),
parser.getModelElementOnSelection());
if (foundItem == null) return;
tree.select(foundItem);
}
private TreeItem findItem(TreeItem[] items, IModelElement element) {
if (element == null) return null;
for (TreeItem item: items) {
if (!(item.getData() instanceof IModelElement)) continue;
IModelElement target = (IModelElement) item.getData();
if (target.getElementName().equals(element.getElementName())) {
return item;
}
if (item.getItems().length > 0) {
TreeItem foundItem = findItem(item.getItems(), element);
if (foundItem != null) return foundItem;
}
}
return null;
}
private void registerActions() {
IToolBarManager manager = getViewSite().getActionBars().getToolBarManager();
manager.add(new CollapseAllAction());
manager.add(toggleSortAction);
manager.add(toggleShowHierarchyAction);
}
private void collectBaseTestClasses(List<TestClass> testClasses) {
baseTestClasses = new ArrayList<IType>();
try {
for (TestClass testClass: testClasses) {
if (testClass.isNamespace()) {
for (IType type: testClass.getTypes()) {
Assert.isTrue(type instanceof TestClass);
baseTestClasses.add(type);
}
} else {
baseTestClasses.add(testClass);
}
}
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
private class TestOutlineTreeViewer extends TreeViewer {
public TestOutlineTreeViewer(Composite parent) {
super(parent);
}
@Override
public ISelection getSelection() {
final TreeSelection origin = (TreeSelection) super.getSelection();
return new TreeSelection(origin.getPaths(), getComparer()) {
@Override
public Object[] toArray() {
List<Object> results = new ArrayList<Object>();
for (Object element: super.toArray()) {
Assert.isTrue(
element instanceof TestMethod
|| element instanceof TestClass);
if (element instanceof TestMethod) {
setBaseTestClassToTestMethod((TestMethod) element);
results.add(element);
continue;
}
TestClass testClass = (TestClass) element;
if (testClass.isNamespace()) {
results.addAll(baseTestClasses);
break;
}
boolean isBaseTestClass = false;
for (IType baseTestClass: baseTestClasses) {
isBaseTestClass |= baseTestClass.getElementName().equals(testClass.getElementName());
}
if (isBaseTestClass) {
results.add(element);
continue;
}
try {
for (IMethod method: testClass.getMethods()) {
setBaseTestClassToTestMethod((TestMethod) method);
results.add(method);
}
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
return results.toArray();
}
private void setBaseTestClassToTestMethod(TestMethod method) {
IType type = (IType) method.getParent();
try {
for (IType baseTestClass: baseTestClasses) {
Assert.isTrue(baseTestClass instanceof TestClass);
if (((TestClass) baseTestClass).isSubtype(type)) {
method.setBaseType(baseTestClass);
break;
}
}
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
};
}
}
private class TestOutlineAppearanceAwareLabelProvider extends AppearanceAwareLabelProvider {
public TestOutlineAppearanceAwareLabelProvider() {
super(
AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS
| ScriptElementLabels.F_APP_TYPE_SIGNATURE
| ScriptElementLabels.ALL_CATEGORY,
AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS,
DLTKUIPlugin.getDefault().getPreferenceStore());
fImageLabelProvider = new ScriptElementImageProvider() {
@Override
public ImageDescriptor getBaseImageDescriptor(
IModelElement element,
int renderFlags) {
if (element instanceof TestClass) {
TestClass type = (TestClass) element;
try {
if ((type.getFlags() & IPHPModifiers.AccTrait) != 0) {
return PHPPluginImages.DESC_OBJS_TRAIT;
}
} catch (ModelException e) {}
}
return super.getBaseImageDescriptor(element, renderFlags);
}
};
}
}
private class TreeEventListener implements ISelectionChangedListener, IDoubleClickListener {
@Override
public void doubleClick(DoubleClickEvent event) {
isOutlineSelecting = true;
showEditor(event.getSelection(), true);
isOutlineSelecting = false;
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
isOutlineSelecting = true;
showEditor(event.getSelection(), false);
isOutlineSelecting = false;
}
private void showEditor(ISelection selection, Boolean showWhenDeactivate) {
if (selection.isEmpty()) return;
Assert.isTrue(selection instanceof StructuredSelection);
StructuredSelection structuredSelection = (StructuredSelection) selection;
Assert.isTrue(structuredSelection.getFirstElement() instanceof IMember);
IMember member = (IMember) structuredSelection.getFirstElement();
if (member == null) return;
if (member.getSourceModule() == null) return;
ISourceRange nameRange = null;
try {
nameRange = member.getNameRange();
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
if (nameRange == null) return;
ISourceModule source = member.getSourceModule();
if (member instanceof IMethod) {
source = ((IMember) member.getParent()).getSourceModule();
}
EditorParser activeEditorParser = EditorParser.createActiveEditorParser();
if (activeEditorParser != null) {
ISourceModule activeSourceModule = activeEditorParser.getSourceModule();
if (activeSourceModule != null) {
if (activeSourceModule.equals(source)) {
ActiveEditor activeEditor = MakeGoodContext.getInstance().getActiveEditor();
((ITextEditor) activeEditor.get()).selectAndReveal(nameRange.getOffset(), nameRange.getLength());
return;
}
}
}
if (showWhenDeactivate) {
EditorOpener.open((IFile) source.getResource(), nameRange.getOffset(), nameRange.getLength());
}
}
}
private class HierarchicalLayoutContentProvider implements ITreeContentProvider {
@Override
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
@Override
@SuppressWarnings("rawtypes")
public Object[] getChildren(Object parentElement) {
List children = null;
if (parentElement instanceof List) {
children = (List) parentElement;
} else if (parentElement instanceof TestClass) {
try {
children = Arrays.asList(((TestClass) parentElement).getChildren());
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
} else if (parentElement instanceof IMethod) {
children = new ArrayList();
}
Assert.isNotNull(children);
return children.toArray();
}
@Override
public Object getParent(Object element) {
if (element instanceof IMember) return ((IMember) element).getParent();
return null;
}
@Override
public boolean hasChildren(Object element) {
if (element instanceof IMethod) return false;
try {
if (element instanceof IMember) return ((IMember) element).hasChildren();
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
return false;
}
@Override
public void dispose() {}
@Override
public void inputChanged(Viewer viewer,
Object oldInput,
Object newInput) {}
}
private class FlatLayoutContentProvider extends HierarchicalLayoutContentProvider {
@Override
public Object[] getChildren(Object parentElement) {
boolean isNotTestClass =
!(parentElement instanceof TestClass)
|| ((TestClass) parentElement).isNamespace();
if (isNotTestClass) return super.getChildren(parentElement);
return collectMethods((TestClass) parentElement);
}
private Object[] collectMethods(TestClass target) {
final List<TestMethod> methods = new ArrayList<TestMethod>();
try {
target.accept(new IModelElementVisitor() {
@Override
public boolean visit(IModelElement element) {
if (element instanceof TestMethod) {
methods.add((TestMethod) element);
return false;
}
return true;
}
});
} catch (ModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage(), e));
}
return methods.toArray();
}
}
private class CollapseAllAction extends Action {
public CollapseAllAction() {
super(Messages.TestOutlineView_CollapseAll, AS_PUSH_BUTTON);
setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL));
setDisabledImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL_DISABLED));
setToolTipText(getText());
}
public void run() {
viewer.collapseAll();
}
}
public class ToggleSortAction extends Action {
private static final int ORDER_DEFINITION = 1;
private static final int ORDER_ALPHABETICAL = 2;
private ViewerSorter sorter = new ViewerSorter();
public ToggleSortAction(int sortingOrder) {
super(Messages.TestOutlineView_ToggleSort, AS_CHECK_BOX);
setImageDescriptor(Activator.getImageDescriptor("icons/toggle_sort.gif")); //$NON-NLS-1$
setToolTipText(getText());
setChecked(sortingOrder == ORDER_ALPHABETICAL);
}
@Override
public void run() {
if (sortingOrder == ORDER_DEFINITION) {
sortingOrder = ORDER_ALPHABETICAL;
} else if (sortingOrder == ORDER_ALPHABETICAL) {
sortingOrder = ORDER_DEFINITION;
}
viewer.setSorter(sortingOrder == ORDER_ALPHABETICAL ? sorter : null);
viewer.expandAll();
setChecked(sortingOrder == ORDER_ALPHABETICAL);
}
}
private class ToggleShowHierarchyAction extends Action {
private static final int LAYOUT_FLAT = 1;
private static final int LAYOUT_HIERARCHICAL = 2;
private IContentProvider hierarchicalLayoutContentProvider = new HierarchicalLayoutContentProvider();
private IContentProvider flatLayoutContentProvider = new FlatLayoutContentProvider();
public ToggleShowHierarchyAction(String text, int layout) {
super(text, AS_RADIO_BUTTON);
setToolTipText(getText());
setImageDescriptor(Activator.getImageDescriptor("icons/toggle_show_hierarchy.gif")); //$NON-NLS-1$
setChecked(layout == LAYOUT_HIERARCHICAL);
}
@Override
public void run() {
if (layout == LAYOUT_HIERARCHICAL) layout = LAYOUT_FLAT;
else if (layout == LAYOUT_FLAT) layout = LAYOUT_HIERARCHICAL;
setChecked(layout == LAYOUT_HIERARCHICAL);
viewer.setContentProvider(getContentProvider(layout));
viewer.expandAll();
}
/**
* @since 2.5.0
*/
public IContentProvider getContentProvider(int layout) {
if (layout == ToggleShowHierarchyAction.LAYOUT_HIERARCHICAL) {
return hierarchicalLayoutContentProvider;
} else if (layout == ToggleShowHierarchyAction.LAYOUT_FLAT) {
return flatLayoutContentProvider;
} else {
return hierarchicalLayoutContentProvider;
}
}
}
}