ADT: GraphicalEditorPart is the new GLE2.

This CL represents the base for the new "Graphical Editor Part".

First, GLE2 has been renamed in GraphicalEditorPart. That's the
final name, I swear I won't change it again (until next month that is.)

The editor part has 3 composites: the top ConfigConfiguration
(same as usual, reused as-is), a new PaletteComposite and a new
LayoutCanavas. This last one displays the rendering image and
will deal with interactivity.

The LayoutCanvas is actually stacked with a label which displays
the rendering error. After a rendering, either the error or the
canvas is visible, depending on the success of the operation.
That would make it easier to have a different mechanism, for example
the error could be next to the last known rendering, they don't
have to be mutually exclusive.

It is worth noting that GraphicalEditorPart is 95% similar to
the GLE1, reusing all the glue code that we had to handle
layout requestes, refresh requests, sdk/framework load listeners,
configuration and file input changes, etc.

Both PaletteComposite and LayoutCanvas are currently embryonic
at best, just to make sure the editor part is structured correctly.

Change-Id: I36c2ae4d85a68e68a349adc63a718f06375e12c5
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index 4aa3a80..f17b3c8 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -244,6 +244,7 @@
      * (non-Javadoc)
      * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
      */
+    @SuppressWarnings("deprecation")
     @Override
     public void start(BundleContext context) throws Exception {
         super.start(context);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java
deleted file mode 100755
index e905990..0000000
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*

- * Copyright (C) 2009 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.layout;

-

-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;

-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;

-import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;

-

-import org.eclipse.core.runtime.IProgressMonitor;

-import org.eclipse.gef.ui.parts.SelectionSynchronizer;

-import org.eclipse.swt.dnd.Clipboard;

-import org.eclipse.swt.widgets.Composite;

-import org.eclipse.ui.IEditorInput;

-import org.eclipse.ui.IEditorSite;

-import org.eclipse.ui.PartInitException;

-import org.eclipse.ui.part.EditorPart;

-

-/**

- * Graphical layout editor part, version 2.

- *

- * @since GLE2

- */

-public class GLE2 extends EditorPart implements IGraphicalLayoutEditor {

-

-    /*

-     * Useful notes:

-     * To understand Drag'n'drop:

-     *   http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html

-     */

-

-    /** Reference to the layout editor */

-    private final LayoutEditor mLayoutEditor;

-

-    public GLE2(LayoutEditor layoutEditor) {

-        mLayoutEditor = layoutEditor;

-        setPartName("Graphical Layout");

-    }

-

-    // ------------------------------------

-    // Methods overridden from base classes

-    //------------------------------------

-

-    /**

-     * Initializes the editor part with a site and input.

-     * {@inheritDoc}

-     */

-    @Override

-    public void init(IEditorSite site, IEditorInput input) throws PartInitException {

-        setSite(site);

-        setInput(input);

-    }

-

-    @Override

-    public void dispose() {

-        super.dispose();

-    }

-

-    @Override

-    public void doSave(IProgressMonitor monitor) {

-        // TODO Auto-generated method stub

-

-    }

-

-    @Override

-    public void doSaveAs() {

-        // TODO Auto-generated method stub

-

-    }

-

-    @Override

-    public boolean isDirty() {

-        // TODO Auto-generated method stub

-        return false;

-    }

-

-    @Override

-    public boolean isSaveAsAllowed() {

-        // TODO Auto-generated method stub

-        return false;

-    }

-

-    @Override

-    public void createPartControl(Composite parent) {

-        // TODO Auto-generated method stub

-

-    }

-

-    @Override

-    public void setFocus() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void activated() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void deactivated() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void editNewFile(FolderConfiguration configuration) {

-        // TODO Auto-generated method stub

-

-    }

-

-    public Clipboard getClipboard() {

-        // TODO Auto-generated method stub

-        return null;

-    }

-

-    public LayoutEditor getLayoutEditor() {

-        // TODO Auto-generated method stub

-        return null;

-    }

-

-    public UiDocumentNode getModel() {

-        // TODO Auto-generated method stub

-        return null;

-    }

-

-    public SelectionSynchronizer getSelectionSynchronizer() {

-        // TODO Auto-generated method stub

-        return null;

-    }

-

-    public void onXmlModelChanged() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void recomputeLayout() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void reloadEditor() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void reloadPalette() {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void selectModel(UiElementNode uiNodeModel) {

-        // TODO Auto-generated method stub

-

-    }

-

-    public void reloadLayout(boolean codeChange, boolean rChange,

-            boolean resChange) {

-        // TODO Auto-generated method stub

-

-    }

-

-}

diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
new file mode 100755
index 0000000..45a42e2
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
@@ -0,0 +1,1078 @@
+/*

+ * Copyright (C) 2009 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.layout;

+

+import com.android.ide.eclipse.adt.AdtPlugin;

+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;

+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;

+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;

+import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;

+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;

+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;

+import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;

+import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier;

+import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density;

+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;

+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;

+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;

+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;

+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;

+import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;

+import com.android.ide.eclipse.adt.internal.sdk.Sdk;

+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;

+import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;

+import com.android.layoutlib.api.ILayoutBridge;

+import com.android.layoutlib.api.ILayoutLog;

+import com.android.layoutlib.api.ILayoutResult;

+import com.android.layoutlib.api.IProjectCallback;

+import com.android.layoutlib.api.IResourceValue;

+import com.android.layoutlib.api.IXmlPullParser;

+import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;

+import com.android.sdklib.IAndroidTarget;

+

+import org.eclipse.core.resources.IFile;

+import org.eclipse.core.resources.IFolder;

+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.Status;

+import org.eclipse.core.runtime.jobs.Job;

+import org.eclipse.draw2d.geometry.Rectangle;

+import org.eclipse.gef.ui.parts.SelectionSynchronizer;

+import org.eclipse.jface.dialogs.Dialog;

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.custom.SashForm;

+import org.eclipse.swt.custom.StackLayout;

+import org.eclipse.swt.dnd.Clipboard;

+import org.eclipse.swt.layout.GridData;

+import org.eclipse.swt.layout.GridLayout;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.swt.widgets.Label;

+import org.eclipse.ui.IEditorInput;

+import org.eclipse.ui.IEditorSite;

+import org.eclipse.ui.PartInitException;

+import org.eclipse.ui.ide.IDE;

+import org.eclipse.ui.part.EditorPart;

+import org.eclipse.ui.part.FileEditorInput;

+

+import java.io.File;

+import java.io.FileOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.PrintStream;

+import java.util.List;

+import java.util.Map;

+

+/**

+ * Graphical layout editor part, version 2.

+ *

+ * @since GLE2

+ *

+ * TODO List:

+ * - finish palette (see palette's todo list)

+ * - finish canvas (see canva's todo list)

+ * - completly rethink the property panel

+ * - link to the existing outline editor (prolly reuse/adapt for simplier model and will need

+ *   to adapt the selection synchronizer.)

+ */

+public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutEditor {

+

+    /*

+     * Useful notes:

+     * To understand Drag'n'drop:

+     *   http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html

+     */

+

+    /** Reference to the layout editor */

+    private final LayoutEditor mLayoutEditor;

+

+    /** reference to the file being edited. */

+    private IFile mEditedFile;

+

+    /** The current clipboard. Must be disposed later. */

+    private Clipboard mClipboard;

+

+    /** The configuration composite at the top of the layout editor. */

+    private ConfigurationComposite mConfigComposite;

+

+    /** The sash that splits the palette from the canvas. */

+    private SashForm mSash;

+

+    /** The palette displayed on the left of the sash. */

+    private PaletteComposite mPalette;

+

+    /** The layout canvas displayed o the right of the sash. */

+    private LayoutCanvas mLayoutCanvas;

+

+    /** The {@link FolderConfiguration} being edited. */

+    private FolderConfiguration mEditedConfig;

+

+    private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes;

+    private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes;

+    private ProjectCallback mProjectCallback;

+    private ILayoutLog mLogger;

+

+    private boolean mNeedsXmlReload = false;

+    private boolean mNeedsRecompute = false;

+

+    private TargetListener mTargetListener;

+

+    private ConfigListener mConfigListener;

+

+    private Composite mCanvasOrErrorStack;

+

+    private StackLayout mCanvasOrErrorStackLayout;

+

+    private Label mErrorLabel;

+

+    private ReloadListener mReloadListener;

+

+    public GraphicalEditorPart(LayoutEditor layoutEditor) {

+        mLayoutEditor = layoutEditor;

+        setPartName("Graphical Layout");

+    }

+

+    // ------------------------------------

+    // Methods overridden from base classes

+    //------------------------------------

+

+    /**

+     * Initializes the editor part with a site and input.

+     * {@inheritDoc}

+     */

+    @Override

+    public void init(IEditorSite site, IEditorInput input) throws PartInitException {

+        setSite(site);

+        useNewEditorInput(input);

+

+        if (mTargetListener == null) {

+            mTargetListener = new TargetListener();

+            AdtPlugin.getDefault().addTargetListener(mTargetListener);

+        }

+

+        if (mReloadListener == null) {

+            mReloadListener = new ReloadListener();

+            LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);

+        }

+    }

+

+    /**

+     * Reloads this editor, by getting the new model from the {@link LayoutEditor}.

+     */

+    public void reloadEditor() {

+        IEditorInput input = mLayoutEditor.getEditorInput();

+

+        try {

+            useNewEditorInput(input);

+        } catch (PartInitException e) {

+            // really this shouldn't happen! Log it in case it happens.

+            mEditedFile = null;

+            AdtPlugin.log(e, "Input is not of type FileEditorInput: %1$s",          //$NON-NLS-1$

+                    input == null ? "null" : input.toString());                     //$NON-NLS-1$

+        }

+    }

+

+    private void useNewEditorInput(IEditorInput input) throws PartInitException {

+        // The contract of init() mentions we need to fail if we can't understand the input.

+        if (!(input instanceof FileEditorInput)) {

+            throw new PartInitException("Input is not of type FileEditorInput: " +  //$NON-NLS-1$

+                    input == null ? "null" : input.toString());                     //$NON-NLS-1$

+        }

+

+        FileEditorInput fileInput = (FileEditorInput)input;

+        mEditedFile = fileInput.getFile();

+    }

+

+    @Override

+    public void createPartControl(Composite parent) {

+

+        mClipboard = new Clipboard(parent.getDisplay());

+

+        GridLayout gl = new GridLayout(1, false);

+        parent.setLayout(gl);

+        gl.marginHeight = gl.marginWidth = 0;

+

+        // create the top part for the configuration control

+        mConfigListener = new ConfigListener();

+        mConfigComposite = new ConfigurationComposite(mConfigListener, parent, SWT.BORDER);

+        mConfigComposite.updateUIFromResources();

+

+        mSash = new SashForm(parent, SWT.HORIZONTAL);

+        mSash.setLayoutData(new GridData(GridData.FILL_BOTH));

+

+        mPalette = new PaletteComposite(mSash);

+

+        mCanvasOrErrorStack = new Composite(mSash, SWT.NONE);

+        mCanvasOrErrorStackLayout = new StackLayout();

+        mCanvasOrErrorStack.setLayout(mCanvasOrErrorStackLayout);

+

+        mLayoutCanvas = new LayoutCanvas(mCanvasOrErrorStack);

+        mErrorLabel = new Label(mCanvasOrErrorStack, SWT.NONE);

+        mCanvasOrErrorStackLayout.topControl = mLayoutCanvas;

+

+        mSash.setWeights(new int[] { 20, 80 });

+

+        // Initialize the state

+        reloadPalette();

+    }

+

+    /** Switches the stack to display the canvas and hide the error label. */

+    private void displayCanvas() {

+        if (mCanvasOrErrorStackLayout.topControl != mLayoutCanvas) {

+            mCanvasOrErrorStackLayout.topControl = mLayoutCanvas;

+            mCanvasOrErrorStack.layout();

+        }

+    }

+

+    /**

+     * Switches the stack to display the error label and hide the canvas.

+     * @param errorFormat The new error to display if not null.

+     * @param parameters String.format parameters for the error format.

+     */

+    private void displayError(String errorFormat, Object...parameters) {

+        if (errorFormat != null) {

+            mErrorLabel.setText(String.format(errorFormat, parameters));

+        }

+        if (mCanvasOrErrorStackLayout.topControl != mErrorLabel) {

+            mCanvasOrErrorStackLayout.topControl = mErrorLabel;

+            mCanvasOrErrorStack.layout();

+        }

+    }

+

+    @Override

+    public void dispose() {

+        if (mTargetListener != null) {

+            AdtPlugin.getDefault().removeTargetListener(mTargetListener);

+            mTargetListener = null;

+        }

+

+        if (mReloadListener != null) {

+            LayoutReloadMonitor.getMonitor().removeListener(mReloadListener);

+            mReloadListener = null;

+        }

+

+        if (mClipboard != null) {

+            mClipboard.dispose();

+            mClipboard = null;

+        }

+

+        super.dispose();

+    }

+

+    private class ConfigListener implements IConfigListener {

+

+        /**

+         * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.

+         * <p/>If there is no match, notify the user.

+         */

+        public void onConfigurationChange() {

+            mConfiguredFrameworkRes = mConfiguredProjectRes = null;

+

+            if (mEditedFile == null || mEditedConfig == null) {

+                return;

+            }

+

+            // get the resources of the file's project.

+            ProjectResources resources = ResourceManager.getInstance().getProjectResources(

+                    mEditedFile.getProject());

+

+            // from the resources, look for a matching file

+            ResourceFile match = null;

+            if (resources != null) {

+                match = resources.getMatchingFile(mEditedFile.getName(),

+                                                  ResourceFolderType.LAYOUT,

+                                                  mConfigComposite.getCurrentConfig());

+            }

+

+            if (match != null) {

+                if (match.getFile().equals(mEditedFile) == false) {

+                    try {

+                        IDE.openEditor(

+                                getSite().getWorkbenchWindow().getActivePage(),

+                                match.getFile().getIFile());

+

+                        // we're done!

+                        return;

+                    } catch (PartInitException e) {

+                        // FIXME: do something!

+                    }

+                }

+

+                // at this point, we have not opened a new file.

+

+                // update the configuration icons with the new edited config.

+                setConfiguration(mEditedConfig, false /*force*/);

+

+                // enable the create button if the current and edited config are not equals

+                mConfigComposite.setEnabledCreate(

+                        mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);

+

+                // Even though the layout doesn't change, the config changed, and referenced

+                // resources need to be updated.

+                recomputeLayout();

+            } else {

+                // enable the Create button

+                mConfigComposite.setEnabledCreate(true);

+

+                // display the error.

+                FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();

+                displayError(

+                        "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.",

+                        currentConfig.toDisplayString(),

+                        currentConfig.getFolderName(ResourceFolderType.LAYOUT,

+                                Sdk.getCurrent().getTarget(mEditedFile.getProject())),

+                        mEditedFile.getName());

+            }

+        }

+

+        public void onThemeChange() {

+            recomputeLayout();

+        }

+

+        public void onCreate() {

+            LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(),

+                    mEditedFile.getName(),

+                    Sdk.getCurrent().getTarget(mEditedFile.getProject()),

+                    mConfigComposite.getCurrentConfig());

+            if (dialog.open() == Dialog.OK) {

+                final FolderConfiguration config = new FolderConfiguration();

+                dialog.getConfiguration(config);

+

+                createAlternateLayout(config);

+            }

+        }

+

+        public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {

+            if (mConfiguredFrameworkRes == null && mConfigComposite != null) {

+                ProjectResources frameworkRes = getFrameworkResources();

+

+                if (frameworkRes == null) {

+                    AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");

+                } else {

+                    // get the framework resource values based on the current config

+                    mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(

+                            mConfigComposite.getCurrentConfig());

+                }

+            }

+

+            return mConfiguredFrameworkRes;

+        }

+

+        public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {

+            if (mConfiguredProjectRes == null && mConfigComposite != null) {

+                ProjectResources project = getProjectResources();

+

+                // make sure they are loaded

+                project.loadAll();

+

+                // get the project resource values based on the current config

+                mConfiguredProjectRes = project.getConfiguredResources(

+                        mConfigComposite.getCurrentConfig());

+            }

+

+            return mConfiguredProjectRes;

+        }

+

+        /**

+         * Returns a {@link ProjectResources} for the framework resources.

+         * @return the framework resources or null if not found.

+         */

+        public ProjectResources getFrameworkResources() {

+            if (mEditedFile != null) {

+                Sdk currentSdk = Sdk.getCurrent();

+                if (currentSdk != null) {

+                    IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());

+

+                    if (target != null) {

+                        AndroidTargetData data = currentSdk.getTargetData(target);

+

+                        if (data != null) {

+                            return data.getFrameworkResources();

+                        }

+                    }

+                }

+            }

+

+            return null;

+        }

+

+        public ProjectResources getProjectResources() {

+            if (mEditedFile != null) {

+                ResourceManager manager = ResourceManager.getInstance();

+                return manager.getProjectResources(mEditedFile.getProject());

+            }

+

+            return null;

+        }

+

+        /**

+         * Creates a new layout file from the specified {@link FolderConfiguration}.

+         */

+        private void createAlternateLayout(final FolderConfiguration config) {

+            new Job("Create Alternate Resource") {

+                @Override

+                protected IStatus run(IProgressMonitor monitor) {

+                    // get the folder name

+                    String folderName = config.getFolderName(ResourceFolderType.LAYOUT,

+                            Sdk.getCurrent().getTarget(mEditedFile.getProject()));

+                    try {

+

+                        // look to see if it exists.

+                        // get the res folder

+                        IFolder res = (IFolder)mEditedFile.getParent().getParent();

+                        String path = res.getLocation().toOSString();

+

+                        File newLayoutFolder = new File(path + File.separator + folderName);

+                        if (newLayoutFolder.isFile()) {

+                            // this should not happen since aapt would have complained

+                            // before, but if one disable the automatic build, this could

+                            // happen.

+                            String message = String.format("File 'res/%1$s' is in the way!",

+                                    folderName);

+

+                            AdtPlugin.displayError("Layout Creation", message);

+

+                            return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);

+                        } else if (newLayoutFolder.exists() == false) {

+                            // create it.

+                            newLayoutFolder.mkdir();

+                        }

+

+                        // now create the file

+                        File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +

+                                    File.separator + mEditedFile.getName());

+

+                        newLayoutFile.createNewFile();

+

+                        InputStream input = mEditedFile.getContents();

+

+                        FileOutputStream fos = new FileOutputStream(newLayoutFile);

+

+                        byte[] data = new byte[512];

+                        int count;

+                        while ((count = input.read(data)) != -1) {

+                            fos.write(data, 0, count);

+                        }

+

+                        input.close();

+                        fos.close();

+

+                        // refreshes the res folder to show up the new

+                        // layout folder (if needed) and the file.

+                        // We use a progress monitor to catch the end of the refresh

+                        // to trigger the edit of the new file.

+                        res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {

+                            public void done() {

+                                mConfigComposite.setConfig(config);

+                                mConfigComposite.getDisplay().asyncExec(new Runnable() {

+                                    public void run() {

+                                        onConfigurationChange();

+                                    }

+                                });

+                            }

+

+                            public void beginTask(String name, int totalWork) {

+                                // pass

+                            }

+

+                            public void internalWorked(double work) {

+                                // pass

+                            }

+

+                            public boolean isCanceled() {

+                                // pass

+                                return false;

+                            }

+

+                            public void setCanceled(boolean value) {

+                                // pass

+                            }

+

+                            public void setTaskName(String name) {

+                                // pass

+                            }

+

+                            public void subTask(String name) {

+                                // pass

+                            }

+

+                            public void worked(int work) {

+                                // pass

+                            }

+                        });

+                    } catch (IOException e2) {

+                        String message = String.format(

+                                "Failed to create File 'res/%1$s/%2$s' : %3$s",

+                                folderName, mEditedFile.getName(), e2.getMessage());

+

+                        AdtPlugin.displayError("Layout Creation", message);

+

+                        return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,

+                                message, e2);

+                    } catch (CoreException e2) {

+                        String message = String.format(

+                                "Failed to create File 'res/%1$s/%2$s' : %3$s",

+                                folderName, mEditedFile.getName(), e2.getMessage());

+

+                        AdtPlugin.displayError("Layout Creation", message);

+

+                        return e2.getStatus();

+                    }

+

+                    return Status.OK_STATUS;

+

+                }

+            }.schedule();

+        }

+    }

+

+    private class TargetListener implements ITargetChangeListener {

+

+        public void onProjectTargetChange(IProject changedProject) {

+            if (changedProject == getLayoutEditor().getProject()) {

+                onTargetsLoaded();

+            }

+        }

+

+        public void onTargetsLoaded() {

+            // because the SDK changed we must reset the configured framework resource.

+            mConfiguredFrameworkRes = null;

+

+            mConfigComposite.updateUIFromResources();

+

+            // updateUiFromFramework will reset language/region combo, so we must call

+            // setConfiguration after, or the settext on language/region will be lost.

+            if (mEditedConfig != null) {

+                setConfiguration(mEditedConfig, false /*force*/);

+            }

+

+            // make sure we remove the custom view loader, since its parent class loader is the

+            // bridge class loader.

+            mProjectCallback = null;

+

+            recomputeLayout();

+        }

+    }

+

+    /**

+     * Update the UI controls state with a given {@link FolderConfiguration}.

+     * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect

+     * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,

+     * the UI control is not modified. However if the value in the control is not the default value,

+     * a warning icon is shown.

+     * @param config The {@link FolderConfiguration} to set.

+     * @param force Whether the UI should be changed to exactly match the received configuration.

+     */

+    void setConfiguration(FolderConfiguration config, boolean force) {

+        mEditedConfig = config;

+        mConfiguredFrameworkRes = mConfiguredProjectRes = null;

+

+        mConfigComposite.setConfiguration(config, force);

+

+    }

+

+    // ----------------

+

+    /**

+     * Save operation in the Graphical Editor Part.

+     * <p/>

+     * In our workflow, the model is owned by the Structured XML Editor.

+     * The graphical layout editor just displays it -- thus we don't really

+     * save anything here.

+     * <p/>

+     * This must NOT call the parent editor part. At the contrary, the parent editor

+     * part will call this *after* having done the actual save operation.

+     * <p/>

+     * The only action this editor must do is mark the undo command stack as

+     * being no longer dirty.

+     */

+    @Override

+    public void doSave(IProgressMonitor monitor) {

+        // TODO implement a command stack

+//        getCommandStack().markSaveLocation();

+//        firePropertyChange(PROP_DIRTY);

+    }

+

+    /**

+     * Save operation in the Graphical Editor Part.

+     * <p/>

+     * In our workflow, the model is owned by the Structured XML Editor.

+     * The graphical layout editor just displays it -- thus we don't really

+     * save anything here.

+     */

+    @Override

+    public void doSaveAs() {

+        // pass

+    }

+

+    /**

+     * In our workflow, the model is owned by the Structured XML Editor.

+     * The graphical layout editor just displays it -- thus we don't really

+     * save anything here.

+     */

+    @Override

+    public boolean isDirty() {

+        return false;

+    }

+

+    /**

+     * In our workflow, the model is owned by the Structured XML Editor.

+     * The graphical layout editor just displays it -- thus we don't really

+     * save anything here.

+     */

+    @Override

+    public boolean isSaveAsAllowed() {

+        return false;

+    }

+

+    @Override

+    public void setFocus() {

+        // TODO Auto-generated method stub

+

+    }

+

+    /**

+     * Responds to a page change that made the Graphical editor page the activated page.

+     */

+    public void activated() {

+        if (mNeedsRecompute || mNeedsXmlReload) {

+            recomputeLayout();

+        }

+    }

+

+    /**

+     * Responds to a page change that made the Graphical editor page the deactivated page

+     */

+    public void deactivated() {

+        // nothing to be done here for now.

+    }

+

+    /**

+     * Sets the UI for the edition of a new file.

+     * @param configuration the configuration of the new file.

+     */

+    public void editNewFile(FolderConfiguration configuration) {

+        // update the configuration UI

+        setConfiguration(configuration, true /*force*/);

+

+        // enable the create button if the current and edited config are not equals

+        mConfigComposite.setEnabledCreate(

+                mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);

+    }

+

+    public Clipboard getClipboard() {

+        return mClipboard;

+    }

+

+    public LayoutEditor getLayoutEditor() {

+        return mLayoutEditor;

+    }

+

+    public UiDocumentNode getModel() {

+        return mLayoutEditor.getUiRootNode();

+    }

+

+    public SelectionSynchronizer getSelectionSynchronizer() {

+        // TODO Auto-generated method stub

+        return null;

+    }

+

+    /**

+     * Callback for XML model changed. Only update/recompute the layout if the editor is visible

+     */

+    public void onXmlModelChanged() {

+        if (mLayoutEditor.isGraphicalEditorActive()) {

+            doXmlReload(true /* force */);

+            recomputeLayout();

+        } else {

+            mNeedsXmlReload = true;

+        }

+    }

+

+    /**

+     * Actually performs the XML reload

+     * @see #onXmlModelChanged()

+     */

+    private void doXmlReload(boolean force) {

+        if (force || mNeedsXmlReload) {

+

+            // TODO : update the mLayoutCanvas, preserving the current selection if possible.

+

+//            GraphicalViewer viewer = getGraphicalViewer();

+//

+//            // try to preserve the selection before changing the content

+//            SelectionManager selMan = viewer.getSelectionManager();

+//            ISelection selection = selMan.getSelection();

+//

+//            try {

+//                viewer.setContents(getModel());

+//            } finally {

+//                selMan.setSelection(selection);

+//            }

+

+            mNeedsXmlReload = false;

+        }

+    }

+

+    public void recomputeLayout() {

+        doXmlReload(false /* force */);

+        try {

+            // check that the resource exists. If the file is opened but the project is closed

+            // or deleted for some reason (changed from outside of eclipse), then this will

+            // return false;

+            if (mEditedFile.exists() == false) {

+                displayError("Resource '%1$s' does not exist.",

+                             mEditedFile.getFullPath().toString());

+                return;

+            }

+

+            IProject iProject = mEditedFile.getProject();

+

+            if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {

+                String message = String.format("%1$s is out of sync. Please refresh.",

+                        mEditedFile.getName());

+

+                displayError(message);

+

+                // also print it in the error console.

+                AdtPlugin.printErrorToConsole(iProject.getName(), message);

+                return;

+            }

+

+            Sdk currentSdk = Sdk.getCurrent();

+            if (currentSdk != null) {

+                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());

+                if (target == null) {

+                    displayError("The project target is not set.");

+                    return;

+                }

+

+                AndroidTargetData data = currentSdk.getTargetData(target);

+                if (data == null) {

+                    // It can happen that the workspace refreshes while the SDK is loading its

+                    // data, which could trigger a redraw of the opened layout if some resources

+                    // changed while Eclipse is closed.

+                    // In this case data could be null, but this is not an error.

+                    // We can just silently return, as all the opened editors are automatically

+                    // refreshed once the SDK finishes loading.

+                    if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) {

+                        displayError("The project target (%s) was not properly loaded.",

+                                     target.getName());

+                    }

+                    return;

+                }

+

+                // check there is actually a model (maybe the file is empty).

+                UiDocumentNode model = getModel();

+

+                if (model.getUiChildren().size() == 0) {

+                    displayError("No Xml content. Go to the Outline view and add nodes.");

+                    return;

+                }

+

+                LayoutBridge bridge = data.getLayoutBridge();

+

+                if (bridge.bridge != null) { // bridge can never be null.

+                    ResourceManager resManager = ResourceManager.getInstance();

+

+                    ProjectResources projectRes = resManager.getProjectResources(iProject);

+                    if (projectRes == null) {

+                        displayError("Missing project resources.");

+                        return;

+                    }

+

+                    // get the resources of the file's project.

+                    Map<String, Map<String, IResourceValue>> configuredProjectRes =

+                        mConfigListener.getConfiguredProjectResources();

+

+                    // get the framework resources

+                    Map<String, Map<String, IResourceValue>> frameworkResources =

+                        mConfigListener.getConfiguredFrameworkResources();

+

+                    if (configuredProjectRes != null && frameworkResources != null) {

+                        if (mProjectCallback == null) {

+                            mProjectCallback = new ProjectCallback(

+                                    bridge.classLoader, projectRes, iProject);

+                        }

+

+                        if (mLogger == null) {

+                            mLogger = new ILayoutLog() {

+                                public void error(String message) {

+                                    AdtPlugin.printErrorToConsole(mEditedFile.getName(), message);

+                                }

+

+                                public void error(Throwable error) {

+                                    String message = error.getMessage();

+                                    if (message == null) {

+                                        message = error.getClass().getName();

+                                    }

+

+                                    PrintStream ps = new PrintStream(AdtPlugin.getErrorStream());

+                                    error.printStackTrace(ps);

+                                }

+

+                                public void warning(String message) {

+                                    AdtPlugin.printToConsole(mEditedFile.getName(), message);

+                                }

+                            };

+                        }

+

+                        // get the selected theme

+                        String theme = mConfigComposite.getTheme();

+                        if (theme != null) {

+

+                            // Compute the layout

+                            UiElementPullParser parser = new UiElementPullParser(getModel());

+                            Rectangle rect = getBounds();

+                            boolean isProjectTheme = mConfigComposite.isProjectTheme();

+

+                            // FIXME pass the density/dpi from somewhere (resource config or skin).

+                            // For now, get it from the config

+                            int density = Density.MEDIUM.getDpiValue();

+                            PixelDensityQualifier qual =

+                                mConfigComposite.getCurrentConfig().getPixelDensityQualifier();

+                            if (qual != null) {

+                                int d = qual.getValue().getDpiValue();

+                                if (d > 0) {

+                                    density = d;

+                                }

+                            }

+

+                            ILayoutResult result = computeLayout(bridge, parser,

+                                    iProject /* projectKey */,

+                                    rect.width, rect.height, density, density, density,

+                                    theme, isProjectTheme,

+                                    configuredProjectRes, frameworkResources, mProjectCallback,

+                                    mLogger);

+

+                            // update the UiElementNode with the layout info.

+                            if (result.getSuccess() == ILayoutResult.SUCCESS) {

+

+                                // Update the image and make sure we're displaying the canvas.

+                                mLayoutCanvas.setImage(result.getImage());

+                                displayCanvas();

+

+                                updateNodeWithBounds(result.getRootView());

+                            } else {

+                                displayError(result.getErrorMessage());

+

+                                // Reset the edit data for all the nodes.

+                                resetNodeBounds(model);

+                            }

+

+                            model.refreshUi();

+                        }

+                    }

+                } else {

+                    // SDK is loaded but not the layout library!

+

+                    // check whether the bridge managed to load, or not

+                    if (bridge.status == LoadStatus.LOADING) {

+                        displayError("Eclipse is loading framework information and the layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.",

+                                     mEditedFile.getName());

+                    } else {

+                        displayError("Eclipse failed to load the framework information and the layout library!");

+                    }

+                }

+            } else {

+                displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",

+                             mEditedFile.getName());

+            }

+        } finally {

+            // no matter the result, we are done doing the recompute based on the latest

+            // resource/code change.

+            mNeedsRecompute = false;

+        }

+    }

+

+    /**

+     * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on

+     * the implementation API level.

+     */

+    @SuppressWarnings("deprecation")

+    private static ILayoutResult computeLayout(LayoutBridge bridge,

+            IXmlPullParser layoutDescription, Object projectKey,

+            int screenWidth, int screenHeight, int density, float xdpi, float ydpi,

+            String themeName, boolean isProjectTheme,

+            Map<String, Map<String, IResourceValue>> projectResources,

+            Map<String, Map<String, IResourceValue>> frameworkResources,

+            IProjectCallback projectCallback, ILayoutLog logger) {

+

+        if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) {

+            // newest API with support for "render full height"

+            // TODO: link boolean to UI.

+            return bridge.bridge.computeLayout(layoutDescription,

+                    projectKey, screenWidth, screenHeight, false /* renderFullHeight */,

+                    density, xdpi, ydpi,

+                    themeName, isProjectTheme,

+                    projectResources, frameworkResources, projectCallback,

+                    logger);

+        } else if (bridge.apiLevel == 3) {

+            // newer api with density support.

+            return bridge.bridge.computeLayout(layoutDescription,

+                    projectKey, screenWidth, screenHeight, density, xdpi, ydpi,

+                    themeName, isProjectTheme,

+                    projectResources, frameworkResources, projectCallback,

+                    logger);

+        } else if (bridge.apiLevel == 2) {

+            // api with boolean for separation of project/framework theme

+            return bridge.bridge.computeLayout(layoutDescription,

+                    projectKey, screenWidth, screenHeight, themeName, isProjectTheme,

+                    projectResources, frameworkResources, projectCallback,

+                    logger);

+        } else {

+            // oldest api with no density/dpi, and project theme boolean mixed

+            // into the theme name.

+

+            // change the string if it's a custom theme to make sure we can

+            // differentiate them

+            if (isProjectTheme) {

+                themeName = "*" + themeName; //$NON-NLS-1$

+            }

+

+            return bridge.bridge.computeLayout(layoutDescription,

+                    projectKey, screenWidth, screenHeight, themeName,

+                    projectResources, frameworkResources, projectCallback,

+                    logger);

+        }

+    }

+

+    public Rectangle getBounds() {

+        return mConfigComposite.getScreenBounds();

+    }

+

+    private void resetNodeBounds(UiElementNode node) {

+        node.setEditData(null);

+

+        List<UiElementNode> children = node.getUiChildren();

+        for (UiElementNode child : children) {

+            resetNodeBounds(child);

+        }

+    }

+

+    private void updateNodeWithBounds(ILayoutViewInfo r) {

+        if (r != null) {

+            // update the node itself, as the viewKey is the XML node in this implementation.

+            Object viewKey = r.getViewKey();

+            if (viewKey instanceof UiElementNode) {

+                Rectangle bounds = new Rectangle(r.getLeft(), r.getTop(),

+                        r.getRight()-r.getLeft(), r.getBottom() - r.getTop());

+

+                ((UiElementNode)viewKey).setEditData(bounds);

+            }

+

+            // and then its children.

+            ILayoutViewInfo[] children = r.getChildren();

+            if (children != null) {

+                for (ILayoutViewInfo child : children) {

+                    updateNodeWithBounds(child);

+                }

+            }

+        }

+    }

+

+    public void reloadPalette() {

+        if (mPalette != null) {

+            mPalette.reloadPalette(mLayoutEditor.getTargetData());

+        }

+    }

+

+    /**

+     * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node

+     * created by {@link ElementCreateCommand#execute()}.

+     *

+     * @param uiNodeModel The {@link UiElementNode} to select.

+     */

+    public void selectModel(UiElementNode uiNodeModel) {

+

+        // TODO this method was useful for GLE1. We may not need it anymore now.

+

+//        GraphicalViewer viewer = getGraphicalViewer();

+//

+//        // Give focus to the graphical viewer (in case the outline has it)

+//        viewer.getControl().forceFocus();

+//

+//        Object editPart = viewer.getEditPartRegistry().get(uiNodeModel);

+//

+//        if (editPart instanceof EditPart) {

+//            viewer.select((EditPart)editPart);

+//        }

+    }

+

+    private class ReloadListener implements ILayoutReloadListener {

+        /*

+         * (non-Javadoc)

+         * @see com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener#reloadLayout(boolean, boolean, boolean)

+         *

+         * Called when the file changes triggered a redraw of the layout

+         */

+        public void reloadLayout(boolean codeChange, boolean rChange, boolean resChange) {

+            boolean recompute = rChange;

+

+            if (resChange) {

+                recompute = true;

+

+                // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache.

+

+                // force a reparse in case a value XML file changed.

+                mConfiguredProjectRes = null;

+

+                // clear the cache in the bridge in case a bitmap/9-patch changed.

+                IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());

+                if (target != null) {

+

+                    AndroidTargetData data = Sdk.getCurrent().getTargetData(target);

+                    if (data != null) {

+                        LayoutBridge bridge = data.getLayoutBridge();

+

+                        if (bridge.bridge != null) {

+                            bridge.bridge.clearCaches(mEditedFile.getProject());

+                        }

+                    }

+                }

+

+                mLayoutCanvas.getDisplay().asyncExec(new Runnable() {

+                    public void run() {

+                        mConfigComposite.updateUIFromResources();

+                    }

+                });

+            }

+

+            if (codeChange) {

+                // only recompute if the custom view loader was used to load some code.

+                if (mProjectCallback != null && mProjectCallback.isUsed()) {

+                    mProjectCallback = null;

+                    recompute = true;

+                }

+            }

+

+            if (recompute) {

+                mLayoutCanvas.getDisplay().asyncExec(new Runnable() {

+                    public void run() {

+                        if (mLayoutEditor.isGraphicalEditorActive()) {

+                            recomputeLayout();

+                        } else {

+                            mNeedsRecompute = true;

+                        }

+                    }

+                });

+            }

+        }

+    }

+}

diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
index 45810af..c70be8d 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
@@ -19,6 +19,7 @@
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor.UiEditorActions;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
@@ -115,7 +116,7 @@
  * @since GLE1
  */
 public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
-        implements IGraphicalLayoutEditor, IConfigListener {
+        implements IGraphicalLayoutEditor, IConfigListener, ILayoutReloadListener {
 
 
     /** Reference to the layout editor */
@@ -388,7 +389,7 @@
 
     /**
      * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
-     * created by  {@link ElementCreateCommand#execute()}.
+     * created by {@link ElementCreateCommand#execute()}.
      *
      * @param uiNodeModel The {@link UiElementNode} to select.
      */
@@ -814,7 +815,6 @@
     /**
      * Recomputes the layout with the help of layoutlib.
      */
-    @SuppressWarnings("deprecation")
     public void recomputeLayout() {
         doXmlReload(false /* force */);
         try {
@@ -1339,4 +1339,5 @@
         }
     }
 
+
 }
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
index 6414b20..4c9c714 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
@@ -16,7 +16,6 @@
 
 package com.android.ide.eclipse.adt.internal.editors.layout;
 
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
 import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
@@ -31,7 +30,7 @@
  *
  * @since GLE2
  */
-/*package*/ interface IGraphicalLayoutEditor extends IEditorPart, ILayoutReloadListener {
+/*package*/ interface IGraphicalLayoutEditor extends IEditorPart {
 
     /**
      * Sets the UI for the edition of a new file.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
new file mode 100755
index 0000000..976554c
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
@@ -0,0 +1,88 @@
+/*

+ * Copyright (C) 2009 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.layout;

+

+import java.awt.image.BufferedImage;

+import java.awt.image.DataBufferInt;

+import java.awt.image.Raster;

+

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.events.PaintEvent;

+import org.eclipse.swt.events.PaintListener;

+import org.eclipse.swt.graphics.GC;

+import org.eclipse.swt.graphics.Image;

+import org.eclipse.swt.graphics.ImageData;

+import org.eclipse.swt.graphics.PaletteData;

+import org.eclipse.swt.widgets.Canvas;

+import org.eclipse.swt.widgets.Composite;

+

+/**

+ * Displays the image rendered by the {@link GraphicalEditorPart} and handles

+ * the interaction with the widgets.

+ * <p/>

+ *

+ * @since GLE2

+ *

+ * TODO list:

+ * - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)

+ * - handle selection (will need the model, aka the root node)/

+ * - handle drop target (from palette)/

+ * - handle drag'n'drop (internal, for moving/duplicating)/

+ * - handle context menu (depending on selection)/

+ * - selection synchronization with the outline (both ways)/

+ * - preserve selection during editor input change if applicable (e.g. when changing configuration.)

+ */

+public class LayoutCanvas extends Canvas {

+

+    private Image mImage;

+

+    public LayoutCanvas(Composite parent) {

+        super(parent, SWT.BORDER);

+

+        addPaintListener(new PaintListener() {

+            public void paintControl(PaintEvent e) {

+                paint(e);

+            }

+        });

+    }

+

+    public void setImage(BufferedImage awtImage) {

+        // Convert the AWT image into an SWT image.

+        int width = awtImage.getWidth();

+        int height = awtImage.getHeight();

+

+        Raster raster = awtImage.getData(new java.awt.Rectangle(width, height));

+        int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();

+

+        ImageData imageData = new ImageData(width, height, 32,

+                new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));

+

+        imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);

+

+        mImage = new Image(getDisplay(), imageData);

+

+        redraw();

+    }

+

+    private void paint(PaintEvent e) {

+        if (mImage != null) {

+            GC gc = e.gc;

+            gc.drawImage(mImage, 0, 0);

+        }

+    }

+

+}

diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
index 1176253..caa9658 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
@@ -132,7 +132,7 @@
                 if (mGraphicalEditor == null) {
 
                     if (System.getenv("USE_GLE2") != null) {            //$NON-NLS-1$ //$NON-NLS-2$
-                        mGraphicalEditor = new GLE2(this);
+                        mGraphicalEditor = new GraphicalEditorPart(this);
                     } else {
                         mGraphicalEditor = new GraphicalLayoutEditor(this);
                     }
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
index d9e81f5..507cb21 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
@@ -31,6 +31,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -39,16 +40,16 @@
  * Monitor for file changes triggering a layout redraw.
  */
 public final class LayoutReloadMonitor implements IFileListener, IResourceEventListener {
-    
+
     // singleton, enforced by private constructor.
     private final static LayoutReloadMonitor sThis = new LayoutReloadMonitor();
-    
+
     /**
      * Map of listeners by IProject.
      */
     private final Map<IProject, List<ILayoutReloadListener>> mListenerMap =
         new HashMap<IProject, List<ILayoutReloadListener>>();
-    
+
     private final static int CHANGE_CODE = 0;
     private final static int CHANGE_RESOURCES = 1;
     private final static int CHANGE_R = 2;
@@ -60,7 +61,7 @@
      * <li>CHANGE_R: R clas change flag</li></ul>
      */
     private final Map<IProject, boolean[]> mChangedProjects = new HashMap<IProject, boolean[]>();
-    
+
     /**
      * Classes which implement this interface provide a method to respond to resource changes
      * triggering a layout redraw
@@ -72,22 +73,22 @@
          * @param rChange The trigger happened due to a change in the R class.
          * @param resChange The trigger happened due to a resource change.
          */
-        void reloadLayout(boolean codeChange, boolean rChange, boolean resChange); 
+        void reloadLayout(boolean codeChange, boolean rChange, boolean resChange);
     }
-    
+
     /**
      * Returns the single instance of {@link LayoutReloadMonitor}.
      */
     public static LayoutReloadMonitor getMonitor() {
         return sThis;
     }
-    
+
     private LayoutReloadMonitor() {
         ResourceMonitor monitor = ResourceMonitor.getMonitor();
         monitor.addFileListener(this, IResourceDelta.ADDED | IResourceDelta.CHANGED);
         monitor.addResourceEventListener(this);
     }
-    
+
     /**
      * Adds a listener for a given {@link IProject}.
      * @param project
@@ -100,15 +101,13 @@
                 list = new ArrayList<ILayoutReloadListener>();
                 mListenerMap.put(project, list);
             }
-            
+
             list.add(listener);
         }
     }
-    
+
     /**
      * Removes a listener for a given {@link IProject}.
-     * @param project
-     * @param listener
      */
     public void removeListener(IProject project, ILayoutReloadListener listener) {
         synchronized (mListenerMap) {
@@ -119,10 +118,28 @@
         }
     }
 
+    /**
+     * Removes a listener, no matter which {@link IProject} it was associated with.
+     */
+    public void removeListener(ILayoutReloadListener listener) {
+        synchronized (mListenerMap) {
+
+            for (List<ILayoutReloadListener> list : mListenerMap.values()) {
+                Iterator<ILayoutReloadListener> it = list.iterator();
+                while (it.hasNext()) {
+                    ILayoutReloadListener i = it.next();
+                    if (i == listener) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+    }
+
     /*
      * (non-Javadoc)
      * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener#fileChanged(org.eclipse.core.resources.IFile, org.eclipse.core.resources.IMarkerDelta[], int)
-     * 
+     *
      * Callback for ResourceMonitor.IFileListener. Called when a file changed.
      * This records the changes for each project, but does not notify listeners.
      * @see #resourceChangeEventEnd
@@ -137,7 +154,7 @@
                 changeFlags[CHANGE_R]) {
             return;
         }
-        
+
         // now check that the file is *NOT* a layout file (those automatically trigger a layout
         // reload and we don't want to do it twice.
         ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
@@ -148,7 +165,7 @@
                     changeFlags = new boolean[CHANGE_COUNT];
                     mChangedProjects.put(project, changeFlags);
                 }
-    
+
                 changeFlags[CHANGE_RESOURCES] = true;
             }
         } else if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) {
@@ -171,14 +188,14 @@
             }
         }
     }
-    
+
     /*
      * (non-Javadoc)
      * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventStart()
-     * 
+     *
      * Callback for ResourceMonitor.IResourceEventListener. Called at the beginning of a resource
      * change event. This is called once, while fileChanged can be called several times.
-     * 
+     *
      */
     public void resourceChangeEventStart() {
         // nothing to be done here, it all happens in the resourceChangeEventEnd
@@ -187,7 +204,7 @@
     /*
      * (non-Javadoc)
      * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventEnd()
-     * 
+     *
      * Callback for ResourceMonitor.IResourceEventListener. Called at the end of a resource
      * change event. This is where we notify the listeners.
      */
@@ -196,9 +213,9 @@
         synchronized (mListenerMap) {
             for (Entry<IProject, boolean[]> project : mChangedProjects.entrySet()) {
                 List<ILayoutReloadListener> listeners = mListenerMap.get(project.getKey());
-                
+
                 boolean[] flags = project.getValue();
-                
+
                 if (listeners != null) {
                     for (ILayoutReloadListener listener : listeners) {
                         listener.reloadLayout(flags[CHANGE_CODE], flags[CHANGE_R],
@@ -207,7 +224,7 @@
                 }
             }
         }
-        
+
         // empty the list.
         mChangedProjects.clear();
     }
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java
new file mode 100755
index 0000000..27398e9
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java
@@ -0,0 +1,118 @@
+/*

+ * Copyright (C) 2009 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.layout;

+

+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;

+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;

+

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.layout.GridLayout;

+import org.eclipse.swt.widgets.Button;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.swt.widgets.Control;

+import org.eclipse.swt.widgets.Label;

+

+import java.util.List;

+

+/**

+ * A palette composite for the {@link GraphicalEditorPart}.

+ * <p/>

+ * The palette contains several groups, each with a UI name (e.g. layouts and views) and each

+ * with a list of element descriptors.

+ * <p/>

+ *

+ * @since GLE2

+ *

+ * TODO list:

+ * - *Mandatory* for a first release:

+ *   - Currently this displays elements as buttons. Eventually this needs to either be replaced

+ *     by custom drawing right in here or we need to use a custom control.

+ *   - Needs to be able to originate drag'n'drop from these controls onto the GEP.

+ *   - Scroll the list.

+ * - For later releases:

+ *   - Ability to collapse palettes or dockable palettes.

+ *   - Different view strategies: big icon, small icons, text vs no text, compact grid.

+ *     - This would only be useful with meaningful icons. Out current 1-letter icons are not enough

+ *       to get rid of text labels.

+ *   - Would be nice to have context-sensitive tools items, e.g. selection arrow tool,

+ *     group selection tool, alignment, etc.

+ */

+public class PaletteComposite extends Composite {

+

+    /**

+     * Create the composite.

+     * @param parent The parent composite.

+     */

+    public PaletteComposite(Composite parent) {

+        super(parent, SWT.BORDER | SWT.V_SCROLL);

+    }

+

+    @Override

+    protected void checkSubclass() {

+        // Disable the check that prevents subclassing of SWT components

+    }

+

+    /**

+     * Load or reload the palette elements by using the layour and view descriptors from the

+     * given target data.

+     *

+     * @param targetData The target data that contains the descriptors. If null or empty,

+     *   no groups will be created.

+     */

+    public void reloadPalette(AndroidTargetData targetData) {

+

+        for (Control c : getChildren()) {

+            c.dispose();

+        }

+

+        if (targetData != null) {

+            GridLayout gl = new GridLayout(1, false);

+            gl.horizontalSpacing = 0;

+            gl.verticalSpacing = 0;

+            gl.marginHeight = 2;

+            gl.marginBottom = 2;

+            gl.marginLeft = 2;

+            gl.marginRight = 2;

+            gl.marginTop = 2;

+            gl.marginBottom = 2;

+            setLayout(gl);

+

+            /* STOPSHIP */

+            Label l = new Label(this, SWT.NONE);

+            l.setText("*** PLACEHOLDER ***");  //$NON-NLS-1$

+            l.setToolTipText("Temporary mock for the palette. Needs to scroll, needs no buttons, needs to drag'n'drop."); //$NON-NLS-1$

+

+            addGroup("Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());

+            addGroup("Views", targetData.getLayoutDescriptors().getViewDescriptors());

+        }

+

+        layout(true);

+    }

+

+    private void addGroup(String uiName, List<ElementDescriptor> descriptors) {

+        Label label = new Label(this, SWT.NONE);

+        label.setText(uiName);

+

+        for (ElementDescriptor desc : descriptors) {

+            Button b = new Button(this, SWT.PUSH);

+            b.setText(desc.getUiName());

+            b.setImage(desc.getIcon());

+            b.setToolTipText(desc.getTooltip());

+            b.setData(desc);

+        }

+    }

+}