diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java | 1709 |
1 files changed, 0 insertions, 1709 deletions
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 deleted file mode 100644 index 1d4e133b6..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java +++ /dev/null @@ -1,1709 +0,0 @@ -/* - * Copyright (C) 2007 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 org.eclipse.wst.sse.ui.internal.actions.StructuredTextEditorActionConstants.ACTION_NAME_FORMAT_DOCUMENT; - -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceXmlTextAction; -import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; -import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener; -import com.android.sdklib.IAndroidTarget; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.dialogs.ErrorDialog; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorReference; -import org.eclipse.ui.IEditorSite; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IURIEditorInput; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.browser.IWorkbenchBrowserSupport; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormEditor; -import org.eclipse.ui.forms.editor.IFormPage; -import org.eclipse.ui.forms.events.HyperlinkAdapter; -import org.eclipse.ui.forms.events.HyperlinkEvent; -import org.eclipse.ui.forms.events.IHyperlinkListener; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.ide.IDEActionFactory; -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; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.sse.ui.StructuredTextEditor; -import org.eclipse.wst.sse.ui.internal.StructuredTextViewer; -import org.eclipse.wst.xml.core.internal.document.NodeContainer; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; - -/** - * Multi-page form editor for Android XML files. - * <p/> - * It is designed to work with a {@link StructuredTextEditor} that will display an XML file. - * <br/> - * Derived classes must implement createFormPages to create the forms before the - * source editor. This can be a no-op if desired. - */ -@SuppressWarnings("restriction") // Uses XML model, which has no non-restricted replacement yet -public abstract class AndroidXmlEditor extends FormEditor { - - /** Icon used for the XML source page. */ - public static final String ICON_XML_PAGE = "editor_page_source"; //$NON-NLS-1$ - - /** Preference name for the current page of this file */ - private static final String PREF_CURRENT_PAGE = "_current_page"; //$NON-NLS-1$ - - /** Id string used to create the Android SDK browser */ - private static String BROWSER_ID = "android"; //$NON-NLS-1$ - - /** Page id of the XML source editor, used for switching tabs programmatically */ - public final static String TEXT_EDITOR_ID = "editor_part"; //$NON-NLS-1$ - - /** Width hint for text fields. Helps the grid layout resize properly on smaller screens */ - public static final int TEXT_WIDTH_HINT = 50; - - /** Page index of the text editor (always the last page) */ - protected int mTextPageIndex; - /** The text editor */ - private StructuredTextEditor mTextEditor; - /** Listener for the XML model from the StructuredEditor */ - private XmlModelStateListener mXmlModelStateListener; - /** Listener to update the root node if the target of the file is changed because of a - * SDK location change or a project target change */ - private TargetChangeListener mTargetListener = null; - - /** flag set during page creation */ - private boolean mIsCreatingPage = false; - - /** - * Flag used to ignore XML model updates. For example, the flag is set during - * formatting. A format operation should completely preserve the semantics of the XML - * so the document listeners can use this flag to skip updating the model when edits - * are observed during a formatting operation - */ - private boolean mIgnoreXmlUpdate; - - /** - * Flag indicating we're inside {@link #wrapEditXmlModel(Runnable)}. - * This is a counter, which allows us to nest the edit XML calls. - * There is no pending operation when the counter is at zero. - */ - private int mIsEditXmlModelPending; - - /** - * Usually null, but during an editing operation, represents the highest - * node which should be formatted when the editing operation is complete. - */ - private UiElementNode mFormatNode; - - /** - * Whether {@link #mFormatNode} should be formatted recursively, or just - * the node itself (its arguments) - */ - private boolean mFormatChildren; - - /** - * Creates a form editor. - * <p/> - * Some derived classes will want to use {@link #addDefaultTargetListener()} - * to setup the default listener to monitor SDK target changes. This - * is no longer the default. - */ - public AndroidXmlEditor() { - super(); - } - - @Override - public void init(IEditorSite site, IEditorInput input) throws PartInitException { - super.init(site, input); - // Trigger a check to see if the SDK needs to be reloaded (which will - // invoke onSdkLoaded or ITargetChangeListener asynchronously as needed). - AdtPlugin.getDefault().refreshSdk(); - } - - /** - * Setups a default {@link ITargetChangeListener} that will call - * {@link #initUiRootNode(boolean)} when the SDK or the target changes. - */ - public void addDefaultTargetListener() { - if (mTargetListener == null) { - mTargetListener = new TargetChangeListener() { - @Override - public IProject getProject() { - return AndroidXmlEditor.this.getProject(); - } - - @Override - public void reload() { - commitPages(false /* onSave */); - - // recreate the ui root node always - initUiRootNode(true /*force*/); - } - }; - AdtPlugin.getDefault().addTargetListener(mTargetListener); - } - } - - // ---- Abstract Methods ---- - - /** - * Returns the root node of the UI element hierarchy manipulated by the current - * UI node editor. - */ - abstract public UiElementNode getUiRootNode(); - - /** - * Creates the various form pages. - * <p/> - * Derived classes must implement this to add their own specific tabs. - */ - abstract protected void createFormPages(); - - /** - * Called by the base class {@link AndroidXmlEditor} once all pages (custom form pages - * as well as text editor page) have been created. This give a chance to deriving - * classes to adjust behavior once the text page has been created. - */ - protected void postCreatePages() { - // Nothing in the base class. - } - - /** - * Creates the initial UI Root Node, including the known mandatory elements. - * @param force if true, a new UiManifestNode is recreated even if it already exists. - */ - abstract protected void initUiRootNode(boolean force); - - /** - * Subclasses should override this method to process the new XML Model, which XML - * root node is given. - * - * The base implementation is empty. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - abstract protected void xmlModelChanged(Document xml_doc); - - /** - * Controls whether XML models are ignored or not. - * - * @param ignore when true, ignore all subsequent XML model updates, when false start - * processing XML model updates again - */ - public void setIgnoreXmlUpdate(boolean ignore) { - mIgnoreXmlUpdate = ignore; - } - - /** - * Returns whether XML model events are ignored or not. This is the case - * when we are deliberately modifying the document in a way which does not - * change the semantics (such as formatting), or when we have already - * directly updated the model ourselves. - * - * @return true if XML events should be ignored - */ - public boolean getIgnoreXmlUpdate() { - return mIgnoreXmlUpdate; - } - - // ---- Base Class Overrides, Interfaces Implemented ---- - - @Override - public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) { - Object result = super.getAdapter(adapter); - - if (result != null && adapter.equals(IGotoMarker.class) ) { - final IGotoMarker gotoMarker = (IGotoMarker) result; - return new IGotoMarker() { - @Override - public void gotoMarker(IMarker marker) { - gotoMarker.gotoMarker(marker); - try { - // Lint markers should always jump to XML text - if (marker.getType().equals(AdtConstants.MARKER_LINT)) { - IEditorPart editor = AdtUtils.getActiveEditor(); - if (editor instanceof AndroidXmlEditor) { - AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor; - xmlEditor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - } - }; - } - - if (result == null && adapter == IContentOutlinePage.class) { - return getStructuredTextEditor().getAdapter(adapter); - } - - return result; - } - - /** - * Creates the pages of the multi-page editor. - */ - @Override - protected void addPages() { - createAndroidPages(); - selectDefaultPage(null /* defaultPageId */); - } - - /** - * Creates the page for the Android Editors - */ - public void createAndroidPages() { - mIsCreatingPage = true; - createFormPages(); - createTextEditor(); - updateActionBindings(); - postCreatePages(); - mIsCreatingPage = false; - } - - /** - * Returns whether the editor is currently creating its pages. - */ - public boolean isCreatingPages() { - return mIsCreatingPage; - } - - /** - * {@inheritDoc} - * <p/> - * If the page is an instance of {@link IPageImageProvider}, the image returned by - * by {@link IPageImageProvider#getPageImage()} will be set on the page's tab. - */ - @Override - public int addPage(IFormPage page) throws PartInitException { - int index = super.addPage(page); - if (page instanceof IPageImageProvider) { - setPageImage(index, ((IPageImageProvider) page).getPageImage()); - } - return index; - } - - /** - * {@inheritDoc} - * <p/> - * If the editor is an instance of {@link IPageImageProvider}, the image returned by - * by {@link IPageImageProvider#getPageImage()} will be set on the page's tab. - */ - @Override - public int addPage(IEditorPart editor, IEditorInput input) throws PartInitException { - int index = super.addPage(editor, input); - if (editor instanceof IPageImageProvider) { - setPageImage(index, ((IPageImageProvider) editor).getPageImage()); - } - return index; - } - - /** - * Creates undo redo (etc) actions for the editor site (so that it works for any page of this - * multi-page editor) by re-using the actions defined by the {@link StructuredTextEditor} - * (aka the XML text editor.) - */ - protected void updateActionBindings() { - IActionBars bars = getEditorSite().getActionBars(); - if (bars != null) { - IAction action = mTextEditor.getAction(ActionFactory.UNDO.getId()); - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), action); - - action = mTextEditor.getAction(ActionFactory.REDO.getId()); - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), action); - - bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), - mTextEditor.getAction(ActionFactory.DELETE.getId())); - bars.setGlobalActionHandler(ActionFactory.CUT.getId(), - mTextEditor.getAction(ActionFactory.CUT.getId())); - bars.setGlobalActionHandler(ActionFactory.COPY.getId(), - mTextEditor.getAction(ActionFactory.COPY.getId())); - bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), - mTextEditor.getAction(ActionFactory.PASTE.getId())); - bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), - mTextEditor.getAction(ActionFactory.SELECT_ALL.getId())); - bars.setGlobalActionHandler(ActionFactory.FIND.getId(), - mTextEditor.getAction(ActionFactory.FIND.getId())); - bars.setGlobalActionHandler(IDEActionFactory.BOOKMARK.getId(), - mTextEditor.getAction(IDEActionFactory.BOOKMARK.getId())); - - bars.updateActionBars(); - } - } - - /** - * Clears the action bindings for the editor site. - */ - protected void clearActionBindings(boolean includeUndoRedo) { - IActionBars bars = getEditorSite().getActionBars(); - if (bars != null) { - // For some reason, undo/redo doesn't seem to work in the form editor. - // This appears to be the case for pure Eclipse form editors too, e.g. see - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=68423 - // However, as a workaround we can use the *text* editor's underlying undo - // to revert operations being done in the UI, and the form automatically updates. - // Therefore, to work around this, we simply leave the text editor bindings - // in place if {@code includeUndoRedo} is not set - if (includeUndoRedo) { - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), null); - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), null); - } - bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), null); - bars.setGlobalActionHandler(ActionFactory.CUT.getId(), null); - bars.setGlobalActionHandler(ActionFactory.COPY.getId(), null); - bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), null); - bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), null); - bars.setGlobalActionHandler(ActionFactory.FIND.getId(), null); - bars.setGlobalActionHandler(IDEActionFactory.BOOKMARK.getId(), null); - - bars.updateActionBars(); - } - } - - /** - * Selects the default active page. - * @param defaultPageId the id of the page to show. If <code>null</code> the editor attempts to - * find the default page in the properties of the {@link IResource} object being edited. - */ - public void selectDefaultPage(String defaultPageId) { - if (defaultPageId == null) { - IFile file = getInputFile(); - if (file != null) { - QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, - getClass().getSimpleName() + PREF_CURRENT_PAGE); - String pageId; - try { - pageId = file.getPersistentProperty(qname); - if (pageId != null) { - defaultPageId = pageId; - } - } catch (CoreException e) { - // ignored - } - } - } - - if (defaultPageId != null) { - try { - setActivePage(Integer.parseInt(defaultPageId)); - } catch (Exception e) { - // We can get NumberFormatException from parseInt but also - // AssertionError from setActivePage when the index is out of bounds. - // Generally speaking we just want to ignore any exception and fall back on the - // first page rather than crash the editor load. Logging the error is enough. - AdtPlugin.log(e, "Selecting page '%s' in AndroidXmlEditor failed", defaultPageId); - } - } else if (AdtPrefs.getPrefs().isXmlEditorPreferred(getPersistenceCategory())) { - setActivePage(mTextPageIndex); - } - } - - /** The layout editor */ - public static final int CATEGORY_LAYOUT = 1 << 0; - /** The manifest editor */ - public static final int CATEGORY_MANIFEST = 1 << 1; - /** Any other XML editor */ - public static final int CATEGORY_OTHER = 1 << 2; - - /** - * Returns the persistence category to use for this editor; this should be - * one of the {@code CATEGORY_} constants such as {@link #CATEGORY_MANIFEST}, - * {@link #CATEGORY_LAYOUT}, {@link #CATEGORY_OTHER}, ... - * <p> - * The persistence category is used to group editors together when it comes - * to certain types of persistence metadata. For example, whether this type - * of file was most recently edited graphically or with an XML text editor. - * We'll open new files in the same text or graphical mode as the last time - * the user edited a file of the same persistence category. - * <p> - * Before we added the persistence category, we had a single boolean flag - * recording whether the XML files were most recently edited graphically or - * not. However, this meant that users can't for example prefer to edit - * Manifest files graphically and string files via XML. By splitting the - * editors up into categories, we can track the mode at a finer granularity, - * and still allow similar editors such as those used for animations and - * colors to be treated the same way. - * - * @return the persistence category constant - */ - protected int getPersistenceCategory() { - return CATEGORY_OTHER; - } - - /** - * Removes all the pages from the editor. - */ - protected void removePages() { - int count = getPageCount(); - for (int i = count - 1 ; i >= 0 ; i--) { - removePage(i); - } - } - - /** - * Overrides the parent's setActivePage to be able to switch to the xml editor. - * - * If the special pageId TEXT_EDITOR_ID is given, switches to the mTextPageIndex page. - * This is needed because the editor doesn't actually derive from IFormPage and thus - * doesn't have the get-by-page-id method. In this case, the method returns null since - * IEditorPart does not implement IFormPage. - */ - @Override - public IFormPage setActivePage(String pageId) { - if (pageId.equals(TEXT_EDITOR_ID)) { - super.setActivePage(mTextPageIndex); - return null; - } else { - return super.setActivePage(pageId); - } - } - - /** - * 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. - * - * @see MultiPageEditorPart#pageChange(int) - */ - @Override - protected void pageChange(int newPageIndex) { - super.pageChange(newPageIndex); - - // Do not record page changes during creation of pages - if (mIsCreatingPage) { - return; - } - - IFile file = getInputFile(); - if (file != null) { - QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, - getClass().getSimpleName() + PREF_CURRENT_PAGE); - try { - file.setPersistentProperty(qname, Integer.toString(newPageIndex)); - } catch (CoreException e) { - // ignore - } - } - - boolean isTextPage = newPageIndex == mTextPageIndex; - AdtPrefs.getPrefs().setXmlEditorPreferred(getPersistenceCategory(), isTextPage); - } - - /** - * 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; - } - - /** - * Returns the {@link IFile} matching the editor's input or null. - */ - @Nullable - public IFile getInputFile() { - IEditorInput input = getEditorInput(); - if (input instanceof IFileEditorInput) { - return ((IFileEditorInput) input).getFile(); - } - return null; - } - - /** - * Removes attached listeners. - * - * @see WorkbenchPart - */ - @Override - public void dispose() { - IStructuredModel xml_model = getModelForRead(); - if (xml_model != null) { - try { - if (mXmlModelStateListener != null) { - xml_model.removeModelStateListener(mXmlModelStateListener); - } - - } finally { - xml_model.releaseFromRead(); - } - } - - if (mTargetListener != null) { - AdtPlugin.getDefault().removeTargetListener(mTargetListener); - mTargetListener = null; - } - - super.dispose(); - } - - /** - * Commit all dirty pages then saves the contents of the text editor. - * <p/> - * This works by committing all data to the XML model and then - * asking the Structured XML Editor to save the XML. - * - * @see IEditorPart - */ - @Override - public void doSave(IProgressMonitor monitor) { - commitPages(true /* onSave */); - - if (AdtPrefs.getPrefs().isFormatOnSave()) { - IAction action = mTextEditor.getAction(ACTION_NAME_FORMAT_DOCUMENT); - if (action != null) { - try { - mIgnoreXmlUpdate = true; - action.run(); - } finally { - mIgnoreXmlUpdate = false; - } - } - } - - // The actual "save" operation is done by the Structured XML Editor - getEditor(mTextPageIndex).doSave(monitor); - - // Check for errors on save, if enabled - if (AdtPrefs.getPrefs().isLintOnSave()) { - runLint(); - } - } - - /** - * Tells the editor to start a Lint check. - * It's up to the caller to check whether this should be done depending on preferences. - * <p/> - * The default implementation is to call {@link #startLintJob()}. - * - * @return The Job started by {@link EclipseLintRunner} or null if no job was started. - */ - protected Job runLint() { - return startLintJob(); - } - - /** - * Utility method that creates a Job to run Lint on the current document. - * Does not wait for the job to finish - just returns immediately. - * - * @return a new job, or null - * @see EclipseLintRunner#startLint(java.util.List, IResource, IDocument, - * boolean, boolean) - */ - @Nullable - public Job startLintJob() { - IFile file = getInputFile(); - if (file != null) { - return EclipseLintRunner.startLint(Collections.singletonList(file), file, - getStructuredDocument(), false /*fatalOnly*/, false /*show*/); - } - - return null; - } - - /* (non-Javadoc) - * Saves the contents of this editor to another object. - * <p> - * Subclasses must override this method to implement the open-save-close lifecycle - * for an editor. For greater details, see <code>IEditorPart</code> - * </p> - * - * @see IEditorPart - */ - @Override - public void doSaveAs() { - commitPages(true /* onSave */); - - IEditorPart editor = getEditor(mTextPageIndex); - editor.doSaveAs(); - setPageText(mTextPageIndex, editor.getTitle()); - setInput(editor.getEditorInput()); - } - - /** - * Commits all dirty pages in the editor. This method should - * be called as a first step of a 'save' operation. - * <p/> - * This is the same implementation as in {@link FormEditor} - * except it fixes two bugs: a cast to IFormPage is done - * from page.get(i) <em>before</em> being tested with instanceof. - * Another bug is that the last page might be a null pointer. - * <p/> - * The incorrect casting makes the original implementation crash due - * to our {@link StructuredTextEditor} not being an {@link IFormPage} - * so we have to override and duplicate to fix it. - * - * @param onSave <code>true</code> if commit is performed as part - * of the 'save' operation, <code>false</code> otherwise. - * @since 3.3 - */ - @Override - public void commitPages(boolean onSave) { - if (pages != null) { - for (int i = 0; i < pages.size(); i++) { - Object page = pages.get(i); - if (page != null && page instanceof IFormPage) { - IFormPage form_page = (IFormPage) page; - IManagedForm managed_form = form_page.getManagedForm(); - if (managed_form != null && managed_form.isDirty()) { - managed_form.commit(onSave); - } - } - } - } - } - - /* (non-Javadoc) - * Returns whether the "save as" operation is supported by this editor. - * <p> - * Subclasses must override this method to implement the open-save-close lifecycle - * for an editor. For greater details, see <code>IEditorPart</code> - * </p> - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return false; - } - - /** - * Returns the page index of the text editor (always the last page) - - * @return the page index of the text editor (always the last page) - */ - public int getTextPageIndex() { - return mTextPageIndex; - } - - // ---- Local methods ---- - - - /** - * Helper method that creates a new hyper-link Listener. - * Used by derived classes which need active links in {@link FormText}. - * <p/> - * This link listener handles two kinds of URLs: - * <ul> - * <li> Links starting with "http" are simply sent to a local browser. - * <li> Links starting with "file:/" are simply sent to a local browser. - * <li> Links starting with "page:" are expected to be an editor page id to switch to. - * <li> Other links are ignored. - * </ul> - * - * @return A new hyper-link listener for FormText to use. - */ - public final IHyperlinkListener createHyperlinkListener() { - return new HyperlinkAdapter() { - /** - * Switch to the page corresponding to the link that has just been clicked. - * For this purpose, the HREF of the <a> tags above is the page ID to switch to. - */ - @Override - public void linkActivated(HyperlinkEvent e) { - super.linkActivated(e); - String link = e.data.toString(); - if (link.startsWith("http") || //$NON-NLS-1$ - link.startsWith("file:/")) { //$NON-NLS-1$ - openLinkInBrowser(link); - } else if (link.startsWith("page:")) { //$NON-NLS-1$ - // Switch to an internal page - setActivePage(link.substring(5 /* strlen("page:") */)); - } - } - }; - } - - /** - * Open the http link into a browser - * - * @param link The URL to open in a browser - */ - private void openLinkInBrowser(String link) { - try { - IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance(); - wbs.createBrowser(BROWSER_ID).openURL(new URL(link)); - } catch (PartInitException e1) { - // pass - } catch (MalformedURLException e1) { - // pass - } - } - - /** - * Creates the XML source editor. - * <p/> - * Memorizes the index page of the source editor (it's always the last page, but the number - * of pages before can change.) - * <br/> - * Retrieves the underlying XML model from the StructuredEditor and attaches a listener to it. - * Finally triggers modelChanged() on the model listener -- derived classes can use this - * to initialize the model the first time. - * <p/> - * Called only once <em>after</em> createFormPages. - */ - private void createTextEditor() { - try { - mTextEditor = new StructuredTextEditor() { - @Override - protected void createActions() { - super.createActions(); - - Action action = new RenameResourceXmlTextAction(mTextEditor); - action.setActionDefinitionId(IJavaEditorActionDefinitionIds.RENAME_ELEMENT); - setAction(IJavaEditorActionDefinitionIds.RENAME_ELEMENT, action); - } - }; - int index = addPage(mTextEditor, getEditorInput()); - mTextPageIndex = index; - setPageText(index, mTextEditor.getTitle()); - setPageImage(index, - IconFactory.getInstance().getIcon(ICON_XML_PAGE)); - - if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) { - Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - "Error opening the Android XML editor. Is the document an XML file?"); - throw new RuntimeException("Android XML Editor Error", new CoreException(status)); - } - - IStructuredModel xml_model = getModelForRead(); - if (xml_model != null) { - try { - mXmlModelStateListener = new XmlModelStateListener(); - xml_model.addModelStateListener(mXmlModelStateListener); - mXmlModelStateListener.modelChanged(xml_model); - } catch (Exception e) { - AdtPlugin.log(e, "Error while loading editor"); //$NON-NLS-1$ - } finally { - xml_model.releaseFromRead(); - } - } - } catch (PartInitException e) { - ErrorDialog.openError(getSite().getShell(), - "Android XML Editor Error", null, e.getStatus()); - } - } - - /** - * Returns the ISourceViewer associated with the Structured Text editor. - */ - public final ISourceViewer getStructuredSourceViewer() { - if (mTextEditor != null) { - // We can't access mDelegate.getSourceViewer() because it is protected, - // however getTextViewer simply returns the SourceViewer casted, so we - // can use it instead. - return mTextEditor.getTextViewer(); - } - return null; - } - - /** - * Return the {@link StructuredTextEditor} associated with this XML editor - * - * @return the associated {@link StructuredTextEditor} - */ - public StructuredTextEditor getStructuredTextEditor() { - return mTextEditor; - } - - /** - * Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source - * Editor) or null if not available. - */ - public IStructuredDocument getStructuredDocument() { - if (mTextEditor != null && mTextEditor.getTextViewer() != null) { - return (IStructuredDocument) mTextEditor.getTextViewer().getDocument(); - } - return null; - } - - /** - * Returns a version of the model that has been shared for read. - * <p/> - * Callers <em>must</em> call model.releaseFromRead() when done, typically - * in a try..finally clause. - * - * Portability note: this uses getModelManager which is part of wst.sse.core; however - * the interface returned is part of wst.sse.core.internal.provisional so we can - * expect it to change in a distant future if they start cleaning their codebase, - * however unlikely that is. - * - * @return The model for the XML document or null if cannot be obtained from the editor - */ - public IStructuredModel getModelForRead() { - IStructuredDocument document = getStructuredDocument(); - if (document != null) { - IModelManager mm = StructuredModelManager.getModelManager(); - if (mm != null) { - // TODO simplify this by not using the internal IStructuredDocument. - // Instead we can now use mm.getModelForRead(getFile()). - // However we must first check that SSE for Eclipse 3.3 or 3.4 has this - // method. IIRC 3.3 didn't have it. - - return mm.getModelForRead(document); - } - } - return null; - } - - /** - * Returns a version of the model that has been shared for edit. - * <p/> - * Callers <em>must</em> call model.releaseFromEdit() when done, typically - * in a try..finally clause. - * <p/> - * Because of this, it is mandatory to use the wrapper - * {@link #wrapEditXmlModel(Runnable)} which executes a runnable into a - * properly configured model and then performs whatever cleanup is necessary. - * - * @return The model for the XML document or null if cannot be obtained from the editor - */ - private IStructuredModel getModelForEdit() { - IStructuredDocument document = getStructuredDocument(); - if (document != null) { - IModelManager mm = StructuredModelManager.getModelManager(); - if (mm != null) { - // TODO simplify this by not using the internal IStructuredDocument. - // Instead we can now use mm.getModelForRead(getFile()). - // However we must first check that SSE for Eclipse 3.3 or 3.4 has this - // method. IIRC 3.3 didn't have it. - - return mm.getModelForEdit(document); - } - } - return null; - } - - /** - * Helper class to perform edits on the XML model whilst making sure the - * model has been prepared to be changed. - * <p/> - * It first gets a model for edition using {@link #getModelForEdit()}, - * then calls {@link IStructuredModel#aboutToChangeModel()}, - * then performs the requested action - * and finally calls {@link IStructuredModel#changedModel()} - * and {@link IStructuredModel#releaseFromEdit()}. - * <p/> - * The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method - * is called, XML model listeners will be triggered. - * <p/> - * Calls can be nested: only the first outer call will actually start and close the edit - * session. - * <p/> - * This method is <em>not synchronized</em> and is not thread safe. - * Callers must be using it from the the main UI thread. - * - * @param editAction Something that will change the XML. - */ - public final void wrapEditXmlModel(Runnable editAction) { - wrapEditXmlModel(editAction, null); - } - - /** - * Perform any editor-specific hooks after applying an edit. When edits are - * nested, the hooks will only run after the final top level edit has been - * performed. - * <p> - * Note that the edit hooks are performed outside of the edit lock so - * the hooks should not perform edits on the model without acquiring - * a lock first. - */ - public void runEditHooks() { - if (!mIgnoreXmlUpdate) { - // Check for errors, if enabled - if (AdtPrefs.getPrefs().isLintOnSave()) { - runLint(); - } - } - } - - /** - * Executor which performs the given action under an edit lock (and optionally as a - * single undo event). - * - * @param editAction the action to be executed - * @param undoLabel if non null, the edit action will be run as a single undo event - * and the label used as the name of the undoable action - */ - private final void wrapEditXmlModel(final Runnable editAction, final String undoLabel) { - Display display = mTextEditor.getSite().getShell().getDisplay(); - if (display.getThread() != Thread.currentThread()) { - display.syncExec(new Runnable() { - @Override - public void run() { - if (!mTextEditor.getTextViewer().getControl().isDisposed()) { - wrapEditXmlModel(editAction, undoLabel); - } - } - }); - return; - } - - IStructuredModel model = null; - int undoReverseCount = 0; - try { - - if (mIsEditXmlModelPending == 0) { - try { - model = getModelForEdit(); - if (undoLabel != null) { - // Run this action as an undoable unit. - // We have to do it more than once, because in some scenarios - // Eclipse WTP decides to cancel the current undo command on its - // own -- see http://code.google.com/p/android/issues/detail?id=15901 - // for one such call chain. By nesting these calls several times - // we've incrementing the command count such that a couple of - // cancellations are ignored. Interfering with this mechanism may - // sound dangerous, but it appears that this undo-termination is - // done for UI reasons to anticipate what the user wants, and we know - // that in *our* scenarios we want the entire unit run as a single - // unit. Here's what the documentation for - // IStructuredTextUndoManager#forceEndOfPendingCommand says - // "Normally, the undo manager can figure out the best - // times when to end a pending command and begin a new - // one ... to the structure of a structured - // document. There are times, however, when clients may - // wish to override those algorithms and end one earlier - // than normal. The one known case is for multi-page - // editors. If a user is on one page, and type '123' as - // attribute value, then click around to other parts of - // page, or different pages, then return to '123|' and - // type 456, then "undo" they typically expect the undo - // to just undo what they just typed, the 456, not the - // whole attribute value." - for (int i = 0; i < 4; i++) { - model.beginRecording(this, undoLabel); - undoReverseCount++; - } - } - model.aboutToChangeModel(); - } catch (Throwable t) { - // This is never supposed to happen unless we suddenly don't have a model. - // If it does, we don't want to even try to modify anyway. - AdtPlugin.log(t, "XML Editor failed to get model to edit"); //$NON-NLS-1$ - return; - } - } - mIsEditXmlModelPending++; - editAction.run(); - } finally { - mIsEditXmlModelPending--; - if (model != null) { - try { - boolean oldIgnore = mIgnoreXmlUpdate; - try { - mIgnoreXmlUpdate = true; - - if (AdtPrefs.getPrefs().getFormatGuiXml() && mFormatNode != null) { - if (mFormatNode == getUiRootNode()) { - reformatDocument(); - } else { - Node node = mFormatNode.getXmlNode(); - if (node instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) node; - int begin = region.getStartOffset(); - int end = region.getEndOffset(); - - if (!mFormatChildren) { - // This will format just the attribute list - end = begin + 1; - } - - if (mFormatChildren - && node == node.getOwnerDocument().getDocumentElement()) { - reformatDocument(); - } else { - reformatRegion(begin, end); - } - } - } - mFormatNode = null; - mFormatChildren = false; - } - - // Notify the model we're done modifying it. This must *always* be executed. - model.changedModel(); - - // Clean up the undo unit. This is done more than once as explained - // above for beginRecording. - for (int i = 0; i < undoReverseCount; i++) { - model.endRecording(this); - } - } finally { - mIgnoreXmlUpdate = oldIgnore; - } - } catch (Exception e) { - AdtPlugin.log(e, "Failed to clean up undo unit"); - } - model.releaseFromEdit(); - - if (mIsEditXmlModelPending < 0) { - AdtPlugin.log(IStatus.ERROR, - "wrapEditXmlModel finished with invalid nested counter==%1$d", //$NON-NLS-1$ - mIsEditXmlModelPending); - mIsEditXmlModelPending = 0; - } - - runEditHooks(); - - // Notify listeners - IStructuredModel readModel = getModelForRead(); - if (readModel != null) { - try { - mXmlModelStateListener.modelChanged(readModel); - } catch (Exception e) { - AdtPlugin.log(e, "Error while notifying changes"); //$NON-NLS-1$ - } finally { - readModel.releaseFromRead(); - } - } - } - } - } - - /** - * Does this editor participate in the "format GUI editor changes" option? - * - * @return true if this editor supports automatically formatting XML - * affected by GUI changes - */ - public boolean supportsFormatOnGuiEdit() { - return false; - } - - /** - * Mark the given node as needing to be formatted when the current edits are - * done, provided the user has turned that option on (see - * {@link AdtPrefs#getFormatGuiXml()}). - * - * @param node the node to be scheduled for formatting - * @param attributesOnly if true, only update the attributes list of the - * node, otherwise update the node recursively (e.g. all children - * too) - */ - public void scheduleNodeReformat(UiElementNode node, boolean attributesOnly) { - if (!supportsFormatOnGuiEdit()) { - return; - } - - if (node == mFormatNode) { - if (!attributesOnly) { - mFormatChildren = true; - } - } else if (mFormatNode == null) { - mFormatNode = node; - mFormatChildren = !attributesOnly; - } else { - if (mFormatNode.isAncestorOf(node)) { - mFormatChildren = true; - } else if (node.isAncestorOf(mFormatNode)) { - mFormatNode = node; - mFormatChildren = true; - } else { - // Two independent nodes; format their closest common ancestor. - // Later we could consider having a small number of independent nodes - // and formatting those, and only switching to formatting the common ancestor - // when the number of individual nodes gets large. - mFormatChildren = true; - mFormatNode = UiElementNode.getCommonAncestor(mFormatNode, node); - } - } - } - - /** - * Creates an "undo recording" session by calling the undoableAction runnable - * under an undo session. - * <p/> - * This also automatically starts an edit XML session, as if - * {@link #wrapEditXmlModel(Runnable)} had been called. - * <p> - * You can nest several calls to {@link #wrapUndoEditXmlModel(String, Runnable)}, only one - * recording session will be created. - * - * @param label The label for the undo operation. Can be null. Ideally we should really try - * to put something meaningful if possible. - * @param undoableAction the action to be run as a single undoable unit - */ - public void wrapUndoEditXmlModel(String label, Runnable undoableAction) { - assert label != null : "All undoable actions should have a label"; - wrapEditXmlModel(undoableAction, label == null ? "" : label); //$NON-NLS-1$ - } - - /** - * Returns true when the runnable of {@link #wrapEditXmlModel(Runnable)} is currently - * being executed. This means it is safe to actually edit the XML model. - * - * @return true if the XML model is already locked for edits - */ - public boolean isEditXmlModelPending() { - return mIsEditXmlModelPending > 0; - } - - /** - * Returns the XML {@link Document} or null if we can't get it - */ - public final Document getXmlDocument(IStructuredModel model) { - if (model == null) { - AdtPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$ - return null; - } - - if (model instanceof IDOMModel) { - IDOMModel dom_model = (IDOMModel) model; - return dom_model.getDocument(); - } - return null; - } - - /** - * Returns the {@link IProject} for the edited file. - */ - @Nullable - public IProject getProject() { - IFile file = getInputFile(); - if (file != null) { - return file.getProject(); - } - - return null; - } - - /** - * Returns the {@link AndroidTargetData} for the edited file. - */ - @Nullable - public AndroidTargetData getTargetData() { - IProject project = getProject(); - if (project != null) { - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - IAndroidTarget target = currentSdk.getTarget(project); - - if (target != null) { - return currentSdk.getTargetData(target); - } - } - } - - IEditorInput input = getEditorInput(); - if (input instanceof IURIEditorInput) { - IURIEditorInput urlInput = (IURIEditorInput) input; - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - try { - String path = AdtUtils.getFile(urlInput.getURI().toURL()).getPath(); - IAndroidTarget[] targets = currentSdk.getTargets(); - for (IAndroidTarget target : targets) { - if (path.startsWith(target.getLocation())) { - return currentSdk.getTargetData(target); - } - } - } catch (MalformedURLException e) { - // File might be in some other weird random location we can't - // handle: Just ignore these - } - } - } - - return null; - } - - /** - * Shows the editor range corresponding to the given XML node. This will - * front the editor and select the text range. - * - * @param xmlNode The DOM node to be shown. The DOM node should be an XML - * node from the existing XML model used by the structured XML - * editor; it will not do attribute matching to find a - * "corresponding" element in the document from some foreign DOM - * tree. - * @return True if the node was shown. - */ - public boolean show(Node xmlNode) { - if (xmlNode instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion)xmlNode; - - IEditorPart textPage = getEditor(mTextPageIndex); - if (textPage instanceof StructuredTextEditor) { - StructuredTextEditor editor = (StructuredTextEditor) textPage; - - setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); - - // Note - we cannot use region.getLength() because that seems to - // always return 0. - int regionLength = region.getEndOffset() - region.getStartOffset(); - editor.selectAndReveal(region.getStartOffset(), regionLength); - return true; - } - } - - return false; - } - - /** - * Selects and reveals the given range in the text editor - * - * @param start the beginning offset - * @param length the length of the region to show - * @param frontTab if true, front the tab, otherwise just make the selection but don't - * change the active tab - */ - public void show(int start, int length, boolean frontTab) { - IEditorPart textPage = getEditor(mTextPageIndex); - if (textPage instanceof StructuredTextEditor) { - StructuredTextEditor editor = (StructuredTextEditor) textPage; - if (frontTab) { - setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); - } - editor.selectAndReveal(start, length); - if (frontTab) { - editor.setFocus(); - } - } - } - - /** - * Returns true if this editor has more than one page (usually a graphical view and an - * editor) - * - * @return true if this editor has multiple pages - */ - public boolean hasMultiplePages() { - return getPageCount() > 1; - } - - /** - * Get the XML text directly from the editor. - * - * @param xmlNode The node whose XML text we want to obtain. - * @return The XML representation of the {@link Node}, or null if there was an error. - */ - public String getXmlText(Node xmlNode) { - String data = null; - IStructuredModel model = getModelForRead(); - try { - IStructuredDocument document = getStructuredDocument(); - if (xmlNode instanceof NodeContainer) { - // The easy way to get the source of an SSE XML node. - data = ((NodeContainer) xmlNode).getSource(); - } else if (xmlNode instanceof IndexedRegion && document != null) { - // Try harder. - IndexedRegion region = (IndexedRegion) xmlNode; - int start = region.getStartOffset(); - int end = region.getEndOffset(); - - if (end > start) { - data = document.get(start, end - start); - } - } - } catch (BadLocationException e) { - // the region offset was invalid. ignore. - } finally { - model.releaseFromRead(); - } - return data; - } - - /** - * Formats the text around the given caret range, using the current Eclipse - * XML formatter settings. - * - * @param begin The starting offset of the range to be reformatted. - * @param end The ending offset of the range to be reformatted. - */ - public void reformatRegion(int begin, int end) { - ISourceViewer textViewer = getStructuredSourceViewer(); - - // Clamp text range to valid offsets. - IDocument document = textViewer.getDocument(); - int documentLength = document.getLength(); - end = Math.min(end, documentLength); - begin = Math.min(begin, end); - - if (!AdtPrefs.getPrefs().getUseCustomXmlFormatter()) { - // Workarounds which only apply to the builtin Eclipse formatter: - // - // It turns out the XML formatter does *NOT* format things correctly if you - // select just a region of text. You *MUST* also include the leading whitespace - // on the line, or it will dedent all the content to column 0. Therefore, - // we must figure out the offset of the start of the line that contains the - // beginning of the tag. - try { - IRegion lineInformation = document.getLineInformationOfOffset(begin); - if (lineInformation != null) { - int lineBegin = lineInformation.getOffset(); - if (lineBegin != begin) { - begin = lineBegin; - } else if (begin > 0) { - // Trick #2: It turns out that, if an XML element starts in column 0, - // then the XML formatter will NOT indent it (even if its parent is - // indented). If you on the other hand include the end of the previous - // line (the newline), THEN the formatter also correctly inserts the - // element. Therefore, we adjust the beginning range to include the - // previous line (if we are not already in column 0 of the first line) - // in the case where the element starts the line. - begin--; - } - } - } catch (BadLocationException e) { - // This cannot happen because we already clamped the offsets - AdtPlugin.log(e, e.toString()); - } - } - - if (textViewer instanceof StructuredTextViewer) { - StructuredTextViewer structuredTextViewer = (StructuredTextViewer) textViewer; - int operation = ISourceViewer.FORMAT; - boolean canFormat = structuredTextViewer.canDoOperation(operation); - if (canFormat) { - StyledText textWidget = textViewer.getTextWidget(); - textWidget.setSelection(begin, end); - - boolean oldIgnore = mIgnoreXmlUpdate; - try { - // Formatting does not affect the XML model so ignore notifications - // about model edits from this - mIgnoreXmlUpdate = true; - structuredTextViewer.doOperation(operation); - } finally { - mIgnoreXmlUpdate = oldIgnore; - } - - textWidget.setSelection(0, 0); - } - } - } - - /** - * Invokes content assist in this editor at the given offset - * - * @param offset the offset to invoke content assist at, or -1 to leave - * caret alone - */ - public void invokeContentAssist(int offset) { - ISourceViewer textViewer = getStructuredSourceViewer(); - if (textViewer instanceof StructuredTextViewer) { - StructuredTextViewer structuredTextViewer = (StructuredTextViewer) textViewer; - int operation = ISourceViewer.CONTENTASSIST_PROPOSALS; - boolean allowed = structuredTextViewer.canDoOperation(operation); - if (allowed) { - if (offset != -1) { - StyledText textWidget = textViewer.getTextWidget(); - // Clamp text range to valid offsets. - IDocument document = textViewer.getDocument(); - int documentLength = document.getLength(); - offset = Math.max(0, Math.min(offset, documentLength)); - textWidget.setSelection(offset, offset); - } - structuredTextViewer.doOperation(operation); - } - } - } - - /** - * Formats the XML region corresponding to the given node. - * - * @param node The node to be formatted. - */ - public void reformatNode(Node node) { - if (mIsCreatingPage) { - return; - } - - if (node instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) node; - int begin = region.getStartOffset(); - int end = region.getEndOffset(); - reformatRegion(begin, end); - } - } - - /** - * Formats the XML document according to the user's XML formatting settings. - */ - public void reformatDocument() { - ISourceViewer textViewer = getStructuredSourceViewer(); - if (textViewer instanceof StructuredTextViewer) { - StructuredTextViewer structuredTextViewer = (StructuredTextViewer) textViewer; - int operation = StructuredTextViewer.FORMAT_DOCUMENT; - boolean canFormat = structuredTextViewer.canDoOperation(operation); - if (canFormat) { - boolean oldIgnore = mIgnoreXmlUpdate; - try { - // Formatting does not affect the XML model so ignore notifications - // about model edits from this - mIgnoreXmlUpdate = true; - structuredTextViewer.doOperation(operation); - } finally { - mIgnoreXmlUpdate = oldIgnore; - } - } - } - } - - /** - * Returns the indentation String of the given node. - * - * @param xmlNode The node whose indentation we want. - * @return The indent-string of the given node, or "" if the indentation for some reason could - * not be computed. - */ - public String getIndent(Node xmlNode) { - return getIndent(getStructuredDocument(), xmlNode); - } - - /** - * Returns the indentation String of the given node. - * - * @param document The Eclipse document containing the XML - * @param xmlNode The node whose indentation we want. - * @return The indent-string of the given node, or "" if the indentation for some reason could - * not be computed. - */ - public static String getIndent(IDocument document, Node xmlNode) { - if (xmlNode instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion)xmlNode; - int startOffset = region.getStartOffset(); - return getIndentAtOffset(document, startOffset); - } - - return ""; //$NON-NLS-1$ - } - - /** - * Returns the indentation String at the line containing the given offset - * - * @param document the document containing the offset - * @param offset The offset of a character on a line whose indentation we seek - * @return The indent-string of the given node, or "" if the indentation for some - * reason could not be computed. - */ - public static String getIndentAtOffset(IDocument document, int offset) { - try { - IRegion lineInformation = document.getLineInformationOfOffset(offset); - if (lineInformation != null) { - int lineBegin = lineInformation.getOffset(); - if (lineBegin != offset) { - String prefix = document.get(lineBegin, offset - lineBegin); - - // It's possible that the tag whose indentation we seek is not - // at the beginning of the line. In that case we'll just return - // the indentation of the line itself. - for (int i = 0; i < prefix.length(); i++) { - if (!Character.isWhitespace(prefix.charAt(i))) { - return prefix.substring(0, i); - } - } - - return prefix; - } - } - } catch (BadLocationException e) { - AdtPlugin.log(e, "Could not obtain indentation"); //$NON-NLS-1$ - } - - return ""; //$NON-NLS-1$ - } - - /** - * Returns the active {@link AndroidXmlEditor}, provided it matches the given source - * viewer - * - * @param viewer the source viewer to ensure the active editor is associated with - * @return the active editor provided it matches the given source viewer or null. - */ - public static AndroidXmlEditor fromTextViewer(ITextViewer viewer) { - IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (wwin != null) { - // Try the active editor first. - IWorkbenchPage page = wwin.getActivePage(); - if (page != null) { - IEditorPart editor = page.getActiveEditor(); - if (editor instanceof AndroidXmlEditor) { - ISourceViewer ssviewer = - ((AndroidXmlEditor) editor).getStructuredSourceViewer(); - if (ssviewer == viewer) { - return (AndroidXmlEditor) editor; - } - } - } - - // If that didn't work, try all the editors - for (IWorkbenchPage page2 : wwin.getPages()) { - if (page2 != null) { - for (IEditorReference editorRef : page2.getEditorReferences()) { - IEditorPart editor = editorRef.getEditor(false /*restore*/); - if (editor instanceof AndroidXmlEditor) { - ISourceViewer ssviewer = - ((AndroidXmlEditor) editor).getStructuredSourceViewer(); - if (ssviewer == viewer) { - return (AndroidXmlEditor) editor; - } - } - } - } - } - } - - return null; - } - - /** Called when this editor is activated */ - public void activated() { - if (getActivePage() == mTextPageIndex) { - updateActionBindings(); - } - } - - /** Called when this editor is deactivated */ - public void deactivated() { - } - - /** - * Listen to changes in the underlying XML model in the structured editor. - */ - private class XmlModelStateListener implements IModelStateListener { - - /** - * A model is about to be changed. This typically is initiated by one - * client of the model, to signal a large change and/or a change to the - * model's ID or base Location. A typical use might be if a client might - * want to suspend processing until all changes have been made. - * <p/> - * This AndroidXmlEditor implementation of IModelChangedListener is empty. - */ - @Override - public void modelAboutToBeChanged(IStructuredModel model) { - // pass - } - - /** - * Signals that the changes foretold by modelAboutToBeChanged have been - * made. A typical use might be to refresh, or to resume processing that - * was suspended as a result of modelAboutToBeChanged. - * <p/> - * This AndroidXmlEditor implementation calls the xmlModelChanged callback. - */ - @Override - public void modelChanged(IStructuredModel model) { - if (mIgnoreXmlUpdate) { - return; - } - xmlModelChanged(getXmlDocument(model)); - } - - /** - * Notifies that a model's dirty state has changed, and passes that state - * in isDirty. A model becomes dirty when any change is made, and becomes - * not-dirty when the model is saved. - * <p/> - * This AndroidXmlEditor implementation of IModelChangedListener is empty. - */ - @Override - public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) { - // pass - } - - /** - * A modelDeleted means the underlying resource has been deleted. The - * model itself is not removed from model management until all have - * released it. Note: baseLocation is not (necessarily) changed in this - * event, but may not be accurate. - * <p/> - * This AndroidXmlEditor implementation of IModelChangedListener is empty. - */ - @Override - public void modelResourceDeleted(IStructuredModel model) { - // pass - } - - /** - * A model has been renamed or copied (as in saveAs..). In the renamed - * case, the two parameters are the same instance, and only contain the - * new info for id and base location. - * <p/> - * This AndroidXmlEditor implementation of IModelChangedListener is empty. - */ - @Override - public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) { - // pass - } - - /** - * This AndroidXmlEditor implementation of IModelChangedListener is empty. - */ - @Override - public void modelAboutToBeReinitialized(IStructuredModel structuredModel) { - // pass - } - - /** - * This AndroidXmlEditor implementation of IModelChangedListener is empty. - */ - @Override - public void modelReinitialized(IStructuredModel structuredModel) { - // pass - } - } -} |