diff options
author | Tor Norbye <tnorbye@google.com> | 2012-03-14 17:55:16 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-03-15 09:08:44 -0700 |
commit | b9317238c40c76f9a31647021f232adba6da0979 (patch) | |
tree | ef90aa6fa9834b5e6090a463685a50bd69493fa9 /eclipse | |
parent | d93207c968119142f468be601f7f2c19a25ec890 (diff) | |
download | sdk-b9317238c40c76f9a31647021f232adba6da0979.zip sdk-b9317238c40c76f9a31647021f232adba6da0979.tar.gz sdk-b9317238c40c76f9a31647021f232adba6da0979.tar.bz2 |
Use XML outline in the layout editor when editing text
This changeset adds an XML outline in the layout XML editor when
editing text, and the graphical outline when the graphical layout
editor tab is shown. This is complicated because it's not really
supported in Eclipse:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=1917
See the long comment in LayoutEditorDelegate.delegateGetAdapter for an
explanation of the two tricks involved:
- Using a multiplexing outline implementation to switch outlines,
triggered by editor page switch notifications
- Using a selection listener to anticipate when the XML outline is
attempting to react to editor selection events and return the
non-multiplexing outline from getAdapter in that specific scenario
While I was at it I also made the outline customized to handle our
icons, similar to how the quick outline already handles it, and fixed
the quick outline to work with the new delegated editors.
Change-Id: I0b7ea8c8d8c09d2ecc1500689f9dda4b5db2b492
Diffstat (limited to 'eclipse')
11 files changed, 565 insertions, 68 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index e5e8229..f7fd525 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -622,7 +622,10 @@ <quickOutlineConfiguration class="com.android.ide.eclipse.adt.internal.editors.AndroidQuickOutlineConfiguration" - target="com.android.ide.eclipse.adt.internal.editors.CommonXmlEditor" /> + target="org.eclipse.core.runtime.xml" /> + <contentOutlineConfiguration + class="com.android.ide.eclipse.adt.internal.editors.AndroidOutlineConfiguration" + target="org.eclipse.core.runtime.xml" /> <provisionalConfiguration type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor" class="com.android.ide.eclipse.adt.internal.editors.formatting.XmlQuickAssistManager" diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidOutlineConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidOutlineConfiguration.java new file mode 100644 index 0000000..6061d92 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidOutlineConfiguration.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors; + + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.wst.xml.ui.views.contentoutline.XMLContentOutlineConfiguration; + +/** + * Custom version of {@link XMLContentOutlineConfiguration} which adds in icons and + * details such as id or name, to the labels. + */ +public class AndroidOutlineConfiguration extends XMLContentOutlineConfiguration { + /** Constructs a new {@link AndroidOutlineConfiguration} */ + public AndroidOutlineConfiguration() { + } + + @Override + public ILabelProvider getLabelProvider(TreeViewer viewer) { + return new OutlineLabelProvider(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidQuickOutlineConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidQuickOutlineConfiguration.java index 7b8b2ac..0a8e9dc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidQuickOutlineConfiguration.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidQuickOutlineConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,61 +16,20 @@ package com.android.ide.eclipse.adt.internal.editors; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; -import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; -import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME; - import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.swt.graphics.Image; -import org.eclipse.wst.xml.ui.internal.contentoutline.JFaceNodeLabelProvider; import org.eclipse.wst.xml.ui.internal.quickoutline.XMLQuickOutlineConfiguration; -import org.w3c.dom.Element; /** * Custom version of {@link XMLQuickOutlineConfiguration} which adds in icons and * details such as id or name, to the labels. */ -@SuppressWarnings("restriction") public class AndroidQuickOutlineConfiguration extends XMLQuickOutlineConfiguration { + /** Constructs a new {@link AndroidQuickOutlineConfiguration} */ public AndroidQuickOutlineConfiguration() { } @Override public ILabelProvider getLabelProvider() { - return new JFaceNodeLabelProvider() { - @Override - public Image getImage(Object element) { - if (element instanceof Element) { - Element e = (Element) element; - String tagName = e.getTagName(); - IconFactory factory = IconFactory.getInstance(); - Image img = factory.getIcon(tagName); - if (img != null) { - return img; - } - } - return super.getImage(element); - } - - @Override - public String getText(Object element) { - String text = super.getText(element); - if (element instanceof Element) { - Element e = (Element) element; - String id = e.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id == null || id.length() == 0) { - id = e.getAttributeNS(ANDROID_URI, ATTR_NAME); - } - if (id == null || id.length() == 0) { - id = e.getAttribute(ATTR_NAME); - } - if (id != null && id.length() > 0) { - return text + ": " + id; //$NON-NLS-1$ - } - } - return text; - } - }; + return new OutlineLabelProvider(); } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java index 7c57549..58e156e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java @@ -76,6 +76,7 @@ import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport; import org.eclipse.ui.part.MultiPageEditorPart; import org.eclipse.ui.part.WorkbenchPart; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IModelManager; import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener; @@ -273,6 +274,10 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh }; } + if (result == null && adapter == IContentOutlinePage.class) { + return getStructuredTextEditor().getAdapter(adapter); + } + return result; } @@ -416,7 +421,6 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh } } - /** * Notifies this multi-page editor that the page with the given id has been * activated. This method is called when the user selects a different tab. @@ -445,6 +449,15 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh } /** + * Returns true if the active page is the editor page + * + * @return true if the active page is the editor page + */ + public boolean isEditorPageActive() { + return getActivePage() == mTextPageIndex; + } + + /** * Notifies this listener that some resource changes * are happening, or have already happened. * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java index ca13d38..22aa687 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java @@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors; import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.sdklib.SdkConstants; @@ -181,10 +182,13 @@ public class IconFactory { * in the editor's "icons" directory. If it doesn't exist, the * fallback will be used instead. * @param fallback the fallback icon name to use if the primary icon does - * not exist. - * @return the icon, which should not be disposed by the caller + * not exist, or null if the method should return null if the + * image does not exist + * @return the icon, which should not be disposed by the caller, or null + * if the image does not exist *and* */ - public Image getIcon(String osName, String fallback) { + @Nullable + public Image getIcon(@NonNull String osName, @Nullable String fallback) { String key = osName; Image icon = mIconMap.get(key); if (icon == null && !mIconMap.containsKey(key)) { @@ -200,21 +204,26 @@ public class IconFactory { } /** - * Returns an icon of the given name, or if that image does not exist and icon - * of the given fallback name. + * Returns an icon of the given name, or if that image does not exist and + * icon of the given fallback name. * * @param key the icon name - * @param fallbackKey the fallback image to use if the primary key does not exist - * @return the image descriptor + * @param fallbackKey the fallback image to use if the primary key does not + * exist + * @return the image descriptor, or null if the image does not exist and the + * fallbackKey is null */ - @NonNull - public ImageDescriptor getImageDescriptor(@NonNull String key, @NonNull String fallbackKey) { + @Nullable + public ImageDescriptor getImageDescriptor(@NonNull String key, @Nullable String fallbackKey) { ImageDescriptor id = mImageDescMap.get(key); if (id == null && !mImageDescMap.containsKey(key)) { id = AbstractUIPlugin.imageDescriptorFromPlugin( AdtPlugin.PLUGIN_ID, String.format("/icons/%1$s.png", key)); //$NON-NLS-1$ if (id == null) { + if (fallbackKey == null) { + return null; + } id = getImageDescriptor(fallbackKey); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java new file mode 100644 index 0000000..ea3f066 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.editors; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME; +import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; +import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX; + +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.wst.xml.ui.internal.contentoutline.JFaceNodeLabelProvider; +import org.w3c.dom.Element; + +/** Label provider for the XML outlines and quick outlines: Use our own icons, + * when available, and and include the most important attribute (id, name, or text) */ +@SuppressWarnings("restriction") // XML UI API +class OutlineLabelProvider extends JFaceNodeLabelProvider { + @Override + public Image getImage(Object element) { + if (element instanceof Element) { + Element e = (Element) element; + String tagName = e.getTagName(); + IconFactory factory = IconFactory.getInstance(); + Image img = factory.getIcon(tagName, null); + if (img != null) { + return img; + } + } + return super.getImage(element); + } + + @Override + public String getText(Object element) { + String text = super.getText(element); + if (element instanceof Element) { + Element e = (Element) element; + String id = e.getAttributeNS(ANDROID_URI, ATTR_ID); + if (id == null || id.length() == 0) { + id = e.getAttributeNS(ANDROID_URI, ATTR_NAME); + if (id == null || id.length() == 0) { + id = e.getAttribute(ATTR_NAME); + if (id == null || id.length() == 0) { + id = e.getAttributeNS(ANDROID_URI, ATTR_TEXT); + if (id != null && id.length() > 15) { + id = id.substring(0, 12) + "..."; + } + if (id == null || id.length() == 0) { + id = e.getAttributeNS(ANDROID_URI, ATTR_SRC); + if (id != null && id.length() > 0) { + if (id.startsWith(DRAWABLE_PREFIX)) { + id = id.substring(DRAWABLE_PREFIX.length()); + } + } else { + id = e.getAttribute(LayoutDescriptors.ATTR_LAYOUT); + if (id != null && id.length() > 0) { + if (id.startsWith(LAYOUT_PREFIX)) { + id = id.substring(LAYOUT_PREFIX.length()); + } + } + } + } + } + } + } + + if (id != null && id.length() > 0) { + return text + ": " + id; //$NON-NLS-1$ + } + } + return text; + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java new file mode 100644 index 0000000..4ccab2d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors; + +import com.android.ide.eclipse.adt.AdtPlugin; + +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IStatusLineManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.IPageBookViewPage; +import org.eclipse.ui.part.Page; +import org.eclipse.ui.part.PageBook; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Outline used for XML editors that have multiple pages with separate outlines: + * switches between them + * <p> + * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=1917 + * <p> + * Modeled after .org.eclipse.pde.internal.ui.editor.PDEMultiPageContentOutline + */ +public class XmlEditorMultiOutline extends Page implements IContentOutlinePage, + ISelectionChangedListener { + private boolean mDisposed; + private PageBook mPageBook; + private IContentOutlinePage mCurrentPage; + private IActionBars mActionBars; + private IContentOutlinePage mEmptyPage; + private List<ISelectionChangedListener> mListeners; + private ISelection mSelection; + + public XmlEditorMultiOutline() { + } + + @Override + public Control getControl() { + return mPageBook; + } + + @Override + public void createControl(Composite parent) { + mPageBook = new PageBook(parent, SWT.NONE); + } + + @Override + public void dispose() { + mDisposed = true; + mListeners = null; + if (mPageBook != null && !mPageBook.isDisposed()) { + mPageBook.dispose(); + mPageBook = null; + } + if (mEmptyPage != null) { + mEmptyPage.dispose(); + mEmptyPage = null; + } + } + + public boolean isDisposed() { + return mDisposed; + } + + @Override + public void makeContributions(IMenuManager menuManager, IToolBarManager toolBarManager, + IStatusLineManager statusLineManager) { + } + + @Override + public void setActionBars(IActionBars actionBars) { + mActionBars = actionBars; + if (mCurrentPage != null) { + setPageActive(mCurrentPage); + } + } + + @Override + public void setFocus() { + if (mCurrentPage != null) { + mCurrentPage.setFocus(); + } + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if (mListeners == null) { + mListeners = new ArrayList<ISelectionChangedListener>(); + } + mListeners.add(listener); + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + mListeners.remove(listener); + } + + @Override + public ISelection getSelection() { + return mSelection; + } + + @Override + public void selectionChanged(SelectionChangedEvent event) { + setSelection(event.getSelection()); + } + + public void setPageActive(IContentOutlinePage page) { + if (page == null) { + if (mEmptyPage == null) { + mEmptyPage = new EmptyPage(); + } + page = mEmptyPage; + } + if (mCurrentPage != null) { + mCurrentPage.removeSelectionChangedListener(this); + } + page.addSelectionChangedListener(this); + mCurrentPage = page; + // Still initializing? + if (mPageBook == null) { + return; + } + Control control = page.getControl(); + if (control == null || control.isDisposed()) { + if (page instanceof IPageBookViewPage) { + try { + ((IPageBookViewPage) page).init(getSite()); + } catch (PartInitException e) { + AdtPlugin.log(e, null); + } + } + page.createControl(mPageBook); + page.setActionBars(mActionBars); + control = page.getControl(); + } + mPageBook.showPage(control); + } + + @Override + public void setSelection(ISelection selection) { + mSelection = selection; + if (mListeners != null) { + SelectionChangedEvent e = new SelectionChangedEvent(this, selection); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).selectionChanged(e); + } + } + } + + private static class EmptyPage implements IContentOutlinePage { + private Composite mControl; + + private EmptyPage() { + } + + @Override + public void createControl(Composite parent) { + mControl = new Composite(parent, SWT.NULL); + } + + @Override + public void dispose() { + } + + @Override + public Control getControl() { + return mControl; + } + + @Override + public void setActionBars(IActionBars actionBars) { + } + + @Override + public void setFocus() { + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + } + + @Override + public ISelection getSelection() { + return StructuredSelection.EMPTY; + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + } + + @Override + public void setSelection(ISelection selection) { + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java index a664800..08bdcf9 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java @@ -29,8 +29,10 @@ import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IURIEditorInput; +import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.part.EditorActionBarContributor; import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.part.MultiPageEditorPart; import org.w3c.dom.Document; /** @@ -201,4 +203,18 @@ public abstract class CommonXmlDelegate { public boolean delegateSupportsFormatOnGuiEdit() { return false; } + + /** + * Called after the editor's active page has been set. + * + * @param superReturned the return value from + * {@link MultiPageEditorPart#setActivePage(int)} + * @param pageIndex the index of the page to be activated; the index must be + * valid + * @return the page, or null + * @see MultiPageEditorPart#setActivePage(int) + */ + public IFormPage delegatePostSetActivePage(IFormPage superReturned, String pageIndex) { + return superReturned; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java index 96ce82b..68c7807 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java @@ -47,6 +47,7 @@ import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IShowEditorInput; import org.eclipse.ui.IURIEditorInput; import org.eclipse.ui.PartInitException; +import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.ide.IDE; import org.w3c.dom.Document; @@ -372,6 +373,17 @@ public class CommonXmlEditor extends AndroidXmlEditor implements IShowEditorInpu } } + @Override + public IFormPage setActivePage(String pageId) { + IFormPage page = super.setActivePage(pageId); + + if (mDelegate != null) { + return mDelegate.delegatePostSetActivePage(page, pageId); + } + + return page; + } + /* Implements showEditorInput(...) in IShowEditorInput */ @Override public void showEditorInput(IEditorInput editorInput) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java index 9c7189b..df39a3d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java @@ -16,11 +16,13 @@ package com.android.ide.eclipse.adt.internal.editors.layout; +import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.annotations.VisibleForTesting; import com.android.annotations.VisibleForTesting.Visibility; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.XmlEditorMultiOutline; import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate; import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; @@ -51,19 +53,25 @@ import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPartListener; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.ISelectionService; import org.eclipse.ui.IShowEditorInput; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; +import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.ui.views.properties.IPropertySheetPage; +import org.eclipse.wst.sse.ui.StructuredTextEditor; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -103,8 +111,24 @@ public class LayoutEditorDelegate extends CommonXmlDelegate private GraphicalEditorPart mGraphicalEditor; private int mGraphicalEditorIndex; + /** Implementation of the {@link IContentOutlinePage} for this editor */ - private IContentOutlinePage mOutline; + private OutlinePage mLayoutOutline; + + /** The XML editor outline */ + private IContentOutlinePage mEditorOutline; + + /** Multiplexing outline, used for multi-page editors that have their own outline */ + private XmlEditorMultiOutline mMultiOutline; + + /** + * Temporary flag set by the editor caret listener which is used to cause + * the next getAdapter(IContentOutlinePage.class) call to return the editor + * outline rather than the multi-outline. See the {@link #delegateGetAdapter} + * method for details. + */ + private boolean mCheckOutlineAdapter; + /** Custom implementation of {@link IPropertySheetPage} for this editor */ private IPropertySheetPage mPropertyPage; @@ -402,16 +426,60 @@ public class LayoutEditorDelegate extends CommonXmlDelegate */ @Override public Object delegateGetAdapter(Class<?> adapter) { - // For the outline, force it to come from the Graphical Editor. - // This fixes the case where a layout file is opened in XML view first and the outline - // gets stuck in the XML outline. - if (IContentOutlinePage.class == adapter && mGraphicalEditor != null) { + if (adapter == IContentOutlinePage.class) { + // Somebody has requested the outline. Eclipse can only have a single outline page, + // even for a multi-part editor: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=1917 + // To work around this we use PDE's workaround of having a single multiplexing + // outline which switches its contents between the outline pages we register + // for it, and then on page switch we notify it to update itself. + + // There is one complication: The XML editor outline listens for the editor + // selection and uses this to automatically expand its tree children and show + // the current node containing the caret as selected. Unfortunately, this + // listener code contains this: + // + // /* Bug 136310, unless this page is that part's + // * IContentOutlinePage, ignore the selection change */ + // if (part.getAdapter(IContentOutlinePage.class) == this) { + // + // This means that when we return the multiplexing outline from this getAdapter + // method, the outline no longer updates to track the selection. + // To work around this, we use the following hack^H^H^H^H technique: + // - Add a selection listener *before* requesting the editor outline, such + // that the selection listener is told about the impending selection event + // right before the editor outline hears about it. Set the flag + // mCheckOutlineAdapter to true. (We also only set it if the editor view + // itself is active.) + // - In this getAdapter method, when somebody requests the IContentOutline.class, + // see if mCheckOutlineAdapter to see if this request is *likely* coming + // from the XML editor outline. If so, make sure it is by actually looking + // at the signature of the caller. If it's the editor outline, then return + // the editor outline instance itself rather than the multiplexing outline. + if (mCheckOutlineAdapter && mEditorOutline != null) { + mCheckOutlineAdapter = false; + // Make *sure* this is really the editor outline calling in case + // future versions of Eclipse changes the sequencing or dispatch of selection + // events: + StackTraceElement[] frames = new Throwable().fillInStackTrace().getStackTrace(); + if (frames.length > 2) { + StackTraceElement frame = frames[2]; + if (frame.getClassName().equals( + "org.eclipse.wst.sse.ui.internal.contentoutline." + //$NON-NLS-1$ + "ConfigurableContentOutlinePage$PostSelectionServiceListener")) { //$NON-NLS-1$ + return mEditorOutline; + } + } + } - if (mOutline == null && mGraphicalEditor != null) { - mOutline = new OutlinePage(mGraphicalEditor); + // Use a multiplexing outline: workaround for + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=1917 + if (mMultiOutline == null || mMultiOutline.isDisposed()) { + mMultiOutline = new XmlEditorMultiOutline(); + updateOutline(getEditor().getActivePageInstance()); } - return mOutline; + return mMultiOutline; } if (IPropertySheetPage.class == adapter && mGraphicalEditor != null) { @@ -426,6 +494,60 @@ public class LayoutEditorDelegate extends CommonXmlDelegate return super.delegateGetAdapter(adapter); } + /** + * Update the contents of the outline to show either the XML editor outline + * or the layout editor graphical outline depending on which tab is visible + */ + private void updateOutline(IFormPage page) { + if (mMultiOutline == null) { + return; + } + + IContentOutlinePage outline; + CommonXmlEditor editor = getEditor(); + if (!editor.isEditorPageActive()) { + outline = getGraphicalOutline(); + } else { + // Use plain XML editor outline instead + if (mEditorOutline == null) { + StructuredTextEditor structuredTextEditor = editor.getStructuredTextEditor(); + if (structuredTextEditor != null) { + IWorkbenchWindow window = editor.getSite().getWorkbenchWindow(); + ISelectionService service = window.getSelectionService(); + service.addPostSelectionListener(new ISelectionListener() { + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + if (getEditor().isEditorPageActive()) { + mCheckOutlineAdapter = true; + } + } + }); + + mEditorOutline = (IContentOutlinePage) structuredTextEditor.getAdapter( + IContentOutlinePage.class); + } + } + + outline = mEditorOutline; + } + + mMultiOutline.setPageActive(outline); + } + + /** + * Returns the graphical outline associated with the layout editor + * + * @return the outline page, never null + */ + @NonNull + public OutlinePage getGraphicalOutline() { + if (mLayoutOutline == null) { + mLayoutOutline = new OutlinePage(mGraphicalEditor); + } + + return mLayoutOutline; + } + @Override public void delegatePageChange(int newPageIndex) { if (getEditor().getCurrentPage() == getEditor().getTextPageIndex() && @@ -454,6 +576,24 @@ public class LayoutEditorDelegate extends CommonXmlDelegate } } + @Override + public void delegatePostPageChange(int newPageIndex) { + super.delegatePostPageChange(newPageIndex); + + IFormPage page = getEditor().getActivePageInstance(); + updateOutline(page); + } + + @Override + public IFormPage delegatePostSetActivePage(IFormPage superReturned, String pageIndex) { + IFormPage page = superReturned; + if (page != null) { + updateOutline(page); + } + + return page; + } + // ----- IActionContributorDelegate methods ---- @Override diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index e45803a..8913850 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -91,7 +91,6 @@ import org.eclipse.ui.actions.ContributionItemFactory; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; import org.eclipse.ui.texteditor.ITextEditor; -import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.w3c.dom.Node; import java.util.HashSet; @@ -305,10 +304,8 @@ public class LayoutCanvas extends Canvas { // --- setup outline --- // Get the outline associated with this editor, if any and of the right type. - Object outline = editorDelegate == null ? null : - editorDelegate.delegateGetAdapter(IContentOutlinePage.class); - if (outline instanceof OutlinePage) { - mOutlinePage = (OutlinePage) outline; + if (editorDelegate != null) { + mOutlinePage = editorDelegate.getGraphicalOutline(); } } |