/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.shared_ui.outline; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.ListResourceBundle; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.ui.progress.UIJob; import org.python.pydev.shared_core.log.Log; import org.python.pydev.shared_core.string.TextSelectionUtils; import org.python.pydev.shared_ui.EditorUtils; import org.python.pydev.shared_ui.ImageCache; import org.python.pydev.shared_ui.UIConstants; import org.python.pydev.shared_ui.editor.BaseEditor; import org.python.pydev.shared_ui.editor.IPyEditListener; import org.python.pydev.shared_ui.editor.IPyEditListener2; /** * This action keeps the outline synched with the text selected in the text * editor. * * Design notes: * It's linked on the constructor and unlinked in the destructor. * * It considers that it's always linked, even if the action is inactive, but before executing it, a * check is done to see if it's active or not. * * @author Fabio */ public class OutlineLinkWithEditorAction extends AbstractOutlineFilterAction implements IPyEditListener, IPyEditListener2 { private WeakReference<BaseEditor> pyEdit; public OutlineLinkWithEditorAction(BaseOutlinePage page, ImageCache imageCache, String pluginId) { super("Link With Editor", page, imageCache, pluginId + ".PREF_LINK_WITH_EDITOR", UIConstants.SYNC_WITH_EDITOR); pyEdit = new WeakReference<BaseEditor>(page.getEditor()); relink(); } /** * When called, it STOPS hearing notifications to update the outline when the cursor changes positions. */ public void unlink() { BaseEditor edit = pyEdit.get(); if (edit != null) { edit.removePyeditListener(this); } } /** * When called, it STARTS hearing notifications to update the outline when the cursor changes positions. */ public void relink() { BaseEditor edit = pyEdit.get(); if (edit != null) { edit.addPyeditListener(this); } } public void dispose() { unlink(); } @Override protected ViewerFilter createFilter() { throw new RuntimeException( "Not implemented: as setActionEnabled is overriden, this action is not needed (as this is not a filter action)."); } /** * Overridden to enable the linking with the editor instead of having a filter created. */ @Override protected void setActionEnabled(boolean enableAction) { BaseOutlinePage p = this.page.get(); if (p != null) { p.getStore().setValue(this.preference, enableAction); if (enableAction && pyEdit != null) { BaseEditor edit = pyEdit.get(); if (edit != null) { handleCursorPositionChanged(edit, EditorUtils.createTextSelectionUtils(edit)); } } } } @Override public void onCreateActions(ListResourceBundle resources, BaseEditor baseEditor, IProgressMonitor monitor) { } @Override public void onDispose(BaseEditor baseEditor, IProgressMonitor monitor) { } @Override public void onSave(BaseEditor baseEditor, IProgressMonitor monitor) { } @Override public void onSetDocument(IDocument document, BaseEditor baseEditor, IProgressMonitor monitor) { } /** * Hear mouse selections to update the selection in the outline */ @Override public void handleCursorPositionChanged(BaseEditor edit, TextSelectionUtils ps) { BaseOutlinePage p = this.page.get(); if (p != null && edit != null) { if (isChecked()) { doLinkOutlinePosition(edit, p, ps); } } } private static class UpdateSelection extends UIJob { private WeakReference<IOutlineModel> outlineModel; private final Object lock = new Object(); private WeakReference<BaseOutlinePage> outlinePage; private ITextSelection ts; public UpdateSelection() { super("Link outline selection"); } @Override public IStatus runInUIThread(IProgressMonitor monitor) { try { IOutlineModel model = null; IParsedItem parsedItem = null; BaseOutlinePage p = null; ITextSelection localTextSelection = ts; synchronized (lock) { if (outlineModel != null) { model = outlineModel.get(); } if (model != null) { parsedItem = model.getRoot(); } p = outlinePage.get(); } if (parsedItem == null || p == null || localTextSelection == null) { return null; } StructuredSelection sel = getSelectionPosition(parsedItem, localTextSelection); if (sel != null) { // we don't want to hear our own selections p.unlinkAll(); try { p.setSelection(sel); } finally { p.relinkAll(); } } } catch (Exception e) { Log.log(e); } return Status.OK_STATUS; } public void setOutline(IOutlineModel outlineModel, BaseOutlinePage p, ITextSelection ts) { synchronized (lock) { this.outlinePage = new WeakReference<BaseOutlinePage>(p); this.ts = ts; this.outlineModel = new WeakReference<IOutlineModel>(outlineModel); } } /** * Convert the text selection to a model node in the outline (parsed item tree path). */ private StructuredSelection getSelectionPosition(IParsedItem r, ITextSelection t) { try { ArrayList<IParsedItem> sel = new ArrayList<IParsedItem>(); if (r != null) { do { IParsedItem item = findSel(r, t.getStartLine() + 1); if (item != null) { sel.add(item); } r = item; } while (r != null); } TreePath treePath = null; if (sel != null && sel.size() > 0) { treePath = new TreePath(sel.toArray()); } if (treePath != null) { return new TreeSelection(treePath); } } catch (Exception e) { Log.log(e); } return null; } /** * @return The parsed item that should be selected given the startLine passed. */ private IParsedItem findSel(IParsedItem r, int startLine) { IParsedItem prev = null; IParsedItem[] children = r.getChildren(); if (children != null) { for (IParsedItem i : children) { int beginLine = i.getBeginLine(); if (beginLine >= 0) { if (beginLine == startLine) { prev = i; break; } if (beginLine > startLine) { break; } } prev = i; } } return prev; } }; private final UpdateSelection updateSelection = new UpdateSelection(); /** * Keeps the outline linked with the editor. */ protected void doLinkOutlinePosition(BaseEditor edit, BaseOutlinePage p, TextSelectionUtils ps) { IOutlineModel outlineModel = p.getOutlineModel(); if (outlineModel != null) { updateSelection.setOutline(outlineModel, p, ps.getTextSelection()); updateSelection.schedule(50); } } }