FileDocCategorySizeDatePackage
AndroidEditor.javaAPI DocAndroid 1.5 API30836Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.editors

AndroidEditor

public abstract class AndroidEditor extends org.eclipse.ui.forms.editor.FormEditor implements org.eclipse.core.resources.IResourceChangeListener
Multi-page form editor for Android XML files.

It is designed to work with a {@link StructuredTextEditor} that will display an XML file.
Derived classes must implement createFormPages to create the forms before the source editor. This can be a no-op if desired.

Fields Summary
private static final String
PREF_CURRENT_PAGE
Preference name for the current page of this file
private static String
BROWSER_ID
Id string used to create the Android SDK browser
public static final String
TEXT_EDITOR_ID
Page id of the XML source editor, used for switching tabs programmatically
public static final int
TEXT_WIDTH_HINT
Width hint for text fields. Helps the grid layout resize properly on smaller screens
private int
mTextPageIndex
Page index of the text editor (always the last page)
private org.eclipse.wst.sse.ui.StructuredTextEditor
mTextEditor
The text editor
private XmlModelStateListener
mXmlModelStateListener
Listener for the XML model from the StructuredEditor
private com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener
mTargetListener
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
Constructors Summary
public AndroidEditor()
Creates a form editor.


             
      
        super();
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
        
        mTargetListener = new ITargetChangeListener() {
            public void onProjectTargetChange(IProject changedProject) {
                if (changedProject == getProject()) {
                    onTargetsLoaded();
                }
            }

            public void onTargetsLoaded() {
                commitPages(false /* onSave */);
                
                // recreate the ui root node always
                initUiRootNode(true /*force*/);
            }
        };
        AdtPlugin.getDefault().addTargetListener(mTargetListener);
    
Methods Summary
protected voidaddPages()
Creates the pages of the multi-page editor.

        createAndroidPages();
        selectDefaultPage(null /* defaultPageId */);
    
private final booleanbeginUndoRecording(java.lang.String label)
Starts an "undo recording" session. This is managed by the underlying undo manager associated to the structured XML model.

There must be a corresponding call to {@link #endUndoRecording()}.

beginUndoRecording/endUndoRecording calls can be nested (inner calls are ignored, only one undo operation is recorded.)

param
label The label for the undo operation. Can be null but we should really try to put something meaningful if possible.
return
True if the undo recording actually started, false if any kind of error occured. {@link #endUndoRecording()} should only be called if True is returned.

        IStructuredDocument document = getStructuredDocument();
        if (document != null) {
            IModelManager mm = StructuredModelManager.getModelManager();
            if (mm != null) {
                IStructuredModel model = mm.getModelForEdit(document);
                if (model != null) {
                    model.beginRecording(this, label);
                    return true;
                }
            }
        }
        return false;
    
public voidcommitPages(boolean onSave)
Commits all dirty pages in the editor. This method should be called as a first step of a 'save' operation.

This is the same implementation as in {@link FormEditor} except it fixes two bugs: a cast to IFormPage is done from page.get(i) before being tested with instanceof. Another bug is that the last page might be a null pointer.

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 true if commit is performed as part of the 'save' operation, false otherwise.
since
3.3

        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);
                    }
                }
            }
        }   
    
protected voidcreateAndroidPages()
Creates the page for the Android Editors

        createFormPages();
        createTextEditor();

        createUndoRedoActions();
    
protected abstract voidcreateFormPages()
Creates the various form pages.

Derived classes must implement this to add their own specific tabs.

public final org.eclipse.ui.forms.events.IHyperlinkListenercreateHyperlinkListener()
Helper method that creates a new hyper-link Listener. Used by derived classes which need active links in {@link FormText}.

This link listener handles two kinds of URLs:

  • Links starting with "http" are simply sent to a local browser.
  • Links starting with "file:/" are simply sent to a local browser.
  • Links starting with "page:" are expected to be an editor page id to switch to.
  • Other links are ignored.

return
A new hyper-link listener for FormText to use.

        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:") */));
                }
            }
        };
    
private voidcreateTextEditor()
Creates the XML source editor.

Memorizes the index page of the source editor (it's always the last page, but the number of pages before can change.)
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.

Called only once after createFormPages.

        try {
            mTextEditor = new StructuredTextEditor();
            int index = addPage(mTextEditor, getEditorInput());
            mTextPageIndex = index;
            setPageText(index, mTextEditor.getTitle());

            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());
        }
    
private voidcreateUndoRedoActions()
Creates undo redo 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.)

        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.updateActionBars();
        }
    
public voiddispose()
Removes attached listeners.

see
WorkbenchPart

        IStructuredModel xml_model = getModelForRead();
        if (xml_model != null) {
            try {
                if (mXmlModelStateListener != null) {
                    xml_model.removeModelStateListener(mXmlModelStateListener);
                }
        
            } finally {
                xml_model.releaseFromRead();
            }
        }
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);

        if (mTargetListener != null) {
            AdtPlugin.getDefault().removeTargetListener(mTargetListener);
            mTargetListener = null;
        }

        super.dispose();
    
public voiddoSave(org.eclipse.core.runtime.IProgressMonitor monitor)
Commit all dirty pages then saves the contents of the text editor.

This works by committing all data to the XML model and then asking the Structured XML Editor to save the XML.

see
IEditorPart

        commitPages(true /* onSave */);

        // The actual "save" operation is done by the Structured XML Editor
        getEditor(mTextPageIndex).doSave(monitor);
    
public voiddoSaveAs()

        commitPages(true /* onSave */);

        IEditorPart editor = getEditor(mTextPageIndex);
        editor.doSaveAs();
        setPageText(mTextPageIndex, editor.getTitle());
        setInput(editor.getEditorInput());
    
public final voideditXmlModel(java.lang.Runnable edit_action)
Helper class to perform edits on the XML model whilst making sure the model has been prepared to be changed.

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()}.

The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method is called, XML model listeners will be triggered.

param
edit_action Something that will change the XML.

        IStructuredModel model = getModelForEdit();
        try {
            model.aboutToChangeModel();
            edit_action.run();
        } finally {
            // Notify the model we're done modifying it. This must *always* be executed.
            model.changedModel();
            model.releaseFromEdit();
        }
    
private final voidendUndoRecording()
Ends an "undo recording" session.

This is the counterpart call to {@link #beginUndoRecording(String)} and should only be used if the initial call returned true.

        IStructuredDocument document = getStructuredDocument();
        if (document != null) {
            IModelManager mm = StructuredModelManager.getModelManager();
            if (mm != null) {
                IStructuredModel model = mm.getModelForEdit(document);
                if (model != null) {
                    model.endRecording(this);
                }
            }
        }
    
public final org.eclipse.wst.sse.core.internal.provisional.IStructuredModelgetModelForEdit()
Returns a version of the model that has been shared for edit.

Callers must call model.releaseFromEdit() when done, typically in a try..finally clause.

return
The model for the XML document or null if cannot be obtained from the editor

        IStructuredDocument document = getStructuredDocument();
        if (document != null) {
            IModelManager mm = StructuredModelManager.getModelManager();
            if (mm != null) {
                return mm.getModelForEdit(document);
            }
        }
        return null;
    
public final org.eclipse.wst.sse.core.internal.provisional.IStructuredModelgetModelForRead()
Returns a version of the model that has been shared for read.

Callers must call model.releaseFromRead() when done, typically in a try..finally clause.

return
The model for the XML document or null if cannot be obtained from the editor

        IStructuredDocument document = getStructuredDocument();
        if (document != null) {
            IModelManager mm = StructuredModelManager.getModelManager();
            if (mm != null) {
                return mm.getModelForRead(document);
            }
        }
        return null;
    
public org.eclipse.core.resources.IProjectgetProject()
Returns the {@link IProject} for the edited file.

        if (mTextEditor != null) {
            IEditorInput input = mTextEditor.getEditorInput();
            if (input instanceof FileEditorInput) {
                FileEditorInput fileInput = (FileEditorInput)input;
                IFile inputFile = fileInput.getFile();
                
                if (inputFile != null) {
                    return inputFile.getProject();
                }
            }
        }
        
        return null;
    
public final org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentgetStructuredDocument()
Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source Editor) or null if not available.

        if (mTextEditor != null && mTextEditor.getTextViewer() != null) {
            return (IStructuredDocument) mTextEditor.getTextViewer().getDocument();
        }
        return null;
    
public final org.eclipse.jface.text.source.ISourceViewergetStructuredSourceViewer()
Returns the ISourceViewer associated with the Structured Text editor.

        if (mTextEditor != null) {
            // We can't access mEditor.getSourceViewer() because it is protected,
            // however getTextViewer simply returns the SourceViewer casted, so we
            // can use it instead.
            return mTextEditor.getTextViewer();
        }
        return null;
    
public com.android.ide.eclipse.adt.sdk.AndroidTargetDatagetTargetData()
Returns the {@link AndroidTargetData} for the edited file.

        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);
                }
            }
        }
        
        return null;
    
public abstract com.android.ide.eclipse.editors.uimodel.UiElementNodegetUiRootNode()
Returns the root node of the UI element hierarchy manipulated by the current UI node editor.

protected final org.w3c.dom.DocumentgetXmlDocument(org.eclipse.wst.sse.core.internal.provisional.IStructuredModel model)
Returns the XML {@link Document} or null if we can't get it

        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;
    
public voidinit(org.eclipse.ui.IEditorSite site, org.eclipse.ui.IEditorInput editorInput)
Initializes the editor part with a site and input.

Checks that the input is an instance of {@link IFileEditorInput}.

see
FormEditor

        if (!(editorInput instanceof IFileEditorInput))
            throw new PartInitException("Invalid Input: Must be IFileEditorInput");
        super.init(site, editorInput);
    
protected abstract voidinitUiRootNode(boolean force)
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.

public booleanisSaveAsAllowed()

        return false;
    
private voidopenLinkInBrowser(java.lang.String link)
Open the http link into a browser

param
link The URL to open in a browser

        try {
            IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance();
            wbs.createBrowser(BROWSER_ID).openURL(new URL(link));
        } catch (PartInitException e1) {
            // pass
        } catch (MalformedURLException e1) {
            // pass
        }
    
protected voidpageChange(int newPageIndex)
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)

        super.pageChange(newPageIndex);
        
        if (getEditorInput() instanceof IFileEditorInput) {
            IFile file = ((IFileEditorInput) getEditorInput()).getFile();

            QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
                    getClass().getSimpleName() + PREF_CURRENT_PAGE);
            try {
                file.setPersistentProperty(qname, Integer.toString(newPageIndex));
            } catch (CoreException e) {
                // ignore
            }
        }
    
protected voidremovePages()
Removes all the pages from the editor.

        int count = getPageCount();
        for (int i = count - 1 ; i >= 0 ; i--) {
            removePage(i);
        }
    
public voidresourceChanged(org.eclipse.core.resources.IResourceChangeEvent event)
Notifies this listener that some resource changes are happening, or have already happened. Closes all project files on project close.

see
IResourceChangeListener

        if (event.getType() == IResourceChangeEvent.PRE_CLOSE) {
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    IWorkbenchPage[] pages = getSite().getWorkbenchWindow()
                            .getPages();
                    for (int i = 0; i < pages.length; i++) {
                        if (((FileEditorInput)mTextEditor.getEditorInput())
                                .getFile().getProject().equals(
                                        event.getResource())) {
                            IEditorPart editorPart = pages[i].findEditor(mTextEditor
                                    .getEditorInput());
                            pages[i].closeEditor(editorPart, true);
                        }
                    }
                }
            });
        }
    
protected voidselectDefaultPage(java.lang.String defaultPageId)
Selects the default active page.

param
defaultPageId the id of the page to show. If null the editor attempts to find the default page in the properties of the {@link IResource} object being edited.

        if (defaultPageId == null) {
            if (getEditorInput() instanceof IFileEditorInput) {
                IFile file = ((IFileEditorInput) getEditorInput()).getFile();
    
                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 AndroidEditor failed", defaultPageId);
            }
        }
    
public org.eclipse.ui.forms.editor.IFormPagesetActivePage(java.lang.String pageId)
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.

        if (pageId.equals(TEXT_EDITOR_ID)) {
            super.setActivePage(mTextPageIndex);
            return null;
        } else {
            return super.setActivePage(pageId);
        }
    
public voidwrapUndoRecording(java.lang.String label, java.lang.Runnable undoableAction)
Creates an "undo recording" session by calling the undoableAction runnable using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}.

You can nest several calls to {@link #wrapUndoRecording(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.

        boolean recording = false;
        try {
            recording = beginUndoRecording(label);
            undoableAction.run();
        } finally {
            if (recording) {
                endUndoRecording();
            }
        }
    
protected voidxmlModelChanged(org.w3c.dom.Document xml_doc)
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.

        // pass