com.aptana.editor.common.outline.CommonQuickOutlinePage.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.editor.common.outline.CommonQuickOutlinePage.java

Source

/**
 * Aptana Studio
 * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
 * Please see the license.html included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.editor.common.outline;

import java.util.ArrayList;
import java.util.IdentityHashMap;
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.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
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.dialogs.PatternFilter;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;

import com.aptana.core.logging.IdeLog;
import com.aptana.core.util.EclipseUtil;
import com.aptana.editor.common.AbstractThemeableEditor;
import com.aptana.editor.common.CommonEditorPlugin;
import com.aptana.parsing.ast.IParseNode;
import com.aptana.ui.util.UIUtils;

/**
 * @author Paul Colton
 * @author Kevin Sawicki
 * @author Kevin Lindsey
 * @author Chris Williams
 */
class CommonQuickOutlinePage extends ContentOutlinePage {
    private static final int FILTER_REFRESH_DELAY = 200;
    private static final int REFRESH_DELAY = 500;

    /**
     * Main container for the page.
     */
    private Composite _composite;
    /**
     * The active editor for this outline.
     */
    private AbstractThemeableEditor _editor;
    /**
     * The filter used to narrow down the {@link #_treeViewer}
     */
    private PatternFilter _filter;
    /**
     * The pattern used to generate the {@link #_filter}
     */
    private String _pattern;
    /**
     * Jobs used to refresh the tree after the filter changes or the document content changes.
     */
    private WorkbenchJob _filterRefreshJob;
    private WorkbenchJob _delayedRefreshJob;
    /**
     * The actual text field where the filter/search pattern is entered.
     */
    private Text _searchBox;
    /**
     * The Tree that holds the outline.
     */
    private TreeViewer _treeViewer;
    /**
     * Listener for document content changes to trigger {@link #_delayedRefreshJob}.
     */
    private IDocumentListener _documentListener;

    private ToolBarManager _toolbarManager;

    /**
     * UnifiedOutlinePage
     * 
     * @param editor
     */
    public CommonQuickOutlinePage(AbstractThemeableEditor editor) {
        this._editor = editor;
    }

    /**
     * createComposite
     * 
     * @param parent
     * @return Composite
     */
    private Composite createComposite(Composite parent) {
        GridLayoutFactory factory = GridLayoutFactory.fillDefaults().spacing(0, 0);
        Composite result = new Composite(parent, SWT.NONE);
        factory.applyTo(result);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(result);

        return result;
    }

    /**
     * @see org.eclipse.ui.views.contentoutline.ContentOutlinePage#createControl(org.eclipse.swt.widgets.Composite)
     */
    public void createControl(Composite parent) {
        // create main container
        this._composite = createComposite(parent);

        // create tree view
        this._treeViewer = this.createTreeViewer(this._composite);

        final Tree tree = this._treeViewer.getTree();
        tree.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.character == 0x1B) // ESC
                    dispose();
            }

            public void keyReleased(KeyEvent e) {
                // do nothing
            }
        });
        tree.addSelectionListener(new SelectionListener() {
            public void widgetSelected(SelectionEvent e) {
                // do nothing
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                gotoSelectedElement();
            }
        });

        // apply tree filters
        this._filter = new PatternFilter() {
            /**
             * @see org.eclipse.ui.dialogs.PatternFilter#isLeafMatch(org.eclipse.jface.viewers.Viewer, java.lang.Object)
             */
            protected boolean isLeafMatch(Viewer viewer, Object element) {
                boolean result = true;
                String label = null;

                if (element instanceof CommonOutlineItem) {
                    label = ((CommonOutlineItem) element).getLabel();
                } else if (element instanceof IParseNode) {
                    label = ((IParseNode) element).getText();
                }

                if (label != null) {
                    result = this.wordMatches(label);
                }

                return result;
            }
        };
        this._filter.setIncludeLeadingWildcard(true);
        this._treeViewer.addFilter(this._filter);

        // create filter refresh job
        this._filterRefreshJob = this.createRefreshJob();
        EclipseUtil.setSystemForJob(this._filterRefreshJob);

        // create delayed update job
        this._delayedRefreshJob = this.createDelayedRefreshJob();
        EclipseUtil.setSystemForJob(this._delayedRefreshJob);

        // create document change listener and add to editor
        this.createDocumentListener();
        IDocument document = getDocument();
        if (document != null) {
            document.addDocumentListener(this._documentListener);
        }

        // refresh tree
        this.refresh();
    }

    /**
     * Reveals position.
     * 
     * @param documentPos
     *            - document position.
     */
    void revealPosition(int documentPos) {
        IStructuredContentProvider provider = (IStructuredContentProvider) getTreeViewer().getContentProvider();
        final Object[] originalElements = provider.getElements(_treeViewer.getInput());
        if (originalElements == null || originalElements.length == 0) {
            return;
        }

        // list of outline elements
        List<Object> elements = new ArrayList<Object>();

        // map from outline elements to the list of its parent elements
        // we need such a construction due to the fact that OutlineItem has no information about its parent
        final Map<Object, List<Object>> parents = new IdentityHashMap<Object, List<Object>>();

        for (Object el : originalElements) {
            elements.add(el);
        }

        if (provider instanceof ITreeContentProvider) {
            ITreeContentProvider treeContentProvider = (ITreeContentProvider) provider;
            for (Object element : originalElements) {
                expandElement(element, treeContentProvider, elements, parents);
            }
        }

        Object bestElement = null;
        int bestElementStartingOffset = -1;
        int bestElementEndingOffset = -1;

        for (Object element : elements) {
            int start = -1;
            int end = -1;
            if (element instanceof IParseNode) {
                IParseNode node = (IParseNode) element;
                start = node.getStartingOffset();
                end = node.getEndingOffset();
            } else if (element instanceof CommonOutlineItem) {
                int refNodeStart = ((CommonOutlineItem) element).getReferenceNode().getStartingOffset();
                int refNodeEnd = ((CommonOutlineItem) element).getReferenceNode().getEndingOffset();
                int elementStart = ((CommonOutlineItem) element).getStartingOffset();
                int elementEnd = ((CommonOutlineItem) element).getEndingOffset();
                start = (refNodeStart < elementStart) ? refNodeStart : elementStart;
                end = (refNodeEnd > elementEnd) ? refNodeEnd : elementEnd;
            }

            if (start != -1) {
                if (start <= documentPos && end >= documentPos) {
                    // choosing the node having the least length
                    if (bestElement == null || bestElementEndingOffset - bestElementStartingOffset > end - start) {
                        bestElement = element;
                        bestElementStartingOffset = start;
                        bestElementEndingOffset = end;
                    }
                }
            }
        }
        final Object toReveal = bestElement;
        WorkbenchJob job = new WorkbenchJob("Initial reveal") {//$NON-NLS-1$
            /**
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
             */
            public IStatus runInUIThread(IProgressMonitor monitor) {
                if (_treeViewer.getControl().isDisposed()) {
                    return Status.CANCEL_STATUS;
                }

                try {
                    // don't want the user to see updates that will be made to the tree
                    _treeViewer.getControl().setRedraw(false);
                    _treeViewer.refresh(true);

                    // _treeViewer.setSelection(new StructuredSelection(toReveal), true);
                    List<Object> path = new ArrayList<Object>();
                    List<Object> p = parents.get(toReveal);
                    if (p != null) {
                        path.addAll(p);
                    }
                    path.add(toReveal);
                    TreePath treePath = new TreePath(path.toArray());
                    _treeViewer.setSelection(new TreeSelection(treePath), true);
                } finally {
                    // done updating the tree - set redraw back to true
                    _treeViewer.getControl().setRedraw(true);
                }

                return Status.OK_STATUS;
            }
        };

        if (bestElement != null) {
            job.schedule(FILTER_REFRESH_DELAY);
        }
    }

    /**
     * createDelayedRefreshJob
     * 
     * @return workbench job
     */
    private WorkbenchJob createDelayedRefreshJob() {
        return new WorkbenchJob("Refresh Content") { //$NON-NLS-1$
            /**
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
             */
            public IStatus runInUIThread(IProgressMonitor monitor) {
                try {
                    if (_treeViewer.getControl().isDisposed()) {
                        return Status.CANCEL_STATUS;
                    }

                    _treeViewer.setSelection(null);
                    _treeViewer.refresh();
                }
                // SWT errors may be thrown here and will show as an error box since this is done on the UI thread
                // Catch everything and log it so that the dialog doesn't annoy the user since they may be typing into
                // the editor when this code throws errors and will impact them severely
                catch (Throwable e) {
                    IdeLog.logError(CommonEditorPlugin.getDefault(), e);
                }

                return Status.OK_STATUS;
            }
        };
    }

    /**
     * createDocumentListener
     */
    private synchronized void createDocumentListener() {
        if (this._documentListener == null) {
            this._documentListener = new IDocumentListener() {
                /**
                 * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
                 */
                public void documentAboutToBeChanged(DocumentEvent event) {
                }

                /**
                 * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
                 */
                public void documentChanged(DocumentEvent event) {
                    // cancel currently running job first, to prevent unnecessary redraw
                    if (_delayedRefreshJob != null) {
                        _delayedRefreshJob.cancel();
                        _delayedRefreshJob.schedule(REFRESH_DELAY);
                    }
                }
            };
        }
    }

    /**
     * createRefreshJob
     * 
     * @return Workbench job
     */
    private WorkbenchJob createRefreshJob() {
        return new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$
            /**
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
             */
            public IStatus runInUIThread(IProgressMonitor monitor) {
                if (_treeViewer.getControl().isDisposed()) {
                    return Status.CANCEL_STATUS;
                }

                if (_pattern == null) {
                    return Status.OK_STATUS;
                }

                _filter.setPattern(_pattern);

                try {
                    // don't want the user to see updates that will be made to the tree
                    _treeViewer.getControl().setRedraw(false);
                    _treeViewer.refresh(true);

                    if (_pattern.length() > 0) {
                        /*
                         * Expand elements one at a time. After each is expanded, check to see if the filter text has
                         * been modified. If it has, then cancel the refresh job so the user doesn't have to endure
                         * expansion of all the nodes.
                         */
                        IStructuredContentProvider provider = (IStructuredContentProvider) _treeViewer
                                .getContentProvider();
                        Object[] elements = provider.getElements(_treeViewer.getInput());

                        for (int i = 0; i < elements.length; i++) {
                            if (monitor.isCanceled()) {
                                return Status.CANCEL_STATUS;
                            }

                            _treeViewer.expandToLevel(elements[i], AbstractTreeViewer.ALL_LEVELS);
                        }

                        TreeItem[] items = _treeViewer.getTree().getItems();

                        if (items.length > 0) {
                            // to prevent scrolling
                            _treeViewer.getTree().showItem(items[0]);
                        }
                    }
                } finally {
                    // done updating the tree - set redraw back to true
                    _treeViewer.getControl().setRedraw(true);
                }

                return Status.OK_STATUS;
            }

        };
    }

    /**
     * Create search area
     * 
     * @param parent
     *            - parent
     * @return Composite
     */
    Composite createSearchArea(Composite parent) {
        Composite top = new Composite(parent, SWT.NONE);
        GridLayoutFactory.fillDefaults().spacing(0, 0).extendedMargins(0, 0, 3, 0).applyTo(top);
        top.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

        // create layout
        Composite result = new Composite(top, SWT.NONE);

        // assign layout and layout data
        GridLayoutFactory.fillDefaults().spacing(0, 0).numColumns(3).applyTo(result);
        GridDataFactory.fillDefaults().align(GridData.BEGINNING, GridData.FILL).grab(true, false).applyTo(result);

        // create text box
        this._searchBox = new Text(result, SWT.SINGLE | SWT.FOCUSED);
        this._searchBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        this._searchBox.setEditable(true);
        this._searchBox.addModifyListener(new ModifyListener() {
            /**
             * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
             */
            public void modifyText(ModifyEvent e) {
                textChanged();
            }
        });
        this._searchBox.addKeyListener(new KeyListener() {

            public void keyPressed(KeyEvent e) {
                if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
                    _treeViewer.getControl().setFocus();
                }
            }

            public void keyReleased(KeyEvent e) {
            }
        });

        ToolBar filtersToolBar = new ToolBar(result, SWT.HORIZONTAL);
        filtersToolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
        _toolbarManager = new ToolBarManager(filtersToolBar);

        _toolbarManager.update(false);
        _toolbarManager.getControl().update();

        return result;
    }

    /**
     * createTreeViewer
     * 
     * @param parent
     * @return TreeViewer
     */
    private TreeViewer createTreeViewer(Composite parent) {
        TreeViewer result = new TreeViewer(new Tree(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL));
        Tree tree = result.getTree();
        GridData gd = new GridData(GridData.FILL_BOTH);
        gd.heightHint = tree.getItemHeight() * 12;
        tree.setLayoutData(gd);

        result.setLabelProvider(_editor.getOutlineLabelProvider());
        result.setContentProvider(_editor.getOutlineContentProvider());
        result.setInput(this._editor);

        result.setComparer(new IElementComparer() {
            /**
             * @see org.eclipse.jface.viewers.IElementComparer#equals(java.lang.Object, java.lang.Object)
             */
            public boolean equals(Object a, Object b) {
                boolean result = false;

                if (a instanceof CommonOutlineItem && b instanceof CommonOutlineItem) {
                    CommonOutlineItem item1 = (CommonOutlineItem) a;
                    CommonOutlineItem item2 = (CommonOutlineItem) b;

                    result = item1.equals(item2);
                } else if (a instanceof IParseNode && b instanceof IParseNode) {
                    if (a == b) {
                        result = true;
                    } else {
                        IParseNode node1 = (IParseNode) a;
                        IParseNode node2 = (IParseNode) b;
                        result = node1.equals(node2);
                    }
                } else {
                    result = (a == b);
                }

                return result;
            }

            /**
             * @see org.eclipse.jface.viewers.IElementComparer#hashCode(java.lang.Object)
             */
            public int hashCode(Object element) {
                return 0;
            }
        });

        return result;
    }

    /**
     * @see org.eclipse.ui.part.Page#dispose()
     */
    public void dispose() {
        if (this._documentListener != null) {
            IDocument document = getDocument();
            if (document != null) {
                document.removeDocumentListener(this._documentListener);
            }
            this._documentListener = null;
        }

        if (this._delayedRefreshJob != null) {
            this._delayedRefreshJob.cancel();
            this._delayedRefreshJob = null;
        }

        if (this._filterRefreshJob != null) {
            this._filterRefreshJob.cancel();
            this._filterRefreshJob = null;
        }

        if (this._toolbarManager != null) {
            this._toolbarManager.dispose();
            this._toolbarManager = null;
        }

        super.dispose();
    }

    private IDocument getDocument() {
        return this._editor.getDocumentProvider().getDocument(this._editor.getEditorInput());
    }

    /**
     * @see org.eclipse.ui.views.contentoutline.ContentOutlinePage#getControl()
     */
    public Control getControl() {
        return this._composite;
    }

    /**
     * @see org.eclipse.ui.views.contentoutline.ContentOutlinePage#getTreeViewer()
     */
    public TreeViewer getTreeViewer() {
        return this._treeViewer;
    }

    /**
     * refresh
     */
    private void refresh() {
        if (!_treeViewer.getControl().isDisposed()) {
            this._treeViewer.refresh();
        }
    }

    /**
     * Contributes actions to quick outline menu.
     * 
     * @param manager
     *            - menu manager.
     */
    void contributeToQuickOutlineMenu(IMenuManager manager) {
        // add sort action
        Action sortAction = new Action(Messages.CommonQuickOutlinePage_SortAlphabetically, Action.AS_CHECK_BOX) {
            public void run() {
                // Hide tree control during redraw
                getTreeViewer().getControl().setVisible(false);

                // Set the sorting according to whether this Action is checked/unchecked
                // TODO Store this persistently across quick outlines per-language?
                if (this.isChecked()) {
                    getTreeViewer().setComparator(new ViewerComparator());
                } else {
                    getTreeViewer().setComparator(null);
                }

                // Show the tree control
                getTreeViewer().getControl().setVisible(true);
            }
        };
        sortAction.setImageDescriptor(UIUtils.getImageDescriptor(CommonEditorPlugin.PLUGIN_ID, "icons/sort.gif")); //$NON-NLS-1$
        sortAction.setToolTipText(Messages.CommonQuickOutlinePage_SortAlphabetically);
        // this._sortItem = new ActionContributionItem(sortAction);
        manager.add(new ActionContributionItem(sortAction));

        // add Collapse All action
        Action collapseAction = new Action(Messages.CommonQuickOutlinePage_CollapseAll, Action.AS_PUSH_BUTTON) {
            public void run() {
                getTreeViewer().collapseAll();
            }
        };
        collapseAction.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL));
        collapseAction.setToolTipText(Messages.CommonQuickOutlinePage_CollapseAll);
        manager.add(new ActionContributionItem(collapseAction));

        // Expand All action
        Action expandAction = new Action(Messages.CommonQuickOutlinePage_ExpandAll) {
            public void run() {
                getTreeViewer().expandAll();
            }
        };
        expandAction.setImageDescriptor(
                UIUtils.getImageDescriptor(CommonEditorPlugin.PLUGIN_ID, "icons/expandall.gif")); //$NON-NLS-1$
        expandAction.setToolTipText(Messages.CommonQuickOutlinePage_ExpandAll);
        manager.add(new ActionContributionItem(expandAction));
    }

    /**
     * @see org.eclipse.ui.views.contentoutline.ContentOutlinePage#setFocus()
     */
    public void setFocus() {
        this._treeViewer.getControl().setFocus();
    }

    /**
     * Gets searhbox.
     */
    Control getSearchBox() {
        return _searchBox;
    }

    /**
     * textChanged
     */
    private void textChanged() {
        // cancel currently running job first, to prevent unnecessary redraw
        this._filterRefreshJob.cancel();
        this._filterRefreshJob.schedule(FILTER_REFRESH_DELAY);

        // set current filter pattern
        this._pattern = this._searchBox.getText();
        this._filter.setPattern(this._pattern);
    }

    @Override
    public ISelection getSelection() {
        if (getTreeViewer() == null) {
            return StructuredSelection.EMPTY;
        }
        return getTreeViewer().getSelection();
    }

    private void gotoSelectedElement() {
        ISelection selection = getSelection();
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection structured = (IStructuredSelection) selection;
            // If a node in the outline view is selected
            if (structured.size() == 1) {
                Object element = structured.getFirstElement();

                if (element instanceof CommonOutlineItem) {
                    CommonOutlineItem item = (CommonOutlineItem) element;
                    this._editor.selectAndReveal(item.getStartingOffset(), item.getLength());
                    closeDialog();
                } else if (element instanceof IParseNode) {
                    int position = ((IParseNode) element).getStartingOffset();
                    this._editor.selectAndReveal(position, 0);
                    closeDialog();
                }
                return;
            }
        }

        this._editor.getISourceViewer().removeRangeIndication();
    }

    private void closeDialog() {
        getControl().getParent().getShell().dispose();
    }

    /**
     * Expands a tree element, also feels parents map.
     * 
     * @param element
     *            - element to expand.
     * @param treeContentProvider
     *            - tree content provider.
     * @param elements
     *            - elements.
     */
    private void expandElement(Object element, ITreeContentProvider treeContentProvider, List<Object> elements,
            Map<Object, List<Object>> parents) {
        // getting children
        Object[] children = treeContentProvider.getChildren(element);

        List<Object> elementParentsList = parents.get(element);

        for (Object child : children) {
            // adding child to the elements list
            elements.add(child);

            // filling parents list for the child
            List<Object> parentsList = new ArrayList<Object>();

            // adding list of parent's parents, if exists
            if (elementParentsList != null) {
                parentsList.addAll(elementParentsList);
            }

            // adding parent
            parentsList.add(element);
            parents.put(child, parentsList);
        }

        // expanding children
        for (Object child : children) {
            expandElement(child, treeContentProvider, elements, parents);
        }
    }
}