OSDN Git Service

ADT/GLE: Cleaned up the open/replace file process in the GLE and the config composite
authorXavier Ducrohet <xav@android.com>
Fri, 8 Jan 2010 01:49:37 +0000 (17:49 -0800)
committerXavier Ducrohet <xav@android.com>
Fri, 8 Jan 2010 04:25:44 +0000 (20:25 -0800)
The GLE and the config composite must handle 3 cases:
- new file opened following the creation of the editor
- replacement file when the user opens a different version of a layout
  that's already opened. The editor stays and switch to the new file.
- replacement file whne the user change the configuration and a new
  file that better matches the config is automicatlly opened.

Additionally, the editor/config composite must handle files being
opened while the SDK or the file's project's target are not yet
loaded.

File change or change in the SDK/Target must attempts to keep the
config/theme selection as close as possible as the previous state
while still remaining compatible with the new file/sdk/target.

Also removed the duplicate ITargetChangeListener, by making the
one in AndroidEditor optional (constructor flag), so that the
one in GraphicalLayoutEditor/Part is the only one used in the GLE
(it gives us more control on the order in which the code previously
executed by both listeners is now executed).

Also split the themes and locales updates as they happen on
different triggers.

Change-Id: If6d73bbe8af11cd221fb7830a37ffcc163135fc4

eclipse/changes.txt
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidEditor.java
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

index a1f5bec..c051eae 100644 (file)
@@ -6,6 +6,7 @@
 - Fixed issue where add-on with no optional library would not show up as valid targets for application launches.
 - Loads the SDK content only when a project requires it. This will make Eclipse use less resources when the SDK contains many versions of Android.
 - DDMS plug-in now contains the Allocation Tracker view.
+- Lots of fixes in the configuration selector of the Visual Layout Editor.
 
 0.9.5:
 - misc fixes in the SDK Updater
index e48a0c4..3cfa50f 100644 (file)
@@ -20,6 +20,7 @@ import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
 import com.android.sdklib.IAndroidTarget;
 
@@ -100,30 +101,45 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
     private XmlModelStateListener mXmlModelStateListener;
     /** Listener to update the root node if the target of the file is changed because of a
      * SDK location change or a project target change */
-    private TargetChangeListener mTargetListener;
+    private TargetChangeListener mTargetListener = null;
 
     /**
      * Creates a form editor.
+     * <p/>The editor will setup a {@link ITargetChangeListener} and call
+     * {@link #initUiRootNode(boolean)}, when the SDK or the target changes.
+     *
+     * @see #AndroidEditor(boolean)
      */
     public AndroidEditor() {
+        this(true);
+    }
+
+    /**
+     * Creates a form editor.
+     * @param addTargetListener whether to create an {@link ITargetChangeListener}.
+     */
+    public AndroidEditor(boolean addTargetListener) {
         super();
-        ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
 
-        mTargetListener = new TargetChangeListener() {
-            @Override
-            public IProject getProject() {
-                return AndroidEditor.this.getProject();
-            }
+        if (addTargetListener) {
+            ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
 
-            @Override
-            public void reload() {
-                commitPages(false /* onSave */);
+            mTargetListener = new TargetChangeListener() {
+                @Override
+                public IProject getProject() {
+                    return AndroidEditor.this.getProject();
+                }
 
-                // recreate the ui root node always
-                initUiRootNode(true /*force*/);
-            }
-        };
-        AdtPlugin.getDefault().addTargetListener(mTargetListener);
+                @Override
+                public void reload() {
+                    commitPages(false /* onSave */);
+
+                    // recreate the ui root node always
+                    initUiRootNode(true /*force*/);
+                }
+            };
+            AdtPlugin.getDefault().addTargetListener(mTargetListener);
+        }
     }
 
     // ---- Abstract Methods ----
index 6d07eb2..2be5b10 100755 (executable)
@@ -33,10 +33,23 @@ import org.eclipse.ui.IEditorPart;
 public interface IGraphicalLayoutEditor extends IEditorPart {
 
     /**
-     * Sets the UI for the edition of a new file.
-     * @param iFile the file being edited.
+     * Opens and initialize the editor with a new file.
+     * @param file the file being edited.
      */
-    abstract void initWithFile(IFile iFile);
+    abstract void openFile(IFile file);
+
+    /**
+     * Resets the editor with a replacement file.
+     * @param file the replacement file.
+     */
+    abstract void replaceFile(IFile file);
+
+    /**
+     * Resets the editor with a replacement file coming from a config change in the config
+     * selector.
+     * @param file the replacement file.
+     */
+    abstract void changeFileOnNewConfig(IFile file);
 
     /**
      * Responds to a target change for the project of the edited file
@@ -49,11 +62,6 @@ public interface IGraphicalLayoutEditor extends IEditorPart {
     abstract void onSdkChange();
 
     /**
-     * Reloads this editor, by getting the new model from the {@link LayoutEditor}.
-     */
-    abstract void reloadEditor();
-
-    /**
      * Callback for XML model changed. Only update/recompute the layout if the editor is visible
      */
     abstract void onXmlModelChanged();
index 9c5e8d3..2533764 100644 (file)
@@ -68,10 +68,16 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
     private UiEditorActions mUiEditorActions;
 
     /**
+     * Flag indicating if the replacement file is due to a config change.
+     * If false, it means the new file is due to an "open action" from the user.
+     */
+    private boolean mNewFileOnConfigChange = false;
+
+    /**
      * Creates the form editor for resources XML files.
      */
     public LayoutEditor() {
-        super();
+        super(false /* addTargetListener */);
     }
 
     /**
@@ -132,6 +138,21 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
             // In case there's an issue we provide a way to disable it using an
             // env variable.
             if (System.getenv("ANDROID_DISABLE_LAYOUT") == null) {      //$NON-NLS-1$
+                // get the file being edited so that it can be passed to the layout editor.
+                IFile editedFile = null;
+                IEditorInput input = getEditorInput();
+                if (input instanceof FileEditorInput) {
+                    FileEditorInput fileInput = (FileEditorInput)input;
+                    editedFile = fileInput.getFile();
+                } else {
+                    AdtPlugin.log(IStatus.ERROR,
+                            "Input is not of type FileEditorInput: %1$s",  //$NON-NLS-1$
+                            input.toString());
+                }
+
+                // It is possible that the Layout Editor alreadys exits if a different version
+                // of the same layout is being opened (either through "open" action from
+                // the user, or through a configuration change in the configuration selector.)
                 if (mGraphicalEditor == null) {
 
                     if (System.getenv("USE_GLE2") != null) {            //$NON-NLS-1$ //$NON-NLS-2$
@@ -142,18 +163,15 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
 
                     mGraphicalEditorIndex = addPage(mGraphicalEditor, getEditorInput());
                     setPageText(mGraphicalEditorIndex, mGraphicalEditor.getTitle());
-                } else {
-                    mGraphicalEditor.reloadEditor();
-                }
 
-                // update the config based on the opened file.
-                IEditorInput input = getEditorInput();
-                if (input instanceof FileEditorInput) {
-                    FileEditorInput fileInput = (FileEditorInput)input;
-                    mGraphicalEditor.initWithFile(fileInput.getFile());
+                    mGraphicalEditor.openFile(editedFile);
                 } else {
-                    AdtPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s",
-                            input.toString());
+                    if (mNewFileOnConfigChange) {
+                        mGraphicalEditor.changeFileOnNewConfig(editedFile);
+                        mNewFileOnConfigChange = false;
+                    } else {
+                        mGraphicalEditor.replaceFile(editedFile);
+                    }
                 }
 
                 // put in place the listener to handle layout recompute only when needed.
@@ -372,7 +390,7 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
     }
 
     @Override
-    protected void initUiRootNode(boolean force) {
+    public void initUiRootNode(boolean force) {
         // The root UI node is always created, even if there's no corresponding XML node.
         if (mUiRootNode == null || force) {
             // get the target data from the opened file (and its project)
@@ -410,9 +428,8 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
         }
 
         if (mGraphicalEditor != null) {
-            mGraphicalEditor.reloadEditor();
-            mGraphicalEditor.reloadPalette();
             mGraphicalEditor.onTargetChange();
+            mGraphicalEditor.reloadPalette();
         }
     }
 
@@ -428,4 +445,8 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
                     file.getName()));
         }
     }
+
+    public void setNewFileOnConfigChange(boolean state) {
+        mNewFileOnConfigChange = state;
+    }
 }
index ff1802c..7879a92 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
 
+import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.resources.ResourceType;
 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
@@ -31,6 +32,7 @@ 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.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.ui.ConfigurationSelector.LanguageRegionVerifier;
@@ -61,11 +63,32 @@ import java.util.Map.Entry;
 
 /**
  * A composite that displays the current configuration displayed in a Graphical Layout Editor.
+ * <p/>
+ * The composite has several entry points:<br>
+ * - {@link #openFile(FolderConfiguration, IAndroidTarget)}<br>
+ *   Called after the creation to init the composite with a file being opened in a new editor.<br>
+ *<br>
+ * - {@link #replaceFile(FolderConfiguration)}<br>
+ *   Called when a file, representing the same resource but with a different config is opened<br>
+ *   by the user.<br>
+ *<br>
+ * - {@link #changeFileOnNewConfig(FolderConfiguration)}<br>
+ *   Called when config change triggers the editing of a file with a different config.
+ *<p/>
+ * Additionally, the composite can handle the following events.<br>
+ * - SDK reload. This is when the main SDK is finished loading.<br>
+ * - Target reload. This is when the target used by the project is the edited file has finished<br>
+ *   loading.<br>
  */
 public class ConfigurationComposite extends Composite {
 
     private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
 
+    private final static String FAKE_LOCALE_VALUE = "__"; //$NON-NLS-1$
+
+    private final static int LOCALE_LANG = 0;
+    private final static int LOCALE_REGION = 1;
+
     private Button mClippingButton;
     private Label mCurrentLayoutLabel;
 
@@ -86,8 +109,15 @@ public class ConfigurationComposite extends Composite {
     private final ArrayList<ResourceQualifier[] > mLocaleList =
         new ArrayList<ResourceQualifier[]>();
 
+    /**
+     * clipping value. If true, the rendering is limited to the screensize. This is the default
+     * value
+     */
     private boolean mClipping = true;
 
+    /**
+     * TODO: remove as it's saved in mCurrentStave. Just need to make sure there's no NPE when mCurrentState is null.
+     */
     private LayoutDevice mCurrentDevice;
 
     /** The config listener given to the constructor. Never null. */
@@ -96,6 +126,10 @@ public class ConfigurationComposite extends Composite {
     private FolderConfiguration mEditedConfig;
     private IAndroidTarget mTarget;
 
+    private SelectionState mCurrentState = null;
+
+    private boolean mSdkChanged = false;
+
     /**
      * Interface implemented by the part which owns a {@link ConfigurationComposite}.
      * This notifies the owners when the configuration change.
@@ -123,7 +157,6 @@ public class ConfigurationComposite extends Composite {
         String configName;
         int locale;
         String theme;
-        boolean clipping;
     }
 
     /**
@@ -287,8 +320,6 @@ public class ConfigurationComposite extends Composite {
         mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN);
         mThemeCombo.setEnabled(false);
 
-        updateThemesAndLocales();
-
         mThemeCombo.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
@@ -313,31 +344,309 @@ public class ConfigurationComposite extends Composite {
                 }
             }
         });
-
-        // load the devices from the SDK and fill the device combo.
-        initDeviceCombos();
     }
 
     // ---- Init and reset/reload methods ----
 
     /**
-     * Init the UI with a given file configuration and project target.
+     * Init the UI with a given file configuration and project target. This must only be called
+     * the first time the {@link ConfigurationComposite} is created.
+     * <p/>This will NOT trigger a redraw event (will not call
+     * {@link IConfigListener#onConfigurationChange()}.)
+     * The state of the selection of the various combos will be initialized to default values that
+     * are compatible with the opened file.
+     *
+     * @param fileConfig The {@link FolderConfiguration} of the opened file.
+     * @param target the {@link IAndroidTarget} of the file's project.
+     *
+     * @see #replaceFile(FolderConfiguration)
+     * @see #changeFileOnNewConfig(FolderConfiguration)
+     */
+    public void openFile(FolderConfiguration fileConfig, IAndroidTarget target) {
+        mTarget = target;
+        mEditedConfig = fileConfig;
+
+        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting
+                                // new values in the widgets.
+
+        // only attempt to do anything if the SDK and targets are loaded.
+        LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
+        if (sdkStatus == LoadStatus.LOADED) {
+            // init the devices since the SDK is loaded.
+            initDevices();
+
+            LoadStatus targetStatus = Sdk.getCurrent().checkAndLoadTargetData(target, null);
+
+            if (targetStatus == LoadStatus.LOADED) {
+                // update the themes and locales.
+                updateThemes();
+                updateLocales();
+
+                // update the clipping state
+                AndroidTargetData data = Sdk.getCurrent().getTargetData(mTarget);
+                if (data != null) {
+                    LayoutBridge bridge = data.getLayoutBridge();
+                    setClippingSupport(bridge.apiLevel >= 4);
+                }
+
+                // attempt to find a device that can display this particular config.
+                findAndSetCompatibleConfig(fileConfig);
+
+                // find a locale matching this file config
+                findAndSetCompatibleLocale(fileConfig);
+
+                // compute the final current config
+                computeCurrentConfig();
+
+                // update the string showing the config value
+                updateConfigDisplay(fileConfig);
+
+                saveState();
+            }
+        }
+
+        mDisableUpdates = false;
+    }
+
+
+    /**
+     * Replaces the UI with a given file configuration. This is meant to answer the user
+     * Explicitly opening a different version of the same layout from the Package Explorer.
+     * <p/>This attempts to keep the current config, but may change it if it's not compatible.
      * <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.
+     *
+     * @see #replaceFile(FolderConfiguration)
+     */
+    public void replaceFile(FolderConfiguration fileConfig) {
+        // if there is no previous selection, revert to default mode.
+        if (mCurrentDevice == null) {
+            openFile(fileConfig, mTarget);
+            return;
+        }
+
+        mEditedConfig = fileConfig;
+
+        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting
+        // new values in the widgets.
+
+        // only attempt to do anything if the SDK and targets are loaded.
+        LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
+        if (sdkStatus == LoadStatus.LOADED) {
+            LoadStatus targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mTarget, null);
+
+            if (targetStatus == LoadStatus.LOADED) {
+
+                // update the current config selection to make sure it's
+                // compatible with the new file
+                adaptConfigSelectionToNewConfig(fileConfig);
+
+                // find a locale matching this file config
+                findAndSetCompatibleLocale(fileConfig);
+
+                // compute the final current config
+                computeCurrentConfig();
+
+                // update the string showing the config value
+                updateConfigDisplay(fileConfig);
+
+                saveState();
+            }
+        }
+
+        mDisableUpdates = false;
+    }
+
+    /**
+     * Updates the UI with a new file that was opened in response to a config change.
+     * @param fileConfig the file being opened.
+     *
+     * @see #openFile(FolderConfiguration, IAndroidTarget)
+     * @see #replaceFile(FolderConfiguration)
+     */
+    public void changeFileOnNewConfig(FolderConfiguration fileConfig) {
+        // there really isn't much to do since the combos were set by the user, and we don't want
+        // to touch them.
+        mEditedConfig = fileConfig;
+
+        // update the string showing the config value
+        updateConfigDisplay(fileConfig);
+    }
+
+    /**
+     * Responds to the event that the basic SDK information finished loading.
+     * @param target the possibly new target object associated with the file being edited (in case
+     * the SDK path was changed).
+     */
+    public void onSdkLoaded(IAndroidTarget target) {
+        // a change to the SDK means that we need to check for new/removed devices.
+        mSdkChanged = true;
+
+        // store the new target.
+        mTarget = target;
+
+        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting
+                                // new values in the widgets.
+
+        // this is going to be followed by a call to onTargetLoaded.
+        // So we can only care about the layout devices in this case.
+        initDevices();
+
+        mDisableUpdates = false;
+    }
+
+    /**
+     * Responds to the event that the {@link IAndroidTarget} data was loaded, or the project's
+     * target changed
+     */
+    public void onTargetChange() {
+        if (mCurrentState == null) {
+            // this means the file was opened before the target finished loaded.
+            // This is basically an initial call to openFile that's delayed.
+            openFile(mEditedConfig, mTarget);
+            return;
+        }
+
+        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting
+                                // new values in the widgets.
+
+        // update the themes.
+        updateThemes();
+
+        // at this point, this means the target of the project was changed, or the whole SDK
+        // was changed reloaded (different location?).
+        // The former means the devices/configs are still there, the latter means they've
+        // been reloaded (in #onSdkLoaded).
+        if (mSdkChanged) {
+            findAndSetCompatibleConfig(mEditedConfig);
+        } else {
+            adaptConfigSelectionToNewConfig(mEditedConfig);
+        }
+
+        // update the clipping state
+        AndroidTargetData data = Sdk.getCurrent().getTargetData(mTarget);
+        if (data != null) {
+            LayoutBridge bridge = data.getLayoutBridge();
+            setClippingSupport(bridge.apiLevel >= 4);
+        }
+
+        computeCurrentConfig();
+
+        mDisableUpdates = false;
+    }
+
+    /**
+     * Finds a device/config that can display the given config.
+     * <p/>Once found the device and config combos are set to the config.
+     * <p/>If there is no compatible comfiguration, a custom one is created.
+     * @param fileConfig
+     */
+    private void findAndSetCompatibleConfig(FolderConfiguration fileConfig) {
+        LayoutDevice deviceMatch = null;
+        String configMatchName = null;
+
+        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;
+                }
+            }
+        }
+
+        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);
+        }
+    }
+
+    /**
+     * Adapts the current device/config selection so that it's compatible with a given config.
+     * <p/>If the current selection is compatible, nothing is changed.
+     * <p/>If it's not compatible, configs from the current devices are tested.
+     * <p/>If none are compatible, it reverts to
+     * {@link #findAndSetCompatibleConfig(FolderConfiguration)}
+     * @param fileConfig
+     */
+    private void adaptConfigSelectionToNewConfig(FolderConfiguration fileConfig) {
+        // check the device config (ie sans locale)
+        boolean needConfigChange = true; // if still true, we need to find another config.
+        int configIndex = mDeviceConfigCombo.getSelectionIndex();
+        if (configIndex != -1) {
+            String configName = mDeviceConfigCombo.getItem(configIndex);
+            FolderConfiguration currentConfig = mCurrentDevice.getConfigs().get(configName);
+            if (fileConfig.isMatchFor(currentConfig)) {
+                needConfigChange = false;
+            }
+        }
+
+        if (needConfigChange) {
+            // first look in the current device.
+            String matchName = null;
+            Map<String, FolderConfiguration> configs = mCurrentDevice.getConfigs();
+            for (Entry<String, FolderConfiguration> entry : configs.entrySet()) {
+                if (fileConfig.isMatchFor(entry.getValue())) {
+                    matchName = entry.getKey();
+                    break;
+                }
+            }
+
+            if (matchName != null) {
+                selectConfig(matchName);
+            } else {
+                // attempt to find a device that can display this particular config.
+                findAndSetCompatibleConfig(fileConfig);
+            }
+        }
+    }
+
+
+    /**
+     * Finds a locale matching the config from a file.
+     * @param fileConfig
      */
-    public void initWith(FolderConfiguration fileConfig, IAndroidTarget target) {
-        SelectionState state = null;
-        if (mEditedConfig != null) {
-            state = getState();
+    private void findAndSetCompatibleLocale(FolderConfiguration fileConfig) {
+        // find the locale match. Since the locale list is based on the content of the
+        // project resources there must be an exact match.
+        // The only trick is that the region could be null in the fileConfig but in our
+        // list of locales, this is represented as a RegionQualifier with value of
+        // FAKE_LOCALE_VALUE.
+        final int count = mLocaleList.size();
+        for (int i = 0 ; i < count ; i++) {
+            ResourceQualifier[] locale = mLocaleList.get(i);
+
+            if (locale[LOCALE_LANG].equals(fileConfig.getLanguageQualifier())) {
+                // region comparison is more complex:
+                RegionQualifier region = fileConfig.getRegionQualifier();
+                if (region == null) {
+                    if (FAKE_LOCALE_VALUE.equals(
+                            ((RegionQualifier)locale[LOCALE_REGION]).getValue())) {
+                        // match!
+                        mLocaleCombo.select(i);
+                        break;
+                    }
+                } else if (region.equals(locale[LOCALE_REGION])) {
+                    // match!
+                    mLocaleCombo.select(i);
+                    break;
+                }
+            }
         }
+    }
 
-        doInitWith(fileConfig, target, state);
+    private void updateConfigDisplay(FolderConfiguration fileConfig) {
+        String current = fileConfig.toDisplayString();
+        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
     }
 
     /**
@@ -353,8 +662,8 @@ public class ConfigurationComposite extends Composite {
      */
     private void doInitWith(FolderConfiguration fileConfig, IAndroidTarget target,
             SelectionState state) {
-        mEditedConfig = fileConfig;
-        mTarget = target;
+
+        // FIXME: delete when onTargetLoaded is fully implemented.
 
         AndroidTargetData data = Sdk.getCurrent().getTargetData(mTarget);
         if (data != null) {
@@ -362,9 +671,6 @@ public class ConfigurationComposite extends Composite {
             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;
 
@@ -409,16 +715,16 @@ public class ConfigurationComposite extends Composite {
         }
 
         if (deviceMatch == null) {
-            // attempt find a device that can display this particular config.
+            // attempt to find a device that can display this particular config.
             mainloop: for (LayoutDevice device : mDeviceList) {
-                for (Entry<String, FolderConfiguration> entry : device.getConfigs().entrySet()) {
+                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;
                     }
-
                 }
             }
         }
@@ -446,8 +752,8 @@ public class ConfigurationComposite extends Composite {
             ResourceQualifier[] locale = mLocaleList.get(state.locale);
 
             // apply it to the current config.
-            mCurrentConfig.setLanguageQualifier((LanguageQualifier)locale[0]); // language
-            mCurrentConfig.setRegionQualifier((RegionQualifier)locale[1]); // region
+            mCurrentConfig.setLanguageQualifier((LanguageQualifier)locale[LOCALE_LANG]);
+            mCurrentConfig.setRegionQualifier((RegionQualifier)locale[LOCALE_REGION]);
 
             // make sure this is compatible
             if (fileConfig.isMatchFor(mCurrentConfig)) {
@@ -464,8 +770,8 @@ public class ConfigurationComposite extends Composite {
                 ResourceQualifier[] locale = mLocaleList.get(i);
 
                 // apply it to the current config.
-                mCurrentConfig.setLanguageQualifier((LanguageQualifier)locale[0]); // language
-                mCurrentConfig.setRegionQualifier((RegionQualifier)locale[1]); // region
+                mCurrentConfig.setLanguageQualifier((LanguageQualifier)locale[LOCALE_LANG]);
+                mCurrentConfig.setRegionQualifier((RegionQualifier)locale[LOCALE_REGION]);
 
                 if (fileConfig.isMatchFor(mCurrentConfig)) {
                     mLocaleCombo.select(i);
@@ -477,62 +783,113 @@ public class ConfigurationComposite extends Composite {
         // compute the final current config
         computeCurrentConfig();
 
-        // update the string showing the folder name
-        String current = fileConfig.toDisplayString();
-        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
+        updateConfigDisplay(fileConfig);
 
         mDisableUpdates = false;
     }
 
-    /**
-     * 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 void resetUi(FolderConfiguration config, IAndroidTarget target, boolean resetDevices) {
-        if (resetDevices) {
-            initDeviceCombos();
-        }
-
-        SelectionState state = getState();
-
-        doInitWith(config, target, state);
-    }
-
-    private SelectionState getState() {
+    private void saveState() {
         if (mCurrentDevice != null) {
-            SelectionState state = new SelectionState();
+            if (mCurrentState == null) {
+                mCurrentState = new SelectionState();
+            }
 
-            state.deviceName = mCurrentDevice.getName();
+            mCurrentState.deviceName = mCurrentDevice.getName();
 
             int index = mDeviceConfigCombo.getSelectionIndex();
             if (index != -1) {
-                state.configName = mDeviceConfigCombo.getItem(index);
+                mCurrentState.configName = mDeviceConfigCombo.getItem(index);
             }
 
             // since the locales are relative to the project, only keeping the index is enough
-            state.locale = mLocaleCombo.getSelectionIndex();
+            mCurrentState.locale = mLocaleCombo.getSelectionIndex();
 
             index = mThemeCombo.getSelectionIndex();
             if (index != -1) {
-                state.theme = mThemeCombo.getItem(index);
+                mCurrentState.theme = mThemeCombo.getItem(index);
             }
+        }
+    }
+
+    /**
+     * Updates the locale combo.
+     * This must be called from the UI thread.
+     */
+    public void updateLocales() {
+        if (mListener == null) {
+            return; // can't do anything w/o it.
+        }
+
+        mDisableUpdates = true;
 
-            state.clipping = mClipping;
+        // Reset the combo
+        mLocaleCombo.removeAll();
+        mLocaleList.clear();
+
+        SortedSet<String> languages = null;
+        boolean hasLocale = false;
+
+        // get the languages from the project.
+        ProjectResources project = mListener.getProjectResources();
+
+        // in cases where the opened file is not linked to a project, this could be null.
+        if (project != null) {
+            // now get the languages from the project.
+            languages = project.getLanguages();
+
+            for (String language : languages) {
+                hasLocale = true;
+
+                LanguageQualifier langQual = new LanguageQualifier(language);
+
+                // 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 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(FAKE_LOCALE_VALUE)
+                });
+            }
+        }
 
-            return state;
+        // add a locale not present in the project resources. This will let the dev
+        // tests his/her default values.
+        if (hasLocale) {
+            mLocaleCombo.add("Other");
+        } else {
+            mLocaleCombo.add("Any");
         }
 
-        return null;
+        // create language/region qualifier that will never be matched by qualified resources.
+        mLocaleList.add(new ResourceQualifier[] {
+                new LanguageQualifier(FAKE_LOCALE_VALUE),
+                new RegionQualifier(FAKE_LOCALE_VALUE)
+        });
+
+        mLocaleCombo.select(0);
+
+        mThemeCombo.getParent().layout();
+
+        mDisableUpdates = false;
     }
 
     /**
-     * Updates the UI from values in the resources, such as languages, regions, themes, etc...
+     * Updates the theme combo.
      * This must be called from the UI thread.
      */
-    public void updateThemesAndLocales() {
+    private void updateThemes() {
         if (mListener == null) {
             return; // can't do anything w/o it.
         }
@@ -541,15 +898,10 @@ public class ConfigurationComposite extends Composite {
 
         mDisableUpdates = true;
 
-        // Reset stuff
-        int selection = mThemeCombo.getSelectionIndex();
+        // Reset the combo
         mThemeCombo.removeAll();
         mPlatformThemeCount = 0;
 
-        mLocaleCombo.removeAll();
-        mLocaleList.clear();
-
-        SortedSet<String> languages = null;
         ArrayList<String> themes = new ArrayList<String>();
 
         // get the themes, and languages from the Framework.
@@ -619,70 +971,21 @@ public class ConfigurationComposite extends Composite {
                     }
                 }
             }
-
-            // now get the languages from the project.
-            languages = project.getLanguages();
         }
 
-        // add the languages to the Combo
-
-        boolean hasLocale = false;
-        if (project != null && languages != null && languages.size() > 0) {
-            for (String language : languages) {
-                hasLocale = true;
-
-                LanguageQualifier langQual = new LanguageQualifier(language);
-
-                // 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 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$
+        // try to reselect the previous theme.
+        if (mCurrentState != null && mCurrentState.theme != null) {
+            final int count = mThemeCombo.getItemCount();
+            for (int i = 0 ; i < count ; i++) {
+                if (mCurrentState.theme.equals(mThemeCombo.getItem(i))) {
+                    mThemeCombo.select(i);
+                    break;
                 }
-                // create a region qualifier that will never be matched by qualified resources.
-                mLocaleList.add(new ResourceQualifier[] {
-                        langQual,
-                        new RegionQualifier("__")  //$NON-NLS-1$
-                });
             }
-        }
-
-        // add a locale not present in the project resources. This will let the dev
-        // tests his/her default values.
-        if (hasLocale) {
-            mLocaleCombo.add("Other");
-        } else {
-            mLocaleCombo.add("Any");
-        }
-
-        // create language/region qualifier that will never be matched by qualified resources.
-        mLocaleList.add(new ResourceQualifier[] {
-                new LanguageQualifier("__"), //$NON-NLS-1$
-                new RegionQualifier("__")    //$NON-NLS-1$
-        });
-
-        mLocaleCombo.select(0);
-
-        // handle default selection of themes
-        if (mThemeCombo.getItemCount() > 0) {
             mThemeCombo.setEnabled(true);
-            if (selection == -1) {
-                selection = 0;
-            }
-
-            if (mThemeCombo.getItemCount() <= selection) {
-                mThemeCombo.select(0);
-            } else {
-                mThemeCombo.select(selection);
-            }
+        } else if (mThemeCombo.getItemCount() > 0) {
+            mThemeCombo.select(0);
+            mThemeCombo.setEnabled(true);
         } else {
             mThemeCombo.setEnabled(false);
         }
@@ -818,7 +1121,10 @@ public class ConfigurationComposite extends Composite {
         }
     }
 
-    private void loadDevices() {
+    /**
+     * Loads the list of {@link LayoutDevice} and inits the UI with it.
+     */
+    private void initDevices() {
         mDeviceList = null;
 
         Sdk sdk = Sdk.getCurrent();
@@ -826,13 +1132,7 @@ public class ConfigurationComposite extends Composite {
             LayoutDeviceManager manager = sdk.getLayoutDeviceManager();
             mDeviceList = manager.getCombinedList();
         }
-    }
 
-    /**
-     * Loads the list of {@link LayoutDevice} and inits the UI with it.
-     */
-    private void initDeviceCombos() {
-        loadDevices();
 
         // remove older devices if applicable
         mDeviceCombo.removeAll();
@@ -862,18 +1162,37 @@ public class ConfigurationComposite extends Composite {
         mDeviceCombo.add("Custom...");
     }
 
-    private void selectDevice(LayoutDevice device) {
-        int index = -1;
+    /**
+     * Selects a given {@link LayoutDevice} in the device combo, if it is found.
+     * @param device the device to select
+     * @return true if the device was found.
+     */
+    private boolean selectDevice(LayoutDevice device) {
         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(i);
+                return true;
             }
         }
 
-        mDeviceCombo.select(index);
+        return false;
+    }
+
+    /**
+     * Selects a config by name.
+     * @param name the name of the config to select.
+     */
+    private void selectConfig(String name) {
+        final int count = mDeviceConfigCombo.getItemCount();
+        for (int i = 0 ; i < count ; i++) {
+            String item = mDeviceConfigCombo.getItem(i);
+            if (name.equals(item)) {
+                mDeviceConfigCombo.select(i);
+                return;
+            }
+        }
     }
 
     /**
@@ -893,28 +1212,7 @@ public class ConfigurationComposite extends Composite {
         if (deviceIndex != -1) {
             // check if the user is asking for the custom item
             if (deviceIndex == mDeviceCombo.getItemCount() - 1) {
-                ConfigManagerDialog dialog = new ConfigManagerDialog(getShell());
-                dialog.open();
-
-                // save the user devices
-                Sdk.getCurrent().getLayoutDeviceManager().save();
-
-                // reload the combo with the new content.
-                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) {
-                    mDeviceCombo.select(index);
-                } else {
-                    // we should at least have one built-in device, so we select it
-                    mDeviceCombo.select(0);
-                }
-
-                // force a redraw
-                onDeviceChange(true /*recomputeLayout*/);
-
+                onCustomDeviceConfig();
                 return;
             }
 
@@ -937,16 +1235,61 @@ public class ConfigurationComposite extends Composite {
         }
 
         fillConfigCombo(newConfigName);
+
+        computeCurrentConfig();
+
         if (recomputeLayout) {
             onDeviceConfigChange();
         }
     }
 
     /**
-     * Attemps for find a close config among a list
+     * Handles a user request for the {@link ConfigManagerDialog}.
+     */
+    private void onCustomDeviceConfig() {
+        ConfigManagerDialog dialog = new ConfigManagerDialog(getShell());
+        dialog.open();
+
+        // save the user devices
+        Sdk.getCurrent().getLayoutDeviceManager().save();
+
+        // Update the UI with no triggered event
+        mDisableUpdates = true;
+
+        LayoutDevice oldCurrent = mCurrentDevice;
+
+        // but first, update the device combo
+        initDevices();
+
+        // attempts to reselect the current device.
+        if (selectDevice(oldCurrent)) {
+            // current device still exists.
+            // reselect the config
+            selectConfig(mCurrentState.configName);
+
+            // reset the UI as if it was just a replacement file, since we can keep
+            // the current device (and possibly config).
+            adaptConfigSelectionToNewConfig(mEditedConfig);
+
+        } else {
+            // find a new device/config to match the current file.
+            findAndSetCompatibleConfig(mEditedConfig);
+        }
+
+        mDisableUpdates = false;
+
+        // recompute the current config
+        computeCurrentConfig();
+
+        // force a redraw
+        onDeviceChange(true /*recomputeLayout*/);
+    }
+
+    /**
+     * Attempts to 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
+     * @return the name of the closest config match, or possibly null if no configs are compatible
      * (this can only happen if the configs don't have a single qualifier that is the same).
      */
     private String getClosestMatch(FolderConfiguration oldConfig,
@@ -1063,7 +1406,12 @@ public class ConfigurationComposite extends Composite {
         }
     }
 
+    /**
+     * Saves the current state and the current configuration
+     */
     private boolean computeCurrentConfig() {
+        saveState();
+
         if (mCurrentDevice != null) {
             // get the device config from the device/config combos.
             int configIndex = mDeviceConfigCombo.getSelectionIndex();
@@ -1078,8 +1426,10 @@ public class ConfigurationComposite extends Composite {
             if (localeIndex != -1) {
                 ResourceQualifier[] localeQualifiers = mLocaleList.get(localeIndex);
 
-                mCurrentConfig.setLanguageQualifier((LanguageQualifier)localeQualifiers[0]); // language
-                mCurrentConfig.setRegionQualifier((RegionQualifier)localeQualifiers[1]); // region
+                mCurrentConfig.setLanguageQualifier(
+                        (LanguageQualifier)localeQualifiers[LOCALE_LANG]);
+                mCurrentConfig.setRegionQualifier(
+                        (RegionQualifier)localeQualifiers[LOCALE_REGION]);
             }
 
             // update the create button.
@@ -1104,9 +1454,11 @@ public class ConfigurationComposite extends Composite {
                 mListener.onThemeChange();
             }
         }
+
+        saveState();
     }
 
-    protected void onClippingChange() {
+    private void onClippingChange() {
         mClipping = mClippingButton.getSelection();
         if (mListener != null) {
             mListener.onClippingChange();
index a38b0bc..97838c8 100644 (file)
@@ -47,12 +47,10 @@ 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;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
 import com.android.layoutlib.api.ILayoutBridge;
 import com.android.layoutlib.api.ILayoutLog;
 import com.android.layoutlib.api.ILayoutResult;
@@ -157,16 +155,34 @@ 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.
+    private ITargetChangeListener mTargetListener = new ITargetChangeListener() {
+        public void onProjectTargetChange(IProject changedProject) {
+            if (changedProject != null && changedProject.equals(getProject())) {
+                updateEditor();
+            }
+        }
 
-        @Override
-        public IProject getProject() {
-            return getLayoutEditor().getProject();
+        public void onTargetLoaded(IAndroidTarget target) {
+            IProject project = getProject();
+            if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {
+                updateEditor();
+            }
         }
 
-        @Override
-        public void reload() {
+        public void onSdkLoaded() {
+            Sdk currentSdk = Sdk.getCurrent();
+            if (currentSdk != null) {
+                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
+                if (target != null) {
+                    mConfigComposite.onSdkLoaded(target);
+                    onConfigurationChange();
+                }
+            }
+        }
+
+        private void updateEditor() {
+            mLayoutEditor.commitPages(false /* onSave */);
+
             // because the target changed we must reset the configured resources.
             mConfiguredFrameworkRes = mConfiguredProjectRes = null;
 
@@ -174,27 +190,13 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
             // bridge class loader.
             mProjectCallback = null;
 
-            // update the themes and locales since the target change could have changed the
-            // theme list.
-            mConfigComposite.updateThemesAndLocales();
-
-            // reset the config selector UI to match the new target (and possibly sdk).
-            if (mEditedConfig != null) {
-                if (mSdkLoaded) {
-                    onSdkChange();
-                } else {
-                    onTargetChange();
-                }
-            }
-
-            // SDK change has been handled, reset the flag.
-            mSdkLoaded = false;
+            // recreate the ui root node always, this will also call onTargetChange
+            // on the config composite
+            mLayoutEditor.initUiRootNode(true /*force*/);
         }
 
-        @Override
-        public void onSdkLoaded() {
-            mSdkLoaded = true; // this will be reset when we get the target loaded event
-                               // which always comes after.
+        private IProject getProject() {
+            return getLayoutEditor().getProject();
         }
     };
 
@@ -208,9 +210,9 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
         }
     };
 
-    private final Runnable mUiUpdateFromResourcesRunnable = new Runnable() {
+    private final Runnable mLocaleUpdaterFromUiRunnable = new Runnable() {
         public void run() {
-            mConfigComposite.updateThemesAndLocales();
+            mConfigComposite.updateLocales();
         }
     };
 
@@ -587,49 +589,86 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
     }
 
     /**
-     * Sets the UI for the edition of a new file.
-     * @param iFile the file being edited.
+     * Opens and initialize the editor with a new file.
+     * @param file the file being edited.
      */
-    public void initWithFile(IFile file) {
+    public void openFile(IFile file) {
         mEditedFile = file;
 
         ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
         mEditedConfig = resFolder.getConfiguration();
 
-        mConfigComposite.updateThemesAndLocales();
-
+        IAndroidTarget target = null;
         Sdk currentSdk = Sdk.getCurrent();
         if (currentSdk != null) {
-            IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
-            if (target != null) {
-                mConfigComposite.initWith(mEditedConfig, target);
-            }
+            target = currentSdk.getTarget(mEditedFile.getProject());
         }
-    }
 
-    public void onTargetChange() {
-        onTargetOrSdkChange(false /* reloadDevices */);
+        mConfigComposite.openFile(mEditedConfig, target);
     }
 
-    public void onSdkChange() {
-        onTargetOrSdkChange(true /* reloadDevices */);
+    /**
+     * Resets the editor with a replacement file.
+     * @param file the replacement file.
+     */
+    public void replaceFile(IFile file) {
+        resetInput();
+
+        mEditedFile = file;
+
+        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
+        mEditedConfig = resFolder.getConfiguration();
+
+        mConfigComposite.replaceFile(mEditedConfig);
+        onConfigurationChange();
     }
 
     /**
-     * Reloads the configuration selector.
-     * @param reloadDevices whether the {@link LayoutDevice} objects should be reloaded.
+     * Resets the editor with a replacement file coming from a config change in the config
+     * selector.
+     * @param file the replacement file.
      */
-    private void onTargetOrSdkChange(boolean reloadDevices) {
+    public void changeFileOnNewConfig(IFile file) {
+        resetInput();
+
+        mEditedFile = file;
+
+        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
+        mEditedConfig = resFolder.getConfiguration();
+
+        mConfigComposite.changeFileOnNewConfig(mEditedConfig);
+        onConfigurationChange();
+    }
+
+    public void onTargetChange() {
+        resetInput();
+        mConfigComposite.onTargetChange();
+        onConfigurationChange();
+    }
+
+    public void onSdkChange() {
         Sdk currentSdk = Sdk.getCurrent();
         if (currentSdk != null) {
             IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
             if (target != null) {
-                mConfigComposite.resetUi(mEditedConfig, target, reloadDevices);
+                mConfigComposite.onSdkLoaded(target);
                 onConfigurationChange();
             }
         }
     }
 
+    /**
+     * Resets the editor's input and the viewer model.
+     */
+    private void resetInput() {
+        GraphicalViewer viewer = getGraphicalViewer();
+        viewer.setContents(getModel());
+
+        IEditorInput input = mLayoutEditor.getEditorInput();
+        setInput(input);
+    }
+
+
     public Rectangle getBounds() {
         return mConfigComposite.getScreenBounds();
     }
@@ -729,17 +768,6 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
     }
 
     /**
-     * Reloads this editor, by getting the new model from the {@link LayoutEditor}.
-     */
-    public void reloadEditor() {
-        GraphicalViewer viewer = getGraphicalViewer();
-        viewer.setContents(getModel());
-
-        IEditorInput input = mLayoutEditor.getEditorInput();
-        setInput(input);
-    }
-
-    /**
      * Callback for XML model changed. Only update/recompute the layout if the editor is visible
      */
     public void onXmlModelChanged() {
@@ -807,6 +835,10 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
         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());
@@ -1136,7 +1168,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
                 }
             }
 
-            mParent.getDisplay().asyncExec(mUiUpdateFromResourcesRunnable);
+            mParent.getDisplay().asyncExec(mLocaleUpdaterFromUiRunnable);
         }
 
         if (codeChange) {
index e8aee91..c3cf35e 100755 (executable)
@@ -39,11 +39,10 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
 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
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;\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
@@ -181,13 +180,6 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
         }\r
     }\r
 \r
-    /**\r
-     * Reloads this editor, by getting the new model from the {@link LayoutEditor}.\r
-     */\r
-    public void reloadEditor() {\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
         // The contract of init() mentions we need to fail if we can't understand the input.\r
         if (!(input instanceof FileEditorInput)) {\r
@@ -234,7 +226,6 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
         mConfigListener = new ConfigListener();\r
         mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER);\r
-        mConfigComposite.updateThemesAndLocales();\r
 \r
         mSashPalette = new SashForm(parent, SWT.HORIZONTAL);\r
         mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));\r
@@ -367,6 +358,10 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
             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 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
@@ -602,16 +597,35 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
     /**\r
      * 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
+    private class TargetListener implements ITargetChangeListener {\r
 \r
-        @Override\r
-        public IProject getProject() {\r
-            return getLayoutEditor().getProject();\r
+        public void onProjectTargetChange(IProject changedProject) {\r
+            if (changedProject != null && changedProject.equals(getProject())) {\r
+                updateEditor();\r
+            }\r
         }\r
 \r
-        @Override\r
-        public void reload() {\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
@@ -619,27 +633,13 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
             // bridge class loader.\r
             mProjectCallback = null;\r
 \r
-            // update the themes and locales since the target change could have changed the\r
-            // theme list.\r
-            mConfigComposite.updateThemesAndLocales();\r
-\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
-            // SDK change has been handled, reset the flag.\r
-            mSdkLoaded = false;\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
-        @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
+        private IProject getProject() {\r
+            return getLayoutEditor().getProject();\r
         }\r
     }\r
 \r
@@ -720,25 +720,23 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
     }\r
 \r
     /**\r
-     * Sets the UI for the edition of a new file.\r
-     * @param iFile the file being edited.\r
+     * Opens and initialize the editor with a new file.\r
+     * @param file the file being edited.\r
      */\r
-    public void initWithFile(IFile file) {\r
+    public void openFile(IFile file) {\r
         mEditedFile = file;\r
 \r
         ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);\r
         mEditedConfig = resFolder.getConfiguration();\r
 \r
-        mConfigComposite.updateThemesAndLocales();\r
-\r
+        IAndroidTarget target = null;\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
+            target = currentSdk.getTarget(mEditedFile.getProject());\r
         }\r
 \r
+        mConfigComposite.openFile(mEditedConfig, target);\r
+\r
         if (mReloadListener == null) {\r
             mReloadListener = new ReloadListener();\r
             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);\r
@@ -749,24 +747,46 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
         }\r
     }\r
 \r
-    public void onTargetChange() {\r
-        onTargetOrSdkChange(false /* reloadDevices */);\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
 \r
-    public void onSdkChange() {\r
-        onTargetOrSdkChange(true /* reloadDevices */);\r
+        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);\r
+        mEditedConfig = resFolder.getConfiguration();\r
+\r
+        mConfigComposite.replaceFile(mEditedConfig);\r
     }\r
 \r
     /**\r
-     * Reloads the configuration selector.\r
-     * @param reloadDevices whether the {@link LayoutDevice} objects should be reloaded.\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
-    private void onTargetOrSdkChange(boolean reloadDevices) {\r
+    public void changeFileOnNewConfig(IFile file) {\r
+        mEditedFile = file;\r
+\r
+        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);\r
+        mEditedConfig = resFolder.getConfiguration();\r
+\r
+        mConfigComposite.changeFileOnNewConfig(mEditedConfig);\r
+    }\r
+\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.resetUi(mEditedConfig, target, reloadDevices);\r
+                mConfigComposite.onSdkLoaded(target);\r
                 mConfigListener.onConfigurationChange();\r
             }\r
         }\r
@@ -1132,7 +1152,7 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 \r
                 mLayoutCanvas.getDisplay().asyncExec(new Runnable() {\r
                     public void run() {\r
-                        mConfigComposite.updateThemesAndLocales();\r
+                        mConfigComposite.updateLocales();\r
                     }\r
                 });\r
             }\r