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;
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.
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;
/** 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.
}
/**
+ * 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.
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*/);
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) {
}
});
- 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.
}
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$
+ });
}
}
} 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) {
}
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() {
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");
}
}
- /**
- * 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;
}
/**
- * 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();
}
// 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();
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
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() {
return false;
}
+
+ private void checkCreateEnable() {
+ mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
+ }
+
}
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;
/** 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();
@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.
}
};
private final Runnable mUiUpdateFromResourcesRunnable = new Runnable() {
public void run() {
- mConfigComposite.updateUIFromResources();
+ mConfigComposite.updateThemesAndLocales();
}
};
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
/**
* 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() {
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());
- }
}
/**
}
}
- /**
- * 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();
}
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.
// 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(
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;
}
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
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
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
\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
\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
* 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
\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
\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
// 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
}\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
\r
mLayoutCanvas.getDisplay().asyncExec(new Runnable() {\r
public void run() {\r
- mConfigComposite.updateUIFromResources();\r
+ mConfigComposite.updateThemesAndLocales();\r
}\r
});\r
}\r