net.sourceforge.texlipse.outline.TexOutlinePage.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.texlipse.outline.TexOutlinePage.java

Source

/*
 * $Id$
 *
 * Copyright (c) 2004-2005 by the TeXlapse Team.
 * All rights reserved. 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 net.sourceforge.texlipse.outline;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import net.sourceforge.texlipse.TexlipsePlugin;
import net.sourceforge.texlipse.editor.TexEditor;
import net.sourceforge.texlipse.model.OutlineNode;
import net.sourceforge.texlipse.model.TexOutlineInput;
import net.sourceforge.texlipse.properties.TexlipseProperties;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;

/**
 * The outline page for the TexEditor.
 *
 * @author Taavi Hupponen
 * @author Laura Takkinen
 */
public class TexOutlinePage extends ContentOutlinePage {

    private static final String ACTION_COPY = "copy";
    private static final String ACTION_CUT = "cut";
    private static final String ACTION_PASTE = "paste";
    private static final String ACTION_DELETE = "delete";
    private static final String ACTION_UPDATE = "update";
    private static final String ACTION_COLLAPSE = "collapse";
    private static final String ACTION_EXPAND = "expand";
    private static final String ACTION_HIDE_SEC = "hideSec";
    private static final String ACTION_HIDE_SUBSEC = "hideSubSec";
    private static final String ACTION_HIDE_SUBSUBSEC = "hideSubSubSec";
    private static final String ACTION_HIDE_PARAGRAPH = "hidePara";
    private static final String ACTION_HIDE_FLOAT = "hideFloat";
    private static final String ACTION_HIDE_LABEL = "hideLabel";

    private TexOutlineInput input;
    private TexEditor editor;
    private TexOutlineFilter filter;
    private Clipboard clipboard;
    private int expandLevel;
    private Map<String, IAction> outlineActions;
    //private Set outlineProperties;

    /**
     * The constructor.
     * 
     * @param texEditor the editor associated with this page
     */
    public TexOutlinePage(TexEditor texEditor) {
        super();
        this.editor = texEditor;
        expandLevel = 1;
        this.outlineActions = new HashMap<String, IAction>();

        TexlipsePlugin.getDefault().getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent event) {

                String property = event.getProperty();
                if (TexlipseProperties.OUTLINE_PART.equals(property)
                        || TexlipseProperties.OUTLINE_CHAPTER.equals(property)
                        || TexlipseProperties.OUTLINE_SECTION.equals(property)
                        || TexlipseProperties.OUTLINE_SUBSECTION.equals(property)
                        || TexlipseProperties.OUTLINE_SUBSUBSECTION.equals(property)
                        || TexlipseProperties.OUTLINE_PARAGRAPH.equals(property)
                        || TexlipseProperties.OUTLINE_ENVS.equals(property)) {
                    getOutlinePreferences();
                    resetToolbarButtons();
                    TreeViewer viewer = getTreeViewer();
                    if (viewer != null) {
                        Control control = viewer.getControl();
                        if (control != null && !control.isDisposed()) {
                            viewer.refresh();
                        }
                    }
                }
            }
        });

    }

    /**
     * Creates the control ie. creates all the stuff that matters and
     * is visible in the outline. 
     * 
     * Actions must be created before menus and toolbars.
     * 
     * @param parent
     */
    public void createControl(Composite parent) {
        super.createControl(parent);

        // create the context actions
        createActions();

        // initialize the tree viewer
        TreeViewer viewer = getTreeViewer();
        filter = new TexOutlineFilter();
        viewer.setContentProvider(new TexContentProvider(filter));
        viewer.setLabelProvider(new TexLabelProvider());
        viewer.setComparer(new TexOutlineNodeComparer());

        // get and apply the preferences
        this.getOutlinePreferences();
        viewer.addFilter(filter);

        // set the selection listener
        viewer.addSelectionChangedListener(this);

        // enable drag'n'drop support
        TexOutlineDNDAdapter dndAdapter = new TexOutlineDNDAdapter(viewer, this);
        int ops = DND.DROP_COPY | DND.DROP_MOVE;
        Transfer[] transfers = new Transfer[] { TextTransfer.getInstance() };
        viewer.addDragSupport(ops, transfers, dndAdapter);
        viewer.addDropSupport(ops, transfers, dndAdapter);

        // enable copy-paste
        initCopyPaste(viewer);

        // create the menu bar and the context menu
        createToolbar();
        resetToolbarButtons();
        createContextMenu();

        // finally set the input
        if (this.input != null) {
            viewer.setInput(this.input.getRootNodes());

            // set update button status and also the context actions
            outlineActions.get(ACTION_UPDATE).setEnabled(false);
            outlineActions.get(ACTION_COPY).setEnabled(true);
            outlineActions.get(ACTION_CUT).setEnabled(true);
            outlineActions.get(ACTION_PASTE).setEnabled(true);
            outlineActions.get(ACTION_DELETE).setEnabled(true);

        }
    }

    @Override
    public void setFocus() {
        getTreeViewer().getTree().setFocus();
    }

    /**
     * Updates the outline with new content. Called by TexDocumentModel
     * through the editor. Saves the visible state of the outline,
     * sets the new content and restores the state.
     *
     * @param input the new outline input
     */
    public void update(TexOutlineInput input) {
        this.input = input;

        TreeViewer viewer = getTreeViewer();
        if (viewer != null) {

            Control control = viewer.getControl();
            if (control != null && !control.isDisposed()) {
                control.setRedraw(false);

                //First try smart update
                boolean succUpdate = ((TexContentProvider) viewer.getContentProvider()).updateElements(viewer,
                        input.getRootNodes());
                if (!succUpdate) {
                    viewer.getTree().deselectAll();
                    // save viewer state
                    Object[] expandedElements = viewer.getExpandedElements();
                    // set new input
                    viewer.setInput(input.getRootNodes());
                    /*viewer.getContentProvider().inputChanged(viewer, null, input.getRootNodes());
                    viewer.refresh(true);*/
                    // restore viewer state
                    viewer.setExpandedElements(expandedElements);
                }
                control.setRedraw(true);

                // disable the refresh button, enable context stuff
                outlineActions.get(ACTION_UPDATE).setEnabled(false);
                outlineActions.get(ACTION_COPY).setEnabled(true);
                outlineActions.get(ACTION_CUT).setEnabled(true);
                outlineActions.get(ACTION_PASTE).setEnabled(true);
                outlineActions.get(ACTION_DELETE).setEnabled(true);
            }
        }
    }

    /**
     * Focuses the editor to the text of the selected item.
     * 
     * @param event the selection event
     */
    public void selectionChanged(SelectionChangedEvent event) {
        super.selectionChanged(event);

        ISelection selection = event.getSelection();
        if (selection.isEmpty()) {
            editor.resetHighlightRange();
        }

        else {
            OutlineNode node = (OutlineNode) ((IStructuredSelection) selection).getFirstElement();
            Position position = node.getPosition();
            if (position != null) {
                try {
                    editor.setHighlightRange(position.getOffset(), position.getLength(), true);
                    editor.getViewer().revealRange(position.getOffset(), position.getLength());
                } catch (IllegalArgumentException x) {
                    editor.resetHighlightRange();
                }
            } else {
                editor.resetHighlightRange();
            }
        }
    }

    /**
     * Gets the text of the currently selected item. Use by copy paste
     * and drag'n'drop operations. 
     * 
     * @return text of the currently selected item or null if no item
     * is selected
     * 
     * TODO handle multiple selections
     */
    public String getSelectedText() {
        IStructuredSelection selection = (IStructuredSelection) getTreeViewer().getSelection();
        if (selection == null) {
            return null;
        }

        OutlineNode node = (OutlineNode) selection.getFirstElement();
        Position pos = node.getPosition();

        String text;
        try {
            text = this.editor.getDocumentProvider().getDocument(this.editor.getEditorInput()).get(pos.getOffset(),
                    pos.getLength());
        } catch (BadLocationException e) {
            return null;
        }
        return text;
    }

    /**
     * Removes the text of the currently selected item From the 
     * document. Used by copy paste and drag'n'drop operations.
     * 
     * Trigger parsing after remove is done. 
     * 
     * TODO handle multiple selections
     */
    public void removeSelectedText() {
        IStructuredSelection selection = (IStructuredSelection) getTreeViewer().getSelection();
        if (selection == null) {
            return;
        }

        OutlineNode node = (OutlineNode) selection.getFirstElement();
        Position pos = node.getPosition();

        try {
            this.editor.getDocumentProvider().getDocument(this.editor.getEditorInput()).replace(pos.getOffset(),
                    pos.getLength(), "");
        } catch (BadLocationException e) {
            return;
        }

        this.editor.updateModelNow();
    }

    /**
     * Dispose the clipboard.
     */
    public void dispose() {
        super.dispose();
        this.clipboard.dispose();
        this.clipboard = null;
    }

    /**
     * Pastes given text after the selected item. Used by the paste
     * action.
     * 
     * Triggers model update afterwards.
     * 
     * @param text the text to be pasted
     * @return true if pasting was succesful, otherwise false
     */
    public boolean paste(String text) {
        // get selection
        IStructuredSelection selection = (IStructuredSelection) getTreeViewer().getSelection();
        if (selection == null) {
            return false;
        }
        OutlineNode node = (OutlineNode) selection.getFirstElement();
        Position pos = node.getPosition();

        // paste the text
        try {
            this.editor.getDocumentProvider().getDocument(this.editor.getEditorInput())
                    .replace(pos.getOffset() + pos.getLength(), 0, text);
        } catch (BadLocationException e) {
            return false;
        }

        // trigger parsing
        this.editor.updateModelNow();
        return true;
    }

    /**
     * Called by the TexDocumentModel when it gets dirty. Enables
     * the update button.
     */
    public void modelGotDirty() {
        outlineActions.get(ACTION_UPDATE).setEnabled(true);
        outlineActions.get(ACTION_COPY).setEnabled(false);
        outlineActions.get(ACTION_CUT).setEnabled(false);
        outlineActions.get(ACTION_PASTE).setEnabled(false);
        outlineActions.get(ACTION_DELETE).setEnabled(false);
    }

    /**
     * Returns whether the current TexDocumentModel is dirty
     * 
     * @return if current model is dirty.
     */
    public boolean isModelDirty() {
        return editor.isModelDirty();
    }

    /**
     * Returns the editor associated with this outline page.
     * 
     * @return the editor associated with this outline page.
     */
    public TexEditor getEditor() {
        return this.editor;
    }

    public void setEditor(TexEditor editor) {
        this.editor = editor;
    }

    /**
     * Gets the clipboard. Used by copy paste actions.
     * 
     * @return the clipboard associated with this outline
     */
    public Clipboard getClipboard() {
        return this.clipboard;
    }

    /*
     * Creates a new action to hide a certain nodeType
     */
    private IAction createHideAction(String desc, final int nodeType, ImageDescriptor img) {
        IAction action = new Action(desc, IAction.AS_CHECK_BOX) {
            public void run() {
                boolean oldState = filter.isTypeVisible(nodeType);
                filter.toggleType(nodeType, !oldState);
                TreeViewer viewer = getTreeViewer();
                if (oldState == false) {
                    revealNodes(nodeType);
                }
                viewer.refresh();
            }
        };
        action.setToolTipText(desc);
        action.setImageDescriptor(img);
        return action;
    }

    /**
     * Creates the actions associated with the outline. 
     */
    private void createActions() {
        // context menu actions 
        TexOutlineActionCut cut = new TexOutlineActionCut(this);
        this.outlineActions.put(ACTION_CUT, cut);

        TexOutlineActionCopy copy = new TexOutlineActionCopy(this);
        this.outlineActions.put(ACTION_COPY, copy);

        TexOutlineActionPaste paste = new TexOutlineActionPaste(this);
        this.outlineActions.put(ACTION_PASTE, paste);

        TexOutlineActionDelete delete = new TexOutlineActionDelete(this);
        this.outlineActions.put(ACTION_DELETE, delete);

        // toolbar actions
        TexOutlineActionUpdate update = new TexOutlineActionUpdate(this);
        this.outlineActions.put(ACTION_UPDATE, update);

        Action collapse = new Action("Collapse one level", IAction.AS_PUSH_BUTTON) {
            public void run() {
                if (expandLevel > 1) {
                    expandLevel--;
                    getTreeViewer().collapseAll();
                    getTreeViewer().expandToLevel(expandLevel);
                }
            }
        };
        collapse.setToolTipText("Collapse one level");
        collapse.setImageDescriptor(TexlipsePlugin.getImageDescriptor("collapse"));
        this.outlineActions.put(ACTION_COLLAPSE, collapse);

        Action expand = new Action("Expand one level", IAction.AS_PUSH_BUTTON) {
            public void run() {
                if (expandLevel < input.getTreeDepth()) {
                    expandLevel++;
                }
                getTreeViewer().collapseAll();
                getTreeViewer().expandToLevel(expandLevel);
            }
        };
        expand.setToolTipText("Expand one level");
        expand.setImageDescriptor(TexlipsePlugin.getImageDescriptor("expand"));
        this.outlineActions.put(ACTION_EXPAND, expand);

        IAction action = createHideAction("Hide sections", OutlineNode.TYPE_SECTION,
                TexlipsePlugin.getImageDescriptor("hide_sec"));
        this.outlineActions.put(ACTION_HIDE_SEC, action);

        action = createHideAction("Hide subsections", OutlineNode.TYPE_SUBSECTION,
                TexlipsePlugin.getImageDescriptor("hide_sub"));
        this.outlineActions.put(ACTION_HIDE_SUBSEC, action);

        action = createHideAction("Hide subsubsections", OutlineNode.TYPE_SUBSUBSECTION,
                TexlipsePlugin.getImageDescriptor("hide_subsub"));
        this.outlineActions.put(ACTION_HIDE_SUBSUBSEC, action);

        action = createHideAction("Hide paragraphs", OutlineNode.TYPE_PARAGRAPH,
                TexlipsePlugin.getImageDescriptor("hide_para"));
        this.outlineActions.put(ACTION_HIDE_PARAGRAPH, action);

        action = createHideAction("Hide floating environments", OutlineNode.TYPE_ENVIRONMENT,
                TexlipsePlugin.getImageDescriptor("hide_env"));
        this.outlineActions.put(ACTION_HIDE_FLOAT, action);

        action = createHideAction("Hide labels", OutlineNode.TYPE_LABEL,
                TexlipsePlugin.getImageDescriptor("hide_label"));
        this.outlineActions.put(ACTION_HIDE_LABEL, action);
    }

    /**
     * Initialize copy paste by getting the clipboard and hooking 
     * the actions to global edit menu.
     * 
     * @param viewer
     */
    private void initCopyPaste(TreeViewer viewer) {
        this.clipboard = new Clipboard(getSite().getShell().getDisplay());

        IActionBars bars = getSite().getActionBars();
        bars.setGlobalActionHandler(ActionFactory.CUT.getId(), (Action) outlineActions.get(ACTION_CUT));

        bars.setGlobalActionHandler(ActionFactory.COPY.getId(), (Action) outlineActions.get(ACTION_COPY));

        bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), (Action) outlineActions.get(ACTION_PASTE));

        bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), (Action) outlineActions.get(ACTION_DELETE));
    }

    /**
     * Get the preferences.
     * 
     */
    private void getOutlinePreferences() {
        filter.reset();

        // add node types to be included
        boolean preamble = TexlipsePlugin.getDefault().getPreferenceStore()
                .getBoolean(TexlipseProperties.OUTLINE_PREAMBLE);
        boolean part = TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.OUTLINE_PART);
        boolean chapter = TexlipsePlugin.getDefault().getPreferenceStore()
                .getBoolean(TexlipseProperties.OUTLINE_CHAPTER);
        boolean section = TexlipsePlugin.getDefault().getPreferenceStore()
                .getBoolean(TexlipseProperties.OUTLINE_SECTION);
        boolean subsection = TexlipsePlugin.getDefault().getPreferenceStore()
                .getBoolean(TexlipseProperties.OUTLINE_SUBSECTION);
        boolean subsubsection = TexlipsePlugin.getDefault().getPreferenceStore()
                .getBoolean(TexlipseProperties.OUTLINE_SUBSUBSECTION);
        boolean paragraph = TexlipsePlugin.getDefault().getPreferenceStore()
                .getBoolean(TexlipseProperties.OUTLINE_PARAGRAPH);

        if (preamble) {
            filter.toggleType(OutlineNode.TYPE_PREAMBLE, true);
        }
        if (part) {
            filter.toggleType(OutlineNode.TYPE_PART, true);
        }
        if (chapter) {
            filter.toggleType(OutlineNode.TYPE_CHAPTER, true);
        }
        if (section) {
            filter.toggleType(OutlineNode.TYPE_SECTION, true);
        }
        if (subsection) {
            filter.toggleType(OutlineNode.TYPE_SUBSECTION, true);
        }
        if (subsubsection) {
            filter.toggleType(OutlineNode.TYPE_SUBSUBSECTION, true);
        }
        if (paragraph) {
            filter.toggleType(OutlineNode.TYPE_PARAGRAPH, true);
        }

        // add floats to be included (and env type)
        filter.toggleType(OutlineNode.TYPE_ENVIRONMENT, true);
        filter.toggleType(OutlineNode.TYPE_LABEL, true);

        String[] environments = TexlipsePlugin.getPreferenceArray(TexlipseProperties.OUTLINE_ENVS);
        for (String env : environments) {
            filter.toggleEnvironment(env, true);
        }
    }

    /**
     * Fill the context menu.
     * 
     * @param the IMenuManager of the context menu
     */
    private void fillContextMenu(IMenuManager mgr) {
        mgr.add(outlineActions.get(ACTION_COPY));
        mgr.add(outlineActions.get(ACTION_CUT));
        mgr.add(outlineActions.get(ACTION_PASTE));
        mgr.add(new Separator());
        mgr.add(outlineActions.get(ACTION_DELETE));
    }

    private void resetToolbarButtons() {
        outlineActions.get(ACTION_HIDE_SEC).setChecked(!filter.isTypeVisible(OutlineNode.TYPE_SECTION));
        outlineActions.get(ACTION_HIDE_SUBSEC).setChecked(!filter.isTypeVisible(OutlineNode.TYPE_SUBSECTION));
        outlineActions.get(ACTION_HIDE_SUBSUBSEC).setChecked(!filter.isTypeVisible(OutlineNode.TYPE_SUBSUBSECTION));
        outlineActions.get(ACTION_HIDE_PARAGRAPH).setChecked(!filter.isTypeVisible(OutlineNode.TYPE_PARAGRAPH));
        outlineActions.get(ACTION_HIDE_FLOAT).setChecked(!filter.isTypeVisible(OutlineNode.TYPE_ENVIRONMENT));
        outlineActions.get(ACTION_HIDE_LABEL).setChecked(!filter.isTypeVisible(OutlineNode.TYPE_LABEL));
    }

    /**
     * Removes own SelectionChangeListener from TreeViewer and uses listener instead 
     * Needed for Full LaTeX Outline
     */
    public void switchTreeViewerSelectionChangeListener(ISelectionChangedListener listener) {
        getTreeViewer().removeSelectionChangedListener(this);
        getTreeViewer().addSelectionChangedListener(listener);
    }

    /**
     * Resets outline (needed for Full LaTeX outline)
     */
    public void reset() {
        this.expandLevel = 1;
    }

    /**
     * Create the toolbar.
     *
     */
    private void createToolbar() {

        // add actions to the toolbar
        IToolBarManager toolbarManager = getSite().getActionBars().getToolBarManager();
        toolbarManager.add(outlineActions.get(ACTION_UPDATE));
        toolbarManager.add(outlineActions.get(ACTION_COLLAPSE));
        toolbarManager.add(outlineActions.get(ACTION_EXPAND));
        toolbarManager.add(outlineActions.get(ACTION_HIDE_SEC));
        toolbarManager.add(outlineActions.get(ACTION_HIDE_SUBSEC));
        toolbarManager.add(outlineActions.get(ACTION_HIDE_SUBSUBSEC));
        toolbarManager.add(outlineActions.get(ACTION_HIDE_PARAGRAPH));
        toolbarManager.add(outlineActions.get(ACTION_HIDE_FLOAT));
        toolbarManager.add(outlineActions.get(ACTION_HIDE_LABEL));
    }

    /**
     * Creates the context menu. 
     */
    private void createContextMenu() {
        // create menu manager
        MenuManager menuMgr = new MenuManager();
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager mgr) {
                fillContextMenu(mgr);
            }
        });

        // create the menu
        Menu menu = menuMgr.createContextMenu(getTreeViewer().getControl());
        getTreeViewer().getControl().setMenu(menu);

        //register menu for extensions
        //getSite().registerContextMenu(menuMgr, getTreeViewer());
    }

    /**
     * Reveals all the nodes of certain type in the outline tree.
     * 
     * @param nodeType the type of nodes to be revealed
     */
    private void revealNodes(int nodeType) {
        List<OutlineNode> nodeList = input.getTypeList(nodeType);
        if (nodeList != null) {
            for (OutlineNode node : nodeList) {
                getTreeViewer().reveal(node);
            }
        }
    }

    /*
     private void restoreExpandState(OutlineNode newNode, ArrayList oldNodes, ArrayList newNodes) {
         
     // check this node
      OutlineNode oldNode;
      for(Iterator iter = oldNodes.iterator(); iter.hasNext();) {
      oldNode = (OutlineNode)iter.next();
      if (newNode.likelySame(oldNode)) {
      //System.out.println(newNode.getName() + " LIKE " + oldNode.getName());
       newNodes.add(newNode);
       iter.remove();
       }
       }
           
       // continue with children
    ArrayList children = newNode.getChildren();
    if (children != null) {
    for (Iterator iter = children.iterator(); iter.hasNext();){
    restoreExpandState((OutlineNode)iter.next(), oldNodes, newNodes);
    }
    }
    }
    */
}