OSDN Git Service

ADT/GLE: Improve the config selector.
authorXavier Ducrohet <xav@android.com>
Thu, 7 Jan 2010 01:21:48 +0000 (17:21 -0800)
committerXavier Ducrohet <xav@android.com>
Thu, 7 Jan 2010 19:22:08 +0000 (11:22 -0800)
- Better support for device/config, properly select config when opening
files, proper support for files that have different config versions

- Better language support, with default values and languages
with no regions.

- (attempt to) Properly set locale combo when opening files

- attempt to keep a somewhat compatible config when changing
device.

- general clean up of the API. More to come.

Change-Id: I45652bb18e6a61b443a7f0c1087a9b2d3f81e033

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle1/GraphicalLayoutEditor.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/sdk/Sdk.java

index 92cb3a0..6d07eb2 100755 (executable)
@@ -19,8 +19,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout;
 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 org.eclipse.core.resources.IFile;
 import org.eclipse.gef.ui.parts.SelectionSynchronizer;
 import org.eclipse.swt.dnd.Clipboard;
 import org.eclipse.ui.IEditorPart;
@@ -34,9 +34,19 @@ public interface IGraphicalLayoutEditor extends IEditorPart {
 
     /**
      * Sets the UI for the edition of a new file.
-     * @param configuration the configuration of the new file.
+     * @param iFile the file being edited.
      */
-    abstract void editNewFile(FolderConfiguration configuration);
+    abstract void initWithFile(IFile iFile);
+
+    /**
+     * Responds to a target change for the project of the edited file
+     */
+    abstract void onTargetChange();
+
+    /**
+     * Responds to an SDK reload/change.
+     */
+    abstract void onSdkChange();
 
     /**
      * Reloads this editor, by getting the new model from the {@link LayoutEditor}.
@@ -81,7 +91,4 @@ public interface IGraphicalLayoutEditor extends IEditorPart {
     abstract LayoutEditor getLayoutEditor();
 
     abstract Clipboard getClipboard();
-
-    abstract void reloadConfigurationUi(boolean notifyListener);
-
 }
index a59b32f..9c5e8d3 100644 (file)
@@ -27,13 +27,12 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorP
 import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiActions;
 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.manager.ResourceFolder;
-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.ui.EclipseUiHelper;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.gef.ui.parts.TreeViewer;
 import org.eclipse.ui.IEditorInput;
@@ -151,11 +150,10 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
                 IEditorInput input = getEditorInput();
                 if (input instanceof FileEditorInput) {
                     FileEditorInput fileInput = (FileEditorInput)input;
-                    ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(
-                            fileInput.getFile());
-                    if (resFolder != null) {
-                        mGraphicalEditor.editNewFile(resFolder.getConfiguration());
-                    }
+                    mGraphicalEditor.initWithFile(fileInput.getFile());
+                } else {
+                    AdtPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s",
+                            input.toString());
                 }
 
                 // put in place the listener to handle layout recompute only when needed.
@@ -414,8 +412,7 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
         if (mGraphicalEditor != null) {
             mGraphicalEditor.reloadEditor();
             mGraphicalEditor.reloadPalette();
-            mGraphicalEditor.reloadConfigurationUi(true /*notify listener */);
-            mGraphicalEditor.recomputeLayout();
+            mGraphicalEditor.onTargetChange();
         }
     }
 
index b190e59..ff1802c 100644 (file)
@@ -25,16 +25,18 @@ import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQuali
 import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
 import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier;
 import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQualifier;
 import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density;
 import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier.ScreenOrientation;
 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
 import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager;
 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.ui.ConfigurationSelector.LanguageRegionVerifier;
 import com.android.layoutlib.api.IResourceValue;
 import com.android.layoutlib.api.IStyleResourceValue;
+import com.android.sdklib.IAndroidTarget;
 
 import org.eclipse.draw2d.geometry.Rectangle;
 import org.eclipse.swt.SWT;
@@ -55,6 +57,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
+import java.util.Map.Entry;
 
 /**
  * A composite that displays the current configuration displayed in a Graphical Layout Editor.
@@ -66,13 +69,12 @@ public class ConfigurationComposite extends Composite {
     private Button mClippingButton;
     private Label mCurrentLayoutLabel;
 
-    private Combo mDeviceCombot;
+    private Combo mDeviceCombo;
     private Combo mDeviceConfigCombo;
     private Combo mLocaleCombo;
     private Combo mThemeCombo;
     private Button mCreateButton;
 
-
     private int mPlatformThemeCount = 0;
     private boolean mDisableUpdates = false;
 
@@ -91,6 +93,9 @@ public class ConfigurationComposite extends Composite {
     /** The config listener given to the constructor. Never null. */
     private final IConfigListener mListener;
 
+    private FolderConfiguration mEditedConfig;
+    private IAndroidTarget mTarget;
+
     /**
      * Interface implemented by the part which owns a {@link ConfigurationComposite}.
      * This notifies the owners when the configuration change.
@@ -110,6 +115,18 @@ public class ConfigurationComposite extends Composite {
     }
 
     /**
+     * State of the config selection. This is used during UI reset to attempt to return the
+     * rendering to its original configuration.
+     */
+    private static class SelectionState {
+        String deviceName;
+        String configName;
+        int locale;
+        String theme;
+        boolean clipping;
+    }
+
+    /**
      * Interface implemented by the part which owns a {@link ConfigurationComposite}
      * to define and handle custom toggle buttons in the button bar. Each toggle is
      * implemented using a button, with a callback when the button is selected.
@@ -226,10 +243,10 @@ public class ConfigurationComposite extends Composite {
         gl.horizontalSpacing = 0;
 
         new Label(this, SWT.NONE).setText("Devices");
-        mDeviceCombot = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mDeviceCombot.setLayoutData(new GridData(
+        mDeviceCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
+        mDeviceCombo.setLayoutData(new GridData(
                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mDeviceCombot.addSelectionListener(new SelectionAdapter() {
+        mDeviceCombo.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
                 onDeviceChange(true /* recomputeLayout*/);
@@ -269,7 +286,9 @@ public class ConfigurationComposite extends Composite {
 
         mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN);
         mThemeCombo.setEnabled(false);
-        updateUIFromResources();
+
+        updateThemesAndLocales();
+
         mThemeCombo.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
@@ -295,104 +314,225 @@ public class ConfigurationComposite extends Composite {
             }
         });
 
-        initUiWithDevices();
-
-        onDeviceConfigChange();
+        // load the devices from the SDK and fill the device combo.
+        initDeviceCombos();
     }
 
+    // ---- Init and reset/reload methods ----
 
-    public FolderConfiguration getCurrentConfig() {
-        return mCurrentConfig;
-    }
+    /**
+     * Init the UI with a given file configuration and project target.
+     * <p/>This will NOT trigger a redraw event (will not call
+     * {@link IConfigListener#onConfigurationChange()}.)
+     * The state of the selection of the various combos will have default values. To keep the
+     * current selection (useful for themes/locales), use
+     * {@link #resetUi(FolderConfiguration, IAndroidTarget)}.
+     *
+     * @param fileConfig The {@link FolderConfiguration} of the opened file.
+     * @param target the {@link IAndroidTarget} of the file's project.
+     */
+    public void initWith(FolderConfiguration fileConfig, IAndroidTarget target) {
+        SelectionState state = null;
+        if (mEditedConfig != null) {
+            state = getState();
+        }
 
-    public void getCurrentConfig(FolderConfiguration config) {
-        config.set(mCurrentConfig);
+        doInitWith(fileConfig, target, state);
     }
 
     /**
-     * Returns the currently selected {@link Density}. This is guaranteed to be non null.
+     * Init the UI with a given file configuration and project target.
+     * <p/>This will NOT trigger a redraw event (will not call
+     * {@link IConfigListener#onConfigurationChange()}.)
+     * The state of the selection of the various combos will have default values. To keep the
+     * current selection (useful for themes/locales), use
+     * {@link #resetUi(FolderConfiguration, IAndroidTarget)}.
+     *
+     * @param fileConfig The {@link FolderConfiguration} of the opened file.
+     * @param target the {@link IAndroidTarget} of the file's project.
      */
-    public Density getDensity() {
-        if (mCurrentConfig != null) {
-            PixelDensityQualifier qual = mCurrentConfig.getPixelDensityQualifier();
-            if (qual != null) {
-                // just a sanity check
-                Density d = qual.getValue();
-                if (d != Density.NODPI) {
-                    return d;
+    private void doInitWith(FolderConfiguration fileConfig, IAndroidTarget target,
+            SelectionState state) {
+        mEditedConfig = fileConfig;
+        mTarget = target;
+
+        AndroidTargetData data = Sdk.getCurrent().getTargetData(mTarget);
+        if (data != null) {
+            LayoutBridge bridge = data.getLayoutBridge();
+            setClippingSupport(bridge.apiLevel >= 4);
+        }
+
+        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting
+                                // new values in the widgets.
+
+        LayoutDevice deviceMatch = null;
+        String configMatchName = null;
+
+        if (state != null) {
+            // try to find the same device/config
+            mainloop: for (LayoutDevice device : mDeviceList) {
+                if (state.deviceName.equals(device.getName())) {
+                    // found a device match, look for the config
+                    FolderConfiguration testConfig = device.getConfigs().get(state.configName);
+                    if (testConfig != null && fileConfig.isMatchFor(testConfig)) {
+                        deviceMatch = device;
+                        configMatchName = state.configName;
+                        break mainloop;
+                    }
+
+                    // let's look for a compatible config in the same device.
+                    for (Entry<String, FolderConfiguration> entry :
+                            device.getConfigs().entrySet()) {
+                        if (fileConfig.isMatchFor(entry.getValue())) {
+                            // this is what we want.
+                            deviceMatch = device;
+                            configMatchName = entry.getKey();
+                            break mainloop;
+                        }
+                    }
+
+                    // still nothing, let's give up and look for any compatible device/config below
+                    break mainloop;
+                }
+            }
+
+            // apply the theme
+            if (state.theme != null) {
+                final int count = mThemeCombo.getItemCount();
+                for (int i = 0 ; i < count ; i++) {
+                    if (state.theme.equals(mThemeCombo.getItem(i))) {
+                        mThemeCombo.select(i);
+                        break;
+                    }
                 }
             }
         }
 
-        // no config? return medium as the default density.
-        return Density.MEDIUM;
-    }
+        if (deviceMatch == null) {
+            // attempt find a device that can display this particular config.
+            mainloop: for (LayoutDevice device : mDeviceList) {
+                for (Entry<String, FolderConfiguration> entry : device.getConfigs().entrySet()) {
+                    if (fileConfig.isMatchFor(entry.getValue())) {
+                        // this is what we want.
+                        deviceMatch = device;
+                        configMatchName = entry.getKey();
+                        break mainloop;
+                    }
 
-    /**
-     * Returns the current device xdpi.
-     */
-    public float getXDpi() {
-        if (mCurrentDevice != null) {
-            float dpi = mCurrentDevice.getXDpi();
-            if (Float.isNaN(dpi) == false) {
-                return dpi;
+                }
             }
         }
 
-        // get the pixel density as the density.
-        return getDensity().getDpiValue();
+        if (deviceMatch == null) {
+            // TODO: there is no device/config able to display the layout, create one.
+            // For the base config values, we'll take the first device and config,
+            // and replace whatever qualifier required by the layout file.
+        } else {
+            selectDevice(mCurrentDevice = deviceMatch);
+            fillConfigCombo(configMatchName);
+        }
+
+        // compute the current config.
+        computeCurrentConfig();
+
+        // now that we have a the base config, try to find a good locale selection.
+        // 1. look for an exact match
+
+        // apply the locale, from the state (if applicable),
+        // making sure this matches the file config.
+        boolean stateLocalIsCompatible = false;
+        if (state != null && state.locale != -1) {
+            // get the local values.
+            ResourceQualifier[] locale = mLocaleList.get(state.locale);
+
+            // apply it to the current config.
+            mCurrentConfig.setLanguageQualifier((LanguageQualifier)locale[0]); // language
+            mCurrentConfig.setRegionQualifier((RegionQualifier)locale[1]); // region
+
+            // make sure this is compatible
+            if (fileConfig.isMatchFor(mCurrentConfig)) {
+                mLocaleCombo.select(state.locale);
+                stateLocalIsCompatible = true;
+            }
+        }
+
+        // no state, or state doesn't match, look for a match
+        if (stateLocalIsCompatible == false) {
+            // loop on all locale and stop on the first compatible one.
+            final int count = mLocaleList.size();
+            for (int i = 0 ; i < count ; i++) {
+                ResourceQualifier[] locale = mLocaleList.get(i);
+
+                // apply it to the current config.
+                mCurrentConfig.setLanguageQualifier((LanguageQualifier)locale[0]); // language
+                mCurrentConfig.setRegionQualifier((RegionQualifier)locale[1]); // region
+
+                if (fileConfig.isMatchFor(mCurrentConfig)) {
+                    mLocaleCombo.select(i);
+                    break;
+                }
+            }
+        }
+
+        // compute the final current config
+        computeCurrentConfig();
+
+        // update the string showing the folder name
+        String current = fileConfig.toDisplayString();
+        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
+
+        mDisableUpdates = false;
     }
 
     /**
-     * Returns the current device ydpi.
+     * Resets the UI by reloading the target content and possibly the list of devices.
+     * <p/>This method will attempt to keep the same selection in the various combos (device,
+     * config, locale, theme).
+     * <p/>This will NOT trigger a redraw event (will not call
+     * {@link IConfigListener#onConfigurationChange()}.)
      */
-    public float getYDpi() {
-        if (mCurrentDevice != null) {
-            float dpi = mCurrentDevice.getYDpi();
-            if (Float.isNaN(dpi) == false) {
-                return dpi;
-            }
+    public void resetUi(FolderConfiguration config, IAndroidTarget target, boolean resetDevices) {
+        if (resetDevices) {
+            initDeviceCombos();
         }
 
-        // get the pixel density as the density.
-        return getDensity().getDpiValue();
+        SelectionState state = getState();
+
+        doInitWith(config, target, state);
     }
 
-    public Rectangle getScreenBounds() {
-        // get the orientation from the current device config
-        ScreenOrientationQualifier qual = mCurrentConfig.getScreenOrientationQualifier();
-        ScreenOrientation orientation = ScreenOrientation.PORTRAIT;
-        if (qual != null) {
-            orientation = qual.getValue();
-        }
+    private SelectionState getState() {
+        if (mCurrentDevice != null) {
+            SelectionState state = new SelectionState();
 
-        // get the device screen dimension
-        ScreenDimensionQualifier qual2 = mCurrentConfig.getScreenDimensionQualifier();
-        int s1, s2;
-        if (qual2 != null) {
-            s1 = qual2.getValue1();
-            s2 = qual2.getValue2();
-        } else {
-            s1 = 480;
-            s2 = 320;
-        }
+            state.deviceName = mCurrentDevice.getName();
 
-        switch (orientation) {
-            default:
-            case PORTRAIT:
-                return new Rectangle(0, 0, s2, s1);
-            case LANDSCAPE:
-                return new Rectangle(0, 0, s1, s2);
-            case SQUARE:
-                return new Rectangle(0, 0, s1, s1);
+            int index = mDeviceConfigCombo.getSelectionIndex();
+            if (index != -1) {
+                state.configName = mDeviceConfigCombo.getItem(index);
+            }
+
+            // since the locales are relative to the project, only keeping the index is enough
+            state.locale = mLocaleCombo.getSelectionIndex();
+
+            index = mThemeCombo.getSelectionIndex();
+            if (index != -1) {
+                state.theme = mThemeCombo.getItem(index);
+            }
+
+            state.clipping = mClipping;
+
+            return state;
         }
+
+        return null;
     }
 
     /**
      * Updates the UI from values in the resources, such as languages, regions, themes, etc...
      * This must be called from the UI thread.
      */
-    public void updateUIFromResources() {
+    public void updateThemesAndLocales() {
         if (mListener == null) {
             return; // can't do anything w/o it.
         }
@@ -491,18 +631,27 @@ public class ConfigurationComposite extends Composite {
             for (String language : languages) {
                 hasLocale = true;
 
-                // first the language alone
-                mLocaleCombo.add(language);
-                LanguageQualifier qual = new LanguageQualifier(language);
-                mLocaleList.add(new ResourceQualifier[] { qual, null });
+                LanguageQualifier langQual = new LanguageQualifier(language);
 
-                // now find the matching regions and add them
+                // find the matching regions and add them
                 SortedSet<String> regions = project.getRegions(language);
                 for (String region : regions) {
-                    mLocaleCombo.add(String.format("%1$s_%2$s", language, region)); //$NON-NLS-1$
-                    RegionQualifier qual2 = new RegionQualifier(region);
-                    mLocaleList.add(new ResourceQualifier[] { qual, qual2 });
+                    mLocaleCombo.add(String.format("%1$s / %2$s", language, region)); //$NON-NLS-1$
+                    RegionQualifier regionQual = new RegionQualifier(region);
+                    mLocaleList.add(new ResourceQualifier[] { langQual, regionQual });
+                }
+
+                // now the entry for the other regions the language alone
+                if (regions.size() > 0) {
+                    mLocaleCombo.add(String.format("%1$s / Other", language)); //$NON-NLS-1$
+                } else {
+                    mLocaleCombo.add(String.format("%1$s / Any", language)); //$NON-NLS-1$
                 }
+                // create a region qualifier that will never be matched by qualified resources.
+                mLocaleList.add(new ResourceQualifier[] {
+                        langQual,
+                        new RegionQualifier("__")  //$NON-NLS-1$
+                });
             }
         }
 
@@ -513,12 +662,14 @@ public class ConfigurationComposite extends Composite {
         } else {
             mLocaleCombo.add("Any");
         }
+
+        // create language/region qualifier that will never be matched by qualified resources.
         mLocaleList.add(new ResourceQualifier[] {
-                new LanguageQualifier("__"),
-                new RegionQualifier("__")
+                new LanguageQualifier("__"), //$NON-NLS-1$
+                new RegionQualifier("__")    //$NON-NLS-1$
         });
 
-        mDisableUpdates = false;
+        mLocaleCombo.select(0);
 
         // handle default selection of themes
         if (mThemeCombo.getItemCount() > 0) {
@@ -537,9 +688,101 @@ public class ConfigurationComposite extends Composite {
         }
 
         mThemeCombo.getParent().layout();
+
+        mDisableUpdates = false;
+    }
+
+    // ---- getters for the config selection values ----
+
+    public FolderConfiguration getCurrentConfig() {
+        return mCurrentConfig;
+    }
+
+    public void getCurrentConfig(FolderConfiguration config) {
+        config.set(mCurrentConfig);
+    }
+
+    /**
+     * Returns the currently selected {@link Density}. This is guaranteed to be non null.
+     */
+    public Density getDensity() {
+        if (mCurrentConfig != null) {
+            PixelDensityQualifier qual = mCurrentConfig.getPixelDensityQualifier();
+            if (qual != null) {
+                // just a sanity check
+                Density d = qual.getValue();
+                if (d != Density.NODPI) {
+                    return d;
+                }
+            }
+        }
+
+        // no config? return medium as the default density.
+        return Density.MEDIUM;
+    }
+
+    /**
+     * Returns the current device xdpi.
+     */
+    public float getXDpi() {
+        if (mCurrentDevice != null) {
+            float dpi = mCurrentDevice.getXDpi();
+            if (Float.isNaN(dpi) == false) {
+                return dpi;
+            }
+        }
+
+        // get the pixel density as the density.
+        return getDensity().getDpiValue();
     }
 
     /**
+     * Returns the current device ydpi.
+     */
+    public float getYDpi() {
+        if (mCurrentDevice != null) {
+            float dpi = mCurrentDevice.getYDpi();
+            if (Float.isNaN(dpi) == false) {
+                return dpi;
+            }
+        }
+
+        // get the pixel density as the density.
+        return getDensity().getDpiValue();
+    }
+
+    public Rectangle getScreenBounds() {
+        // get the orientation from the current device config
+        ScreenOrientationQualifier qual = mCurrentConfig.getScreenOrientationQualifier();
+        ScreenOrientation orientation = ScreenOrientation.PORTRAIT;
+        if (qual != null) {
+            orientation = qual.getValue();
+        }
+
+        // get the device screen dimension
+        ScreenDimensionQualifier qual2 = mCurrentConfig.getScreenDimensionQualifier();
+        int s1, s2;
+        if (qual2 != null) {
+            s1 = qual2.getValue1();
+            s2 = qual2.getValue2();
+        } else {
+            s1 = 480;
+            s2 = 320;
+        }
+
+        switch (orientation) {
+            default:
+            case PORTRAIT:
+                return new Rectangle(0, 0, s2, s1);
+            case LANDSCAPE:
+                return new Rectangle(0, 0, s1, s2);
+            case SQUARE:
+                return new Rectangle(0, 0, s1, s1);
+        }
+    }
+
+
+    /**
      * Returns the current theme, or null if the combo has no selection.
      */
     public String getTheme() {
@@ -564,11 +807,7 @@ public class ConfigurationComposite extends Composite {
         return mClipping;
     }
 
-    public void setEnabledCreate(boolean enabled) {
-        mCreateButton.setEnabled(enabled);
-    }
-
-    public void setClippingSupport(boolean b) {
+    private void setClippingSupport(boolean b) {
         mClippingButton.setEnabled(b);
         if (b) {
             mClippingButton.setToolTipText("Toggles screen clipping on/off");
@@ -579,38 +818,6 @@ public class ConfigurationComposite extends Composite {
         }
     }
 
-    /**
-     * Update the UI controls state with a given {@link FolderConfiguration}.
-     * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
-     * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
-     * the UI control is not modified. However if the value in the control is not the default value,
-     * a warning icon is shown.
-     * @param config The {@link FolderConfiguration} to set.
-     * @param force Whether the UI should be changed to exactly match the received configuration.
-     */
-    public void setConfiguration(FolderConfiguration config, boolean force) {
-        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets.
-
-        // TODO: find a device that can display this particular config or create a custom one if needed.
-
-
-        // update the string showing the folder name
-        String current = config.toDisplayString();
-        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
-
-        mDisableUpdates = false;
-    }
-
-    /**
-     * Reloads the list of {@link LayoutDevice} from the {@link Sdk}.
-     * @param notifyListener
-     */
-    public void reloadDevices(boolean notifyListener) {
-        loadDevices();
-        initUiWithDevices();
-        onDeviceChange(notifyListener);
-    }
-
     private void loadDevices() {
         mDeviceList = null;
 
@@ -622,19 +829,21 @@ public class ConfigurationComposite extends Composite {
     }
 
     /**
-     * Init the UI with the list of Devices.
+     * Loads the list of {@link LayoutDevice} and inits the UI with it.
      */
-    private void initUiWithDevices() {
+    private void initDeviceCombos() {
+        loadDevices();
+
         // remove older devices if applicable
-        mDeviceCombot.removeAll();
+        mDeviceCombo.removeAll();
         mDeviceConfigCombo.removeAll();
 
         // fill with the devices
         if (mDeviceList != null) {
             for (LayoutDevice device : mDeviceList) {
-                mDeviceCombot.add(device.getName());
+                mDeviceCombo.add(device.getName());
             }
-            mDeviceCombot.select(0);
+            mDeviceCombo.select(0);
 
             if (mDeviceList.size() > 0) {
                 Map<String, FolderConfiguration> configs = mDeviceList.get(0).getConfigs();
@@ -650,36 +859,40 @@ public class ConfigurationComposite extends Composite {
         }
 
         // add the custom item
-        mDeviceCombot.add("Custom...");
+        mDeviceCombo.add("Custom...");
+    }
+
+    private void selectDevice(LayoutDevice device) {
+        int index = -1;
+        final int count = mDeviceList.size();
+        for (int i = 0 ; i < count ; i++) {
+            // since device comes from mDeviceList, we can use the == operator.
+            if (device == mDeviceList.get(i)) {
+                index = i;
+                break;
+            }
+        }
+
+        mDeviceCombo.select(index);
     }
 
     /**
-     * Call back for language combo selection
+     * Called when the selection of the device combo changes.
+     * @param recomputeLayout
      */
-    private void onLocaleChange() {
-        // because mLanguage triggers onLanguageChange at each modification, the filling
-        // of the combo with data will trigger notifications, and we don't want that.
+    private void onDeviceChange(boolean recomputeLayout) {
+        // because changing the content of a combo triggers a change event, respect the
+        // mDisableUpdates flag
         if (mDisableUpdates == true) {
             return;
         }
 
-        int localeIndex = mLocaleCombo.getSelectionIndex();
-        ResourceQualifier[] localeQualifiers = mLocaleList.get(localeIndex);
-
-        mCurrentConfig.setLanguageQualifier((LanguageQualifier)localeQualifiers[0]); // language
-        mCurrentConfig.setRegionQualifier((RegionQualifier)localeQualifiers[1]); // region
-
-        if (mListener != null) {
-            mListener.onConfigurationChange();
-        }
-    }
-
-    private void onDeviceChange(boolean recomputeLayout) {
+        String newConfigName = null;
 
-        int deviceIndex = mDeviceCombot.getSelectionIndex();
+        int deviceIndex = mDeviceCombo.getSelectionIndex();
         if (deviceIndex != -1) {
-            // check if the user is ask for the custom item
-            if (deviceIndex == mDeviceCombot.getItemCount() - 1) {
+            // check if the user is asking for the custom item
+            if (deviceIndex == mDeviceCombo.getItemCount() - 1) {
                 ConfigManagerDialog dialog = new ConfigManagerDialog(getShell());
                 dialog.open();
 
@@ -687,17 +900,16 @@ public class ConfigurationComposite extends Composite {
                 Sdk.getCurrent().getLayoutDeviceManager().save();
 
                 // reload the combo with the new content.
-                loadDevices();
-                initUiWithDevices();
+                initWith(mEditedConfig, mTarget);
 
                 // at this point we need to reset the combo to something (hopefully) valid.
                 // look for the previous selected device
                 int index = mDeviceList.indexOf(mCurrentDevice);
                 if (index != -1) {
-                    mDeviceCombot.select(index);
+                    mDeviceCombo.select(index);
                 } else {
                     // we should at least have one built-in device, so we select it
-                    mDeviceCombot.select(0);
+                    mDeviceCombo.select(0);
                 }
 
                 // force a redraw
@@ -706,50 +918,177 @@ public class ConfigurationComposite extends Composite {
                 return;
             }
 
+            // get the previous config, so that we can look for a close match
+            if (mCurrentDevice != null) {
+                int index = mDeviceConfigCombo.getSelectionIndex();
+                if (index != -1) {
+                    FolderConfiguration oldConfig = mCurrentDevice.getConfigs().get(
+                            mDeviceConfigCombo.getItem(index));
+
+                    LayoutDevice newDevice = mDeviceList.get(deviceIndex);
+
+                    newConfigName = getClosestMatch(oldConfig, newDevice.getConfigs());
+                }
+            }
+
             mCurrentDevice = mDeviceList.get(deviceIndex);
         } else {
             mCurrentDevice = null;
         }
 
+        fillConfigCombo(newConfigName);
+        if (recomputeLayout) {
+            onDeviceConfigChange();
+        }
+    }
+
+    /**
+     * Attemps for find a close config among a list
+     * @param oldConfig the reference config.
+     * @param configs the list of config to search through
+     * @return the name of the closest config match, or possibly null if no config are compatible
+     * (this can only happen if the configs don't have a single qualifier that is the same).
+     */
+    private String getClosestMatch(FolderConfiguration oldConfig,
+            Map<String, FolderConfiguration> configs) {
+
+        // create 2 lists as we're going to go through one and put the candidates in the other.
+        ArrayList<Entry<String, FolderConfiguration>> list1 =
+            new ArrayList<Entry<String,FolderConfiguration>>();
+        ArrayList<Entry<String, FolderConfiguration>> list2 =
+            new ArrayList<Entry<String,FolderConfiguration>>();
+
+        list1.addAll(configs.entrySet());
+
+        final int count = FolderConfiguration.getQualifierCount();
+        for (int i = 0 ; i < count ; i++) {
+            // compute the new candidate list by only taking configs that have
+            // the same i-th qualifier as the old config
+            for (Entry<String, FolderConfiguration> entry : list1) {
+                ResourceQualifier oldQualifier = oldConfig.getQualifier(i);
+
+                FolderConfiguration config = entry.getValue();
+                ResourceQualifier newQualifier = config.getQualifier(i);
+
+                if (oldQualifier == null) {
+                    if (newQualifier == null) {
+                        list2.add(entry);
+                    }
+                } else if (oldQualifier.equals(newQualifier)) {
+                    list2.add(entry);
+                }
+            }
+
+            // at any moment if the new candidate list contains only one match, its name
+            // is returned.
+            if (list2.size() == 1) {
+                return list2.get(0).getKey();
+            }
+
+            // if the list is empty, then all the new configs failed. It is considered ok, and
+            // we move to the next qualifier anyway. This way, if a qualifier is different for
+            // all new configs it is simply ignored.
+            if (list2.size() != 0) {
+                // move the candidates back into list1.
+                list1.clear();
+                list1.addAll(list2);
+                list2.clear();
+            }
+        }
+
+        // the only way to reach this point is if there's an exact match.
+        // (if there are more than one, then there's a duplicate config and it doesn't matter,
+        // we take the first one).
+        if (list1.size() > 0) {
+            return list1.get(0).getKey();
+        }
+
+        return null;
+    }
+
+    /**
+     * fills the config combo with new values based on {@link #mCurrentDevice}.
+     * @param refName an optional name. if set the selection will match this name (if found)
+     */
+    private void fillConfigCombo(String refName) {
         mDeviceConfigCombo.removeAll();
 
         if (mCurrentDevice != null) {
             Set<String> configNames = mCurrentDevice.getConfigs().keySet();
+
+            int selectionIndex = 0;
+            int i = 0;
+
             for (String name : configNames) {
                 mDeviceConfigCombo.add(name);
+
+                if (name.equals(refName)) {
+                    selectionIndex = i;
+                }
+                i++;
             }
 
-            mDeviceConfigCombo.select(0);
+            mDeviceConfigCombo.select(selectionIndex);
             mDeviceConfigCombo.setEnabled(configNames.size() > 1);
         }
-        if (recomputeLayout) {
-            onDeviceConfigChange();
-        }
     }
 
+    /**
+     * Called when the device config selection changes.
+     */
     private void onDeviceConfigChange() {
+        // because changing the content of a combo triggers a change event, respect the
+        // mDisableUpdates flag
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        if (computeCurrentConfig() && mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    /**
+     * Call back for language combo selection
+     */
+    private void onLocaleChange() {
+        // because mLanguage triggers onLanguageChange at each modification, the filling
+        // of the combo with data will trigger notifications, and we don't want that.
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        if (computeCurrentConfig() &&  mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private boolean computeCurrentConfig() {
         if (mCurrentDevice != null) {
+            // get the device config from the device/config combos.
             int configIndex = mDeviceConfigCombo.getSelectionIndex();
             String name = mDeviceConfigCombo.getItem(configIndex);
             FolderConfiguration config = mCurrentDevice.getConfigs().get(name);
 
-            // get the current qualifiers from the current config
-            LanguageQualifier lang = mCurrentConfig.getLanguageQualifier();
-            RegionQualifier region = mCurrentConfig.getRegionQualifier();
-            VersionQualifier version = mCurrentConfig.getVersionQualifier();
-
             // replace the config with the one from the device
             mCurrentConfig.set(config);
 
-            // and put back the rest of the qualifiers
-            mCurrentConfig.addQualifier(lang);
-            mCurrentConfig.addQualifier(region);
-            mCurrentConfig.addQualifier(version);
+            // replace the locale qualifiers with the one coming from the locale combo
+            int localeIndex = mLocaleCombo.getSelectionIndex();
+            if (localeIndex != -1) {
+                ResourceQualifier[] localeQualifiers = mLocaleList.get(localeIndex);
 
-            if (mListener != null) {
-                mListener.onConfigurationChange();
+                mCurrentConfig.setLanguageQualifier((LanguageQualifier)localeQualifiers[0]); // language
+                mCurrentConfig.setRegionQualifier((RegionQualifier)localeQualifiers[1]); // region
             }
+
+            // update the create button.
+            checkCreateEnable();
+
+            return true;
         }
+
+        return false;
     }
 
     private void onThemeChange() {
@@ -830,5 +1169,10 @@ public class ConfigurationComposite extends Composite {
 
         return false;
     }
+
+    private void checkCreateEnable() {
+        mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
+    }
+
 }
 
index 2280b30..a38b0bc 100644 (file)
@@ -43,9 +43,11 @@ 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.ResourceFolder;
 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.LayoutDevice;
 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;
@@ -156,6 +158,8 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
     /** Listener to update the root node if the target of the file is changed because of a
      * SDK location change or a project target change */
     private ITargetChangeListener mTargetListener = new TargetChangeListener() {
+        boolean mSdkLoaded = true; // indicates whether we got a sdk loaded event.
+
         @Override
         public IProject getProject() {
             return getLayoutEditor().getProject();
@@ -163,22 +167,34 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
 
         @Override
         public void reload() {
-            // because the SDK changed we must reset the configured framework resource.
-            mConfiguredFrameworkRes = null;
+            // 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;
 
-            mConfigComposite.updateUIFromResources();
+            // update the themes and locales since the target change could have changed the
+            // theme list.
+            mConfigComposite.updateThemesAndLocales();
 
-            // updateUiFromFramework will reset language/region combo, so we must call
-            // setConfiguration after, or the settext on language/region will be lost.
+            // reset the config selector UI to match the new target (and possibly sdk).
             if (mEditedConfig != null) {
-                setConfiguration(mEditedConfig, false /*force*/);
+                if (mSdkLoaded) {
+                    onSdkChange();
+                } else {
+                    onTargetChange();
+                }
             }
 
-            // make sure we remove the custom view loader, since its parent class loader is the
-            // bridge class loader.
-            mProjectCallback = null;
+            // SDK change has been handled, reset the flag.
+            mSdkLoaded = false;
+        }
 
-            recomputeLayout();
+        @Override
+        public void onSdkLoaded() {
+            mSdkLoaded = true; // this will be reset when we get the target loaded event
+                               // which always comes after.
         }
     };
 
@@ -194,7 +210,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
 
     private final Runnable mUiUpdateFromResourcesRunnable = new Runnable() {
         public void run() {
-            mConfigComposite.updateUIFromResources();
+            mConfigComposite.updateThemesAndLocales();
         }
     };
 
@@ -400,8 +416,6 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
             FileEditorInput fileInput = (FileEditorInput)input;
             mEditedFile = fileInput.getFile();
 
-            mConfigComposite.updateUIFromResources();
-
             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), this);
         } else {
             // really this shouldn't happen! Log it in case it happens
@@ -574,17 +588,46 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
 
     /**
      * Sets the UI for the edition of a new file.
-     * @param configuration the configuration of the new file.
+     * @param iFile the file being edited.
      */
-    public void editNewFile(FolderConfiguration configuration) {
-        // update the configuration UI
-        setConfiguration(configuration, true /*force*/);
+    public void initWithFile(IFile file) {
+        mEditedFile = file;
+
+        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
+        mEditedConfig = resFolder.getConfiguration();
+
+        mConfigComposite.updateThemesAndLocales();
 
-        // enable the create button if the current and edited config are not equals
-        mConfigComposite.setEnabledCreate(
-                mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
+        Sdk currentSdk = Sdk.getCurrent();
+        if (currentSdk != null) {
+            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
+            if (target != null) {
+                mConfigComposite.initWith(mEditedConfig, target);
+            }
+        }
+    }
+
+    public void onTargetChange() {
+        onTargetOrSdkChange(false /* reloadDevices */);
+    }
+
+    public void onSdkChange() {
+        onTargetOrSdkChange(true /* reloadDevices */);
+    }
 
-        reloadConfigurationUi(false /*notifyListener*/);
+    /**
+     * Reloads the configuration selector.
+     * @param reloadDevices whether the {@link LayoutDevice} objects should be reloaded.
+     */
+    private void onTargetOrSdkChange(boolean reloadDevices) {
+        Sdk currentSdk = Sdk.getCurrent();
+        if (currentSdk != null) {
+            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
+            if (target != null) {
+                mConfigComposite.resetUi(mEditedConfig, target, reloadDevices);
+                onConfigurationChange();
+            }
+        }
     }
 
     public Rectangle getBounds() {
@@ -694,16 +737,6 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
 
         IEditorInput input = mLayoutEditor.getEditorInput();
         setInput(input);
-
-        if (input instanceof FileEditorInput) {
-            FileEditorInput fileInput = (FileEditorInput)input;
-            mEditedFile = fileInput.getFile();
-        } else {
-            // really this shouldn't happen! Log it in case it happens
-            mEditedFile = null;
-            AdtPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s",
-                    input.toString());
-        }
     }
 
     /**
@@ -740,24 +773,6 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
         }
     }
 
-    /**
-     * Update the UI controls state with a given {@link FolderConfiguration}.
-     * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
-     * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
-     * the UI control is not modified. However if the value in the control is not the default value,
-     * a warning icon is shown.
-     * @param config The {@link FolderConfiguration} to set.
-     * @param force Whether the UI should be changed to exactly match the received configuration.
-     */
-    void setConfiguration(FolderConfiguration config, boolean force) {
-        mEditedConfig = config;
-        mConfiguredFrameworkRes = mConfiguredProjectRes = null;
-
-        mConfigComposite.setConfiguration(config, force);
-
-    }
-
-
     public UiDocumentNode getModel() {
         return mLayoutEditor.getUiRootNode();
     }
@@ -766,20 +781,6 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
         PaletteFactory.createPaletteRoot(mPaletteRoot, mLayoutEditor.getTargetData());
     }
 
-    public void reloadConfigurationUi(boolean notifyListener) {
-        // enable the clipping button if it's supported.
-        Sdk currentSdk = Sdk.getCurrent();
-        if (currentSdk != null) {
-            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
-            AndroidTargetData data = currentSdk.getTargetData(target);
-            if (data != null) {
-                LayoutBridge bridge = data.getLayoutBridge();
-                mConfigComposite.reloadDevices(notifyListener);
-                mConfigComposite.setClippingSupport(bridge.apiLevel >= 4);
-            }
-        }
-    }
-
     /**
      * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
      * <p/>If there is no match, notify the user.
@@ -819,20 +820,10 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
 
             // at this point, we have not opened a new file.
 
-            // update the configuration icons with the new edited config.
-            setConfiguration(mEditedConfig, false /*force*/);
-
-            // enable the create button if the current and edited config are not equals
-            mConfigComposite.setEnabledCreate(
-                    mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
-
             // Even though the layout doesn't change, the config changed, and referenced
             // resources need to be updated.
             recomputeLayout();
         } else {
-            // enable the Create button
-            mConfigComposite.setEnabledCreate(true);
-
             // display the error.
             FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
             String message = String.format(
@@ -908,16 +899,25 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
 
                 AndroidTargetData data = currentSdk.getTargetData(target);
                 if (data == null) {
-                    // It can happen that the workspace refreshes while the SDK is loading its
+                    // It can happen that the workspace refreshes while the target is loading its
                     // data, which could trigger a redraw of the opened layout if some resources
                     // changed while Eclipse is closed.
                     // In this case data could be null, but this is not an error.
                     // We can just silently return, as all the opened editors are automatically
                     // refreshed once the SDK finishes loading.
-                    if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) {
-                        showErrorInEditor(String.format(
-                                "The project target (%s) was not properly loaded.",
-                                target.getName()));
+                    LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);
+                    switch (targetLoadStatus) {
+                        case LOADING:
+                            showErrorInEditor(String.format(
+                                    "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?!?!
+                            showErrorInEditor(String.format(
+                                    "The project target (%s) was not properly loaded.",
+                                    target.getName()));
+                            break;
                     }
                     return;
                 }
index deddf02..e8aee91 100755 (executable)
@@ -35,9 +35,11 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
 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.ResourceFolder;\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.LayoutDevice;\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
@@ -177,31 +179,13 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
             mTargetListener = new TargetListener();\r
             AdtPlugin.getDefault().addTargetListener(mTargetListener);\r
         }\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
-        }\r
     }\r
 \r
     /**\r
      * Reloads this editor, by getting the new model from the {@link LayoutEditor}.\r
      */\r
     public void reloadEditor() {\r
-        IEditorInput input = mLayoutEditor.getEditorInput();\r
-\r
-        try {\r
-            useNewEditorInput(input);\r
-        } catch (PartInitException e) {\r
-            // really this shouldn't happen! Log it in case it happens.\r
-            mEditedFile = null;\r
-            AdtPlugin.log(e, "Input is not of type FileEditorInput: %1$s",          //$NON-NLS-1$\r
-                    input == null ? "null" : input.toString());                     //$NON-NLS-1$\r
-        }\r
+        // nothing to be done here. the edited file is now set by initWithFile(IFile)\r
     }\r
 \r
     private void useNewEditorInput(IEditorInput input) throws PartInitException {\r
@@ -210,9 +194,6 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
             throw new PartInitException("Input is not of type FileEditorInput: " +  //$NON-NLS-1$\r
                     input == null ? "null" : input.toString());                     //$NON-NLS-1$\r
         }\r
-\r
-        FileEditorInput fileInput = (FileEditorInput)input;\r
-        mEditedFile = fileInput.getFile();\r
     }\r
 \r
     @Override\r
@@ -253,7 +234,7 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
         mConfigListener = new ConfigListener();\r
         mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER);\r
-        mConfigComposite.updateUIFromResources();\r
+        mConfigComposite.updateThemesAndLocales();\r
 \r
         mSashPalette = new SashForm(parent, SWT.HORIZONTAL);\r
         mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));\r
@@ -399,20 +380,10 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
                 // at this point, we have not opened a new file.\r
 \r
-                // update the configuration icons with the new edited config.\r
-                setConfiguration(mEditedConfig, false /*force*/);\r
-\r
-                // enable the create button if the current and edited config are not equals\r
-                mConfigComposite.setEnabledCreate(\r
-                        mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);\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
-                // enable the Create button\r
-                mConfigComposite.setEnabledCreate(true);\r
-\r
                 // display the error.\r
                 FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();\r
                 displayError(\r
@@ -632,6 +603,8 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
      * Listens to target changed in the current project, to trigger a new layout rendering.\r
      */\r
     private class TargetListener extends TargetChangeListener {\r
+        boolean mSdkLoaded = false; // indicates whether we got a sdk loaded event.\r
+\r
         @Override\r
         public IProject getProject() {\r
             return getLayoutEditor().getProject();\r
@@ -639,40 +612,35 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
         @Override\r
         public void reload() {\r
-            // because the SDK changed we must reset the configured framework resource.\r
-            mConfiguredFrameworkRes = null;\r
-\r
-            mConfigComposite.updateUIFromResources();\r
-\r
-            // updateUiFromFramework will reset language/region combo, so we must call\r
-            // setConfiguration after, or the settext on language/region will be lost.\r
-            if (mEditedConfig != null) {\r
-                setConfiguration(mEditedConfig, false /*force*/);\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
-            recomputeLayout();\r
-        }\r
-    }\r
+            // update the themes and locales since the target change could have changed the\r
+            // theme list.\r
+            mConfigComposite.updateThemesAndLocales();\r
 \r
-    /**\r
-     * Update the UI controls state with a given {@link FolderConfiguration}.\r
-     * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect\r
-     * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,\r
-     * the UI control is not modified. However if the value in the control is not the default value,\r
-     * a warning icon is shown.\r
-     * @param config The {@link FolderConfiguration} to set.\r
-     * @param force Whether the UI should be changed to exactly match the received configuration.\r
-     */\r
-    void setConfiguration(FolderConfiguration config, boolean force) {\r
-        mEditedConfig = config;\r
-        mConfiguredFrameworkRes = mConfiguredProjectRes = null;\r
+            // reset the config selector UI to match the new target (and possibly sdk).\r
+            if (mEditedConfig != null) {\r
+                if (mSdkLoaded) {\r
+                    onSdkChange();\r
+                } else {\r
+                    onTargetChange();\r
+                }\r
+            }\r
 \r
-        mConfigComposite.setConfiguration(config, force);\r
+            // SDK change has been handled, reset the flag.\r
+            mSdkLoaded = false;\r
+        }\r
 \r
+        @Override\r
+        public void onSdkLoaded() {\r
+            mSdkLoaded = true; // this will be reset when we get the target loaded event\r
+                               // which always comes after.\r
+        }\r
     }\r
 \r
     // ----------------\r
@@ -753,17 +721,55 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
     /**\r
      * Sets the UI for the edition of a new file.\r
-     * @param configuration the configuration of the new file.\r
+     * @param iFile the file being edited.\r
      */\r
-    public void editNewFile(FolderConfiguration configuration) {\r
-        // update the configuration UI\r
-        setConfiguration(configuration, true /*force*/);\r
+    public void initWithFile(IFile file) {\r
+        mEditedFile = file;\r
+\r
+        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);\r
+        mEditedConfig = resFolder.getConfiguration();\r
 \r
-        // enable the create button if the current and edited config are not equals\r
-        mConfigComposite.setEnabledCreate(\r
-                mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);\r
+        mConfigComposite.updateThemesAndLocales();\r
 \r
-        reloadConfigurationUi(false /*notifyListener*/);\r
+        Sdk currentSdk = Sdk.getCurrent();\r
+        if (currentSdk != null) {\r
+            IAndroidTarget target = currentSdk.getTarget(file.getProject());\r
+            if (target != null) {\r
+                mConfigComposite.initWith(mEditedConfig, target);\r
+            }\r
+        }\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
+        }\r
+    }\r
+\r
+    public void onTargetChange() {\r
+        onTargetOrSdkChange(false /* reloadDevices */);\r
+    }\r
+\r
+    public void onSdkChange() {\r
+        onTargetOrSdkChange(true /* reloadDevices */);\r
+    }\r
+\r
+    /**\r
+     * Reloads the configuration selector.\r
+     * @param reloadDevices whether the {@link LayoutDevice} objects should be reloaded.\r
+     */\r
+    private void onTargetOrSdkChange(boolean reloadDevices) {\r
+        Sdk currentSdk = Sdk.getCurrent();\r
+        if (currentSdk != null) {\r
+            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());\r
+            if (target != null) {\r
+                mConfigComposite.resetUi(mEditedConfig, target, reloadDevices);\r
+                mConfigListener.onConfigurationChange();\r
+            }\r
+        }\r
     }\r
 \r
     public Clipboard getClipboard() {\r
@@ -861,10 +867,20 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
                     // 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
-                    if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) {\r
-                        displayError("The project target (%s) was not properly loaded.",\r
-                                     target.getName());\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
@@ -1060,20 +1076,6 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
         }\r
     }\r
 \r
-    public void reloadConfigurationUi(boolean notifyListener) {\r
-        // enable the clipping button if it's supported.\r
-        Sdk currentSdk = Sdk.getCurrent();\r
-        if (currentSdk != null) {\r
-            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());\r
-            AndroidTargetData data = currentSdk.getTargetData(target);\r
-            if (data != null) {\r
-                LayoutBridge bridge = data.getLayoutBridge();\r
-                mConfigComposite.reloadDevices(notifyListener);\r
-                mConfigComposite.setClippingSupport(bridge.apiLevel >= 4);\r
-            }\r
-        }\r
-    }\r
-\r
     /**\r
      * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node\r
      * created by {@link ElementCreateCommand#execute()}.\r
@@ -1130,7 +1132,7 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
                 mLayoutCanvas.getDisplay().asyncExec(new Runnable() {\r
                     public void run() {\r
-                        mConfigComposite.updateUIFromResources();\r
+                        mConfigComposite.updateThemesAndLocales();\r
                     }\r
                 });\r
             }\r
index 39a0a79..87281f7 100644 (file)
@@ -480,6 +480,8 @@ public class Sdk implements IProjectListener, IFileListener {
                 if (project != null) {
                     bundle.projecsToReload.add(project);
                 }
+
+                return bundle.status;
             } else if (bundle.status == LoadStatus.LOADED || bundle.status == LoadStatus.FAILED) {
                 return bundle.status;
             }
@@ -538,7 +540,9 @@ public class Sdk implements IProjectListener, IFileListener {
             job.schedule();
         }
 
-        return null;
+        // The only way to go through here is when the loading starts through the Job.
+        // Therefore the current status of the target is LOADING.
+        return LoadStatus.LOADING;
     }
 
     /**