/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package org.absmodels.abs.plugin.editor.outline;
import static org.absmodels.abs.plugin.editor.outline.ABSContentOutlineConstants.FILTER_COMMANDS;
import static org.absmodels.abs.plugin.editor.outline.ABSContentOutlineConstants.SORT_COMMAND_ID;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.absmodels.abs.plugin.editor.ABSEditor;
import org.absmodels.abs.plugin.editor.reconciling.CompilationUnitChangeListener;
import org.absmodels.abs.plugin.util.InternalASTNode;
import org.eclipse.core.commands.State;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.handlers.RegistryToggleState;
import org.eclipse.ui.services.IServiceScopes;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
import abs.frontend.ast.CompilationUnit;
/**
* Implements the Content Outline for ABS files
* @author cseise
*
*/
public class ABSContentOutlinePage extends ContentOutlinePage {
/**
* The text editor associated with this content outline page instance
*/
private final ABSEditor editor;
private final CompilationUnitChangeListener modelChangeListener;
/**
* The ITreeContentProvider delivers the elements that should be shown in the outline
*/
private final ITreeContentProvider coProv;
/**
* ICommandService for retrieving the states of the filter buttons
*/
private final ICommandService commandService;
/** flag indicating whether a selection should move
* cursor inside the editor
*/
private boolean selectionMovesCursor = true;
public ABSContentOutlinePage(ABSEditor editor) {
this.editor = editor;
commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
assert commandService != null;
coProv = new ABSContentOutlineProvider();
// When the project is built this listener is responsible for updating the input
modelChangeListener = new ABSContentOutlineChangeListener();
}
/**
* @see org.eclipse.ui.views.contentoutline.ContentOutlinePage#createControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createControl(Composite parent) {
super.createControl(parent);
initTreeViewer();
addSelectionListener();
setInput();
restoreFilters();
editor.addModelChangeListener(modelChangeListener);
}
private void restoreFilters() {
boolean value = getValueOfCommand(SORT_COMMAND_ID);
TreeViewer tw = getTreeViewer();
if (tw != null) {
if (value) {
tw.setComparator(ABSContentOutlineFiltersAndComparators.getAlphabeticalASTNodeComparator());
} else {
tw.setComparator(null);
}
List<ViewerFilter> filters = Arrays.asList(tw.getFilters());
for (String Command : FILTER_COMMANDS) {
value = getValueOfCommand(Command);
ViewerFilter filterFromCommand = ABSContentOutlineFiltersAndComparators.getFilterOfCommand(Command);
if (value) {
if (!filters.contains(filterFromCommand)) {
tw.addFilter(filterFromCommand);
}
} else {
tw.removeFilter(filterFromCommand);
}
}
//Trigger update of visible button state (pressed/not pressed)
Map<String, IWorkbenchWindow> filter = new HashMap<String, IWorkbenchWindow>();
filter.put(IServiceScopes.WINDOW_SCOPE, editor.getSite().getPage().getWorkbenchWindow());
commandService.refreshElements(SORT_COMMAND_ID, filter);
}
}
private boolean getValueOfCommand(String commandID){
assert commandService != null;
State state = commandService.getCommand(commandID).getState(RegistryToggleState.STATE_ID);
boolean value = ((Boolean)state.getValue()).booleanValue();
return value;
}
private void initTreeViewer() {
TreeViewer tw = getTreeViewer();
tw.setContentProvider(coProv);
// The label provider is responsible for converting ASTNodes into their String representations
tw.setLabelProvider(new ABSContentOutlineStyledLabelProvider());
// TODO: Making tree expansion more "intelligent" than only expanding all tree elements
tw.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
}
private void setInput() {
InternalASTNode<CompilationUnit> cu = editor.getCompilationUnit();
// Update the input of the tree viewer to reflect the new outline of the AST
getTreeViewer().setInput(cu);
}
/**
* Two selection listeners:
* - the first propagates a click in the outline to the editor.
* - the second updates the position in the outline based on the editor-position.
* In principle we could add the first listener in ABSEditor.getAdapter(), but
* that wouldn't gain us much in terms of visibility.
*/
private void addSelectionListener() {
// Handle selection changes in the outline
addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection sel = event.getSelection();
if (sel.isEmpty()) {
editor.resetHighlightRange();
} else {
ABSContentOutlineUtils.insertCostabsItems(sel);
// Get only the first element of the selection
InternalASTNode<?> t = ((InternalASTNode<?>) ((IStructuredSelection) sel)
.getFirstElement());
editor.highlightInEditor(t, selectionMovesCursor);
}
}
});
// Select the current element under cursor in the outlineView:
editor.getSelectionProvider().addPostSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (selection instanceof ITextSelection) {
// select the current element in the outlineView
ITextSelection ts = (ITextSelection) selection;
selectNodeByPos(ts.getStartLine());
}
}
});
}
/**
* Returns the internal TreeViewer component of the Content Outline Page.
* (Useful for setting filters, sorters or other TreeViewer properties)
* @return The TreeViewer component associated with this Content Outline Page.
*/
public TreeViewer getTreeView(){
return getTreeViewer();
}
@Override
public void dispose() {
editor.removeModelChangeListener(modelChangeListener);
super.dispose();
}
/**
* updates the outline whenever the compilationUnit of the editor changes
*/
class ABSContentOutlineChangeListener implements CompilationUnitChangeListener {
@Override
public void onCompilationUnitChange(final CompilationUnit newCu) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
ViewerFilter[] vf = getTreeViewer().getFilters();
ViewerComparator sort = getTreeViewer().getComparator();
InternalASTNode<CompilationUnit> cu = editor.getCompilationUnit();
getTreeViewer().setInput(cu);
getTreeViewer().setFilters(vf);
getTreeViewer().setComparator(sort);
editor.getSelectionProvider().setSelection(editor.getSelectionProvider().getSelection());
}
});
}
}
/**
* selects a node in the outline without moving the cursor
* in the editor
*/
private void setSelectionWithoutCursorMove(ISelection sel) {
selectionMovesCursor = false;
setSelection(sel);
selectionMovesCursor = true;
}
/**
* Select the closest node to the given line.
* Note that the outline might actually not be visible.
* startLine < 0 indicates invalid info, e.g. from upstream, will reset selection.
*/
private void selectNodeByPos(int startLine) {
// Do nothing if viewer not created yet (e.g. invisible on startup)
if (getTreeViewer() == null || !getValueOfCommand(ABSContentOutlineConstants.LINK_EDITOR_COMMAND_ID)) {
// linking with editor not enabled ...
return;
}
Object input = getTreeViewer().getInput();
if (input instanceof InternalASTNode<?>) {
InternalASTNode<?> internalASTNode = (InternalASTNode<?>) input;
final ISelection selection;
if (startLine > -1) { // valid line info?
InternalASTNode<?> sel = findNodeInLine(internalASTNode, startLine+1);
if (sel == null)
selection = new TreeSelection();
else
selection = new TreeSelection(new TreePath(new Object[] {sel}));
} else
selection = new TreeSelection();
setSelectionWithoutCursorMove(selection);
}
}
private InternalASTNode<?> findNodeInLine(InternalASTNode<?> node, int startLine) {
final int line = node.getASTNode().getStartLine();
if (line > startLine) {
/* If a module starts with comments, the "module" declaration will be the
* root node in line n > 1, yet the editor will be placed in startLine=1.
*/
return null;
}
InternalASTNode<?> result = node;
for (Object child : coProv.getChildren(node)) {
if (child instanceof InternalASTNode<?>) {
InternalASTNode<?> childNode = (InternalASTNode<?>) child;
// FIXME: probably shouldn't be recursive.
InternalASTNode<?> r = findNodeInLine(childNode, startLine);
if (r != null) {
result = r;
} else {
break;
}
}
}
return result;
}
}