org.absmodels.abs.plugin.editor.outline.ABSContentOutlinePage.java Source code

Java tutorial

Introduction

Here is the source code for org.absmodels.abs.plugin.editor.outline.ABSContentOutlinePage.java

Source

/** 
 * 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;
    }
}