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