OSDN Git Service

ADT GRE: Move gscripts package.
authorRaphael <raphael@google.com>
Fri, 15 Jan 2010 00:13:26 +0000 (16:13 -0800)
committerRaphael <raphael@google.com>
Fri, 15 Jan 2010 00:33:37 +0000 (16:33 -0800)
Moving the public API from com.android.ide.eclispse.adt.gscripts
to ...adt.editors.layout.gscripts.

Change-Id: Idf5b979d47dbbbe2514cce8cc3c688eb273bcce6

15 files changed:
eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy
eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy
eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy
eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropZone.java [moved from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/DropZone.java with 96% similarity]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INodeProxy.java [moved from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/INodeProxy.java with 98% similarity]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java [moved from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/IViewRule.java with 98% similarity]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Point.java [moved from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/Point.java with 92% similarity]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java [moved from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/Rect.java with 97% similarity]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java

index cbaa73c..ceab48f 100755 (executable)
 
 package com.android.adt.gscripts;
 
-import com.android.ide.eclipse.adt.gscripts.IViewRule;
-import com.android.ide.eclipse.adt.gscripts.INodeProxy;
-import com.android.ide.eclipse.adt.gscripts.DropZone;
-import com.android.ide.eclipse.adt.gscripts.Rect;
-import com.android.ide.eclipse.adt.gscripts.Point;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
 
 import java.util.Map;
 import java.util.ArrayList;
index a94005b..2884af2 100755 (executable)
 
 package com.android.adt.gscripts;
 
-import com.android.ide.eclipse.adt.gscripts.IViewRule;
-import com.android.ide.eclipse.adt.gscripts.INodeProxy;
-import com.android.ide.eclipse.adt.gscripts.DropZone;
-import com.android.ide.eclipse.adt.gscripts.Rect;
-import com.android.ide.eclipse.adt.gscripts.Point;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
 
 import java.util.Map;
 import java.util.ArrayList;
index 5df24eb..3ce2bb3 100755 (executable)
 
 package com.android.adt.gscripts;
 
-import com.android.ide.eclipse.adt.gscripts.IViewRule;
-import com.android.ide.eclipse.adt.gscripts.INodeProxy;
-import com.android.ide.eclipse.adt.gscripts.DropZone;
-import com.android.ide.eclipse.adt.gscripts.Rect;
-import com.android.ide.eclipse.adt.gscripts.Point;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
 
 import java.util.Map;
 import java.util.ArrayList;
index 8a78824..17713aa 100755 (executable)
 
 package com.android.adt.gscripts;
 
-import com.android.ide.eclipse.adt.gscripts.IViewRule;
-import com.android.ide.eclipse.adt.gscripts.INodeProxy;
-import com.android.ide.eclipse.adt.gscripts.DropZone;
-import com.android.ide.eclipse.adt.gscripts.Rect;
-import com.android.ide.eclipse.adt.gscripts.Point;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
 
 import java.util.Map;
 import java.util.ArrayList;
index 36a80fb..f070811 100755 (executable)
@@ -17,7 +17,7 @@
 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
 
 import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
 
 import org.eclipse.swt.dnd.DND;
@@ -163,7 +163,7 @@ import java.util.ArrayList;
 
         Point p = eventToCanvasPoint(event);
         mCanvas.getRulesEngine().dropFinish(viewFqcn, mTargetNode, mCurrentZone,
-                new com.android.ide.eclipse.adt.gscripts.Point(p.x, p.y));
+                new com.android.ide.eclipse.adt.editors.layout.gscripts.Point(p.x, p.y));
 
         clearDropInfo();
     }
index 5c16b93..c277c25 100755 (executable)
-/*\r
- * Copyright (C) 2009 The Android Open Source Project\r
- *\r
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.eclipse.org/org/documents/epl-v10.php\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.android.ide.eclipse.adt.internal.editors.layout.gle2;\r
-\r
-import com.android.ide.eclipse.adt.AdtPlugin;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;\r
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;\r
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;\r
-import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;\r
-import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;\r
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;\r
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;\r
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;\r
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;\r
-import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;\r
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;\r
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;\r
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;\r
-import com.android.layoutlib.api.ILayoutBridge;\r
-import com.android.layoutlib.api.ILayoutLog;\r
-import com.android.layoutlib.api.ILayoutResult;\r
-import com.android.layoutlib.api.IProjectCallback;\r
-import com.android.layoutlib.api.IResourceValue;\r
-import com.android.layoutlib.api.IXmlPullParser;\r
-import com.android.sdklib.IAndroidTarget;\r
-\r
-import org.eclipse.core.resources.IFile;\r
-import org.eclipse.core.resources.IFolder;\r
-import org.eclipse.core.resources.IProject;\r
-import org.eclipse.core.resources.IResource;\r
-import org.eclipse.core.runtime.CoreException;\r
-import org.eclipse.core.runtime.IProgressMonitor;\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.core.runtime.jobs.Job;\r
-import org.eclipse.draw2d.geometry.Rectangle;\r
-import org.eclipse.gef.ui.parts.SelectionSynchronizer;\r
-import org.eclipse.jface.action.Action;\r
-import org.eclipse.jface.dialogs.Dialog;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.custom.SashForm;\r
-import org.eclipse.swt.custom.StyledText;\r
-import org.eclipse.swt.dnd.Clipboard;\r
-import org.eclipse.swt.layout.GridData;\r
-import org.eclipse.swt.layout.GridLayout;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.ui.IActionBars;\r
-import org.eclipse.ui.IEditorInput;\r
-import org.eclipse.ui.IEditorSite;\r
-import org.eclipse.ui.PartInitException;\r
-import org.eclipse.ui.actions.ActionFactory;\r
-import org.eclipse.ui.ide.IDE;\r
-import org.eclipse.ui.part.EditorPart;\r
-import org.eclipse.ui.part.FileEditorInput;\r
-\r
-import java.io.File;\r
-import java.io.FileOutputStream;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.io.PrintStream;\r
-import java.util.Map;\r
-\r
-/**\r
- * Graphical layout editor part, version 2.\r
- *\r
- * @since GLE2\r
- *\r
- * TODO List:\r
- * - display error icon\r
- * - finish palette (see palette's todo list)\r
- * - finish canvas (see canva's todo list)\r
- * - completly rethink the property panel\r
- * - link to the existing outline editor (prolly reuse/adapt for simplier model and will need\r
- *   to adapt the selection synchronizer.)\r
- */\r
-public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutEditor {\r
-\r
-    /*\r
-     * Useful notes:\r
-     * To understand Drag'n'drop:\r
-     *   http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html\r
-     */\r
-\r
-    /** Reference to the layout editor */\r
-    private final LayoutEditor mLayoutEditor;\r
-\r
-    /** reference to the file being edited. Can also be used to access the {@link IProject}. */\r
-    private IFile mEditedFile;\r
-\r
-    /** The current clipboard. Must be disposed later. */\r
-    private Clipboard mClipboard;\r
-\r
-    /** The configuration composite at the top of the layout editor. */\r
-    private ConfigurationComposite mConfigComposite;\r
-\r
-    /** The sash that splits the palette from the canvas. */\r
-    private SashForm mSashPalette;\r
-    private SashForm mSashError;\r
-\r
-    /** The palette displayed on the left of the sash. */\r
-    private PaletteComposite mPalette;\r
-\r
-    /** The layout canvas displayed to the right of the sash. */\r
-    private LayoutCanvas mLayoutCanvas;\r
-\r
-    /** The Groovy Rules Engine associated with this editor. It is project-specific. */\r
-    private RulesEngine mRulesEngine;\r
-\r
-    private StyledText mErrorLabel;\r
-\r
-    private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes;\r
-    private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes;\r
-    private ProjectCallback mProjectCallback;\r
-    private ILayoutLog mLogger;\r
-\r
-    private boolean mNeedsXmlReload = false;\r
-    private boolean mNeedsRecompute = false;\r
-\r
-    private TargetListener mTargetListener;\r
-\r
-    private ConfigListener mConfigListener;\r
-\r
-    private ReloadListener mReloadListener;\r
-\r
-    protected boolean mUseExplodeMode;\r
-\r
-\r
-    public GraphicalEditorPart(LayoutEditor layoutEditor) {\r
-        mLayoutEditor = layoutEditor;\r
-        setPartName("Graphical Layout");\r
-    }\r
-\r
-    // ------------------------------------\r
-    // Methods overridden from base classes\r
-    //------------------------------------\r
-\r
-    /**\r
-     * Initializes the editor part with a site and input.\r
-     * {@inheritDoc}\r
-     */\r
-    @Override\r
-    public void init(IEditorSite site, IEditorInput input) throws PartInitException {\r
-        setSite(site);\r
-        useNewEditorInput(input);\r
-\r
-        if (mTargetListener == null) {\r
-            mTargetListener = new TargetListener();\r
-            AdtPlugin.getDefault().addTargetListener(mTargetListener);\r
-        }\r
-    }\r
-\r
-    private void useNewEditorInput(IEditorInput input) throws PartInitException {\r
-        // The contract of init() mentions we need to fail if we can't understand the input.\r
-        if (!(input instanceof FileEditorInput)) {\r
-            throw new PartInitException("Input is not of type FileEditorInput: " +  //$NON-NLS-1$\r
-                    input == null ? "null" : input.toString());                     //$NON-NLS-1$\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public void createPartControl(Composite parent) {\r
-\r
-        Display d = parent.getDisplay();\r
-        mClipboard = new Clipboard(d);\r
-\r
-        GridLayout gl = new GridLayout(1, false);\r
-        parent.setLayout(gl);\r
-        gl.marginHeight = gl.marginWidth = 0;\r
-\r
-        // create the top part for the configuration control\r
-\r
-        CustomToggle[] toggles = new CustomToggle[] {\r
-                new CustomToggle(\r
-                        "Explode",\r
-                        null, //image\r
-                        "Displays extra margins in the layout."\r
-                        ) {\r
-                    @Override\r
-                    public void onSelected(boolean newState) {\r
-                        mUseExplodeMode = newState;\r
-                        recomputeLayout();\r
-                    }\r
-                },\r
-                new CustomToggle(\r
-                        "Outline",\r
-                        null, //image\r
-                        "Shows the of all views in the layout."\r
-                        ) {\r
-                    @Override\r
-                    public void onSelected(boolean newState) {\r
-                        mLayoutCanvas.setShowOutline(newState);\r
-                    }\r
-                }\r
-        };\r
-\r
-        mConfigListener = new ConfigListener();\r
-        mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER);\r
-\r
-        mSashPalette = new SashForm(parent, SWT.HORIZONTAL);\r
-        mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));\r
-\r
-        mPalette = new PaletteComposite(mSashPalette);\r
-\r
-        mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER);\r
-        mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));\r
-\r
-        mLayoutCanvas = new LayoutCanvas(mRulesEngine, mSashError, SWT.NONE);\r
-\r
-        mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY);\r
-        mErrorLabel.setEditable(false);\r
-        mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));\r
-        mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND));\r
-\r
-        mSashPalette.setWeights(new int[] { 20, 80 });\r
-        mSashError.setWeights(new int[] { 80, 20 });\r
-        mSashError.setMaximizedControl(mLayoutCanvas);\r
-\r
-        setupEditActions();\r
-\r
-        // Initialize the state\r
-        reloadPalette();\r
-    }\r
-\r
-    private void setupEditActions() {\r
-\r
-        IActionBars actionBars = getEditorSite().getActionBars();\r
-\r
-        actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), new Action("Copy") {\r
-            @Override\r
-            public void run() {\r
-                // TODO enable copy only when there's a selection\r
-                mLayoutCanvas.onCopy(mClipboard);\r
-            }\r
-        });\r
-\r
-        actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), new Action("Cut") {\r
-            @Override\r
-            public void run() {\r
-                // TODO enable cut only when there's a selection\r
-                mLayoutCanvas.onCut(mClipboard);\r
-            }\r
-        });\r
-\r
-        actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new Action("Paste") {\r
-            @Override\r
-            public void run() {\r
-                mLayoutCanvas.onPaste(mClipboard);\r
-            }\r
-        });\r
-\r
-        actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),\r
-                new Action("Select All") {\r
-            @Override\r
-            public void run() {\r
-                mLayoutCanvas.onSelectAll();\r
-            }\r
-        });\r
-    }\r
-\r
-    /**\r
-     * Switches the stack to display the error label and hide the canvas.\r
-     * @param errorFormat The new error to display if not null.\r
-     * @param parameters String.format parameters for the error format.\r
-     */\r
-    private void displayError(String errorFormat, Object...parameters) {\r
-        if (errorFormat != null) {\r
-            mErrorLabel.setText(String.format(errorFormat, parameters));\r
-        }\r
-        mSashError.setMaximizedControl(null);\r
-    }\r
-\r
-    /** Displays the canvas and hides the error label. */\r
-    private void hideError() {\r
-        mSashError.setMaximizedControl(mLayoutCanvas);\r
-    }\r
-\r
-    @Override\r
-    public void dispose() {\r
-        if (mTargetListener != null) {\r
-            AdtPlugin.getDefault().removeTargetListener(mTargetListener);\r
-            mTargetListener = null;\r
-        }\r
-\r
-        if (mReloadListener != null) {\r
-            LayoutReloadMonitor.getMonitor().removeListener(mReloadListener);\r
-            mReloadListener = null;\r
-        }\r
-\r
-        if (mClipboard != null) {\r
-            mClipboard.dispose();\r
-            mClipboard = null;\r
-        }\r
-\r
-        super.dispose();\r
-    }\r
-\r
-    /**\r
-     * Listens to changes from the Configuration UI banner and triggers layout rendering when\r
-     * changed. Also provide the Configuration UI with the list of resources/layout to display.\r
-     */\r
-    private class ConfigListener implements IConfigListener {\r
-\r
-        /**\r
-         * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.\r
-         * <p/>If there is no match, notify the user.\r
-         */\r
-        public void onConfigurationChange() {\r
-            mConfiguredFrameworkRes = mConfiguredProjectRes = null;\r
-\r
-            if (mEditedFile == null || mConfigComposite.getEditedConfig() == null) {\r
-                return;\r
-            }\r
-\r
-            // Before doing the normal process, test for the following case.\r
-            // - the editor is being opened (or reset for a new input)\r
-            // - the file being opened is not the best match for any possible configuration\r
-            // - another random compatible config was chosen in the config composite.\r
-            // The result is that 'match' will not be the file being edited, but because this is not\r
-            // due to a config change, we should not trigger opening the actual best match (also,\r
-            // because the editor is still opening the MatchingStrategy woudln't answer true\r
-            // and the best match file would open in a different editor).\r
-            // So the solution is that if the editor is being created, we just call recomputeLayout\r
-            // without looking for a better matching layout file.\r
-            if (mLayoutEditor.isCreatingPages()) {\r
-                recomputeLayout();\r
-            } else {\r
-                // get the resources of the file's project.\r
-                ProjectResources resources = ResourceManager.getInstance().getProjectResources(\r
-                        mEditedFile.getProject());\r
-\r
-                // from the resources, look for a matching file\r
-                ResourceFile match = null;\r
-                if (resources != null) {\r
-                    match = resources.getMatchingFile(mEditedFile.getName(),\r
-                                                      ResourceFolderType.LAYOUT,\r
-                                                      mConfigComposite.getCurrentConfig());\r
-                }\r
-\r
-                if (match != null) {\r
-                    if (match.getFile().equals(mEditedFile) == false) {\r
-                        try {\r
-                            // tell the editor that the next replacement file is due to a config\r
-                            // change.\r
-                            mLayoutEditor.setNewFileOnConfigChange(true);\r
-\r
-                            // ask the IDE to open the replacement file.\r
-                            IDE.openEditor(\r
-                                    getSite().getWorkbenchWindow().getActivePage(),\r
-                                    match.getFile().getIFile());\r
-\r
-                            // we're done!\r
-                            return;\r
-                        } catch (PartInitException e) {\r
-                            // FIXME: do something!\r
-                        }\r
-                    }\r
-\r
-                    // at this point, we have not opened a new file.\r
-\r
-                    // Even though the layout doesn't change, the config changed, and referenced\r
-                    // resources need to be updated.\r
-                    recomputeLayout();\r
-                } else {\r
-                    // display the error.\r
-                    FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();\r
-                    displayError(\r
-                            "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.",\r
-                            currentConfig.toDisplayString(),\r
-                            currentConfig.getFolderName(ResourceFolderType.LAYOUT,\r
-                                    Sdk.getCurrent().getTarget(mEditedFile.getProject())),\r
-                            mEditedFile.getName());\r
-                }\r
-            }\r
-        }\r
-\r
-        public void onThemeChange() {\r
-            recomputeLayout();\r
-        }\r
-\r
-        public void onClippingChange() {\r
-            recomputeLayout();\r
-        }\r
-\r
-        public void onCreate() {\r
-            LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(),\r
-                    mEditedFile.getName(),\r
-                    Sdk.getCurrent().getTarget(mEditedFile.getProject()),\r
-                    mConfigComposite.getCurrentConfig());\r
-            if (dialog.open() == Dialog.OK) {\r
-                final FolderConfiguration config = new FolderConfiguration();\r
-                dialog.getConfiguration(config);\r
-\r
-                createAlternateLayout(config);\r
-            }\r
-        }\r
-\r
-        public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {\r
-            if (mConfiguredFrameworkRes == null && mConfigComposite != null) {\r
-                ProjectResources frameworkRes = getFrameworkResources();\r
-\r
-                if (frameworkRes == null) {\r
-                    AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");\r
-                } else {\r
-                    // get the framework resource values based on the current config\r
-                    mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(\r
-                            mConfigComposite.getCurrentConfig());\r
-                }\r
-            }\r
-\r
-            return mConfiguredFrameworkRes;\r
-        }\r
-\r
-        public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {\r
-            if (mConfiguredProjectRes == null && mConfigComposite != null) {\r
-                ProjectResources project = getProjectResources();\r
-\r
-                // make sure they are loaded\r
-                project.loadAll();\r
-\r
-                // get the project resource values based on the current config\r
-                mConfiguredProjectRes = project.getConfiguredResources(\r
-                        mConfigComposite.getCurrentConfig());\r
-            }\r
-\r
-            return mConfiguredProjectRes;\r
-        }\r
-\r
-        /**\r
-         * Returns a {@link ProjectResources} for the framework resources.\r
-         * @return the framework resources or null if not found.\r
-         */\r
-        public ProjectResources getFrameworkResources() {\r
-            if (mEditedFile != null) {\r
-                Sdk currentSdk = Sdk.getCurrent();\r
-                if (currentSdk != null) {\r
-                    IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());\r
-\r
-                    if (target != null) {\r
-                        AndroidTargetData data = currentSdk.getTargetData(target);\r
-\r
-                        if (data != null) {\r
-                            return data.getFrameworkResources();\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-\r
-            return null;\r
-        }\r
-\r
-        public ProjectResources getProjectResources() {\r
-            if (mEditedFile != null) {\r
-                ResourceManager manager = ResourceManager.getInstance();\r
-                return manager.getProjectResources(mEditedFile.getProject());\r
-            }\r
-\r
-            return null;\r
-        }\r
-\r
-        /**\r
-         * Creates a new layout file from the specified {@link FolderConfiguration}.\r
-         */\r
-        private void createAlternateLayout(final FolderConfiguration config) {\r
-            new Job("Create Alternate Resource") {\r
-                @Override\r
-                protected IStatus run(IProgressMonitor monitor) {\r
-                    // get the folder name\r
-                    String folderName = config.getFolderName(ResourceFolderType.LAYOUT,\r
-                            Sdk.getCurrent().getTarget(mEditedFile.getProject()));\r
-                    try {\r
-\r
-                        // look to see if it exists.\r
-                        // get the res folder\r
-                        IFolder res = (IFolder)mEditedFile.getParent().getParent();\r
-                        String path = res.getLocation().toOSString();\r
-\r
-                        File newLayoutFolder = new File(path + File.separator + folderName);\r
-                        if (newLayoutFolder.isFile()) {\r
-                            // this should not happen since aapt would have complained\r
-                            // before, but if one disable the automatic build, this could\r
-                            // happen.\r
-                            String message = String.format("File 'res/%1$s' is in the way!",\r
-                                    folderName);\r
-\r
-                            AdtPlugin.displayError("Layout Creation", message);\r
-\r
-                            return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);\r
-                        } else if (newLayoutFolder.exists() == false) {\r
-                            // create it.\r
-                            newLayoutFolder.mkdir();\r
-                        }\r
-\r
-                        // now create the file\r
-                        File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +\r
-                                    File.separator + mEditedFile.getName());\r
-\r
-                        newLayoutFile.createNewFile();\r
-\r
-                        InputStream input = mEditedFile.getContents();\r
-\r
-                        FileOutputStream fos = new FileOutputStream(newLayoutFile);\r
-\r
-                        byte[] data = new byte[512];\r
-                        int count;\r
-                        while ((count = input.read(data)) != -1) {\r
-                            fos.write(data, 0, count);\r
-                        }\r
-\r
-                        input.close();\r
-                        fos.close();\r
-\r
-                        // refreshes the res folder to show up the new\r
-                        // layout folder (if needed) and the file.\r
-                        // We use a progress monitor to catch the end of the refresh\r
-                        // to trigger the edit of the new file.\r
-                        res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {\r
-                            public void done() {\r
-                                mConfigComposite.getDisplay().asyncExec(new Runnable() {\r
-                                    public void run() {\r
-                                        onConfigurationChange();\r
-                                    }\r
-                                });\r
-                            }\r
-\r
-                            public void beginTask(String name, int totalWork) {\r
-                                // pass\r
-                            }\r
-\r
-                            public void internalWorked(double work) {\r
-                                // pass\r
-                            }\r
-\r
-                            public boolean isCanceled() {\r
-                                // pass\r
-                                return false;\r
-                            }\r
-\r
-                            public void setCanceled(boolean value) {\r
-                                // pass\r
-                            }\r
-\r
-                            public void setTaskName(String name) {\r
-                                // pass\r
-                            }\r
-\r
-                            public void subTask(String name) {\r
-                                // pass\r
-                            }\r
-\r
-                            public void worked(int work) {\r
-                                // pass\r
-                            }\r
-                        });\r
-                    } catch (IOException e2) {\r
-                        String message = String.format(\r
-                                "Failed to create File 'res/%1$s/%2$s' : %3$s",\r
-                                folderName, mEditedFile.getName(), e2.getMessage());\r
-\r
-                        AdtPlugin.displayError("Layout Creation", message);\r
-\r
-                        return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,\r
-                                message, e2);\r
-                    } catch (CoreException e2) {\r
-                        String message = String.format(\r
-                                "Failed to create File 'res/%1$s/%2$s' : %3$s",\r
-                                folderName, mEditedFile.getName(), e2.getMessage());\r
-\r
-                        AdtPlugin.displayError("Layout Creation", message);\r
-\r
-                        return e2.getStatus();\r
-                    }\r
-\r
-                    return Status.OK_STATUS;\r
-\r
-                }\r
-            }.schedule();\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Listens to target changed in the current project, to trigger a new layout rendering.\r
-     */\r
-    private class TargetListener implements ITargetChangeListener {\r
-\r
-        public void onProjectTargetChange(IProject changedProject) {\r
-            if (changedProject != null && changedProject.equals(getProject())) {\r
-                updateEditor();\r
-            }\r
-        }\r
-\r
-        public void onTargetLoaded(IAndroidTarget target) {\r
-            IProject project = getProject();\r
-            if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {\r
-                updateEditor();\r
-            }\r
-        }\r
-\r
-        public void onSdkLoaded() {\r
-            Sdk currentSdk = Sdk.getCurrent();\r
-            if (currentSdk != null) {\r
-                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());\r
-                if (target != null) {\r
-                    mConfigComposite.onSdkLoaded(target);\r
-                    mConfigListener.onConfigurationChange();\r
-                }\r
-            }\r
-        }\r
-\r
-        private void updateEditor() {\r
-            mLayoutEditor.commitPages(false /* onSave */);\r
-\r
-            // because the target changed we must reset the configured resources.\r
-            mConfiguredFrameworkRes = mConfiguredProjectRes = null;\r
-\r
-            // make sure we remove the custom view loader, since its parent class loader is the\r
-            // bridge class loader.\r
-            mProjectCallback = null;\r
-\r
-            // recreate the ui root node always, this will also call onTargetChange\r
-            // on the config composite\r
-            mLayoutEditor.initUiRootNode(true /*force*/);\r
-        }\r
-\r
-        private IProject getProject() {\r
-            return getLayoutEditor().getProject();\r
-        }\r
-    }\r
-\r
-    // ----------------\r
-\r
-    /**\r
-     * Save operation in the Graphical Editor Part.\r
-     * <p/>\r
-     * In our workflow, the model is owned by the Structured XML Editor.\r
-     * The graphical layout editor just displays it -- thus we don't really\r
-     * save anything here.\r
-     * <p/>\r
-     * This must NOT call the parent editor part. At the contrary, the parent editor\r
-     * part will call this *after* having done the actual save operation.\r
-     * <p/>\r
-     * The only action this editor must do is mark the undo command stack as\r
-     * being no longer dirty.\r
-     */\r
-    @Override\r
-    public void doSave(IProgressMonitor monitor) {\r
-        // TODO implement a command stack\r
-//        getCommandStack().markSaveLocation();\r
-//        firePropertyChange(PROP_DIRTY);\r
-    }\r
-\r
-    /**\r
-     * Save operation in the Graphical Editor Part.\r
-     * <p/>\r
-     * In our workflow, the model is owned by the Structured XML Editor.\r
-     * The graphical layout editor just displays it -- thus we don't really\r
-     * save anything here.\r
-     */\r
-    @Override\r
-    public void doSaveAs() {\r
-        // pass\r
-    }\r
-\r
-    /**\r
-     * In our workflow, the model is owned by the Structured XML Editor.\r
-     * The graphical layout editor just displays it -- thus we don't really\r
-     * save anything here.\r
-     */\r
-    @Override\r
-    public boolean isDirty() {\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * In our workflow, the model is owned by the Structured XML Editor.\r
-     * The graphical layout editor just displays it -- thus we don't really\r
-     * save anything here.\r
-     */\r
-    @Override\r
-    public boolean isSaveAsAllowed() {\r
-        return false;\r
-    }\r
-\r
-    @Override\r
-    public void setFocus() {\r
-        // TODO Auto-generated method stub\r
-\r
-    }\r
-\r
-    /**\r
-     * Responds to a page change that made the Graphical editor page the activated page.\r
-     */\r
-    public void activated() {\r
-        if (mNeedsRecompute || mNeedsXmlReload) {\r
-            recomputeLayout();\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Responds to a page change that made the Graphical editor page the deactivated page\r
-     */\r
-    public void deactivated() {\r
-        // nothing to be done here for now.\r
-    }\r
-\r
-    /**\r
-     * Opens and initialize the editor with a new file.\r
-     * @param file the file being edited.\r
-     */\r
-    public void openFile(IFile file) {\r
-        mEditedFile = file;\r
-        mConfigComposite.openFile(mEditedFile);\r
-\r
-        if (mReloadListener == null) {\r
-            mReloadListener = new ReloadListener();\r
-            LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);\r
-        }\r
-\r
-        if (mRulesEngine == null) {\r
-            mRulesEngine = new RulesEngine(mEditedFile.getProject());\r
-            if (mLayoutCanvas != null) {\r
-                mLayoutCanvas.setRulesEngine(mRulesEngine);\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Resets the editor with a replacement file.\r
-     * @param file the replacement file.\r
-     */\r
-    public void replaceFile(IFile file) {\r
-        mEditedFile = file;\r
-        mConfigComposite.replaceFile(mEditedFile);\r
-    }\r
-\r
-    /**\r
-     * Resets the editor with a replacement file coming from a config change in the config\r
-     * selector.\r
-     * @param file the replacement file.\r
-     */\r
-    public void changeFileOnNewConfig(IFile file) {\r
-        mEditedFile = file;\r
-        mConfigComposite.changeFileOnNewConfig(mEditedFile);\r
-    }\r
-\r
-    public void onTargetChange() {\r
-        mConfigComposite.onTargetChange();\r
-        mConfigListener.onConfigurationChange();\r
-    }\r
-\r
-    public void onSdkChange() {\r
-        Sdk currentSdk = Sdk.getCurrent();\r
-        if (currentSdk != null) {\r
-            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());\r
-            if (target != null) {\r
-                mConfigComposite.onSdkLoaded(target);\r
-                mConfigListener.onConfigurationChange();\r
-            }\r
-        }\r
-    }\r
-\r
-    public Clipboard getClipboard() {\r
-        return mClipboard;\r
-    }\r
-\r
-    public LayoutEditor getLayoutEditor() {\r
-        return mLayoutEditor;\r
-    }\r
-\r
-    public UiDocumentNode getModel() {\r
-        return mLayoutEditor.getUiRootNode();\r
-    }\r
-\r
-    public SelectionSynchronizer getSelectionSynchronizer() {\r
-        // TODO Auto-generated method stub\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Callback for XML model changed. Only update/recompute the layout if the editor is visible\r
-     */\r
-    public void onXmlModelChanged() {\r
-        if (mLayoutEditor.isGraphicalEditorActive()) {\r
-            doXmlReload(true /* force */);\r
-            recomputeLayout();\r
-        } else {\r
-            mNeedsXmlReload = true;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Actually performs the XML reload\r
-     * @see #onXmlModelChanged()\r
-     */\r
-    private void doXmlReload(boolean force) {\r
-        if (force || mNeedsXmlReload) {\r
-\r
-            // TODO : update the mLayoutCanvas, preserving the current selection if possible.\r
-\r
-//            GraphicalViewer viewer = getGraphicalViewer();\r
-//\r
-//            // try to preserve the selection before changing the content\r
-//            SelectionManager selMan = viewer.getSelectionManager();\r
-//            ISelection selection = selMan.getSelection();\r
-//\r
-//            try {\r
-//                viewer.setContents(getModel());\r
-//            } finally {\r
-//                selMan.setSelection(selection);\r
-//            }\r
-\r
-            mNeedsXmlReload = false;\r
-        }\r
-    }\r
-\r
-    public void recomputeLayout() {\r
-        doXmlReload(false /* force */);\r
-        try {\r
-            // check that the resource exists. If the file is opened but the project is closed\r
-            // or deleted for some reason (changed from outside of eclipse), then this will\r
-            // return false;\r
-            if (mEditedFile.exists() == false) {\r
-                displayError("Resource '%1$s' does not exist.",\r
-                             mEditedFile.getFullPath().toString());\r
-                return;\r
-            }\r
-\r
-            IProject iProject = mEditedFile.getProject();\r
-\r
-            if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {\r
-                String message = String.format("%1$s is out of sync. Please refresh.",\r
-                        mEditedFile.getName());\r
-\r
-                displayError(message);\r
-\r
-                // also print it in the error console.\r
-                AdtPlugin.printErrorToConsole(iProject.getName(), message);\r
-                return;\r
-            }\r
-\r
-            Sdk currentSdk = Sdk.getCurrent();\r
-            if (currentSdk != null) {\r
-                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());\r
-                if (target == null) {\r
-                    displayError("The project target is not set.");\r
-                    return;\r
-                }\r
-\r
-                AndroidTargetData data = currentSdk.getTargetData(target);\r
-                if (data == null) {\r
-                    // It can happen that the workspace refreshes while the SDK is loading its\r
-                    // data, which could trigger a redraw of the opened layout if some resources\r
-                    // changed while Eclipse is closed.\r
-                    // In this case data could be null, but this is not an error.\r
-                    // We can just silently return, as all the opened editors are automatically\r
-                    // refreshed once the SDK finishes loading.\r
-                    LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);\r
-                    switch (targetLoadStatus) {\r
-                        case LOADING:\r
-                            displayError("The project target (%1$s) is still loading.\n%2$s will refresh automatically once the process is finished.",\r
-                                    target.getName(), mEditedFile.getName());\r
-\r
-                            break;\r
-                        case FAILED: // known failure\r
-                        case LOADED: // success but data isn't loaded?!?!\r
-                            displayError("The project target (%s) was not properly loaded.",\r
-                                    target.getName());\r
-                            break;\r
-                    }\r
-\r
-                    return;\r
-                }\r
-\r
-                // check there is actually a model (maybe the file is empty).\r
-                UiDocumentNode model = getModel();\r
-\r
-                if (model.getUiChildren().size() == 0) {\r
-                    displayError("No Xml content. Go to the Outline view and add nodes.");\r
-                    return;\r
-                }\r
-\r
-                LayoutBridge bridge = data.getLayoutBridge();\r
-\r
-                if (bridge.bridge != null) { // bridge can never be null.\r
-                    ResourceManager resManager = ResourceManager.getInstance();\r
-\r
-                    ProjectResources projectRes = resManager.getProjectResources(iProject);\r
-                    if (projectRes == null) {\r
-                        displayError("Missing project resources.");\r
-                        return;\r
-                    }\r
-\r
-                    // get the resources of the file's project.\r
-                    Map<String, Map<String, IResourceValue>> configuredProjectRes =\r
-                        mConfigListener.getConfiguredProjectResources();\r
-\r
-                    // get the framework resources\r
-                    Map<String, Map<String, IResourceValue>> frameworkResources =\r
-                        mConfigListener.getConfiguredFrameworkResources();\r
-\r
-                    if (configuredProjectRes != null && frameworkResources != null) {\r
-                        if (mProjectCallback == null) {\r
-                            mProjectCallback = new ProjectCallback(\r
-                                    bridge.classLoader, projectRes, iProject);\r
-                        }\r
-\r
-                        if (mLogger == null) {\r
-                            mLogger = new ILayoutLog() {\r
-                                public void error(String message) {\r
-                                    AdtPlugin.printErrorToConsole(mEditedFile.getName(), message);\r
-                                }\r
-\r
-                                public void error(Throwable error) {\r
-                                    String message = error.getMessage();\r
-                                    if (message == null) {\r
-                                        message = error.getClass().getName();\r
-                                    }\r
-\r
-                                    PrintStream ps = new PrintStream(AdtPlugin.getErrorStream());\r
-                                    error.printStackTrace(ps);\r
-                                }\r
-\r
-                                public void warning(String message) {\r
-                                    AdtPlugin.printToConsole(mEditedFile.getName(), message);\r
-                                }\r
-                            };\r
-                        }\r
-\r
-                        // get the selected theme\r
-                        String theme = mConfigComposite.getTheme();\r
-                        if (theme != null) {\r
-                            // Compute the layout\r
-                            Rectangle rect = getBounds();\r
-\r
-                            int width = rect.width;\r
-                            int height = rect.height;\r
-                            if (mUseExplodeMode) {\r
-                                // compute how many padding in x and y will bump the screen size\r
-                                ExplodedRenderingHelper helper = new ExplodedRenderingHelper(\r
-                                        getModel(), iProject);\r
-\r
-                                // there are 2 paddings for each view\r
-                                // left and right, or top and bottom.\r
-                                int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;\r
-\r
-                                width += helper.getWidthPadding() * paddingValue;\r
-                                height += helper.getHeightPadding() * paddingValue;\r
-                            }\r
-\r
-                            int density = mConfigComposite.getDensity().getDpiValue();\r
-                            float xdpi = mConfigComposite.getXDpi();\r
-                            float ydpi = mConfigComposite.getYDpi();\r
-                            boolean isProjectTheme = mConfigComposite.isProjectTheme();\r
-\r
-                            UiElementPullParser parser = new UiElementPullParser(getModel(),\r
-                                    mUseExplodeMode, density, xdpi, iProject);\r
-\r
-                            ILayoutResult result = computeLayout(bridge, parser,\r
-                                    iProject /* projectKey */,\r
-                                    width, height, !mConfigComposite.getClipping(),\r
-                                    density, xdpi, ydpi,\r
-                                    theme, isProjectTheme,\r
-                                    configuredProjectRes, frameworkResources, mProjectCallback,\r
-                                    mLogger);\r
-\r
-                            mLayoutCanvas.setResult(result);\r
-\r
-                            // update the UiElementNode with the layout info.\r
-                            if (result.getSuccess() == ILayoutResult.SUCCESS) {\r
-                                hideError();\r
-                            } else {\r
-                                displayError(result.getErrorMessage());\r
-                            }\r
-\r
-                            model.refreshUi();\r
-                        }\r
-                    }\r
-                } else {\r
-                    // SDK is loaded but not the layout library!\r
-\r
-                    // check whether the bridge managed to load, or not\r
-                    if (bridge.status == LoadStatus.LOADING) {\r
-                        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.",\r
-                                     mEditedFile.getName());\r
-                    } else {\r
-                        displayError("Eclipse failed to load the framework information and the layout library!");\r
-                    }\r
-                }\r
-            } else {\r
-                displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",\r
-                             mEditedFile.getName());\r
-            }\r
-        } finally {\r
-            // no matter the result, we are done doing the recompute based on the latest\r
-            // resource/code change.\r
-            mNeedsRecompute = false;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on\r
-     * the implementation API level.\r
-     *\r
-     * Implementation detail: the bridge's computeLayout() method already returns a newly\r
-     * allocated ILayoutResult.\r
-     */\r
-    @SuppressWarnings("deprecation")\r
-    private static ILayoutResult computeLayout(LayoutBridge bridge,\r
-            IXmlPullParser layoutDescription, Object projectKey,\r
-            int screenWidth, int screenHeight, boolean renderFullSize,\r
-            int density, float xdpi, float ydpi,\r
-            String themeName, boolean isProjectTheme,\r
-            Map<String, Map<String, IResourceValue>> projectResources,\r
-            Map<String, Map<String, IResourceValue>> frameworkResources,\r
-            IProjectCallback projectCallback, ILayoutLog logger) {\r
-\r
-        if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) {\r
-            // newest API with support for "render full height"\r
-            // TODO: link boolean to UI.\r
-            return bridge.bridge.computeLayout(layoutDescription,\r
-                    projectKey, screenWidth, screenHeight, renderFullSize,\r
-                    density, xdpi, ydpi,\r
-                    themeName, isProjectTheme,\r
-                    projectResources, frameworkResources, projectCallback,\r
-                    logger);\r
-        } else if (bridge.apiLevel == 3) {\r
-            // newer api with density support.\r
-            return bridge.bridge.computeLayout(layoutDescription,\r
-                    projectKey, screenWidth, screenHeight, density, xdpi, ydpi,\r
-                    themeName, isProjectTheme,\r
-                    projectResources, frameworkResources, projectCallback,\r
-                    logger);\r
-        } else if (bridge.apiLevel == 2) {\r
-            // api with boolean for separation of project/framework theme\r
-            return bridge.bridge.computeLayout(layoutDescription,\r
-                    projectKey, screenWidth, screenHeight, themeName, isProjectTheme,\r
-                    projectResources, frameworkResources, projectCallback,\r
-                    logger);\r
-        } else {\r
-            // oldest api with no density/dpi, and project theme boolean mixed\r
-            // into the theme name.\r
-\r
-            // change the string if it's a custom theme to make sure we can\r
-            // differentiate them\r
-            if (isProjectTheme) {\r
-                themeName = "*" + themeName; //$NON-NLS-1$\r
-            }\r
-\r
-            return bridge.bridge.computeLayout(layoutDescription,\r
-                    projectKey, screenWidth, screenHeight, themeName,\r
-                    projectResources, frameworkResources, projectCallback,\r
-                    logger);\r
-        }\r
-    }\r
-\r
-    public Rectangle getBounds() {\r
-        return mConfigComposite.getScreenBounds();\r
-    }\r
-\r
-    public void reloadPalette() {\r
-        if (mPalette != null) {\r
-            mPalette.reloadPalette(mLayoutEditor.getTargetData());\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node\r
-     * created by {@link ElementCreateCommand#execute()}.\r
-     *\r
-     * @param uiNodeModel The {@link UiElementNode} to select.\r
-     */\r
-    public void selectModel(UiElementNode uiNodeModel) {\r
-\r
-        // TODO this method was useful for GLE1. We may not need it anymore now.\r
-\r
-//        GraphicalViewer viewer = getGraphicalViewer();\r
-//\r
-//        // Give focus to the graphical viewer (in case the outline has it)\r
-//        viewer.getControl().forceFocus();\r
-//\r
-//        Object editPart = viewer.getEditPartRegistry().get(uiNodeModel);\r
-//\r
-//        if (editPart instanceof EditPart) {\r
-//            viewer.select((EditPart)editPart);\r
-//        }\r
-    }\r
-\r
-    private class ReloadListener implements ILayoutReloadListener {\r
-        /*\r
-         * Called when the file changes triggered a redraw of the layout\r
-         */\r
-        public void reloadLayout(ChangeFlags flags) {\r
-            boolean recompute = false;\r
-\r
-            if (flags.rClass) {\r
-                recompute = true;\r
-                if (mEditedFile != null) {\r
-                    ProjectResources projectRes = ResourceManager.getInstance().getProjectResources(\r
-                            mEditedFile.getProject());\r
-\r
-                    if (projectRes != null) {\r
-                        projectRes.resetDynamicIds();\r
-                    }\r
-                }\r
-            }\r
-\r
-            if (flags.localeList) {\r
-                // the locale list *potentially* changed so we update the locale in the\r
-                // config composite.\r
-                // However there's no recompute, as it could not be needed\r
-                // (for instance a new layout)\r
-                // If a resource that's not a layout changed this will trigger a recompute anyway.\r
-                mLayoutCanvas.getDisplay().asyncExec(new Runnable() {\r
-                    public void run() {\r
-                        mConfigComposite.updateLocales();\r
-                    }\r
-                });\r
-            }\r
-\r
-            if (flags.resources) {\r
-                recompute = true;\r
-\r
-                // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache.\r
-\r
-                // force a reparse in case a value XML file changed.\r
-                mConfiguredProjectRes = null;\r
-\r
-                // clear the cache in the bridge in case a bitmap/9-patch changed.\r
-                IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());\r
-                if (target != null) {\r
-\r
-                    AndroidTargetData data = Sdk.getCurrent().getTargetData(target);\r
-                    if (data != null) {\r
-                        LayoutBridge bridge = data.getLayoutBridge();\r
-\r
-                        if (bridge.bridge != null) {\r
-                            bridge.bridge.clearCaches(mEditedFile.getProject());\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-\r
-            if (flags.code) {\r
-                // only recompute if the custom view loader was used to load some code.\r
-                if (mProjectCallback != null && mProjectCallback.isUsed()) {\r
-                    mProjectCallback = null;\r
-                    recompute = true;\r
-                }\r
-            }\r
-\r
-            if (recompute) {\r
-                mLayoutCanvas.getDisplay().asyncExec(new Runnable() {\r
-                    public void run() {\r
-                        if (mLayoutEditor.isGraphicalEditorActive()) {\r
-                            recomputeLayout();\r
-                        } else {\r
-                            mNeedsRecompute = true;\r
-                        }\r
-                    }\r
-                });\r
-            }\r
-        }\r
-    }\r
-}\r
+/*
+ * 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.gle2;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
+import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
+import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
+import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
+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.LayoutCreatorDialog;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
+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.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.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.action.Action;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.custom.StyledText;
+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.Display;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.actions.ActionFactory;
+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.Map;
+
+/**
+ * Graphical layout editor part, version 2.
+ *
+ * @since GLE2
+ *
+ * TODO List:
+ * - display error icon
+ * - 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. Can also be used to access the {@link IProject}. */
+    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 mSashPalette;
+    private SashForm mSashError;
+
+    /** The palette displayed on the left of the sash. */
+    private PaletteComposite mPalette;
+
+    /** The layout canvas displayed to the right of the sash. */
+    private LayoutCanvas mLayoutCanvas;
+
+    /** The Groovy Rules Engine associated with this editor. It is project-specific. */
+    private RulesEngine mRulesEngine;
+
+    private StyledText mErrorLabel;
+
+    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 ReloadListener mReloadListener;
+
+    protected boolean mUseExplodeMode;
+
+
+    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);
+        }
+    }
+
+    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$
+        }
+    }
+
+    @Override
+    public void createPartControl(Composite parent) {
+
+        Display d = parent.getDisplay();
+        mClipboard = new Clipboard(d);
+
+        GridLayout gl = new GridLayout(1, false);
+        parent.setLayout(gl);
+        gl.marginHeight = gl.marginWidth = 0;
+
+        // create the top part for the configuration control
+
+        CustomToggle[] toggles = new CustomToggle[] {
+                new CustomToggle(
+                        "Explode",
+                        null, //image
+                        "Displays extra margins in the layout."
+                        ) {
+                    @Override
+                    public void onSelected(boolean newState) {
+                        mUseExplodeMode = newState;
+                        recomputeLayout();
+                    }
+                },
+                new CustomToggle(
+                        "Outline",
+                        null, //image
+                        "Shows the of all views in the layout."
+                        ) {
+                    @Override
+                    public void onSelected(boolean newState) {
+                        mLayoutCanvas.setShowOutline(newState);
+                    }
+                }
+        };
+
+        mConfigListener = new ConfigListener();
+        mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER);
+
+        mSashPalette = new SashForm(parent, SWT.HORIZONTAL);
+        mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+        mPalette = new PaletteComposite(mSashPalette);
+
+        mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER);
+        mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+        mLayoutCanvas = new LayoutCanvas(mRulesEngine, mSashError, SWT.NONE);
+
+        mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY);
+        mErrorLabel.setEditable(false);
+        mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
+        mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
+
+        mSashPalette.setWeights(new int[] { 20, 80 });
+        mSashError.setWeights(new int[] { 80, 20 });
+        mSashError.setMaximizedControl(mLayoutCanvas);
+
+        setupEditActions();
+
+        // Initialize the state
+        reloadPalette();
+    }
+
+    private void setupEditActions() {
+
+        IActionBars actionBars = getEditorSite().getActionBars();
+
+        actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), new Action("Copy") {
+            @Override
+            public void run() {
+                // TODO enable copy only when there's a selection
+                mLayoutCanvas.onCopy(mClipboard);
+            }
+        });
+
+        actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), new Action("Cut") {
+            @Override
+            public void run() {
+                // TODO enable cut only when there's a selection
+                mLayoutCanvas.onCut(mClipboard);
+            }
+        });
+
+        actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new Action("Paste") {
+            @Override
+            public void run() {
+                mLayoutCanvas.onPaste(mClipboard);
+            }
+        });
+
+        actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
+                new Action("Select All") {
+            @Override
+            public void run() {
+                mLayoutCanvas.onSelectAll();
+            }
+        });
+    }
+
+    /**
+     * 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));
+        }
+        mSashError.setMaximizedControl(null);
+    }
+
+    /** Displays the canvas and hides the error label. */
+    private void hideError() {
+        mSashError.setMaximizedControl(mLayoutCanvas);
+    }
+
+    @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();
+    }
+
+    /**
+     * Listens to changes from the Configuration UI banner and triggers layout rendering when
+     * changed. Also provide the Configuration UI with the list of resources/layout to display.
+     */
+    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 || mConfigComposite.getEditedConfig() == null) {
+                return;
+            }
+
+            // Before doing the normal process, test for the following case.
+            // - the editor is being opened (or reset for a new input)
+            // - the file being opened is not the best match for any possible configuration
+            // - another random compatible config was chosen in the config composite.
+            // The result is that 'match' will not be the file being edited, but because this is not
+            // due to a config change, we should not trigger opening the actual best match (also,
+            // because the editor is still opening the MatchingStrategy woudln't answer true
+            // and the best match file would open in a different editor).
+            // So the solution is that if the editor is being created, we just call recomputeLayout
+            // without looking for a better matching layout file.
+            if (mLayoutEditor.isCreatingPages()) {
+                recomputeLayout();
+            } else {
+                // 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 {
+                            // tell the editor that the next replacement file is due to a config
+                            // change.
+                            mLayoutEditor.setNewFileOnConfigChange(true);
+
+                            // ask the IDE to open the replacement file.
+                            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.
+
+                    // Even though the layout doesn't change, the config changed, and referenced
+                    // resources need to be updated.
+                    recomputeLayout();
+                } else {
+                    // 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 onClippingChange() {
+            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.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();
+        }
+    }
+
+    /**
+     * Listens to target changed in the current project, to trigger a new layout rendering.
+     */
+    private class TargetListener implements ITargetChangeListener {
+
+        public void onProjectTargetChange(IProject changedProject) {
+            if (changedProject != null && changedProject.equals(getProject())) {
+                updateEditor();
+            }
+        }
+
+        public void onTargetLoaded(IAndroidTarget target) {
+            IProject project = getProject();
+            if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {
+                updateEditor();
+            }
+        }
+
+        public void onSdkLoaded() {
+            Sdk currentSdk = Sdk.getCurrent();
+            if (currentSdk != null) {
+                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
+                if (target != null) {
+                    mConfigComposite.onSdkLoaded(target);
+                    mConfigListener.onConfigurationChange();
+                }
+            }
+        }
+
+        private void updateEditor() {
+            mLayoutEditor.commitPages(false /* onSave */);
+
+            // because the target changed we must reset the configured resources.
+            mConfiguredFrameworkRes = mConfiguredProjectRes = null;
+
+            // make sure we remove the custom view loader, since its parent class loader is the
+            // bridge class loader.
+            mProjectCallback = null;
+
+            // recreate the ui root node always, this will also call onTargetChange
+            // on the config composite
+            mLayoutEditor.initUiRootNode(true /*force*/);
+        }
+
+        private IProject getProject() {
+            return getLayoutEditor().getProject();
+        }
+    }
+
+    // ----------------
+
+    /**
+     * 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.
+    }
+
+    /**
+     * Opens and initialize the editor with a new file.
+     * @param file the file being edited.
+     */
+    public void openFile(IFile file) {
+        mEditedFile = file;
+        mConfigComposite.openFile(mEditedFile);
+
+        if (mReloadListener == null) {
+            mReloadListener = new ReloadListener();
+            LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);
+        }
+
+        if (mRulesEngine == null) {
+            mRulesEngine = new RulesEngine(mEditedFile.getProject());
+            if (mLayoutCanvas != null) {
+                mLayoutCanvas.setRulesEngine(mRulesEngine);
+            }
+        }
+    }
+
+    /**
+     * Resets the editor with a replacement file.
+     * @param file the replacement file.
+     */
+    public void replaceFile(IFile file) {
+        mEditedFile = file;
+        mConfigComposite.replaceFile(mEditedFile);
+    }
+
+    /**
+     * Resets the editor with a replacement file coming from a config change in the config
+     * selector.
+     * @param file the replacement file.
+     */
+    public void changeFileOnNewConfig(IFile file) {
+        mEditedFile = file;
+        mConfigComposite.changeFileOnNewConfig(mEditedFile);
+    }
+
+    public void onTargetChange() {
+        mConfigComposite.onTargetChange();
+        mConfigListener.onConfigurationChange();
+    }
+
+    public void onSdkChange() {
+        Sdk currentSdk = Sdk.getCurrent();
+        if (currentSdk != null) {
+            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
+            if (target != null) {
+                mConfigComposite.onSdkLoaded(target);
+                mConfigListener.onConfigurationChange();
+            }
+        }
+    }
+
+    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.
+                    LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);
+                    switch (targetLoadStatus) {
+                        case LOADING:
+                            displayError("The project target (%1$s) is still loading.\n%2$s will refresh automatically once the process is finished.",
+                                    target.getName(), mEditedFile.getName());
+
+                            break;
+                        case FAILED: // known failure
+                        case LOADED: // success but data isn't loaded?!?!
+                            displayError("The project target (%s) was not properly loaded.",
+                                    target.getName());
+                            break;
+                    }
+
+                    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
+                            Rectangle rect = getBounds();
+
+                            int width = rect.width;
+                            int height = rect.height;
+                            if (mUseExplodeMode) {
+                                // compute how many padding in x and y will bump the screen size
+                                ExplodedRenderingHelper helper = new ExplodedRenderingHelper(
+                                        getModel(), iProject);
+
+                                // there are 2 paddings for each view
+                                // left and right, or top and bottom.
+                                int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;
+
+                                width += helper.getWidthPadding() * paddingValue;
+                                height += helper.getHeightPadding() * paddingValue;
+                            }
+
+                            int density = mConfigComposite.getDensity().getDpiValue();
+                            float xdpi = mConfigComposite.getXDpi();
+                            float ydpi = mConfigComposite.getYDpi();
+                            boolean isProjectTheme = mConfigComposite.isProjectTheme();
+
+                            UiElementPullParser parser = new UiElementPullParser(getModel(),
+                                    mUseExplodeMode, density, xdpi, iProject);
+
+                            ILayoutResult result = computeLayout(bridge, parser,
+                                    iProject /* projectKey */,
+                                    width, height, !mConfigComposite.getClipping(),
+                                    density, xdpi, ydpi,
+                                    theme, isProjectTheme,
+                                    configuredProjectRes, frameworkResources, mProjectCallback,
+                                    mLogger);
+
+                            mLayoutCanvas.setResult(result);
+
+                            // update the UiElementNode with the layout info.
+                            if (result.getSuccess() == ILayoutResult.SUCCESS) {
+                                hideError();
+                            } else {
+                                displayError(result.getErrorMessage());
+                            }
+
+                            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.
+     *
+     * Implementation detail: the bridge's computeLayout() method already returns a newly
+     * allocated ILayoutResult.
+     */
+    @SuppressWarnings("deprecation")
+    private static ILayoutResult computeLayout(LayoutBridge bridge,
+            IXmlPullParser layoutDescription, Object projectKey,
+            int screenWidth, int screenHeight, boolean renderFullSize,
+            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, renderFullSize,
+                    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();
+    }
+
+    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 {
+        /*
+         * Called when the file changes triggered a redraw of the layout
+         */
+        public void reloadLayout(ChangeFlags flags) {
+            boolean recompute = false;
+
+            if (flags.rClass) {
+                recompute = true;
+                if (mEditedFile != null) {
+                    ProjectResources projectRes = ResourceManager.getInstance().getProjectResources(
+                            mEditedFile.getProject());
+
+                    if (projectRes != null) {
+                        projectRes.resetDynamicIds();
+                    }
+                }
+            }
+
+            if (flags.localeList) {
+                // the locale list *potentially* changed so we update the locale in the
+                // config composite.
+                // However there's no recompute, as it could not be needed
+                // (for instance a new layout)
+                // If a resource that's not a layout changed this will trigger a recompute anyway.
+                mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
+                    public void run() {
+                        mConfigComposite.updateLocales();
+                    }
+                });
+            }
+
+            if (flags.resources) {
+                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());
+                        }
+                    }
+                }
+            }
+
+            if (flags.code) {
+                // 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;
+                        }
+                    }
+                });
+            }
+        }
+    }
+}
index 1463e87..0751492 100755 (executable)
-/*\r
- * Copyright (C) 2009 The Android Open Source Project\r
- *\r
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.eclipse.org/org/documents/epl-v10.php\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.android.ide.eclipse.adt.internal.editors.layout.gle2;\r
-\r
-import com.android.ide.eclipse.adt.gscripts.DropZone;\r
-import com.android.ide.eclipse.adt.gscripts.Rect;\r
-import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;\r
-import com.android.layoutlib.api.ILayoutResult;\r
-\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.SWTException;\r
-import org.eclipse.swt.dnd.Clipboard;\r
-import org.eclipse.swt.dnd.DND;\r
-import org.eclipse.swt.dnd.DropTarget;\r
-import org.eclipse.swt.dnd.Transfer;\r
-import org.eclipse.swt.events.MouseEvent;\r
-import org.eclipse.swt.events.MouseListener;\r
-import org.eclipse.swt.events.MouseMoveListener;\r
-import org.eclipse.swt.events.PaintEvent;\r
-import org.eclipse.swt.events.PaintListener;\r
-import org.eclipse.swt.graphics.Color;\r
-import org.eclipse.swt.graphics.Font;\r
-import org.eclipse.swt.graphics.FontMetrics;\r
-import org.eclipse.swt.graphics.GC;\r
-import org.eclipse.swt.graphics.Image;\r
-import org.eclipse.swt.graphics.ImageData;\r
-import org.eclipse.swt.graphics.PaletteData;\r
-import org.eclipse.swt.graphics.Rectangle;\r
-import org.eclipse.swt.widgets.Canvas;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
-\r
-import java.awt.image.BufferedImage;\r
-import java.awt.image.DataBufferInt;\r
-import java.awt.image.Raster;\r
-import java.util.ArrayList;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.ListIterator;\r
-\r
-/**\r
- * Displays the image rendered by the {@link GraphicalEditorPart} and handles\r
- * the interaction with the widgets.\r
- * <p/>\r
- *\r
- * @since GLE2\r
- *\r
- * TODO list:\r
- * - gray on error, keep select but disable d'n'd.\r
- * - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)\r
- * - handle drop target (from palette).\r
- * - handle drag'n'drop (internal, for moving/duplicating).\r
- * - handle context menu (depending on selection).\r
- * - selection synchronization with the outline (both ways).\r
- */\r
-/* package */  class LayoutCanvas extends Canvas {\r
-\r
-    /**\r
-     * Margin around the rendered image. Should be enough space to display the layout\r
-     * width and height pseudo widgets.\r
-     */\r
-    static final int IMAGE_MARGIN = 5;\r
-\r
-\r
-    /** The Groovy Rules Engine, associated with the current project. */\r
-    private RulesEngine mRulesEngine;\r
-\r
-    /*\r
-     * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}.\r
-     * This can be null.\r
-     * When non null, {@link #mLastValidViewInfoRoot} is guaranteed to be non-null too.\r
-    */\r
-    private ILayoutResult mLastValidResult;\r
-\r
-    /**\r
-     * The CanvasViewInfo root created for the last update of {@link #mLastValidResult}.\r
-     * This is null when {@link #mLastValidResult} is null.\r
-     * When non null, {@link #mLastValidResult} is guaranteed to be non-null too.\r
-     */\r
-    private CanvasViewInfo mLastValidViewInfoRoot;\r
-\r
-    /**\r
-     * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}\r
-     * in which case it is also available in {@link #mLastValidResult}.\r
-     * When false this means the canvas is displaying an out-dated result image & bounds and some\r
-     * features should be disabled accordingly such a drag'n'drop.\r
-     * <p/>\r
-     * When this is false, {@link #mLastValidResult} can be non-null and points to an older\r
-     * layout result.\r
-     */\r
-    private boolean mIsResultValid;\r
-\r
-    /** Current background image. Null when there's no image. */\r
-    private Image mImage;\r
-\r
-    /** The current selection list. The list is never null, however it can be empty. */\r
-    private final LinkedList<CanvasSelection> mSelections = new LinkedList<CanvasSelection>();\r
-\r
-    /** CanvasSelection border color. Do not dispose, it's a system color. */\r
-    private Color mSelectionFgColor;\r
-\r
-    /** CanvasSelection name font. Do not dispose, it's a system font. */\r
-    private Font mSelectionFont;\r
-\r
-    /** Pixel height of the font displaying the selection name. Initially set to 0 and only\r
-     * initialized in onPaint() when we have a GC. */\r
-    private int mSelectionFontHeight;\r
-\r
-    /** Current hover view info. Null when no mouse hover. */\r
-    private CanvasViewInfo mHoverViewInfo;\r
-\r
-    /** Current mouse hover border rectangle. Null when there's no mouse hover. */\r
-    private Rectangle mHoverRect;\r
-\r
-    /** Hover border color. Must be disposed, it's NOT a system color. */\r
-    private Color mHoverFgColor;\r
-\r
-    /** Outline color. Do not dispose, it's a system color. */\r
-    private Color mOutlineColor;\r
-\r
-    /**\r
-     * The <em>current</em> alternate selection, if any, which changes when the Alt key is\r
-     * used during a selection. Can be null.\r
-     */\r
-    private CanvasAlternateSelection mAltSelection;\r
-\r
-    /** When true, always display the outline of all views. */\r
-    private boolean mShowOutline;\r
-\r
-    /** Drop target associated with this composite. */\r
-    private DropTarget mDropTarget;\r
-\r
-    /** Drop listener, with feedback from current drop */\r
-    private CanvasDropListener mDropListener;\r
-\r
-    /** Drop color. Do not dispose, it's a system color. */\r
-    private Color mDropFgColor;\r
-\r
-\r
-    public LayoutCanvas(RulesEngine rulesEngine, Composite parent, int style) {\r
-        super(parent, style | SWT.DOUBLE_BUFFERED);\r
-        mRulesEngine = rulesEngine;\r
-\r
-        Display d = getDisplay();\r
-        mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);\r
-        mHoverFgColor     = new Color(d, 0xFF, 0x99, 0x00); // orange\r
-        mOutlineColor     = d.getSystemColor(SWT.COLOR_GREEN);\r
-        mSelectionFont    = d.getSystemFont();\r
-        mDropFgColor      = d.getSystemColor(SWT.COLOR_YELLOW);\r
-\r
-        addPaintListener(new PaintListener() {\r
-            public void paintControl(PaintEvent e) {\r
-                onPaint(e);\r
-            }\r
-        });\r
-\r
-        addMouseMoveListener(new MouseMoveListener() {\r
-            public void mouseMove(MouseEvent e) {\r
-                onMouseMove(e);\r
-            }\r
-        });\r
-\r
-        addMouseListener(new MouseListener() {\r
-            public void mouseUp(MouseEvent e) {\r
-                onMouseUp(e);\r
-            }\r
-\r
-            public void mouseDown(MouseEvent e) {\r
-                onMouseDown(e);\r
-            }\r
-\r
-            public void mouseDoubleClick(MouseEvent e) {\r
-                onDoubleClick(e);\r
-            }\r
-        });\r
-\r
-        mDropTarget = new DropTarget(this, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT);\r
-        mDropTarget.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });\r
-        mDropListener = new CanvasDropListener(this);\r
-        mDropTarget.addDropListener(mDropListener);\r
-    }\r
-\r
-    @Override\r
-    public void dispose() {\r
-        super.dispose();\r
-\r
-        if (mHoverFgColor != null) {\r
-            mHoverFgColor.dispose();\r
-            mHoverFgColor = null;\r
-        }\r
-\r
-        if (mDropTarget != null) {\r
-            mDropTarget.dispose();\r
-            mDropTarget = null;\r
-        }\r
-\r
-        if (mRulesEngine != null) {\r
-            mRulesEngine.dispose();\r
-            mRulesEngine = null;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid\r
-     * {@link ILayoutResult} in which case it is also available in {@link #mLastValidResult}.\r
-     * When false this means the canvas is displaying an out-dated result image & bounds and some\r
-     * features should be disabled accordingly such a drag'n'drop.\r
-     * <p/>\r
-     * When this is false, {@link #mLastValidResult} can be non-null and points to an older\r
-     * layout result.\r
-     */\r
-    /* package */ boolean isResultValid() {\r
-        return mIsResultValid;\r
-    }\r
-\r
-    /** Returns the Groovy Rules Engine, associated with the current project. */\r
-    /* package */ RulesEngine getRulesEngine() {\r
-        return mRulesEngine;\r
-    }\r
-\r
-    /** Sets the Groovy Rules Engine, associated with the current project. */\r
-    /* package */ void setRulesEngine(RulesEngine rulesEngine) {\r
-        mRulesEngine = rulesEngine;\r
-    }\r
-\r
-    /**\r
-     * Sets the result of the layout rendering. The result object indicates if the layout\r
-     * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.\r
-     *\r
-     * Implementation detail: the bridge's computeLayout() method already returns a newly\r
-     * allocated ILayourResult. That means we can keep this result and hold on to it\r
-     * when it is valid.\r
-     *\r
-     * @param result The new rendering result, either valid or not.\r
-     */\r
-    public void setResult(ILayoutResult result) {\r
-        // disable any hover\r
-        mHoverRect = null;\r
-\r
-        mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);\r
-\r
-        if (mIsResultValid && result != null) {\r
-            mLastValidResult = result;\r
-            mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView());\r
-            setImage(result.getImage());\r
-\r
-            // Check if the selection is still the same (based on the object keys)\r
-            // and eventually recompute their bounds.\r
-            for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {\r
-                CanvasSelection s = it.next();\r
-\r
-                // Check the if the selected object still exists\r
-                Object key = s.getViewInfo().getUiViewKey();\r
-                CanvasViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot);\r
-\r
-                // Remove the previous selection -- if the selected object still exists\r
-                // we need to recompute its bounds in case it moved so we'll insert a new one\r
-                // at the same place.\r
-                it.remove();\r
-                if (vi != null) {\r
-                    it.add(new CanvasSelection(vi, mRulesEngine));\r
-                }\r
-            }\r
-\r
-            // remove the current alternate selection views\r
-            mAltSelection = null;\r
-        }\r
-\r
-        redraw();\r
-    }\r
-\r
-    public void setShowOutline(boolean newState) {\r
-        mShowOutline = newState;\r
-        redraw();\r
-    }\r
-\r
-    /**\r
-     * Called by the {@link GraphicalEditorPart} when the Copy action is requested.\r
-     *\r
-     * @param clipboard The shared clipboard. Must not be disposed.\r
-     */\r
-    public void onCopy(Clipboard clipboard) {\r
-        // TODO implement copy to clipbard. Also will need to provide feedback to enable\r
-        // copy only when there's a selection.\r
-    }\r
-\r
-    /**\r
-     * Called by the {@link GraphicalEditorPart} when the Cut action is requested.\r
-     *\r
-     * @param clipboard The shared clipboard. Must not be disposed.\r
-     */\r
-    public void onCut(Clipboard clipboard) {\r
-        // TODO implement copy to clipbard. Also will need to provide feedback to enable\r
-        // cut only when there's a selection.\r
-    }\r
-\r
-    /**\r
-     * Called by the {@link GraphicalEditorPart} when the Paste action is requested.\r
-     *\r
-     * @param clipboard The shared clipboard. Must not be disposed.\r
-     */\r
-    public void onPaste(Clipboard clipboard) {\r
-\r
-    }\r
-\r
-    /**\r
-     * Called by the {@link GraphicalEditorPart} when the Select All action is requested.\r
-     */\r
-    public void onSelectAll() {\r
-        // First clear the current selection, if any.\r
-        mSelections.clear();\r
-        mAltSelection = null;\r
-\r
-        // Now select everything if there's a valid layout\r
-        if (mIsResultValid && mLastValidResult != null) {\r
-            selectAllViewInfos(mLastValidViewInfoRoot);\r
-            redraw();\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Delete action\r
-     */\r
-    public void onDelete() {\r
-        // TODO not implemented yet, not even hooked in yet!\r
-    }\r
-\r
-    //---\r
-\r
-    /**\r
-     * Sets the image of the last *successful* rendering.\r
-     * Converts the AWT image into an SWT image.\r
-     */\r
-    private void setImage(BufferedImage awtImage) {\r
-        int width = awtImage.getWidth();\r
-        int height = awtImage.getHeight();\r
-\r
-        Raster raster = awtImage.getData(new java.awt.Rectangle(width, height));\r
-        int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();\r
-\r
-        ImageData imageData = new ImageData(width, height, 32,\r
-                new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));\r
-\r
-        imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);\r
-\r
-        mImage = new Image(getDisplay(), imageData);\r
-    }\r
-\r
-    /**\r
-     * Sets the alpha for the given GC.\r
-     * <p/>\r
-     * Alpha may not work on all platforms and may fail with an exception.\r
-     *\r
-     * @param gc the GC to change\r
-     * @param alpha the new alpha, 0 for transparent, 255 for opaque.\r
-     * @return True if the operation worked, false if it failed with an exception.\r
-     *\r
-     * @see GC#setAlpha(int)\r
-     */\r
-    private boolean gc_setAlpha(GC gc, int alpha) {\r
-        try {\r
-            gc.setAlpha(alpha);\r
-            return true;\r
-        } catch (SWTException e) {\r
-            return false;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Paints the canvas in response to paint events.\r
-     */\r
-    private void onPaint(PaintEvent e) {\r
-        GC gc = e.gc;\r
-\r
-        if (mImage != null) {\r
-            if (!mIsResultValid) {\r
-                gc_setAlpha(gc, 128);  // half-transparent\r
-            }\r
-\r
-            gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN);\r
-\r
-            if (!mIsResultValid) {\r
-                gc_setAlpha(gc, 255);  // opaque\r
-            }\r
-        }\r
-\r
-        if (mShowOutline) {\r
-            gc.setForeground(mOutlineColor);\r
-            gc.setLineStyle(SWT.LINE_DOT);\r
-            drawOutline(gc, mLastValidViewInfoRoot);\r
-        }\r
-\r
-        if (mHoverRect != null) {\r
-            gc.setForeground(mHoverFgColor);\r
-            gc.setLineStyle(SWT.LINE_DOT);\r
-            gc.drawRectangle(mHoverRect);\r
-        }\r
-\r
-        // initialize the selection font height once. We need the GC to do that.\r
-        if (mSelectionFontHeight == 0) {\r
-            gc.setFont(mSelectionFont);\r
-            FontMetrics fm = gc.getFontMetrics();\r
-            mSelectionFontHeight = fm.getHeight();\r
-        }\r
-\r
-        for (CanvasSelection s : mSelections) {\r
-            drawSelection(gc, s);\r
-        }\r
-\r
-        drawDropZones(gc);\r
-    }\r
-\r
-    private void drawOutline(GC gc, CanvasViewInfo info) {\r
-\r
-        Rectangle r = info.getAbsRect();\r
-        gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);\r
-\r
-        for (CanvasViewInfo vi : info.getChildren()) {\r
-            drawOutline(gc, vi);\r
-        }\r
-    }\r
-\r
-    private void drawSelection(GC gc, CanvasSelection s) {\r
-        Rectangle r = s.getRect();\r
-\r
-        gc.setForeground(mSelectionFgColor);\r
-        gc.setLineStyle(SWT.LINE_SOLID);\r
-        gc.drawRectangle(s.getRect());\r
-\r
-        String name = s.getName();\r
-\r
-        if (name != null) {\r
-            int xs = r.x + 2;\r
-            int ys = r.y - mSelectionFontHeight;\r
-            if (ys < 0) {\r
-                ys = r.y + r.height;\r
-            }\r
-            gc.drawString(name, xs, ys, true /*transparent*/);\r
-        }\r
-    }\r
-\r
-    private void drawDropZones(GC gc) {\r
-        if (mDropListener == null) {\r
-            return;\r
-        }\r
-\r
-        CanvasViewInfo vi = mDropListener.getTargetView();\r
-        if (vi == null) {\r
-            return;\r
-        }\r
-\r
-        gc.setForeground(mDropFgColor);\r
-\r
-        ArrayList<DropZone> zones = mDropListener.getZones();\r
-        if (zones != null) {\r
-\r
-            gc.setLineStyle(SWT.LINE_SOLID);\r
-            gc.setLineWidth(1);\r
-\r
-            DropZone curr = mDropListener.getCurrentZone();\r
-\r
-            for (DropZone zone : zones) {\r
-                Rect r = zone.bounds;\r
-                if (r != null && r.w > 0 && r.h > 0) {\r
-                    int x = r.x + IMAGE_MARGIN;\r
-                    int y = r.y + IMAGE_MARGIN;\r
-\r
-                    int alpha = 128;                        // half-transparent\r
-                    if (zone == curr) {\r
-                        alpha = 192;\r
-                    }\r
-\r
-                    if (gc_setAlpha(gc, alpha)) {\r
-                        gc.fillRectangle(x, y, r.w, r.h);\r
-                        gc_setAlpha(gc, 255);               // opaque\r
-                    }\r
-\r
-                    gc.drawRectangle(x, y, r.w, r.h);\r
-                }\r
-            }\r
-\r
-        }\r
-\r
-        gc.setLineStyle(SWT.LINE_DOT);\r
-        gc.setLineWidth(3);\r
-        Rectangle r = vi.getAbsRect();\r
-        gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);\r
-        gc.setLineWidth(1);\r
-    }\r
-\r
-    /**\r
-     * Hover on top of a known child.\r
-     */\r
-    private void onMouseMove(MouseEvent e) {\r
-        if (mLastValidResult != null) {\r
-            CanvasViewInfo root = mLastValidViewInfoRoot;\r
-            CanvasViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN);\r
-\r
-            // We don't hover on the root since it's not a widget per see and it is always there.\r
-            if (vi == root) {\r
-                vi = null;\r
-            }\r
-\r
-            boolean needsUpdate = vi != mHoverViewInfo;\r
-            mHoverViewInfo = vi;\r
-\r
-            if (vi == null) {\r
-                mHoverRect = null;\r
-            } else {\r
-                Rectangle r = vi.getSelectionRect();\r
-                mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN,\r
-                                           r.width, r.height);\r
-            }\r
-\r
-            if (needsUpdate) {\r
-                redraw();\r
-            }\r
-        }\r
-    }\r
-\r
-    private void onMouseDown(MouseEvent e) {\r
-        // pass, not used yet.\r
-    }\r
-\r
-    /**\r
-     * Performs selection on mouse up (not mouse down).\r
-     * <p/>\r
-     * Shift key is used to toggle in multi-selection.\r
-     * Alt key is used to cycle selection through objects at the same level than the one\r
-     * pointed at (i.e. click on an object then alt-click to cycle).\r
-     */\r
-    private void onMouseUp(MouseEvent e) {\r
-        if (mLastValidResult != null) {\r
-\r
-            boolean isShift = (e.stateMask & SWT.SHIFT) != 0;\r
-            boolean isAlt   = (e.stateMask & SWT.ALT)   != 0;\r
-\r
-            int x = e.x - IMAGE_MARGIN;\r
-            int y = e.y - IMAGE_MARGIN;\r
-            CanvasViewInfo vi = findViewInfoAt(x, y);\r
-\r
-            if (isShift && !isAlt) {\r
-                // Case where shift is pressed: pointed object is toggled.\r
-\r
-                // reset alternate selection if any\r
-                mAltSelection = null;\r
-\r
-                // If nothing has been found at the cursor, assume it might be a user error\r
-                // and avoid clearing the existing selection.\r
-\r
-                if (vi != null) {\r
-                    // toggle this selection on-off: remove it if already selected\r
-                    if (deselect(vi)) {\r
-                        redraw();\r
-                        return;\r
-                    }\r
-\r
-                    // otherwise add it.\r
-                    mSelections.add(new CanvasSelection(vi, mRulesEngine));\r
-                    redraw();\r
-                }\r
-\r
-            } else if (isAlt) {\r
-                // Case where alt is pressed: select or cycle the object pointed at.\r
-\r
-                // Note: if shift and alt are pressed, shift is ignored. The alternate selection\r
-                // mechanism does not reset the current multiple selection unless they intersect.\r
-\r
-                // We need to remember the "origin" of the alternate selection, to be\r
-                // able to continue cycling through it later. If there's no alternate selection,\r
-                // create one. If there's one but not for the same origin object, create a new\r
-                // one too.\r
-                if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) {\r
-                    mAltSelection = new CanvasAlternateSelection(vi, findAltViewInfoAt(\r
-                                                    x, y, mLastValidViewInfoRoot, null));\r
-\r
-                    // deselect them all, in case they were partially selected\r
-                    deselectAll(mAltSelection.getAltViews());\r
-\r
-                    // select the current one\r
-                    CanvasViewInfo vi2 = mAltSelection.getCurrent();\r
-                    if (vi2 != null) {\r
-                        mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine));\r
-                    }\r
-                } else {\r
-                    // We're trying to cycle through the current alternate selection.\r
-                    // First remove the current object.\r
-                    CanvasViewInfo vi2 = mAltSelection.getCurrent();\r
-                    deselect(vi2);\r
-\r
-                    // Now select the next one.\r
-                    vi2 = mAltSelection.getNext();\r
-                    if (vi2 != null) {\r
-                        mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine));\r
-                    }\r
-                }\r
-                redraw();\r
-\r
-            } else {\r
-                // Case where no modifier is pressed: either select or reset the selection.\r
-\r
-                // reset alternate selection if any\r
-                mAltSelection = null;\r
-\r
-                // reset (multi)selection if any\r
-                if (mSelections.size() > 0) {\r
-                    if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {\r
-                        // CanvasSelection remains the same, don't touch it.\r
-                        return;\r
-                    }\r
-                    mSelections.clear();\r
-                }\r
-\r
-                if (vi != null) {\r
-                    mSelections.add(new CanvasSelection(vi, mRulesEngine));\r
-                }\r
-                redraw();\r
-            }\r
-        }\r
-    }\r
-\r
-    /** Deselects a view info. Returns true if the object was actually selected. */\r
-    private boolean deselect(CanvasViewInfo canvasViewInfo) {\r
-        if (canvasViewInfo == null) {\r
-            return false;\r
-        }\r
-\r
-        for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {\r
-            CanvasSelection s = it.next();\r
-            if (canvasViewInfo == s.getViewInfo()) {\r
-                it.remove();\r
-                return true;\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    /** Deselects multiple view infos, */\r
-    private void deselectAll(List<CanvasViewInfo> canvasViewInfos) {\r
-        for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {\r
-            CanvasSelection s = it.next();\r
-            if (canvasViewInfos.contains(s.getViewInfo())) {\r
-                it.remove();\r
-            }\r
-        }\r
-    }\r
-\r
-    private void onDoubleClick(MouseEvent e) {\r
-        // pass, not used yet.\r
-    }\r
-\r
-    /**\r
-     * Tries to find a child with the same view key in the view info sub-tree.\r
-     * Returns null if not found.\r
-     */\r
-    private CanvasViewInfo findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo) {\r
-        if (canvasViewInfo.getUiViewKey() == viewKey) {\r
-            return canvasViewInfo;\r
-        }\r
-\r
-        // try to find a matching child\r
-        for (CanvasViewInfo child : canvasViewInfo.getChildren()) {\r
-            CanvasViewInfo v = findViewInfoKey(viewKey, child);\r
-            if (v != null) {\r
-                return v;\r
-            }\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-\r
-    /**\r
-     * Tries to find the inner most child matching the given x,y coordinates in the view\r
-     * info sub-tree, starting at the last know view info root.\r
-     * This uses the potentially-expanded selection bounds.\r
-     *\r
-     * Returns null if not found or if there's view info root.\r
-     */\r
-    /* package */ CanvasViewInfo findViewInfoAt(int x, int y) {\r
-        if (mLastValidViewInfoRoot == null) {\r
-            return null;\r
-        } else {\r
-            return findViewInfoAt(x, y, mLastValidViewInfoRoot);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Tries to find the inner most child matching the given x,y coordinates in the view\r
-     * info sub-tree. This uses the potentially-expanded selection bounds.\r
-     *\r
-     * Returns null if not found.\r
-     */\r
-    private CanvasViewInfo findViewInfoAt(int x, int y, CanvasViewInfo canvasViewInfo) {\r
-        Rectangle r = canvasViewInfo.getSelectionRect();\r
-        if (r.contains(x, y)) {\r
-\r
-            // try to find a matching child first\r
-            for (CanvasViewInfo child : canvasViewInfo.getChildren()) {\r
-                CanvasViewInfo v = findViewInfoAt(x, y, child);\r
-                if (v != null) {\r
-                    return v;\r
-                }\r
-            }\r
-\r
-            // if no children matched, this is the view that we're looking for\r
-            return canvasViewInfo;\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    private ArrayList<CanvasViewInfo> findAltViewInfoAt(\r
-            int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList) {\r
-        Rectangle r;\r
-\r
-        if (outList == null) {\r
-            outList = new ArrayList<CanvasViewInfo>();\r
-\r
-            // add the parent root only once\r
-            r = parent.getSelectionRect();\r
-            if (r.contains(x, y)) {\r
-                outList.add(parent);\r
-            }\r
-        }\r
-\r
-        if (parent.getChildren().size() > 0) {\r
-            // then add all children that match the position\r
-            for (CanvasViewInfo child : parent.getChildren()) {\r
-                r = child.getSelectionRect();\r
-                if (r.contains(x, y)) {\r
-                    outList.add(child);\r
-                }\r
-            }\r
-\r
-            // finally recurse in the children\r
-            for (CanvasViewInfo child : parent.getChildren()) {\r
-                r = child.getSelectionRect();\r
-                if (r.contains(x, y)) {\r
-                    findAltViewInfoAt(x, y, child, outList);\r
-                }\r
-            }\r
-        }\r
-\r
-        return outList;\r
-    }\r
-\r
-    /**\r
-     * Used by {@link #onSelectAll()} to add all current view infos to the selection list.\r
-     *\r
-     * @param canvasViewInfo The root to add. This info and all its children will be added to the\r
-     *                 selection list.\r
-     */\r
-    private void selectAllViewInfos(CanvasViewInfo canvasViewInfo) {\r
-        mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine));\r
-        for (CanvasViewInfo vi : canvasViewInfo.getChildren()) {\r
-            selectAllViewInfos(vi);\r
-        }\r
-    }\r
-}\r
+/*
+ * 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.gle2;
+
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
+import com.android.layoutlib.api.ILayoutResult;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontMetrics;
+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.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Displays the image rendered by the {@link GraphicalEditorPart} and handles
+ * the interaction with the widgets.
+ * <p/>
+ *
+ * @since GLE2
+ *
+ * TODO list:
+ * - gray on error, keep select but disable d'n'd.
+ * - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)
+ * - 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).
+ */
+/* package */  class LayoutCanvas extends Canvas {
+
+    /**
+     * Margin around the rendered image. Should be enough space to display the layout
+     * width and height pseudo widgets.
+     */
+    static final int IMAGE_MARGIN = 5;
+
+
+    /** The Groovy Rules Engine, associated with the current project. */
+    private RulesEngine mRulesEngine;
+
+    /*
+     * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}.
+     * This can be null.
+     * When non null, {@link #mLastValidViewInfoRoot} is guaranteed to be non-null too.
+    */
+    private ILayoutResult mLastValidResult;
+
+    /**
+     * The CanvasViewInfo root created for the last update of {@link #mLastValidResult}.
+     * This is null when {@link #mLastValidResult} is null.
+     * When non null, {@link #mLastValidResult} is guaranteed to be non-null too.
+     */
+    private CanvasViewInfo mLastValidViewInfoRoot;
+
+    /**
+     * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}
+     * in which case it is also available in {@link #mLastValidResult}.
+     * When false this means the canvas is displaying an out-dated result image & bounds and some
+     * features should be disabled accordingly such a drag'n'drop.
+     * <p/>
+     * When this is false, {@link #mLastValidResult} can be non-null and points to an older
+     * layout result.
+     */
+    private boolean mIsResultValid;
+
+    /** Current background image. Null when there's no image. */
+    private Image mImage;
+
+    /** The current selection list. The list is never null, however it can be empty. */
+    private final LinkedList<CanvasSelection> mSelections = new LinkedList<CanvasSelection>();
+
+    /** CanvasSelection border color. Do not dispose, it's a system color. */
+    private Color mSelectionFgColor;
+
+    /** CanvasSelection name font. Do not dispose, it's a system font. */
+    private Font mSelectionFont;
+
+    /** Pixel height of the font displaying the selection name. Initially set to 0 and only
+     * initialized in onPaint() when we have a GC. */
+    private int mSelectionFontHeight;
+
+    /** Current hover view info. Null when no mouse hover. */
+    private CanvasViewInfo mHoverViewInfo;
+
+    /** Current mouse hover border rectangle. Null when there's no mouse hover. */
+    private Rectangle mHoverRect;
+
+    /** Hover border color. Must be disposed, it's NOT a system color. */
+    private Color mHoverFgColor;
+
+    /** Outline color. Do not dispose, it's a system color. */
+    private Color mOutlineColor;
+
+    /**
+     * The <em>current</em> alternate selection, if any, which changes when the Alt key is
+     * used during a selection. Can be null.
+     */
+    private CanvasAlternateSelection mAltSelection;
+
+    /** When true, always display the outline of all views. */
+    private boolean mShowOutline;
+
+    /** Drop target associated with this composite. */
+    private DropTarget mDropTarget;
+
+    /** Drop listener, with feedback from current drop */
+    private CanvasDropListener mDropListener;
+
+    /** Drop color. Do not dispose, it's a system color. */
+    private Color mDropFgColor;
+
+
+    public LayoutCanvas(RulesEngine rulesEngine, Composite parent, int style) {
+        super(parent, style | SWT.DOUBLE_BUFFERED);
+        mRulesEngine = rulesEngine;
+
+        Display d = getDisplay();
+        mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
+        mHoverFgColor     = new Color(d, 0xFF, 0x99, 0x00); // orange
+        mOutlineColor     = d.getSystemColor(SWT.COLOR_GREEN);
+        mSelectionFont    = d.getSystemFont();
+        mDropFgColor      = d.getSystemColor(SWT.COLOR_YELLOW);
+
+        addPaintListener(new PaintListener() {
+            public void paintControl(PaintEvent e) {
+                onPaint(e);
+            }
+        });
+
+        addMouseMoveListener(new MouseMoveListener() {
+            public void mouseMove(MouseEvent e) {
+                onMouseMove(e);
+            }
+        });
+
+        addMouseListener(new MouseListener() {
+            public void mouseUp(MouseEvent e) {
+                onMouseUp(e);
+            }
+
+            public void mouseDown(MouseEvent e) {
+                onMouseDown(e);
+            }
+
+            public void mouseDoubleClick(MouseEvent e) {
+                onDoubleClick(e);
+            }
+        });
+
+        mDropTarget = new DropTarget(this, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT);
+        mDropTarget.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });
+        mDropListener = new CanvasDropListener(this);
+        mDropTarget.addDropListener(mDropListener);
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+
+        if (mHoverFgColor != null) {
+            mHoverFgColor.dispose();
+            mHoverFgColor = null;
+        }
+
+        if (mDropTarget != null) {
+            mDropTarget.dispose();
+            mDropTarget = null;
+        }
+
+        if (mRulesEngine != null) {
+            mRulesEngine.dispose();
+            mRulesEngine = null;
+        }
+    }
+
+    /**
+     * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid
+     * {@link ILayoutResult} in which case it is also available in {@link #mLastValidResult}.
+     * When false this means the canvas is displaying an out-dated result image & bounds and some
+     * features should be disabled accordingly such a drag'n'drop.
+     * <p/>
+     * When this is false, {@link #mLastValidResult} can be non-null and points to an older
+     * layout result.
+     */
+    /* package */ boolean isResultValid() {
+        return mIsResultValid;
+    }
+
+    /** Returns the Groovy Rules Engine, associated with the current project. */
+    /* package */ RulesEngine getRulesEngine() {
+        return mRulesEngine;
+    }
+
+    /** Sets the Groovy Rules Engine, associated with the current project. */
+    /* package */ void setRulesEngine(RulesEngine rulesEngine) {
+        mRulesEngine = rulesEngine;
+    }
+
+    /**
+     * Sets the result of the layout rendering. The result object indicates if the layout
+     * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
+     *
+     * Implementation detail: the bridge's computeLayout() method already returns a newly
+     * allocated ILayourResult. That means we can keep this result and hold on to it
+     * when it is valid.
+     *
+     * @param result The new rendering result, either valid or not.
+     */
+    public void setResult(ILayoutResult result) {
+        // disable any hover
+        mHoverRect = null;
+
+        mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
+
+        if (mIsResultValid && result != null) {
+            mLastValidResult = result;
+            mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView());
+            setImage(result.getImage());
+
+            // Check if the selection is still the same (based on the object keys)
+            // and eventually recompute their bounds.
+            for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
+                CanvasSelection s = it.next();
+
+                // Check the if the selected object still exists
+                Object key = s.getViewInfo().getUiViewKey();
+                CanvasViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot);
+
+                // Remove the previous selection -- if the selected object still exists
+                // we need to recompute its bounds in case it moved so we'll insert a new one
+                // at the same place.
+                it.remove();
+                if (vi != null) {
+                    it.add(new CanvasSelection(vi, mRulesEngine));
+                }
+            }
+
+            // remove the current alternate selection views
+            mAltSelection = null;
+        }
+
+        redraw();
+    }
+
+    public void setShowOutline(boolean newState) {
+        mShowOutline = newState;
+        redraw();
+    }
+
+    /**
+     * Called by the {@link GraphicalEditorPart} when the Copy action is requested.
+     *
+     * @param clipboard The shared clipboard. Must not be disposed.
+     */
+    public void onCopy(Clipboard clipboard) {
+        // TODO implement copy to clipbard. Also will need to provide feedback to enable
+        // copy only when there's a selection.
+    }
+
+    /**
+     * Called by the {@link GraphicalEditorPart} when the Cut action is requested.
+     *
+     * @param clipboard The shared clipboard. Must not be disposed.
+     */
+    public void onCut(Clipboard clipboard) {
+        // TODO implement copy to clipbard. Also will need to provide feedback to enable
+        // cut only when there's a selection.
+    }
+
+    /**
+     * Called by the {@link GraphicalEditorPart} when the Paste action is requested.
+     *
+     * @param clipboard The shared clipboard. Must not be disposed.
+     */
+    public void onPaste(Clipboard clipboard) {
+
+    }
+
+    /**
+     * Called by the {@link GraphicalEditorPart} when the Select All action is requested.
+     */
+    public void onSelectAll() {
+        // First clear the current selection, if any.
+        mSelections.clear();
+        mAltSelection = null;
+
+        // Now select everything if there's a valid layout
+        if (mIsResultValid && mLastValidResult != null) {
+            selectAllViewInfos(mLastValidViewInfoRoot);
+            redraw();
+        }
+    }
+
+    /**
+     * Delete action
+     */
+    public void onDelete() {
+        // TODO not implemented yet, not even hooked in yet!
+    }
+
+    //---
+
+    /**
+     * Sets the image of the last *successful* rendering.
+     * Converts the AWT image into an SWT image.
+     */
+    private void setImage(BufferedImage awtImage) {
+        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);
+    }
+
+    /**
+     * Sets the alpha for the given GC.
+     * <p/>
+     * Alpha may not work on all platforms and may fail with an exception.
+     *
+     * @param gc the GC to change
+     * @param alpha the new alpha, 0 for transparent, 255 for opaque.
+     * @return True if the operation worked, false if it failed with an exception.
+     *
+     * @see GC#setAlpha(int)
+     */
+    private boolean gc_setAlpha(GC gc, int alpha) {
+        try {
+            gc.setAlpha(alpha);
+            return true;
+        } catch (SWTException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Paints the canvas in response to paint events.
+     */
+    private void onPaint(PaintEvent e) {
+        GC gc = e.gc;
+
+        if (mImage != null) {
+            if (!mIsResultValid) {
+                gc_setAlpha(gc, 128);  // half-transparent
+            }
+
+            gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN);
+
+            if (!mIsResultValid) {
+                gc_setAlpha(gc, 255);  // opaque
+            }
+        }
+
+        if (mShowOutline) {
+            gc.setForeground(mOutlineColor);
+            gc.setLineStyle(SWT.LINE_DOT);
+            drawOutline(gc, mLastValidViewInfoRoot);
+        }
+
+        if (mHoverRect != null) {
+            gc.setForeground(mHoverFgColor);
+            gc.setLineStyle(SWT.LINE_DOT);
+            gc.drawRectangle(mHoverRect);
+        }
+
+        // initialize the selection font height once. We need the GC to do that.
+        if (mSelectionFontHeight == 0) {
+            gc.setFont(mSelectionFont);
+            FontMetrics fm = gc.getFontMetrics();
+            mSelectionFontHeight = fm.getHeight();
+        }
+
+        for (CanvasSelection s : mSelections) {
+            drawSelection(gc, s);
+        }
+
+        drawDropZones(gc);
+    }
+
+    private void drawOutline(GC gc, CanvasViewInfo info) {
+
+        Rectangle r = info.getAbsRect();
+        gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
+
+        for (CanvasViewInfo vi : info.getChildren()) {
+            drawOutline(gc, vi);
+        }
+    }
+
+    private void drawSelection(GC gc, CanvasSelection s) {
+        Rectangle r = s.getRect();
+
+        gc.setForeground(mSelectionFgColor);
+        gc.setLineStyle(SWT.LINE_SOLID);
+        gc.drawRectangle(s.getRect());
+
+        String name = s.getName();
+
+        if (name != null) {
+            int xs = r.x + 2;
+            int ys = r.y - mSelectionFontHeight;
+            if (ys < 0) {
+                ys = r.y + r.height;
+            }
+            gc.drawString(name, xs, ys, true /*transparent*/);
+        }
+    }
+
+    private void drawDropZones(GC gc) {
+        if (mDropListener == null) {
+            return;
+        }
+
+        CanvasViewInfo vi = mDropListener.getTargetView();
+        if (vi == null) {
+            return;
+        }
+
+        gc.setForeground(mDropFgColor);
+
+        ArrayList<DropZone> zones = mDropListener.getZones();
+        if (zones != null) {
+
+            gc.setLineStyle(SWT.LINE_SOLID);
+            gc.setLineWidth(1);
+
+            DropZone curr = mDropListener.getCurrentZone();
+
+            for (DropZone zone : zones) {
+                Rect r = zone.bounds;
+                if (r != null && r.w > 0 && r.h > 0) {
+                    int x = r.x + IMAGE_MARGIN;
+                    int y = r.y + IMAGE_MARGIN;
+
+                    int alpha = 128;                        // half-transparent
+                    if (zone == curr) {
+                        alpha = 192;
+                    }
+
+                    if (gc_setAlpha(gc, alpha)) {
+                        gc.fillRectangle(x, y, r.w, r.h);
+                        gc_setAlpha(gc, 255);               // opaque
+                    }
+
+                    gc.drawRectangle(x, y, r.w, r.h);
+                }
+            }
+
+        }
+
+        gc.setLineStyle(SWT.LINE_DOT);
+        gc.setLineWidth(3);
+        Rectangle r = vi.getAbsRect();
+        gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
+        gc.setLineWidth(1);
+    }
+
+    /**
+     * Hover on top of a known child.
+     */
+    private void onMouseMove(MouseEvent e) {
+        if (mLastValidResult != null) {
+            CanvasViewInfo root = mLastValidViewInfoRoot;
+            CanvasViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN);
+
+            // We don't hover on the root since it's not a widget per see and it is always there.
+            if (vi == root) {
+                vi = null;
+            }
+
+            boolean needsUpdate = vi != mHoverViewInfo;
+            mHoverViewInfo = vi;
+
+            if (vi == null) {
+                mHoverRect = null;
+            } else {
+                Rectangle r = vi.getSelectionRect();
+                mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN,
+                                           r.width, r.height);
+            }
+
+            if (needsUpdate) {
+                redraw();
+            }
+        }
+    }
+
+    private void onMouseDown(MouseEvent e) {
+        // pass, not used yet.
+    }
+
+    /**
+     * Performs selection on mouse up (not mouse down).
+     * <p/>
+     * Shift key is used to toggle in multi-selection.
+     * Alt key is used to cycle selection through objects at the same level than the one
+     * pointed at (i.e. click on an object then alt-click to cycle).
+     */
+    private void onMouseUp(MouseEvent e) {
+        if (mLastValidResult != null) {
+
+            boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
+            boolean isAlt   = (e.stateMask & SWT.ALT)   != 0;
+
+            int x = e.x - IMAGE_MARGIN;
+            int y = e.y - IMAGE_MARGIN;
+            CanvasViewInfo vi = findViewInfoAt(x, y);
+
+            if (isShift && !isAlt) {
+                // Case where shift is pressed: pointed object is toggled.
+
+                // reset alternate selection if any
+                mAltSelection = null;
+
+                // If nothing has been found at the cursor, assume it might be a user error
+                // and avoid clearing the existing selection.
+
+                if (vi != null) {
+                    // toggle this selection on-off: remove it if already selected
+                    if (deselect(vi)) {
+                        redraw();
+                        return;
+                    }
+
+                    // otherwise add it.
+                    mSelections.add(new CanvasSelection(vi, mRulesEngine));
+                    redraw();
+                }
+
+            } else if (isAlt) {
+                // Case where alt is pressed: select or cycle the object pointed at.
+
+                // Note: if shift and alt are pressed, shift is ignored. The alternate selection
+                // mechanism does not reset the current multiple selection unless they intersect.
+
+                // We need to remember the "origin" of the alternate selection, to be
+                // able to continue cycling through it later. If there's no alternate selection,
+                // create one. If there's one but not for the same origin object, create a new
+                // one too.
+                if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) {
+                    mAltSelection = new CanvasAlternateSelection(vi, findAltViewInfoAt(
+                                                    x, y, mLastValidViewInfoRoot, null));
+
+                    // deselect them all, in case they were partially selected
+                    deselectAll(mAltSelection.getAltViews());
+
+                    // select the current one
+                    CanvasViewInfo vi2 = mAltSelection.getCurrent();
+                    if (vi2 != null) {
+                        mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine));
+                    }
+                } else {
+                    // We're trying to cycle through the current alternate selection.
+                    // First remove the current object.
+                    CanvasViewInfo vi2 = mAltSelection.getCurrent();
+                    deselect(vi2);
+
+                    // Now select the next one.
+                    vi2 = mAltSelection.getNext();
+                    if (vi2 != null) {
+                        mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine));
+                    }
+                }
+                redraw();
+
+            } else {
+                // Case where no modifier is pressed: either select or reset the selection.
+
+                // reset alternate selection if any
+                mAltSelection = null;
+
+                // reset (multi)selection if any
+                if (mSelections.size() > 0) {
+                    if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {
+                        // CanvasSelection remains the same, don't touch it.
+                        return;
+                    }
+                    mSelections.clear();
+                }
+
+                if (vi != null) {
+                    mSelections.add(new CanvasSelection(vi, mRulesEngine));
+                }
+                redraw();
+            }
+        }
+    }
+
+    /** Deselects a view info. Returns true if the object was actually selected. */
+    private boolean deselect(CanvasViewInfo canvasViewInfo) {
+        if (canvasViewInfo == null) {
+            return false;
+        }
+
+        for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
+            CanvasSelection s = it.next();
+            if (canvasViewInfo == s.getViewInfo()) {
+                it.remove();
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /** Deselects multiple view infos, */
+    private void deselectAll(List<CanvasViewInfo> canvasViewInfos) {
+        for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
+            CanvasSelection s = it.next();
+            if (canvasViewInfos.contains(s.getViewInfo())) {
+                it.remove();
+            }
+        }
+    }
+
+    private void onDoubleClick(MouseEvent e) {
+        // pass, not used yet.
+    }
+
+    /**
+     * Tries to find a child with the same view key in the view info sub-tree.
+     * Returns null if not found.
+     */
+    private CanvasViewInfo findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo) {
+        if (canvasViewInfo.getUiViewKey() == viewKey) {
+            return canvasViewInfo;
+        }
+
+        // try to find a matching child
+        for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
+            CanvasViewInfo v = findViewInfoKey(viewKey, child);
+            if (v != null) {
+                return v;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Tries to find the inner most child matching the given x,y coordinates in the view
+     * info sub-tree, starting at the last know view info root.
+     * This uses the potentially-expanded selection bounds.
+     *
+     * Returns null if not found or if there's view info root.
+     */
+    /* package */ CanvasViewInfo findViewInfoAt(int x, int y) {
+        if (mLastValidViewInfoRoot == null) {
+            return null;
+        } else {
+            return findViewInfoAt(x, y, mLastValidViewInfoRoot);
+        }
+    }
+
+    /**
+     * Tries to find the inner most child matching the given x,y coordinates in the view
+     * info sub-tree. This uses the potentially-expanded selection bounds.
+     *
+     * Returns null if not found.
+     */
+    private CanvasViewInfo findViewInfoAt(int x, int y, CanvasViewInfo canvasViewInfo) {
+        Rectangle r = canvasViewInfo.getSelectionRect();
+        if (r.contains(x, y)) {
+
+            // try to find a matching child first
+            for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
+                CanvasViewInfo v = findViewInfoAt(x, y, child);
+                if (v != null) {
+                    return v;
+                }
+            }
+
+            // if no children matched, this is the view that we're looking for
+            return canvasViewInfo;
+        }
+
+        return null;
+    }
+
+    private ArrayList<CanvasViewInfo> findAltViewInfoAt(
+            int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList) {
+        Rectangle r;
+
+        if (outList == null) {
+            outList = new ArrayList<CanvasViewInfo>();
+
+            // add the parent root only once
+            r = parent.getSelectionRect();
+            if (r.contains(x, y)) {
+                outList.add(parent);
+            }
+        }
+
+        if (parent.getChildren().size() > 0) {
+            // then add all children that match the position
+            for (CanvasViewInfo child : parent.getChildren()) {
+                r = child.getSelectionRect();
+                if (r.contains(x, y)) {
+                    outList.add(child);
+                }
+            }
+
+            // finally recurse in the children
+            for (CanvasViewInfo child : parent.getChildren()) {
+                r = child.getSelectionRect();
+                if (r.contains(x, y)) {
+                    findAltViewInfoAt(x, y, child, outList);
+                }
+            }
+        }
+
+        return outList;
+    }
+
+    /**
+     * Used by {@link #onSelectAll()} to add all current view infos to the selection list.
+     *
+     * @param canvasViewInfo The root to add. This info and all its children will be added to the
+     *                 selection list.
+     */
+    private void selectAllViewInfos(CanvasViewInfo canvasViewInfo) {
+        mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine));
+        for (CanvasViewInfo vi : canvasViewInfo.getChildren()) {
+            selectAllViewInfos(vi);
+        }
+    }
+}
index e780595..7a64789 100755 (executable)
-/*\r
- * Copyright (C) 2009 The Android Open Source Project\r
- *\r
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.eclipse.org/org/documents/epl-v10.php\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.android.ide.eclipse.adt.internal.editors.layout.gle2;\r
-\r
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;\r
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;\r
-\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.custom.CLabel;\r
-import org.eclipse.swt.dnd.DND;\r
-import org.eclipse.swt.dnd.DragSource;\r
-import org.eclipse.swt.dnd.DragSourceEvent;\r
-import org.eclipse.swt.dnd.DragSourceListener;\r
-import org.eclipse.swt.dnd.Transfer;\r
-import org.eclipse.swt.events.MouseEvent;\r
-import org.eclipse.swt.events.MouseListener;\r
-import org.eclipse.swt.events.MouseTrackListener;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.layout.GridData;\r
-import org.eclipse.swt.layout.GridLayout;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Control;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Listener;\r
-import org.eclipse.swt.widgets.ScrollBar;\r
-\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-\r
-/**\r
- * A palette composite for the {@link GraphicalEditorPart}.\r
- * <p/>\r
- * The palette contains several groups, each with a UI name (e.g. layouts and views) and each\r
- * with a list of element descriptors.\r
- * <p/>\r
- *\r
- * @since GLE2\r
- *\r
- * TODO list:\r
- *   - The available items should depend on the actual GLE2 Canvas selection. Selected android\r
- *     views should force filtering on what they accept can be dropped on them (e.g. TabHost,\r
- *     TableLayout). Should enable/disable them, not hide them, to avoid shuffling around.\r
- *   - Optional: a text filter\r
- *   - Optional: have icons that depict the element and/or automatically rendered icons\r
- *     based on a rendering of the widget.\r
- *   - Optional: have context-sensitive tools items, e.g. selection arrow tool,\r
- *     group selection tool, alignment, etc.\r
- *   - Different view strategies: big icon, small icons, text vs no text, compact grid.\r
- *     - This would only be useful with meaningful icons. Out current 1-letter icons are not enough\r
- *       to get rid of text labels.\r
- */\r
-public class PaletteComposite extends Composite {\r
-\r
-\r
-    /** The parent grid layout that contains all the {@link Toggle} and {@link Item} widgets. */\r
-    private Composite mRoot;\r
-\r
-    /**\r
-     * Create the composite.\r
-     * @param parent The parent composite.\r
-     */\r
-    public PaletteComposite(Composite parent) {\r
-        super(parent, SWT.BORDER | SWT.V_SCROLL);\r
-    }\r
-\r
-    @Override\r
-    protected void checkSubclass() {\r
-        // Disable the check that prevents subclassing of SWT components\r
-    }\r
-\r
-    /**\r
-     * Loads or reloads the palette elements by using the layout and view descriptors from the\r
-     * given target data.\r
-     *\r
-     * @param targetData The target data that contains the descriptors. If null or empty,\r
-     *   no groups will be created.\r
-     */\r
-    public void reloadPalette(AndroidTargetData targetData) {\r
-\r
-        for (Control c : getChildren()) {\r
-            c.dispose();\r
-        }\r
-\r
-        setGridLayout(this, 2);\r
-\r
-        mRoot = new Composite(this, SWT.NONE);\r
-        setGridLayout(mRoot, 0);\r
-\r
-        if (targetData != null) {\r
-            addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors());\r
-            addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());\r
-        }\r
-\r
-        layout(true);\r
-\r
-        final ScrollBar vbar = getVerticalBar();\r
-\r
-        vbar.setMaximum(getSize().y);\r
-\r
-        for (Listener listener : vbar.getListeners(SWT.Selection)) {\r
-            vbar.removeListener(SWT.Selection, listener);\r
-        }\r
-\r
-        vbar.addListener(SWT.Selection, new Listener() {\r
-            public void handleEvent(Event event) {\r
-                Point p = mRoot.getLocation();\r
-                p.y = - vbar.getSelection();\r
-                mRoot.setLocation(p);\r
-            }\r
-        });\r
-    }\r
-\r
-    private void setGridLayout(Composite parent, int spacing) {\r
-        GridLayout gl = new GridLayout(1, false);\r
-        gl.horizontalSpacing = 0;\r
-        gl.verticalSpacing = 0;\r
-        gl.marginHeight = spacing;\r
-        gl.marginBottom = spacing;\r
-        gl.marginLeft = spacing;\r
-        gl.marginRight = spacing;\r
-        gl.marginTop = spacing;\r
-        gl.marginBottom = spacing;\r
-        parent.setLayout(gl);\r
-    }\r
-\r
-    private void addGroup(Composite parent,\r
-            String uiName,\r
-            List<ElementDescriptor> descriptors) {\r
-\r
-        Composite group = new Composite(parent, SWT.NONE);\r
-        setGridLayout(group, 0);\r
-\r
-        Toggle toggle = new Toggle(group, uiName);\r
-\r
-        for (ElementDescriptor desc : descriptors) {\r
-            Item item = new Item(group, desc);\r
-            toggle.addItem(item);\r
-            GridData gd = new GridData();\r
-            item.setLayoutData(gd);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * A Toggle widget is a row that is the head of a group.\r
-     * <p/>\r
-     * When clicked, the toggle will show/hide all the {@link Item} widgets that have been\r
-     * added to it using {@link #addItem(Item)}.\r
-     */\r
-    private static class Toggle extends CLabel implements MouseTrackListener, MouseListener {\r
-        private boolean mMouseIn;\r
-        private DragSource mSource;\r
-        private ArrayList<Item> mItems = new ArrayList<Item>();\r
-\r
-        public Toggle(Composite parent, String groupName) {\r
-            super(parent, SWT.NONE);\r
-            mMouseIn = false;\r
-\r
-            setData(null);\r
-\r
-            String s = String.format("-= %s =-", groupName);\r
-            setText(s);\r
-            setToolTipText(s);\r
-            //TODO use triangle icon and swap it -- setImage(desc.getIcon());\r
-            addMouseTrackListener(this);\r
-            addMouseListener(this);\r
-        }\r
-\r
-        public void addItem(Item item) {\r
-            mItems.add(item);\r
-        }\r
-\r
-        @Override\r
-        public void dispose() {\r
-            if (mSource != null) {\r
-                mSource.dispose();\r
-                mSource = null;\r
-            }\r
-            super.dispose();\r
-        }\r
-\r
-        @Override\r
-        public int getStyle() {\r
-            int style = super.getStyle();\r
-            if (mMouseIn) {\r
-                style |= SWT.SHADOW_IN;\r
-            }\r
-            return style;\r
-        }\r
-\r
-        // -- MouseTrackListener callbacks\r
-\r
-        public void mouseEnter(MouseEvent e) {\r
-            if (!mMouseIn) {\r
-                mMouseIn = true;\r
-                redraw();\r
-            }\r
-        }\r
-\r
-        public void mouseExit(MouseEvent e) {\r
-            if (mMouseIn) {\r
-                mMouseIn = false;\r
-                redraw();\r
-            }\r
-        }\r
-\r
-        public void mouseHover(MouseEvent e) {\r
-            // pass\r
-        }\r
-\r
-        // -- MouseListener callbacks\r
-\r
-        public void mouseDoubleClick(MouseEvent arg0) {\r
-            // pass\r
-        }\r
-\r
-        public void mouseDown(MouseEvent arg0) {\r
-            // pass\r
-        }\r
-\r
-        public void mouseUp(MouseEvent arg0) {\r
-            for (Item i : mItems) {\r
-                if (i.isVisible()) {\r
-                    Object ld = i.getLayoutData();\r
-                    if (ld instanceof GridData) {\r
-                        GridData gd = (GridData) ld;\r
-\r
-                        i.setData(gd.heightHint != SWT.DEFAULT ?\r
-                                    Integer.valueOf(gd.heightHint) :\r
-                                        null);\r
-                        gd.heightHint = 0;\r
-                    }\r
-                } else {\r
-                    Object ld = i.getLayoutData();\r
-                    if (ld instanceof GridData) {\r
-                        GridData gd = (GridData) ld;\r
-\r
-                        Object d = i.getData();\r
-                        if (d instanceof Integer) {\r
-                            gd.heightHint = ((Integer) d).intValue();\r
-                        } else {\r
-                            gd.heightHint = SWT.DEFAULT;\r
-                        }\r
-                    }\r
-                }\r
-                i.setVisible(!i.isVisible());\r
-            }\r
-\r
-            getParent().getParent().layout(true /*changed*/);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * An Item widget represents one {@link ElementDescriptor} that can be dropped on the\r
-     * GLE2 canvas using drag'n'drop.\r
-     */\r
-    private static class Item extends CLabel implements MouseTrackListener {\r
-\r
-        private boolean mMouseIn;\r
-        private DragSource mSource;\r
-\r
-        public Item(Composite parent, ElementDescriptor desc) {\r
-            super(parent, SWT.NONE);\r
-            mMouseIn = false;\r
-\r
-            setText(desc.getUiName());\r
-            setImage(desc.getIcon());\r
-            setToolTipText(desc.getTooltip());\r
-            addMouseTrackListener(this);\r
-\r
-            // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html\r
-            mSource = new DragSource(this, DND.DROP_COPY);\r
-            mSource.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });\r
-            mSource.addDragListener(new DescDragSourceListener(desc));\r
-        }\r
-\r
-        @Override\r
-        public void dispose() {\r
-            if (mSource != null) {\r
-                mSource.dispose();\r
-                mSource = null;\r
-            }\r
-            super.dispose();\r
-        }\r
-\r
-        @Override\r
-        public int getStyle() {\r
-            int style = super.getStyle();\r
-            if (mMouseIn) {\r
-                style |= SWT.SHADOW_IN;\r
-            }\r
-            return style;\r
-        }\r
-\r
-        public void mouseEnter(MouseEvent e) {\r
-            if (!mMouseIn) {\r
-                mMouseIn = true;\r
-                redraw();\r
-            }\r
-        }\r
-\r
-        public void mouseExit(MouseEvent e) {\r
-            if (mMouseIn) {\r
-                mMouseIn = false;\r
-                redraw();\r
-            }\r
-        }\r
-\r
-        public void mouseHover(MouseEvent e) {\r
-            // pass\r
-        }\r
-    }\r
-\r
-    /**\r
-     * A {@link DragSourceListener} that deals with drag'n'drop of\r
-     * {@link ElementDescriptor}s.\r
-     */\r
-    private static class DescDragSourceListener implements DragSourceListener {\r
-\r
-        private final ElementDescriptor mDesc;\r
-\r
-        public DescDragSourceListener(ElementDescriptor desc) {\r
-            mDesc = desc;\r
-        }\r
-\r
-        public void dragStart(DragSourceEvent e) {\r
-            if (mDesc == null) {\r
-                e.doit = false;\r
-            }\r
-        }\r
-\r
-\r
-        public void dragSetData(DragSourceEvent e) {\r
-            // Provide the data for the drop when requested by the other side.\r
-            if (ElementDescTransfer.getInstance().isSupportedType(e.dataType)) {\r
-                e.data = mDesc;\r
-            }\r
-        }\r
-\r
-        public void dragFinished(DragSourceEvent e) {\r
-            // Nothing to do here.\r
-        }\r
-    }\r
-\r
-}\r
+/*
+ * 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.gle2;
+
+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.custom.CLabel;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DragSource;
+import org.eclipse.swt.dnd.DragSourceEvent;
+import org.eclipse.swt.dnd.DragSourceListener;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.ScrollBar;
+
+import java.util.ArrayList;
+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:
+ *   - The available items should depend on the actual GLE2 Canvas selection. Selected android
+ *     views should force filtering on what they accept can be dropped on them (e.g. TabHost,
+ *     TableLayout). Should enable/disable them, not hide them, to avoid shuffling around.
+ *   - Optional: a text filter
+ *   - Optional: have icons that depict the element and/or automatically rendered icons
+ *     based on a rendering of the widget.
+ *   - Optional: have context-sensitive tools items, e.g. selection arrow tool,
+ *     group selection tool, alignment, etc.
+ *   - 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.
+ */
+public class PaletteComposite extends Composite {
+
+
+    /** The parent grid layout that contains all the {@link Toggle} and {@link Item} widgets. */
+    private Composite mRoot;
+
+    /**
+     * 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
+    }
+
+    /**
+     * Loads or reloads the palette elements by using the layout 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();
+        }
+
+        setGridLayout(this, 2);
+
+        mRoot = new Composite(this, SWT.NONE);
+        setGridLayout(mRoot, 0);
+
+        if (targetData != null) {
+            addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors());
+            addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());
+        }
+
+        layout(true);
+
+        final ScrollBar vbar = getVerticalBar();
+
+        vbar.setMaximum(getSize().y);
+
+        for (Listener listener : vbar.getListeners(SWT.Selection)) {
+            vbar.removeListener(SWT.Selection, listener);
+        }
+
+        vbar.addListener(SWT.Selection, new Listener() {
+            public void handleEvent(Event event) {
+                Point p = mRoot.getLocation();
+                p.y = - vbar.getSelection();
+                mRoot.setLocation(p);
+            }
+        });
+    }
+
+    private void setGridLayout(Composite parent, int spacing) {
+        GridLayout gl = new GridLayout(1, false);
+        gl.horizontalSpacing = 0;
+        gl.verticalSpacing = 0;
+        gl.marginHeight = spacing;
+        gl.marginBottom = spacing;
+        gl.marginLeft = spacing;
+        gl.marginRight = spacing;
+        gl.marginTop = spacing;
+        gl.marginBottom = spacing;
+        parent.setLayout(gl);
+    }
+
+    private void addGroup(Composite parent,
+            String uiName,
+            List<ElementDescriptor> descriptors) {
+
+        Composite group = new Composite(parent, SWT.NONE);
+        setGridLayout(group, 0);
+
+        Toggle toggle = new Toggle(group, uiName);
+
+        for (ElementDescriptor desc : descriptors) {
+            Item item = new Item(group, desc);
+            toggle.addItem(item);
+            GridData gd = new GridData();
+            item.setLayoutData(gd);
+        }
+    }
+
+    /**
+     * A Toggle widget is a row that is the head of a group.
+     * <p/>
+     * When clicked, the toggle will show/hide all the {@link Item} widgets that have been
+     * added to it using {@link #addItem(Item)}.
+     */
+    private static class Toggle extends CLabel implements MouseTrackListener, MouseListener {
+        private boolean mMouseIn;
+        private DragSource mSource;
+        private ArrayList<Item> mItems = new ArrayList<Item>();
+
+        public Toggle(Composite parent, String groupName) {
+            super(parent, SWT.NONE);
+            mMouseIn = false;
+
+            setData(null);
+
+            String s = String.format("-= %s =-", groupName);
+            setText(s);
+            setToolTipText(s);
+            //TODO use triangle icon and swap it -- setImage(desc.getIcon());
+            addMouseTrackListener(this);
+            addMouseListener(this);
+        }
+
+        public void addItem(Item item) {
+            mItems.add(item);
+        }
+
+        @Override
+        public void dispose() {
+            if (mSource != null) {
+                mSource.dispose();
+                mSource = null;
+            }
+            super.dispose();
+        }
+
+        @Override
+        public int getStyle() {
+            int style = super.getStyle();
+            if (mMouseIn) {
+                style |= SWT.SHADOW_IN;
+            }
+            return style;
+        }
+
+        // -- MouseTrackListener callbacks
+
+        public void mouseEnter(MouseEvent e) {
+            if (!mMouseIn) {
+                mMouseIn = true;
+                redraw();
+            }
+        }
+
+        public void mouseExit(MouseEvent e) {
+            if (mMouseIn) {
+                mMouseIn = false;
+                redraw();
+            }
+        }
+
+        public void mouseHover(MouseEvent e) {
+            // pass
+        }
+
+        // -- MouseListener callbacks
+
+        public void mouseDoubleClick(MouseEvent arg0) {
+            // pass
+        }
+
+        public void mouseDown(MouseEvent arg0) {
+            // pass
+        }
+
+        public void mouseUp(MouseEvent arg0) {
+            for (Item i : mItems) {
+                if (i.isVisible()) {
+                    Object ld = i.getLayoutData();
+                    if (ld instanceof GridData) {
+                        GridData gd = (GridData) ld;
+
+                        i.setData(gd.heightHint != SWT.DEFAULT ?
+                                    Integer.valueOf(gd.heightHint) :
+                                        null);
+                        gd.heightHint = 0;
+                    }
+                } else {
+                    Object ld = i.getLayoutData();
+                    if (ld instanceof GridData) {
+                        GridData gd = (GridData) ld;
+
+                        Object d = i.getData();
+                        if (d instanceof Integer) {
+                            gd.heightHint = ((Integer) d).intValue();
+                        } else {
+                            gd.heightHint = SWT.DEFAULT;
+                        }
+                    }
+                }
+                i.setVisible(!i.isVisible());
+            }
+
+            getParent().getParent().layout(true /*changed*/);
+        }
+    }
+
+    /**
+     * An Item widget represents one {@link ElementDescriptor} that can be dropped on the
+     * GLE2 canvas using drag'n'drop.
+     */
+    private static class Item extends CLabel implements MouseTrackListener {
+
+        private boolean mMouseIn;
+        private DragSource mSource;
+
+        public Item(Composite parent, ElementDescriptor desc) {
+            super(parent, SWT.NONE);
+            mMouseIn = false;
+
+            setText(desc.getUiName());
+            setImage(desc.getIcon());
+            setToolTipText(desc.getTooltip());
+            addMouseTrackListener(this);
+
+            // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
+            mSource = new DragSource(this, DND.DROP_COPY);
+            mSource.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });
+            mSource.addDragListener(new DescDragSourceListener(desc));
+        }
+
+        @Override
+        public void dispose() {
+            if (mSource != null) {
+                mSource.dispose();
+                mSource = null;
+            }
+            super.dispose();
+        }
+
+        @Override
+        public int getStyle() {
+            int style = super.getStyle();
+            if (mMouseIn) {
+                style |= SWT.SHADOW_IN;
+            }
+            return style;
+        }
+
+        public void mouseEnter(MouseEvent e) {
+            if (!mMouseIn) {
+                mMouseIn = true;
+                redraw();
+            }
+        }
+
+        public void mouseExit(MouseEvent e) {
+            if (mMouseIn) {
+                mMouseIn = false;
+                redraw();
+            }
+        }
+
+        public void mouseHover(MouseEvent e) {
+            // pass
+        }
+    }
+
+    /**
+     * A {@link DragSourceListener} that deals with drag'n'drop of
+     * {@link ElementDescriptor}s.
+     */
+    private static class DescDragSourceListener implements DragSourceListener {
+
+        private final ElementDescriptor mDesc;
+
+        public DescDragSourceListener(ElementDescriptor desc) {
+            mDesc = desc;
+        }
+
+        public void dragStart(DragSourceEvent e) {
+            if (mDesc == null) {
+                e.doit = false;
+            }
+        }
+
+
+        public void dragSetData(DragSourceEvent e) {
+            // Provide the data for the drop when requested by the other side.
+            if (ElementDescTransfer.getInstance().isSupportedType(e.dataType)) {
+                e.data = mDesc;
+            }
+        }
+
+        public void dragFinished(DragSourceEvent e) {
+            // Nothing to do here.
+        }
+    }
+
+}
index d639fb9..fd0f475 100755 (executable)
@@ -17,8 +17,8 @@
 package com.android.ide.eclipse.adt.internal.editors.layout.gre;
 
 import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.gscripts.INodeProxy;
-import com.android.ide.eclipse.adt.gscripts.Rect;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
 import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
index 48986b6..0149096 100755 (executable)
@@ -18,10 +18,10 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre;
 
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.AndroidConstants;
-import com.android.ide.eclipse.adt.gscripts.DropZone;
-import com.android.ide.eclipse.adt.gscripts.INodeProxy;
-import com.android.ide.eclipse.adt.gscripts.IViewRule;
-import com.android.ide.eclipse.adt.gscripts.Point;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;