diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java | 2096 |
1 files changed, 0 insertions, 2096 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java deleted file mode 100644 index 009b8646c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java +++ /dev/null @@ -1,2096 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.configuration; - -import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX; -import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.ATTR_CONTEXT; -import static com.android.SdkConstants.PREFIX_RESOURCE_REF; -import static com.android.SdkConstants.RES_QUALIFIER_SEP; -import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.ide.eclipse.adt.AdtUtils.isUiThread; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_LOCALE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_THEME; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_ALL; -import static com.google.common.base.Objects.equal; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.rendering.api.StyleResourceValue; -import com.android.ide.common.resources.LocaleManager; -import com.android.ide.common.resources.ResourceFile; -import com.android.ide.common.resources.ResourceFolder; -import com.android.ide.common.resources.ResourceRepository; -import com.android.ide.common.resources.configuration.DeviceConfigHelper; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.common.resources.configuration.LocaleQualifier; -import com.android.ide.common.resources.configuration.ResourceQualifier; -import com.android.ide.common.sdk.LoadStatus; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate; -import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes; -import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -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.Sdk; -import com.android.resources.ResourceType; -import com.android.resources.ScreenOrientation; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.devices.Device; -import com.android.sdklib.devices.DeviceManager; -import com.android.sdklib.devices.DeviceManager.DevicesChangedListener; -import com.android.sdklib.devices.State; -import com.android.utils.Pair; -import com.google.common.base.Objects; -import com.google.common.base.Strings; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.ToolBar; -import org.eclipse.swt.widgets.ToolItem; -import org.eclipse.ui.IEditorPart; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; - -/** - * The {@linkplain ConfigurationChooser} allows the user to pick a - * {@link Configuration} by configuring various constraints. - */ -public class ConfigurationChooser extends Composite - implements DevicesChangedListener, DisposeListener { - private static final String ICON_SQUARE = "square"; //$NON-NLS-1$ - private static final String ICON_LANDSCAPE = "landscape"; //$NON-NLS-1$ - private static final String ICON_PORTRAIT = "portrait"; //$NON-NLS-1$ - private static final String ICON_LANDSCAPE_FLIP = "flip_landscape";//$NON-NLS-1$ - private static final String ICON_PORTRAIT_FLIP = "flip_portrait";//$NON-NLS-1$ - private static final String ICON_DISPLAY = "display"; //$NON-NLS-1$ - private static final String ICON_THEMES = "themes"; //$NON-NLS-1$ - private static final String ICON_ACTIVITY = "activity"; //$NON-NLS-1$ - - /** The configuration state associated with this editor */ - private @NonNull Configuration mConfiguration = Configuration.create(this); - - /** Serialized state to use when initializing the configuration after the SDK is loaded */ - private String mInitialState; - - /** The client of the configuration editor */ - private final ConfigurationClient mClient; - - /** Counter for programmatic UI changes: if greater than 0, we're within a call */ - private int mDisableUpdates = 0; - - /** List of available devices */ - private Collection<Device> mDevices = Collections.emptyList(); - - /** List of available targets */ - private final List<IAndroidTarget> mTargetList = new ArrayList<IAndroidTarget>(); - - /** List of available themes */ - private final List<String> mThemeList = new ArrayList<String>(); - - /** List of available locales */ - private final List<Locale > mLocaleList = new ArrayList<Locale>(); - - /** The file being edited */ - private IFile mEditedFile; - - /** The {@link ProjectResources} for the edited file's project */ - private ProjectResources mResources; - - /** The target of the project of the file being edited. */ - private IAndroidTarget mProjectTarget; - - /** Dropdown for configurations */ - private ToolItem mConfigCombo; - - /** Dropdown for devices */ - private ToolItem mDeviceCombo; - - /** Dropdown for device states */ - private ToolItem mOrientationCombo; - - /** Dropdown for themes */ - private ToolItem mThemeCombo; - - /** Dropdown for locales */ - private ToolItem mLocaleCombo; - - /** Dropdown for activities */ - private ToolItem mActivityCombo; - - /** Dropdown for rendering targets */ - private ToolItem mTargetCombo; - - /** Whether the SDK has changed since the last model reload; if so we must reload targets */ - private boolean mSdkChanged = true; - - /** - * Creates a new {@linkplain ConfigurationChooser} and adds it to the - * parent. The method also receives custom buttons to set into the - * configuration composite. The list is organized as an array of arrays. - * Each array represents a group of buttons thematically grouped together. - * - * @param client the client embedding this configuration chooser - * @param parent The parent composite. - * @param initialState The initial state (serialized form) to use for the - * configuration - */ - public ConfigurationChooser( - @NonNull ConfigurationClient client, - Composite parent, - @Nullable String initialState) { - super(parent, SWT.NONE); - mClient = client; - - setVisible(false); // Delayed until the targets are loaded - - mInitialState = initialState; - setLayout(new GridLayout(1, false)); - - IconFactory icons = IconFactory.getInstance(); - - // TODO: Consider switching to a CoolBar instead - ToolBar toolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); - toolBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); - - mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN ); - mConfigCombo.setImage(icons.getIcon("android_file")); //$NON-NLS-1$ - mConfigCombo.setToolTipText("Configuration to render this layout with in Eclipse"); - - @SuppressWarnings("unused") - ToolItem separator2 = new ToolItem(toolBar, SWT.SEPARATOR); - - mDeviceCombo = new ToolItem(toolBar, SWT.DROP_DOWN); - mDeviceCombo.setImage(icons.getIcon(ICON_DISPLAY)); - - @SuppressWarnings("unused") - ToolItem separator3 = new ToolItem(toolBar, SWT.SEPARATOR); - - mOrientationCombo = new ToolItem(toolBar, SWT.DROP_DOWN); - mOrientationCombo.setImage(icons.getIcon(ICON_PORTRAIT)); - mOrientationCombo.setToolTipText("Go to next state"); - - @SuppressWarnings("unused") - ToolItem separator4 = new ToolItem(toolBar, SWT.SEPARATOR); - - mThemeCombo = new ToolItem(toolBar, SWT.DROP_DOWN); - mThemeCombo.setImage(icons.getIcon(ICON_THEMES)); - - @SuppressWarnings("unused") - ToolItem separator5 = new ToolItem(toolBar, SWT.SEPARATOR); - - mActivityCombo = new ToolItem(toolBar, SWT.DROP_DOWN); - mActivityCombo.setToolTipText("Associated activity or fragment providing context"); - // The JDT class icon is lopsided, presumably because they've left room in the - // bottom right corner for badges (for static, final etc). Unfortunately, this - // means that the icon looks out of place when sitting close to the language globe - // icon, the theme icon, etc so that it looks vertically misaligned: - //mActivityCombo.setImage(JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CLASS)); - // ...so use one that is centered instead: - mActivityCombo.setImage(icons.getIcon(ICON_ACTIVITY)); - - @SuppressWarnings("unused") - ToolItem separator6 = new ToolItem(toolBar, SWT.SEPARATOR); - - //ToolBar rightToolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); - //rightToolBar.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); - ToolBar rightToolBar = toolBar; - - mLocaleCombo = new ToolItem(rightToolBar, SWT.DROP_DOWN); - mLocaleCombo.setImage(FlagManager.getGlobeIcon()); - mLocaleCombo.setToolTipText("Locale to use when rendering layouts in Eclipse"); - - @SuppressWarnings("unused") - ToolItem separator7 = new ToolItem(rightToolBar, SWT.SEPARATOR); - - mTargetCombo = new ToolItem(rightToolBar, SWT.DROP_DOWN); - mTargetCombo.setImage(AdtPlugin.getAndroidLogo()); - mTargetCombo.setToolTipText("Android version to use when rendering layouts in Eclipse"); - - SelectionListener listener = new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - Object source = e.getSource(); - - if (source == mConfigCombo) { - ConfigurationMenuListener.show(ConfigurationChooser.this, mConfigCombo); - } else if (source == mActivityCombo) { - ActivityMenuListener.show(ConfigurationChooser.this, mActivityCombo); - } else if (source == mLocaleCombo) { - LocaleMenuListener.show(ConfigurationChooser.this, mLocaleCombo); - } else if (source == mDeviceCombo) { - DeviceMenuListener.show(ConfigurationChooser.this, mDeviceCombo); - } else if (source == mTargetCombo) { - TargetMenuListener.show(ConfigurationChooser.this, mTargetCombo); - } else if (source == mThemeCombo) { - ThemeMenuAction.showThemeMenu(ConfigurationChooser.this, mThemeCombo, - mThemeList); - } else if (source == mOrientationCombo) { - if (e.detail == SWT.ARROW) { - OrientationMenuAction.showMenu(ConfigurationChooser.this, - mOrientationCombo); - } else { - gotoNextState(); - } - } - } - }; - mConfigCombo.addSelectionListener(listener); - mActivityCombo.addSelectionListener(listener); - mLocaleCombo.addSelectionListener(listener); - mDeviceCombo.addSelectionListener(listener); - mTargetCombo.addSelectionListener(listener); - mThemeCombo.addSelectionListener(listener); - mOrientationCombo.addSelectionListener(listener); - - addDisposeListener(this); - - initDevices(); - initTargets(); - } - - /** - * Returns the edited file - * - * @return the file - */ - @Nullable - public IFile getEditedFile() { - return mEditedFile; - } - - /** - * Returns the project of the edited file - * - * @return the project - */ - @Nullable - public IProject getProject() { - if (mEditedFile != null) { - return mEditedFile.getProject(); - } else { - return null; - } - } - - ConfigurationClient getClient() { - return mClient; - } - - /** - * Returns the project resources for the project being configured by this - * chooser - * - * @return the project resources - */ - @Nullable - public ProjectResources getResources() { - return mResources; - } - - /** - * Returns the full, complete {@link FolderConfiguration} - * - * @return the full configuration - */ - public FolderConfiguration getFullConfiguration() { - return mConfiguration.getFullConfig(); - } - - /** - * Returns the project target - * - * @return the project target - */ - public IAndroidTarget getProjectTarget() { - return mProjectTarget; - } - - /** - * Returns the configuration being edited by this {@linkplain ConfigurationChooser} - * - * @return the configuration - */ - public Configuration getConfiguration() { - return mConfiguration; - } - - /** - * Returns the list of locales - * @return a list of {@link ResourceQualifier} pairs - */ - @NonNull - public List<Locale> getLocaleList() { - return mLocaleList; - } - - /** - * Returns the list of available devices - * - * @return a list of {@link Device} objects - */ - @NonNull - public Collection<Device> getDevices() { - return mDevices; - } - - /** - * Returns the list of available render targets - * - * @return a list of {@link IAndroidTarget} objects - */ - @NonNull - public List<IAndroidTarget> getTargetList() { - return mTargetList; - } - - // ---- Configuration State Lookup ---- - - /** - * Returns the rendering target to be used - * - * @return the target - */ - @NonNull - public IAndroidTarget getTarget() { - IAndroidTarget target = mConfiguration.getTarget(); - if (target == null) { - target = mProjectTarget; - } - - return target; - } - - /** - * Returns the current device string, or null if no device is selected - * - * @return the device name, or null - */ - @Nullable - public String getDeviceName() { - Device device = mConfiguration.getDevice(); - if (device != null) { - return device.getName(); - } - - return null; - } - - /** - * Returns the current theme, or null if none has been selected - * - * @return the theme name, or null - */ - @Nullable - public String getThemeName() { - String theme = mConfiguration.getTheme(); - if (theme != null) { - theme = ResourceHelper.styleToTheme(theme); - } - - return theme; - } - - /** Move to the next device state, changing the icon if it changes orientation */ - private void gotoNextState() { - State state = mConfiguration.getDeviceState(); - State flipped = mConfiguration.getNextDeviceState(state); - if (flipped != state) { - selectDeviceState(flipped); - onDeviceConfigChange(); - } - } - - // ---- Implements DisposeListener ---- - - @Override - public void widgetDisposed(DisposeEvent e) { - dispose(); - } - - @Override - public void dispose() { - if (!isDisposed()) { - super.dispose(); - - final Sdk sdk = Sdk.getCurrent(); - if (sdk != null) { - DeviceManager manager = sdk.getDeviceManager(); - manager.unregisterListener(this); - } - } - } - - // ---- Init and reset/reload methods ---- - - /** - * Sets the reference to the file being edited. - * <p/>The UI is initialized in {@link #onXmlModelLoaded()} which is called as the XML model is - * loaded (or reloaded as the SDK/target changes). - * - * @param file the file being opened - * - * @see #onXmlModelLoaded() - * @see #replaceFile(IFile) - * @see #changeFileOnNewConfig(IFile) - */ - public void setFile(IFile file) { - mEditedFile = file; - ensureInitialized(); - } - - /** - * 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 or - * not the best match - * @param file the file being opened. - */ - public void replaceFile(IFile file) { - // if there is no previous selection, revert to default mode. - if (mConfiguration.getDevice() == null) { - setFile(file); // onTargetChanged will be called later. - return; - } - - setFile(file); - IProject project = mEditedFile.getProject(); - mResources = ResourceManager.getInstance().getProjectResources(project); - - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file); - mConfiguration.setEditedConfig(resFolder.getConfiguration()); - - mDisableUpdates++; // we do not want to trigger onXXXChange when setting - // new values in the widgets. - - try { - // only attempt to do anything if the SDK and targets are loaded. - LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus(); - - if (sdkStatus == LoadStatus.LOADED) { - setVisible(true); - - LoadStatus targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget, - null /*project*/); - - if (targetStatus == LoadStatus.LOADED) { - - // update the current config selection to make sure it's - // compatible with the new file - ConfigurationMatcher matcher = new ConfigurationMatcher(this); - matcher.adaptConfigSelection(true /*needBestMatch*/); - mConfiguration.syncFolderConfig(); - - // update the string showing the config value - selectConfiguration(mConfiguration.getEditedConfig()); - updateActivity(); - } - } else if (sdkStatus == LoadStatus.FAILED) { - setVisible(true); - } - } finally { - mDisableUpdates--; - } - } - - /** - * Updates the UI with a new file that was opened in response to a config change. - * @param file the file being opened. - * - * @see #replaceFile(IFile) - */ - public void changeFileOnNewConfig(IFile file) { - setFile(file); - IProject project = mEditedFile.getProject(); - mResources = ResourceManager.getInstance().getProjectResources(project); - - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file); - FolderConfiguration config = resFolder.getConfiguration(); - mConfiguration.setEditedConfig(config); - - // All that's needed is to update the string showing the config value - // (since the config combo settings chosen by the user). - selectConfiguration(config); - } - - /** - * Resets the configuration chooser to reflect the given file configuration. This is - * intended to be used by the "Show Included In" functionality where the user has - * picked a non-default configuration (such as a particular landscape layout) and the - * configuration chooser must be switched to a landscape layout. This method will - * trigger a model change. - * <p> - * This will NOT trigger a redraw event! - * <p> - * FIXME: We are currently setting the configuration file to be the configuration for - * the "outer" (the including) file, rather than the inner file, which is the file the - * user is actually editing. We need to refine this, possibly with a way for the user - * to choose which configuration they are editing. And in particular, we should be - * filtering the configuration chooser to only show options in the outer configuration - * that are compatible with the inner included file. - * - * @param file the file to be configured - */ - public void resetConfigFor(IFile file) { - setFile(file); - - IFolder parent = (IFolder) mEditedFile.getParent(); - ResourceFolder resFolder = mResources.getResourceFolder(parent); - if (resFolder != null) { - mConfiguration.setEditedConfig(resFolder.getConfiguration()); - } else { - FolderConfiguration config = FolderConfiguration.getConfig( - parent.getName().split(RES_QUALIFIER_SEP)); - if (config != null) { - mConfiguration.setEditedConfig(config); - } else { - mConfiguration.setEditedConfig(new FolderConfiguration()); - } - } - - onXmlModelLoaded(); - } - - - /** - * Sets the current configuration to match the given folder configuration, - * the given theme name, the given device and device state. - * - * @param configuration new folder configuration to use - */ - public void setConfiguration(@NonNull Configuration configuration) { - if (mClient != null) { - mClient.aboutToChange(MASK_ALL); - } - - Configuration oldConfiguration = mConfiguration; - mConfiguration = configuration; - mConfiguration.setChooser(this); - - selectTheme(configuration.getTheme()); - selectLocale(configuration.getLocale()); - selectDevice(configuration.getDevice()); - selectDeviceState(configuration.getDeviceState()); - selectTarget(configuration.getTarget()); - selectActivity(configuration.getActivity()); - - // This may be a second refresh after triggered by theme above - if (mClient != null) { - LayoutCanvas canvas = mClient.getCanvas(); - if (canvas != null) { - assert mConfiguration != oldConfiguration; - canvas.getPreviewManager().updateChooserConfig(oldConfiguration, mConfiguration); - } - - boolean accepted = mClient.changed(MASK_ALL); - if (!accepted) { - configuration = oldConfiguration; - selectTheme(configuration.getTheme()); - selectLocale(configuration.getLocale()); - selectDevice(configuration.getDevice()); - selectDeviceState(configuration.getDeviceState()); - selectTarget(configuration.getTarget()); - selectActivity(configuration.getActivity()); - if (canvas != null && mConfiguration != oldConfiguration) { - canvas.getPreviewManager().updateChooserConfig(mConfiguration, - oldConfiguration); - } - return; - } else { - int changed = 0; - if (!equal(oldConfiguration.getTheme(), mConfiguration.getTheme())) { - changed |= CFG_THEME; - } - if (!equal(oldConfiguration.getDevice(), mConfiguration.getDevice())) { - changed |= CFG_DEVICE | CFG_DEVICE_STATE; - } - if (changed != 0) { - syncToVariations(changed, mEditedFile, mConfiguration, false, true); - } - } - } - - saveConstraints(); - } - - /** - * 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. - mProjectTarget = target; - - mDisableUpdates++; // we do not want to trigger onXXXChange when setting - // new values in the widgets. - try { - updateDevices(); - updateTargets(); - ensureInitialized(); - } finally { - mDisableUpdates--; - } - } - - /** - * Responds to the XML model being loaded, either the first time or when the - * Target/SDK changes. - * <p> - * This initializes the UI, either with the first compatible configuration - * found, or it will attempt to restore a configuration if one is found to - * have been saved in the file persistent storage. - * <p> - * If the SDK or target are not loaded, nothing will happen (but the method - * must be called back when they are.) - * <p> - * The method automatically handles being called the first time after editor - * creation, or being called after during SDK/Target changes (as long as - * {@link #onSdkLoaded(IAndroidTarget)} is properly called). - * - * @return the target data for the rendering target used to render the - * layout - * - * @see #saveConstraints() - * @see #onSdkLoaded(IAndroidTarget) - */ - public AndroidTargetData onXmlModelLoaded() { - AndroidTargetData targetData = null; - - // only attempt to do anything if the SDK and targets are loaded. - LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus(); - if (sdkStatus == LoadStatus.LOADED) { - mDisableUpdates++; // we do not want to trigger onXXXChange when setting - - try { - // init the devices if needed (new SDK or first time going through here) - if (mSdkChanged) { - updateDevices(); - updateTargets(); - ensureInitialized(); - mSdkChanged = false; - } - - IProject project = mEditedFile.getProject(); - - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - mProjectTarget = currentSdk.getTarget(project); - } - - LoadStatus targetStatus = LoadStatus.FAILED; - if (mProjectTarget != null) { - targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget, null); - updateTargets(); - ensureInitialized(); - } - - if (targetStatus == LoadStatus.LOADED) { - setVisible(true); - if (mResources == null) { - mResources = ResourceManager.getInstance().getProjectResources(project); - } - if (mConfiguration.getEditedConfig() == null) { - IFolder parent = (IFolder) mEditedFile.getParent(); - ResourceFolder resFolder = mResources.getResourceFolder(parent); - if (resFolder != null) { - mConfiguration.setEditedConfig(resFolder.getConfiguration()); - } else { - FolderConfiguration config = FolderConfiguration.getConfig( - parent.getName().split(RES_QUALIFIER_SEP)); - if (config != null) { - mConfiguration.setEditedConfig(config); - } else { - mConfiguration.setEditedConfig(new FolderConfiguration()); - } - } - } - - targetData = Sdk.getCurrent().getTargetData(mProjectTarget); - - // get the file stored state - ensureInitialized(); - boolean loadedConfigData = mConfiguration.getDevice() != null && - mConfiguration.getDeviceState() != null; - - // Load locale list. This must be run after we initialize the - // configuration above, since it attempts to sync the UI with - // the value loaded into the configuration. - updateLocales(); - - // If the current state was loaded from the persistent storage, we update the - // UI with it and then try to adapt it (which will handle incompatible - // configuration). - // Otherwise, just look for the first compatible configuration. - ConfigurationMatcher matcher = new ConfigurationMatcher(this); - if (loadedConfigData) { - // first make sure we have the config to adapt - selectDevice(mConfiguration.getDevice()); - selectDeviceState(mConfiguration.getDeviceState()); - mConfiguration.syncFolderConfig(); - - matcher.adaptConfigSelection(false); - - IAndroidTarget target = mConfiguration.getTarget(); - selectTarget(target); - targetData = Sdk.getCurrent().getTargetData(target); - } else { - matcher.findAndSetCompatibleConfig(false); - - // Default to modern layout lib - IAndroidTarget target = ConfigurationMatcher.findDefaultRenderTarget(this); - if (target != null) { - targetData = Sdk.getCurrent().getTargetData(target); - selectTarget(target); - mConfiguration.setTarget(target, true); - } - } - - // Update activity: This is done before updateThemes() since - // the themes selection can depend on the currently selected activity - // (e.g. when there are manifest registrations for the theme to use - // for a given activity) - updateActivity(); - - // Update themes. This is done after updating the devices above, - // since we want to look at the chosen device size to decide - // what the default theme (for example, with Honeycomb we choose - // Holo as the default theme but only if the screen size is XLARGE - // (and of course only if the manifest does not specify another - // default theme). - updateThemes(); - - // update the string showing the config value - selectConfiguration(mConfiguration.getEditedConfig()); - - // compute the final current config - mConfiguration.syncFolderConfig(); - } else if (targetStatus == LoadStatus.FAILED) { - setVisible(true); - } - } finally { - mDisableUpdates--; - } - } - - return targetData; - } - - /** - * This is a temporary workaround for a infrequently happening bug; apparently - * there are cases where the configuration chooser isn't shown - */ - public void ensureVisible() { - if (!isVisible()) { - LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus(); - if (sdkStatus == LoadStatus.LOADED) { - onXmlModelLoaded(); - } - } - } - - /** - * An alternate layout for this layout has been created. This means that the - * current layout may no longer be a best fit. However, since we support multiple - * layouts being open at the same time, we need to adjust the current configuration - * back to something where this layout <b>is</b> a best match. - */ - public void onAlternateLayoutCreated() { - IFile best = ConfigurationMatcher.getBestFileMatch(this); - if (best != null && !best.equals(mEditedFile)) { - ConfigurationMatcher matcher = new ConfigurationMatcher(this); - matcher.adaptConfigSelection(true /*needBestMatch*/); - mConfiguration.syncFolderConfig(); - if (mClient != null) { - mClient.changed(MASK_ALL); - } - } - } - - /** - * Loads the list of {@link Device}s and inits the UI with it. - */ - private void initDevices() { - final Sdk sdk = Sdk.getCurrent(); - if (sdk != null) { - DeviceManager manager = sdk.getDeviceManager(); - // This method can be called more than once, so avoid duplicate entries - manager.unregisterListener(this); - manager.registerListener(this); - mDevices = manager.getDevices(DeviceManager.ALL_DEVICES); - } else { - mDevices = new ArrayList<Device>(); - } - } - - /** - * Loads the list of {@link IAndroidTarget} and inits the UI with it. - */ - private boolean initTargets() { - mTargetList.clear(); - - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - IAndroidTarget[] targets = currentSdk.getTargets(); - for (int i = 0 ; i < targets.length; i++) { - if (targets[i].hasRenderingLibrary()) { - mTargetList.add(targets[i]); - } - } - - return true; - } - - return false; - } - - /** Ensures that the configuration has been initialized */ - public void ensureInitialized() { - if (mConfiguration.getDevice() == null && mEditedFile != null) { - String data = ConfigurationDescription.getDescription(mEditedFile); - if (mInitialState != null) { - data = mInitialState; - mInitialState = null; - } - if (data != null) { - mConfiguration.initialize(data); - mConfiguration.syncFolderConfig(); - } - } - } - - private void updateDevices() { - if (mDevices.size() == 0) { - initDevices(); - } - } - - private void updateTargets() { - if (mTargetList.size() == 0) { - if (!initTargets()) { - return; - } - } - - IAndroidTarget renderingTarget = mConfiguration.getTarget(); - - IAndroidTarget match = null; - for (IAndroidTarget target : mTargetList) { - if (renderingTarget != null) { - // use equals because the rendering could be from a previous SDK, so - // it may not be the same instance. - if (renderingTarget.equals(target)) { - match = target; - } - } else if (mProjectTarget == target) { - match = target; - } - - } - - if (match == null) { - // the rendering target is the same as the project. - renderingTarget = mProjectTarget; - } else { - // set the rendering target to the new object. - renderingTarget = match; - } - - mConfiguration.setTarget(renderingTarget, true); - selectTarget(renderingTarget); - } - - /** Update the toolbar whenever a label has changed, to not only - * cause the layout in the current toolbar to update, but to possibly - * wrap the toolbars and update the layout of the surrounding area. - */ - private void resizeToolBar() { - Point size = getSize(); - Point newSize = computeSize(size.x, SWT.DEFAULT, true); - setSize(newSize); - Composite parent = getParent(); - parent.layout(); - parent.redraw(); - } - - - Image getOrientationIcon(ScreenOrientation orientation, boolean flip) { - IconFactory icons = IconFactory.getInstance(); - switch (orientation) { - case LANDSCAPE: - return icons.getIcon(flip ? ICON_LANDSCAPE_FLIP : ICON_LANDSCAPE); - case SQUARE: - return icons.getIcon(ICON_SQUARE); - case PORTRAIT: - default: - return icons.getIcon(flip ? ICON_PORTRAIT_FLIP : ICON_PORTRAIT); - } - } - - ImageDescriptor getOrientationImage(ScreenOrientation orientation, boolean flip) { - IconFactory icons = IconFactory.getInstance(); - switch (orientation) { - case LANDSCAPE: - return icons.getImageDescriptor(flip ? ICON_LANDSCAPE_FLIP : ICON_LANDSCAPE); - case SQUARE: - return icons.getImageDescriptor(ICON_SQUARE); - case PORTRAIT: - default: - return icons.getImageDescriptor(flip ? ICON_PORTRAIT_FLIP : ICON_PORTRAIT); - } - } - - @NonNull - ScreenOrientation getOrientation(State state) { - FolderConfiguration config = DeviceConfigHelper.getFolderConfig(state); - ScreenOrientation orientation = null; - if (config != null && config.getScreenOrientationQualifier() != null) { - orientation = config.getScreenOrientationQualifier().getValue(); - } - - if (orientation == null) { - orientation = ScreenOrientation.PORTRAIT; - } - - return orientation; - } - - /** - * Stores the current config selection into the edited file such that we can - * bring it back the next time this layout is opened. - */ - public void saveConstraints() { - String description = mConfiguration.toPersistentString(); - if (description != null && !description.isEmpty()) { - ConfigurationDescription.setDescription(mEditedFile, description); - } - } - - // ---- Setting the current UI state ---- - - void selectDeviceState(@Nullable State state) { - assert isUiThread(); - try { - mDisableUpdates++; - mOrientationCombo.setData(state); - - State nextState = mConfiguration.getNextDeviceState(state); - mOrientationCombo.setImage(getOrientationIcon(getOrientation(state), - nextState != state)); - } finally { - mDisableUpdates--; - } - } - - void selectTarget(IAndroidTarget target) { - assert isUiThread(); - try { - mDisableUpdates++; - mTargetCombo.setData(target); - String label = getRenderingTargetLabel(target, true); - mTargetCombo.setText(label); - resizeToolBar(); - } finally { - mDisableUpdates--; - } - } - - /** - * Selects a given {@link Device} in the device combo, if it is found. - * @param device the device to select - * @return true if the device was found. - */ - boolean selectDevice(@Nullable Device device) { - assert isUiThread(); - try { - mDisableUpdates++; - mDeviceCombo.setData(device); - if (device != null) { - mDeviceCombo.setText(getDeviceLabel(device, true)); - } else { - mDeviceCombo.setText("Device"); - } - resizeToolBar(); - } finally { - mDisableUpdates--; - } - - return false; - } - - void selectActivity(@Nullable String fqcn) { - assert isUiThread(); - try { - mDisableUpdates++; - if (fqcn != null) { - mActivityCombo.setData(fqcn); - String label = getActivityLabel(fqcn, true); - mActivityCombo.setText(label); - } else { - mActivityCombo.setText("(Select)"); - } - resizeToolBar(); - } finally { - mDisableUpdates--; - } - } - - void selectTheme(@Nullable String theme) { - assert isUiThread(); - try { - mDisableUpdates++; - assert theme == null || theme.startsWith(STYLE_RESOURCE_PREFIX) - || theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX) : theme; - mThemeCombo.setData(theme); - if (theme != null) { - mThemeCombo.setText(getThemeLabel(theme, true)); - } else { - // FIXME eclipse claims this is dead code. - mThemeCombo.setText("(Set Theme)"); - } - resizeToolBar(); - } finally { - mDisableUpdates--; - } - } - - void selectLocale(@Nullable Locale locale) { - assert isUiThread(); - try { - mDisableUpdates++; - mLocaleCombo.setData(locale); - String label = Strings.nullToEmpty(getLocaleLabel(this, locale, true)); - mLocaleCombo.setText(label); - - Image image = getFlagImage(locale); - mLocaleCombo.setImage(image); - - resizeToolBar(); - } finally { - mDisableUpdates--; - } - } - - @NonNull - Image getFlagImage(@Nullable Locale locale) { - if (locale != null) { - return locale.getFlagImage(); - } - - return FlagManager.getGlobeIcon(); - } - - private void selectConfiguration(FolderConfiguration fileConfig) { - /* For now, don't show any text in the configuration combo, use just an - icon. This has the advantage that the configuration contents don't - shift around, so you can for example click back and forth between - portrait and landscape without the icon moving under the mouse. - If this works well, remove this whole method post ADT 21. - assert isUiThread(); - try { - String current = mEditedFile.getParent().getName(); - if (current.equals(FD_RES_LAYOUT)) { - current = "default"; - } - - // Pretty things up a bit - //if (current == null || current.equals("default")) { - // current = "Default Configuration"; - //} - mConfigCombo.setText(current); - resizeToolBar(); - } finally { - mDisableUpdates--; - } - */ - } - - /** - * Finds a locale matching the config from a file. - * - * @param language the language qualifier or null if none is set. - * @param region the region qualifier or null if none is set. - * @return true if there was a change in the combobox as a result of - * applying the locale - */ - private boolean setLocale(@Nullable Locale locale) { - boolean changed = !Objects.equal(mConfiguration.getLocale(), locale); - selectLocale(locale); - - return changed; - } - - // ---- Creating UI labels ---- - - /** - * Returns a suitable label to use to display the given activity - * - * @param fqcn the activity class to look up a label for - * @param brief if true, generate a brief label (suitable for a toolbar - * button), otherwise a fuller name (suitable for a menu item) - * @return the label - */ - public static String getActivityLabel(String fqcn, boolean brief) { - if (brief) { - String label = fqcn; - int packageIndex = label.lastIndexOf('.'); - if (packageIndex != -1) { - label = label.substring(packageIndex + 1); - } - int innerClass = label.lastIndexOf('$'); - if (innerClass != -1) { - label = label.substring(innerClass + 1); - } - - // Also strip out the "Activity" or "Fragment" common suffix - // if this is a long name - if (label.endsWith("Activity") && label.length() > 8 + 12) { // 12 chars + 8 in suffix - label = label.substring(0, label.length() - 8); - } else if (label.endsWith("Fragment") && label.length() > 8 + 12) { - label = label.substring(0, label.length() - 8); - } - - return label; - } - - return fqcn; - } - - /** - * Returns a suitable label to use to display the given theme - * - * @param theme the theme to produce a label for - * @param brief if true, generate a brief label (suitable for a toolbar - * button), otherwise a fuller name (suitable for a menu item) - * @return the label - */ - public static String getThemeLabel(String theme, boolean brief) { - theme = ResourceHelper.styleToTheme(theme); - - if (brief) { - int index = theme.lastIndexOf('.'); - if (index < theme.length() - 1) { - return theme.substring(index + 1); - } - } - return theme; - } - - /** - * Returns a suitable label to use to display the given rendering target - * - * @param target the target to produce a label for - * @param brief if true, generate a brief label (suitable for a toolbar - * button), otherwise a fuller name (suitable for a menu item) - * @return the label - */ - public static String getRenderingTargetLabel(IAndroidTarget target, boolean brief) { - if (target == null) { - return "<null>"; - } - - AndroidVersion version = target.getVersion(); - - if (brief) { - if (target.isPlatform()) { - return Integer.toString(version.getApiLevel()); - } else { - return target.getName() + ':' + Integer.toString(version.getApiLevel()); - } - } - - String label = String.format("API %1$d: %2$s", - version.getApiLevel(), - target.getShortClasspathName()); - - return label; - } - - /** - * Returns a suitable label to use to display the given device - * - * @param device the device to produce a label for - * @param brief if true, generate a brief label (suitable for a toolbar - * button), otherwise a fuller name (suitable for a menu item) - * @return the label - */ - public static String getDeviceLabel(@Nullable Device device, boolean brief) { - if (device == null) { - return ""; - } - String name = device.getName(); - - if (brief) { - // Produce a really brief summary of the device name, suitable for - // use in the narrow space available in the toolbar for example - int nexus = name.indexOf("Nexus"); //$NON-NLS-1$ - if (nexus != -1) { - int begin = name.indexOf('('); - if (begin != -1) { - begin++; - int end = name.indexOf(')', begin); - if (end != -1) { - return name.substring(begin, end).trim(); - } - } - } - } - - return name; - } - - /** - * Returns a suitable label to use to display the given locale - * - * @param chooser the chooser, if known - * @param locale the locale to look up a label for - * @param brief if true, generate a brief label (suitable for a toolbar - * button), otherwise a fuller name (suitable for a menu item) - * @return the label - */ - @Nullable - public static String getLocaleLabel( - @Nullable ConfigurationChooser chooser, - @Nullable Locale locale, - boolean brief) { - if (locale == null) { - return null; - } - - if (!locale.hasLanguage()) { - if (brief) { - // Just use the icon - return ""; - } - - boolean hasLocale = false; - ResourceRepository projectRes = chooser != null ? chooser.mClient.getProjectResources() - : null; - if (projectRes != null) { - hasLocale = projectRes.getLanguages().size() > 0; - } - - if (hasLocale) { - return "Other"; - } else { - return "Any"; - } - } - - String languageCode = locale.qualifier.getLanguage(); - String languageName = LocaleManager.getLanguageName(languageCode); - - if (!locale.hasRegion()) { - // TODO: Make the region string use "Other" instead of "Any" if - // there is more than one region for a given language - //if (regions.size() > 0) { - // return String.format("%1$s / Other", language); - //} else { - // return String.format("%1$s / Any", language); - //} - if (!brief && languageName != null) { - return String.format("%1$s (%2$s)", languageName, languageCode); - } else { - return languageCode; - } - } else { - String regionCode = locale.qualifier.getRegion(); - if (!brief && languageName != null) { - String regionName = LocaleManager.getRegionName(regionCode); - if (regionName != null) { - return String.format("%1$s (%2$s) in %3$s (%4$s)", languageName, languageCode, - regionName, regionCode); - } - return String.format("%1$s (%2$s) in %3$s", languageName, languageCode, - regionCode); - } - return String.format("%1$s / %2$s", languageCode, regionCode); - } - } - - // ---- Implements DevicesChangedListener ---- - - @Override - public void onDevicesChanged() { - final Sdk sdk = Sdk.getCurrent(); - if (sdk != null) { - mDevices = sdk.getDeviceManager().getDevices(DeviceManager.ALL_DEVICES); - } else { - mDevices = new ArrayList<Device>(); - } - } - - // ---- Reacting to UI changes ---- - - /** - * Called when the selection of the device combo changes. - */ - void onDeviceChange() { - // because changing the content of a combo triggers a change event, respect the - // mDisableUpdates flag - if (mDisableUpdates > 0) { - return; - } - - // Attempt to preserve the device state - String stateName = null; - Device prevDevice = mConfiguration.getDevice(); - State prevState = mConfiguration.getDeviceState(); - Device device = (Device) mDeviceCombo.getData(); - if (prevDevice != null && prevState != null && device != null) { - // get the previous config, so that we can look for a close match - FolderConfiguration oldConfig = DeviceConfigHelper.getFolderConfig(prevState); - if (oldConfig != null) { - stateName = ConfigurationMatcher.getClosestMatch(oldConfig, device.getAllStates()); - } - } - mConfiguration.setDevice(device, true); - State newState = Configuration.getState(device, stateName); - mConfiguration.setDeviceState(newState, true); - selectDeviceState(newState); - mConfiguration.syncFolderConfig(); - - // Notify - IFile file = mEditedFile; - boolean accepted = mClient.changed(CFG_DEVICE | CFG_DEVICE_STATE); - if (!accepted) { - mConfiguration.setDevice(prevDevice, true); - mConfiguration.setDeviceState(prevState, true); - mConfiguration.syncFolderConfig(); - selectDevice(prevDevice); - selectDeviceState(prevState); - return; - } else { - syncToVariations(CFG_DEVICE | CFG_DEVICE_STATE, file, mConfiguration, false, true); - } - - saveConstraints(); - } - - /** - * Synchronizes changes to the given attributes (indicated by the mask - * referencing the {@code CFG_} configuration attribute bit flags in - * {@link Configuration} to the layout variations of the given updated file. - * - * @param flags the attributes which were updated - * @param updatedFile the file which was updated - * @param base the base configuration to base the chooser off of - * @param includeSelf whether the updated file itself should be updated - * @param async whether the updates should be performed asynchronously - */ - public void syncToVariations( - final int flags, - final @NonNull IFile updatedFile, - final @NonNull Configuration base, - final boolean includeSelf, - boolean async) { - if (async) { - getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - doSyncToVariations(flags, updatedFile, includeSelf, base); - } - }); - } else { - doSyncToVariations(flags, updatedFile, includeSelf, base); - } - } - - private void doSyncToVariations(int flags, IFile updatedFile, boolean includeSelf, - Configuration base) { - // Synchronize the given changes to other configurations as well - List<IFile> files = AdtUtils.getResourceVariations(updatedFile, includeSelf); - for (IFile file : files) { - Configuration configuration = Configuration.create(base, file); - configuration.setTheme(base.getTheme()); - configuration.setActivity(base.getActivity()); - Collection<IEditorPart> editors = AdtUtils.findEditorsFor(file, false); - boolean found = false; - for (IEditorPart editor : editors) { - if (editor instanceof CommonXmlEditor) { - CommonXmlDelegate delegate = ((CommonXmlEditor) editor).getDelegate(); - if (delegate instanceof LayoutEditorDelegate) { - editor = ((LayoutEditorDelegate) delegate).getGraphicalEditor(); - } - } - if (editor instanceof GraphicalEditorPart) { - ConfigurationChooser chooser = - ((GraphicalEditorPart) editor).getConfigurationChooser(); - chooser.setConfiguration(configuration); - found = true; - } - } - if (!found) { - // Just update the file persistence - String description = configuration.toPersistentString(); - ConfigurationDescription.setDescription(file, description); - } - } - } - - /** - * Called when the device config selection changes. - */ - void onDeviceConfigChange() { - // because changing the content of a combo triggers a change event, respect the - // mDisableUpdates flag - if (mDisableUpdates > 0) { - return; - } - - State prev = mConfiguration.getDeviceState(); - State state = (State) mOrientationCombo.getData(); - mConfiguration.setDeviceState(state, false); - - if (mClient != null) { - boolean accepted = mClient.changed(CFG_DEVICE | CFG_DEVICE_STATE); - if (!accepted) { - mConfiguration.setDeviceState(prev, false); - selectDeviceState(prev); - return; - } - } - - saveConstraints(); - } - - /** - * Call back for language combo selection - */ - void onLocaleChange() { - // because mLocaleList triggers onLocaleChange at each modification, the filling - // of the combo with data will trigger notifications, and we don't want that. - if (mDisableUpdates > 0) { - return; - } - - Locale prev = mConfiguration.getLocale(); - Locale locale = (Locale) mLocaleCombo.getData(); - if (locale == null) { - locale = Locale.ANY; - } - mConfiguration.setLocale(locale, false); - - if (mClient != null) { - boolean accepted = mClient.changed(CFG_LOCALE); - if (!accepted) { - mConfiguration.setLocale(prev, false); - selectLocale(prev); - } - } - - // Store locale project-wide setting - mConfiguration.saveRenderState(); - } - - - void onThemeChange() { - if (mDisableUpdates > 0) { - return; - } - - String prev = mConfiguration.getTheme(); - mConfiguration.setTheme((String) mThemeCombo.getData()); - - if (mClient != null) { - boolean accepted = mClient.changed(CFG_THEME); - if (!accepted) { - mConfiguration.setTheme(prev); - selectTheme(prev); - return; - } else { - syncToVariations(CFG_DEVICE|CFG_DEVICE_STATE, mEditedFile, mConfiguration, - false, true); - } - } - - saveConstraints(); - } - - void notifyFolderConfigChanged() { - if (mDisableUpdates > 0 || mClient == null) { - return; - } - - if (mClient.changed(CFG_FOLDER)) { - saveConstraints(); - } - } - - void onSelectActivity() { - if (mDisableUpdates > 0) { - return; - } - - String activity = (String) mActivityCombo.getData(); - mConfiguration.setActivity(activity); - - if (activity == null) { - return; - } - - // See if there is a default theme assigned to this activity, and if so, use it - ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject()); - String preferred = null; - ActivityAttributes attributes = manifest.getActivityAttributes(activity); - if (attributes != null) { - preferred = attributes.getTheme(); - } - if (preferred != null && !Objects.equal(preferred, mConfiguration.getTheme())) { - // Yes, switch to it - selectTheme(preferred); - onThemeChange(); - } - - // Persist in XML - if (mClient != null) { - mClient.setActivity(activity); - } - - saveConstraints(); - } - - /** - * Call back for api level combo selection - */ - void onRenderingTargetChange() { - // because mApiCombo triggers onApiLevelChange at each modification, the filling - // of the combo with data will trigger notifications, and we don't want that. - if (mDisableUpdates > 0) { - return; - } - - IAndroidTarget prevTarget = mConfiguration.getTarget(); - String prevTheme = mConfiguration.getTheme(); - - int changeFlags = 0; - - // tell the listener a new rendering target is being set. Need to do this before updating - // mRenderingTarget. - if (prevTarget != null) { - changeFlags |= CFG_TARGET; - mClient.aboutToChange(changeFlags); - } - - IAndroidTarget target = (IAndroidTarget) mTargetCombo.getData(); - mConfiguration.setTarget(target, true); - - // force a theme update to reflect the new rendering target. - // This must be done after computeCurrentConfig since it'll depend on the currentConfig - // to figure out the theme list. - String oldTheme = mConfiguration.getTheme(); - updateThemes(); - // updateThemes may change the theme (based on theme availability in the new rendering - // target) so mark theme change if necessary - if (!Objects.equal(oldTheme, mConfiguration.getTheme())) { - changeFlags |= CFG_THEME; - } - - if (target != null) { - changeFlags |= CFG_TARGET; - changeFlags |= CFG_FOLDER; // In case we added a -vNN qualifier - } - - // Store project-wide render-target setting - mConfiguration.saveRenderState(); - - mConfiguration.syncFolderConfig(); - - if (mClient != null) { - boolean accepted = mClient.changed(changeFlags); - if (!accepted) { - mConfiguration.setTarget(prevTarget, true); - mConfiguration.setTheme(prevTheme); - mConfiguration.syncFolderConfig(); - selectTheme(prevTheme); - selectTarget(prevTarget); - } - } - } - - /** - * Syncs this configuration to the project wide locale and render target settings. The - * locale may ignore the project-wide setting if it is a locale-specific - * configuration. - * - * @return true if one or both of the toggles were changed, false if there were no - * changes - */ - public boolean syncRenderState() { - if (mConfiguration.getEditedConfig() == null) { - // Startup; ignore - return false; - } - - boolean renderTargetChanged = false; - - // When a page is re-activated, force the toggles to reflect the current project - // state - - Pair<Locale, IAndroidTarget> pair = Configuration.loadRenderState(this); - - int changeFlags = 0; - // Only sync the locale if this layout is not already a locale-specific layout! - if (pair != null && !mConfiguration.isLocaleSpecificLayout()) { - Locale locale = pair.getFirst(); - if (locale != null) { - boolean localeChanged = setLocale(locale); - if (localeChanged) { - changeFlags |= CFG_LOCALE; - } - } else { - locale = Locale.ANY; - } - mConfiguration.setLocale(locale, true); - } - - // Sync render target - IAndroidTarget configurationTarget = mConfiguration.getTarget(); - IAndroidTarget target = pair != null ? pair.getSecond() : configurationTarget; - if (target != null && configurationTarget != target) { - if (mClient != null && configurationTarget != null) { - changeFlags |= CFG_TARGET; - mClient.aboutToChange(changeFlags); - } - - mConfiguration.setTarget(target, true); - selectTarget(target); - renderTargetChanged = true; - } - - // Neither locale nor render target changed: nothing to do - if (changeFlags == 0) { - return false; - } - - // Update the locale and/or the render target. This code contains a logical - // merge of the onRenderingTargetChange() and onLocaleChange() methods, combined - // such that we don't duplicate work. - - // Compute the new configuration; we want to do this both for locale changes - // and for render targets. - mConfiguration.syncFolderConfig(); - changeFlags |= CFG_FOLDER; // in case we added/remove a -v<NN> qualifier - - if (renderTargetChanged) { - // force a theme update to reflect the new rendering target. - // This must be done after computeCurrentConfig since it'll depend on the currentConfig - // to figure out the theme list. - updateThemes(); - } - - if (mClient != null) { - mClient.changed(changeFlags); - } - - return true; - } - - // ---- Populate data structures with themes, locales, etc ---- - - /** - * Updates the internal list of themes. - */ - private void updateThemes() { - if (mClient == null) { - return; // can't do anything without it. - } - - ResourceRepository frameworkRes = mClient.getFrameworkResources( - mConfiguration.getTarget()); - - mDisableUpdates++; - - try { - if (mEditedFile != null) { - String theme = mConfiguration.getTheme(); - if (theme == null || theme.isEmpty() || mClient.getIncludedWithin() != null) { - mConfiguration.setTheme(null); - mConfiguration.computePreferredTheme(); - } - assert mConfiguration.getTheme() != null; - } - - mThemeList.clear(); - - ArrayList<String> themes = new ArrayList<String>(); - ResourceRepository projectRes = mClient.getProjectResources(); - // in cases where the opened file is not linked to a project, this could be null. - if (projectRes != null) { - // get the configured resources for the project - Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = - mClient.getConfiguredProjectResources(); - - if (configuredProjectRes != null) { - // get the styles. - Map<String, ResourceValue> styleMap = configuredProjectRes.get( - ResourceType.STYLE); - - if (styleMap != null) { - // collect the themes out of all the styles, ie styles that extend, - // directly or indirectly a platform theme. - for (ResourceValue value : styleMap.values()) { - if (isTheme(value, styleMap, null)) { - String theme = value.getName(); - themes.add(theme); - } - } - - Collections.sort(themes); - - for (String theme : themes) { - if (!theme.startsWith(PREFIX_RESOURCE_REF)) { - theme = STYLE_RESOURCE_PREFIX + theme; - } - mThemeList.add(theme); - } - } - } - themes.clear(); - } - - // get the themes, and languages from the Framework. - if (frameworkRes != null) { - // get the configured resources for the framework - Map<ResourceType, Map<String, ResourceValue>> frameworResources = - frameworkRes.getConfiguredResources(mConfiguration.getFullConfig()); - - if (frameworResources != null) { - // get the styles. - Map<String, ResourceValue> styles = frameworResources.get(ResourceType.STYLE); - - // collect the themes out of all the styles. - for (ResourceValue value : styles.values()) { - String name = value.getName(); - if (name.startsWith("Theme.") || name.equals("Theme")) { //$NON-NLS-1$ //$NON-NLS-2$ - themes.add(value.getName()); - } - } - - // sort them and add them to the combo - Collections.sort(themes); - - for (String theme : themes) { - if (!theme.startsWith(PREFIX_RESOURCE_REF)) { - theme = ANDROID_STYLE_RESOURCE_PREFIX + theme; - } - mThemeList.add(theme); - } - - themes.clear(); - } - } - - // Migration: In the past we didn't store the style prefix in the settings; - // this meant we might lose track of whether the theme is a project style - // or a framework style. For now we need to migrate. Search through the - // theme list until we have a match - String theme = mConfiguration.getTheme(); - if (theme != null && !theme.startsWith(PREFIX_RESOURCE_REF)) { - String projectStyle = STYLE_RESOURCE_PREFIX + theme; - String frameworkStyle = ANDROID_STYLE_RESOURCE_PREFIX + theme; - for (String t : mThemeList) { - if (t.equals(projectStyle)) { - mConfiguration.setTheme(projectStyle); - break; - } else if (t.equals(frameworkStyle)) { - mConfiguration.setTheme(frameworkStyle); - break; - } - } - if (!theme.startsWith(PREFIX_RESOURCE_REF)) { - // Arbitrary guess - if (theme.startsWith("Theme.")) { - theme = ANDROID_STYLE_RESOURCE_PREFIX + theme; - } else { - theme = STYLE_RESOURCE_PREFIX + theme; - } - } - } - - // TODO: Handle the case where you have a theme persisted that isn't available?? - // We could look up mConfiguration.theme and make sure it appears in the list! And if - // not, picking one. - selectTheme(mConfiguration.getTheme()); - } finally { - mDisableUpdates--; - } - } - - private void updateActivity() { - if (mEditedFile != null) { - String preferred = getPreferredActivity(mEditedFile); - selectActivity(preferred); - } - } - - /** - * Updates the locale combo. - * This must be called from the UI thread. - */ - public void updateLocales() { - if (mClient == null) { - return; // can't do anything w/o it. - } - - mDisableUpdates++; - - try { - mLocaleList.clear(); - - SortedSet<String> languages = null; - - // get the languages from the project. - ResourceRepository projectRes = mClient.getProjectResources(); - - // in cases where the opened file is not linked to a project, this could be null. - if (projectRes != null) { - // now get the languages from the project. - languages = projectRes.getLanguages(); - - for (String language : languages) { - // find the matching regions and add them - SortedSet<String> regions = projectRes.getRegions(language); - for (String region : regions) { - LocaleQualifier locale = LocaleQualifier.getQualifier(language + "-r" + region); - if (locale != null) { - mLocaleList.add(Locale.create(locale)); - } - } - - // now the entry for the other regions the language alone - // create a region qualifier that will never be matched by qualified resources. - LocaleQualifier locale = new LocaleQualifier(language); - mLocaleList.add(Locale.create(locale)); - } - } - - // create language/region qualifier that will never be matched by qualified resources. - mLocaleList.add(Locale.ANY); - - Locale locale = mConfiguration.getLocale(); - setLocale(locale); - } finally { - mDisableUpdates--; - } - } - - @Nullable - private String getPreferredActivity(@NonNull IFile file) { - // Store/restore the activity context in the config state to help with - // performance if for some reason we can't write it into the XML file and to - // avoid having to open the model below - if (mConfiguration.getActivity() != null) { - return mConfiguration.getActivity(); - } - - IProject project = file.getProject(); - - // Look up from XML file - Document document = DomUtilities.getDocument(file); - if (document != null) { - Element element = document.getDocumentElement(); - if (element != null) { - String activity = element.getAttributeNS(TOOLS_URI, ATTR_CONTEXT); - if (activity != null && !activity.isEmpty()) { - if (activity.startsWith(".") || activity.indexOf('.') == -1) { //$NON-NLS-1$ - ManifestInfo manifest = ManifestInfo.get(project); - String pkg = manifest.getPackage(); - if (!pkg.isEmpty()) { - if (activity.startsWith(".")) { //$NON-NLS-1$ - activity = pkg + activity; - } else { - activity = activity + '.' + pkg; - } - } - } - - mConfiguration.setActivity(activity); - saveConstraints(); - return activity; - } - } - } - - // No, not available there: try to infer it from the code index - String includedIn = null; - Reference includedWithin = mClient.getIncludedWithin(); - if (mClient != null && includedWithin != null) { - includedIn = includedWithin.getName(); - } - - ManifestInfo manifest = ManifestInfo.get(project); - String pkg = manifest.getPackage(); - String layoutName = ResourceHelper.getLayoutName(mEditedFile); - - // If we are rendering a layout in included context, pick the theme - // from the outer layout instead - if (includedIn != null) { - layoutName = includedIn; - } - - String activity = ManifestInfo.guessActivity(project, layoutName, pkg); - - if (activity == null) { - List<String> activities = ManifestInfo.getProjectActivities(project); - if (activities.size() == 1) { - activity = activities.get(0); - } - } - - if (activity != null) { - mConfiguration.setActivity(activity); - saveConstraints(); - return activity; - } - - // TODO: Do anything else, such as pick the first activity found? - // Or just leave some default label instead? - // Also, figure out what to store in the mState so I don't keep trying - - return null; - } - - /** - * Returns whether the given <var>style</var> is a theme. - * This is done by making sure the parent is a theme. - * @param value the style to check - * @param styleMap the map of styles for the current project. Key is the style name. - * @param seen the map of styles we have already processed (or null if not yet - * initialized). Only the keys are significant (since there is no IdentityHashSet). - * @return True if the given <var>style</var> is a theme. - */ - private static boolean isTheme(ResourceValue value, Map<String, ResourceValue> styleMap, - IdentityHashMap<ResourceValue, Boolean> seen) { - if (value instanceof StyleResourceValue) { - StyleResourceValue style = (StyleResourceValue)value; - - boolean frameworkStyle = false; - String parentStyle = style.getParentStyle(); - if (parentStyle == null) { - // if there is no specified parent style we look an implied one. - // For instance 'Theme.light' is implied child style of 'Theme', - // and 'Theme.light.fullscreen' is implied child style of 'Theme.light' - String name = style.getName(); - int index = name.lastIndexOf('.'); - if (index != -1) { - parentStyle = name.substring(0, index); - } - } else { - // remove the useless @ if it's there - if (parentStyle.startsWith("@")) { - parentStyle = parentStyle.substring(1); - } - - // check for framework identifier. - if (parentStyle.startsWith(ANDROID_NS_NAME_PREFIX)) { - frameworkStyle = true; - parentStyle = parentStyle.substring(ANDROID_NS_NAME_PREFIX.length()); - } - - // at this point we could have the format style/<name>. we want only the name - if (parentStyle.startsWith("style/")) { - parentStyle = parentStyle.substring("style/".length()); - } - } - - if (parentStyle != null) { - if (frameworkStyle) { - // if the parent is a framework style, it has to be 'Theme' or 'Theme.*' - return parentStyle.equals("Theme") || parentStyle.startsWith("Theme."); - } else { - // if it's a project style, we check this is a theme. - ResourceValue parentValue = styleMap.get(parentStyle); - - // also prevent stack overflow in case the dev mistakenly declared - // the parent of the style as the style itself. - if (parentValue != null && !parentValue.equals(value)) { - if (seen == null) { - seen = new IdentityHashMap<ResourceValue, Boolean>(); - seen.put(value, Boolean.TRUE); - } else if (seen.containsKey(parentValue)) { - return false; - } - seen.put(parentValue, Boolean.TRUE); - return isTheme(parentValue, styleMap, seen); - } - } - } - } - - return false; - } - - /** - * Returns true if this configuration chooser represents the best match for - * the given file - * - * @param file the file to test - * @param config the config to test - * @return true if the given config is the best match for the given file - */ - public boolean isBestMatchFor(IFile file, FolderConfiguration config) { - ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(), - ResourceType.LAYOUT, config); - if (match != null) { - return match.getFile().equals(mEditedFile); - } - - return false; - } -} |