aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java11
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java33
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java39
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java10
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java12
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java30
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorMatchingStrategy.java14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java162
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java891
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java1945
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java142
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java3418
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java797
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java157
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java126
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Locale.java169
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java115
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java80
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java163
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java56
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java133
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java663
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java13
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java11
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java22
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java34
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java104
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleTest.java56
36 files changed, 5413 insertions, 4084 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index 692adc7..a7ef6c6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -88,7 +88,6 @@ import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
-import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
@@ -1964,15 +1963,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
*/
public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab)
throws PartInitException {
- IWorkbench workbench = PlatformUI.getWorkbench();
- if (workbench == null) {
- return null;
- }
- IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
- if (activeWorkbenchWindow == null) {
- return null;
- }
- IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
+ IWorkbenchPage page = AdtUtils.getActiveWorkbenchPage();
if (page == null) {
return null;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index f5f7770..40d5e6f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -405,6 +405,29 @@ public class AdtUtils {
}
/**
+ * Returns the current active workbench page, or null if not found
+ *
+ * @return the current page, or null
+ */
+ @Nullable
+ public static IWorkbenchPage getActiveWorkbenchPage() {
+ IWorkbenchWindow window = getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page == null) {
+ IWorkbenchPage[] pages = window.getPages();
+ if (pages.length > 0) {
+ page = pages[0];
+ }
+ }
+
+ return page;
+ }
+
+ return null;
+ }
+
+ /**
* Returns the current active workbench part, or null if not found
*
* @return the current active workbench part, or null
@@ -1327,4 +1350,14 @@ public class AdtUtils {
return null;
}
+
+ /**
+ * Returns whether the current thread is the UI thread
+ *
+ * @return true if the current thread is the UI thread
+ */
+ public static boolean isUiThread() {
+ return AdtPlugin.getDisplay() != null
+ && AdtPlugin.getDisplay().getThread() == Thread.currentThread();
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java
index 421ffb0..55d463b 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java
@@ -16,9 +16,12 @@
package com.android.ide.eclipse.adt.internal.editors.common;
+import static com.android.SdkConstants.FD_RES_LAYOUT;
+
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorMatchingStrategy;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.resources.ResourceFolderType;
@@ -41,27 +44,29 @@ public class CommonMatchingStrategy implements IEditorMatchingStrategy {
FileEditorInput fileInput = (FileEditorInput)input;
// get the IFile object and check it's in one of the layout folders.
- IFile iFile = fileInput.getFile();
- ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(iFile);
-
- if (resFolder != null && resFolder.getType() == ResourceFolderType.LAYOUT) {
+ IFile file = fileInput.getFile();
+ if (file.getParent().getName().startsWith(FD_RES_LAYOUT)) {
+ ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
+ if (resFolder != null && resFolder.getType() == ResourceFolderType.LAYOUT
+ && AdtPrefs.getPrefs().isSharedLayoutEditor()) {
+ LayoutEditorMatchingStrategy m = new LayoutEditorMatchingStrategy();
+ return m.matches(editorRef, fileInput);
+ }
+ }
- LayoutEditorMatchingStrategy m = new LayoutEditorMatchingStrategy();
- return m.matches(editorRef, fileInput);
- } else {
- // Per the IEditorMatchingStrategy documentation, editorRef.getEditorInput()
- // is expensive so try exclude files that definitely don't match, such
- // as those with the wrong extension or wrong file name
- if (iFile.getName().equals(editorRef.getName()) &&
- editorRef.getId().equals(CommonXmlEditor.ID)) {
- try {
- return input.equals(editorRef.getEditorInput());
- } catch (PartInitException e) {
- AdtPlugin.log(e, null);
- }
+ // Per the IEditorMatchingStrategy documentation, editorRef.getEditorInput()
+ // is expensive so try exclude files that definitely don't match, such
+ // as those with the wrong extension or wrong file name
+ if (file.getName().equals(editorRef.getName()) &&
+ editorRef.getId().equals(CommonXmlEditor.ID)) {
+ try {
+ return input.equals(editorRef.getEditorInput());
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, null);
}
}
}
+
return false;
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java
index 6edb68c..b6cbf2c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlDelegate.java
@@ -225,4 +225,14 @@ public abstract class CommonXmlDelegate {
/** Called after an editor has been deactivated */
public void delegateDeactivated() {
}
+
+ /**
+ * Returns the name of the editor to be shown in the editor tab etc. Return
+ * null to keep the default.
+ *
+ * @return the part name, or null to use the default
+ */
+ public String delegateGetPartName() {
+ return null;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java
index e3b5721..7fb820c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java
@@ -417,6 +417,18 @@ public class CommonXmlEditor extends AndroidXmlEditor implements IShowEditorInpu
}
}
+ @Override
+ public String getPartName() {
+ if (mDelegate != null) {
+ String name = mDelegate.delegateGetPartName();
+ if (name != null) {
+ return name;
+ }
+ }
+
+ return super.getPartName();
+ }
+
// --------------------
// Base methods exposed so that XmlEditorDelegate can access them
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index 6e151cc..e10c33b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -49,6 +49,7 @@ import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
import com.android.tools.lint.client.api.IssueRegistry;
+import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
@@ -82,6 +83,7 @@ import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
+import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -95,6 +97,9 @@ import java.util.Set;
public class LayoutEditorDelegate extends CommonXmlDelegate
implements IShowEditorInput, CommonXmlDelegate.IActionContributorDelegate {
+ /** The prefix for layout folders that are not the default layout folder */
+ private static final String LAYOUT_FOLDER_PREFIX = "layout-"; //$NON-NLS-1$
+
public static class Creator implements IDelegateCreator {
@Override
@SuppressWarnings("unchecked")
@@ -313,8 +318,10 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
/**
* Called to replace the current {@link IEditorInput} with another one.
- * <p/>This is used when {@link LayoutEditorMatchingStrategy} returned <code>true</code> which means we're
- * opening a different configuration of the same layout.
+ * <p/>
+ * This is used when {@link LayoutEditorMatchingStrategy} returned
+ * <code>true</code> which means we're opening a different configuration of
+ * the same layout.
*/
@Override
public void showEditorInput(IEditorInput editorInput) {
@@ -737,6 +744,25 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
}
}
+ @Override
+ public String delegateGetPartName() {
+ IEditorInput editorInput = getEditor().getEditorInput();
+ if (editorInput instanceof IFileEditorInput) {
+ IFileEditorInput fileInput = (IFileEditorInput) editorInput;
+ IFile file = fileInput.getFile();
+ IContainer parent = file.getParent();
+ if (parent != null) {
+ String parentName = parent.getName();
+ if (parentName.startsWith(LAYOUT_FOLDER_PREFIX)) {
+ parentName = parentName.substring(LAYOUT_FOLDER_PREFIX.length());
+ return parentName + File.separatorChar + file.getName();
+ }
+ }
+ }
+
+ return super.delegateGetPartName();
+ }
+
// ---- Local Methods ----
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorMatchingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorMatchingStrategy.java
index 4ea49f9..c1c6068 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorMatchingStrategy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorMatchingStrategy.java
@@ -41,14 +41,14 @@ public class LayoutEditorMatchingStrategy implements IEditorMatchingStrategy {
FileEditorInput fileInput = (FileEditorInput)input;
// get the IFile object and check it's in one of the layout folders.
- IFile iFile = fileInput.getFile();
+ IFile file = fileInput.getFile();
ResourceManager manager = ResourceManager.getInstance();
- ResourceFolder resFolder = manager.getResourceFolder(iFile);
+ ResourceFolder resFolder = manager.getResourceFolder(file);
// Per the IEditorMatchingStrategy documentation, editorRef.getEditorInput()
// is expensive so try exclude files that definitely don't match, such
// as those with the wrong extension or wrong file name
- if (!iFile.getName().equals(editorRef.getName()) ||
+ if (!file.getName().equals(editorRef.getName()) ||
!editorRef.getId().equals(CommonXmlEditor.ID)) {
return false;
}
@@ -60,16 +60,16 @@ public class LayoutEditorMatchingStrategy implements IEditorMatchingStrategy {
IEditorInput editorInput = editorRef.getEditorInput();
if (editorInput instanceof FileEditorInput) {
FileEditorInput editorFileInput = (FileEditorInput)editorInput;
- IFile editorIFile = editorFileInput.getFile();
+ IFile editorFile = editorFileInput.getFile();
- ResourceFolder editorFolder = manager.getResourceFolder(editorIFile);
+ ResourceFolder editorFolder = manager.getResourceFolder(editorFile);
if (editorFolder == null
|| editorFolder.getType() != ResourceFolderType.LAYOUT) {
return false;
}
- return editorIFile.getProject().equals(iFile.getProject())
- && editorIFile.getName().equals(iFile.getName());
+ return editorFile.getProject().equals(file.getProject())
+ && editorFile.getName().equals(file.getName());
}
} catch (PartInitException e) {
// we do nothing, we'll just return false.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
new file mode 100644
index 0000000..1f85a32
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
@@ -0,0 +1,162 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jdt.ui.ISharedImages;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The {@linkplain ActivityMenuListener} class is responsible for
+ * generating the activity menu in the {@link ConfigurationChooser}.
+ */
+class ActivityMenuListener extends SelectionAdapter {
+ private static final int ACTION_OPEN_ACTIVITY = 1;
+ private static final int ACTION_SELECT_ACTIVITY = 2;
+
+ private final ConfigurationChooser mConfigChooser;
+ private final int mAction;
+ private final String mFqcn;
+
+ ActivityMenuListener(
+ @NonNull ConfigurationChooser configChooser,
+ int action,
+ @Nullable String fqcn) {
+ mConfigChooser = configChooser;
+ mAction = action;
+ mFqcn = fqcn;
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ switch (mAction) {
+ case ACTION_OPEN_ACTIVITY: {
+ Configuration configuration = mConfigChooser.getConfiguration();
+ String fqcn = configuration.getActivity();
+ AdtPlugin.openJavaClass(mConfigChooser.getProject(), fqcn);
+ break;
+ }
+ case ACTION_SELECT_ACTIVITY: {
+ mConfigChooser.selectActivity(mFqcn);
+ mConfigChooser.onSelectActivity();
+ break;
+ }
+ default: assert false : mAction;
+ }
+ }
+
+ static void show(ConfigurationChooser chooser, ToolItem combo) {
+ // TODO: Allow using fragments here as well?
+ Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+ ISharedImages sharedImages = JavaUI.getSharedImages();
+ Configuration configuration = chooser.getConfiguration();
+ String current = configuration.getActivity();
+
+ if (current != null) {
+ MenuItem item = new MenuItem(menu, SWT.PUSH);
+ String label = ConfigurationChooser.getActivityLabel(current, true);;
+ item.setText( String.format("Open %1$s...", label));
+ Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT);
+ item.setImage(image);
+ item.addSelectionListener(
+ new ActivityMenuListener(chooser, ACTION_OPEN_ACTIVITY, null));
+
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ IProject project = chooser.getProject();
+ Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CLASS);
+
+ // Add activities found to be relevant to this layout
+ String layoutName = ResourceHelper.getLayoutName(chooser.getEditedFile());
+ String pkg = ManifestInfo.get(project).getPackage();
+ List<String> preferred = ManifestInfo.guessActivities(project, layoutName, pkg);
+ current = addActivities(chooser, menu, current, image, preferred);
+
+ // Add all activities
+ List<String> activities = ManifestInfo.getProjectActivities(project);
+ if (preferred.size() > 0) {
+ // Filter out the activities we've already listed above
+ List<String> filtered = new ArrayList<String>(activities.size());
+ Set<String> remove = new HashSet<String>(preferred);
+ for (String fqcn : activities) {
+ if (!remove.contains(fqcn)) {
+ filtered.add(fqcn);
+ }
+ }
+ activities = filtered;
+ }
+
+ if (activities.size() > 0) {
+ if (preferred.size() > 0) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ addActivities(chooser, menu, current, image, activities);
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+
+ private static String addActivities(ConfigurationChooser chooser, Menu menu, String current,
+ Image image, List<String> activities) {
+ for (final String fqcn : activities) {
+ String title = ConfigurationChooser.getActivityLabel(fqcn, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+ item.setImage(image);
+
+ boolean selected = title.equals(current);
+ if (selected) {
+ item.setSelection(true);
+ current = null; // Only show the first occurrence as selected
+ // such that we don't show it selected again in the full activity list
+ }
+
+ item.addSelectionListener(new ActivityMenuListener(chooser,
+ ACTION_OPEN_ACTIVITY, fqcn));
+ }
+
+ return current;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
new file mode 100644
index 0000000..2106f8d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
@@ -0,0 +1,891 @@
+/*
+ * 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_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.ide.common.resources.configuration.DeviceConfigHelper;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.NightModeQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
+import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.common.resources.configuration.UiModeQualifier;
+import com.android.ide.common.resources.configuration.VersionQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.resources.Density;
+import com.android.resources.NightMode;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.UiMode;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+
+import java.util.List;
+
+/**
+ * A {@linkplain Configuration} is a selection of device, orientation, theme,
+ * etc for use when rendering a layout.
+ */
+public class Configuration {
+ /**
+ * Setting name for project-wide setting controlling rendering target and locale which
+ * is shared for all files
+ */
+ public final static QualifiedName NAME_RENDER_STATE =
+ new QualifiedName(AdtPlugin.PLUGIN_ID, "render"); //$NON-NLS-1$
+
+ private final static String MARKER_FRAMEWORK = "-"; //$NON-NLS-1$
+ private final static String MARKER_PROJECT = "+"; //$NON-NLS-1$
+ private final static String SEP = ":"; //$NON-NLS-1$
+ private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
+
+ @NonNull
+ protected final ConfigurationChooser mConfigChooser;
+
+ /** The {@link FolderConfiguration} representing the state of the UI controls */
+ @NonNull
+ protected final FolderConfiguration mFullConfig = new FolderConfiguration();
+
+ /** The {@link FolderConfiguration} being edited. */
+ @Nullable
+ protected FolderConfiguration mEditedConfig;
+
+ /** The target of the project of the file being edited. */
+ @Nullable
+ private IAndroidTarget mTarget;
+
+ /** The theme style to render with */
+ @Nullable
+ private String mTheme;
+
+ /** The device to render with */
+ @Nullable
+ private Device mDevice;
+
+ /** The device state */
+ @Nullable
+ private State mState;
+
+ /**
+ * The activity associated with the layout. This is just a cached value of
+ * the true value stored on the layout.
+ */
+ @Nullable
+ private String mActivity;
+
+ /** The locale to use for this configuration */
+ @NonNull
+ private Locale mLocale = Locale.ANY;
+
+ /** UI mode */
+ @NonNull
+ private UiMode mUiMode = UiMode.NORMAL;
+
+ /** Night mode */
+ @NonNull
+ private NightMode mNightMode = NightMode.NOTNIGHT;
+
+ /**
+ * Creates a new {@linkplain Configuration}
+ *
+ * @param chooser the associated chooser
+ */
+ protected Configuration(@NonNull ConfigurationChooser chooser) {
+ mConfigChooser = chooser;
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration}
+ *
+ * @param chooser the associated chooser
+ * @return a new configuration
+ */
+ @NonNull
+ public static Configuration create(@NonNull ConfigurationChooser chooser) {
+ return new Configuration(chooser);
+ }
+
+ /**
+ * Returns the associated activity
+ *
+ * @return the activity
+ */
+ @Nullable
+ public String getActivity() {
+ return mActivity;
+ }
+
+ /**
+ * Returns the chosen device.
+ *
+ * @return the chosen device
+ */
+ @Nullable
+ public Device getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Returns the chosen device state
+ *
+ * @return the device state
+ */
+ @Nullable
+ public State getDeviceState() {
+ return mState;
+ }
+
+ /**
+ * Returns the chosen locale
+ *
+ * @return the locale
+ */
+ @NonNull
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * Returns the UI mode
+ *
+ * @return the UI mode
+ */
+ @NonNull
+ public UiMode getUiMode() {
+ return mUiMode;
+ }
+
+ /**
+ * Returns the day/night mode
+ *
+ * @return the night mode
+ */
+ @NonNull
+ public NightMode getNightMode() {
+ return mNightMode;
+ }
+
+ /**
+ * Returns the current theme style
+ *
+ * @return the theme style
+ */
+ @Nullable
+ public String getTheme() {
+ return mTheme;
+ }
+
+ /**
+ * Returns the rendering target
+ *
+ * @return the target
+ */
+ @Nullable
+ public IAndroidTarget getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Returns whether the configuration's theme is a project theme.
+ * <p/>
+ * The returned value is meaningless if {@link #getTheme()} returns
+ * <code>null</code>.
+ *
+ * @return true for project a theme, false for a framework theme
+ */
+ public boolean isProjectTheme() {
+ String theme = getTheme();
+ if (theme != null) {
+ assert theme.startsWith(STYLE_RESOURCE_PREFIX)
+ || theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
+
+ return ResourceHelper.isProjectStyle(theme);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the current layout is locale-specific
+ *
+ * @return if this configuration represents a locale-specific layout
+ */
+ public boolean isLocaleSpecificLayout() {
+ return mEditedConfig == null || mEditedConfig.getLanguageQualifier() != null;
+ }
+
+ /**
+ * Returns the full, complete {@link FolderConfiguration}
+ *
+ * @return the full configuration
+ */
+ @NonNull
+ public FolderConfiguration getFullConfig() {
+ return mFullConfig;
+ }
+
+ /**
+ * Copies the full, complete {@link FolderConfiguration} into the given
+ * folder config instance.
+ *
+ * @param dest the {@link FolderConfiguration} instance to copy into
+ */
+ public void copyFullConfig(FolderConfiguration dest) {
+ dest.set(mFullConfig);
+ }
+
+ /**
+ * Returns the edited {@link FolderConfiguration} (this is not a full
+ * configuration, so you can think of it as the "constraints" used by the
+ * {@link ConfigurationMatcher} to produce a full configuration.
+ *
+ * @return the constraints configuration
+ */
+ @NonNull
+ public FolderConfiguration getEditedConfig() {
+ return mEditedConfig;
+ }
+
+ /**
+ * Sets the edited {@link FolderConfiguration} (this is not a full
+ * configuration, so you can think of it as the "constraints" used by the
+ * {@link ConfigurationMatcher} to produce a full configuration.
+ *
+ * @param editedConfig the constraints configuration
+ */
+ public void setEditedConfig(@NonNull FolderConfiguration editedConfig) {
+ mEditedConfig = editedConfig;
+ }
+
+ /**
+ * Sets the associated activity
+ *
+ * @param activity the activity
+ */
+ public void setActivity(String activity) {
+ mActivity = activity;
+ }
+
+ /**
+ * Sets the device
+ *
+ * @param device the device
+ * @param skipSync if true, don't sync folder configuration (typically because
+ * you are going to set other configuration parameters and you'll call
+ * {@link #syncFolderConfig()} once at the end)
+ */
+ public void setDevice(Device device, boolean skipSync) {
+ mDevice = device;
+
+ if (!skipSync) {
+ syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the device state
+ *
+ * @param state the device state
+ * @param skipSync if true, don't sync folder configuration (typically because
+ * you are going to set other configuration parameters and you'll call
+ * {@link #syncFolderConfig()} once at the end)
+ */
+ public void setDeviceState(State state, boolean skipSync) {
+ mState = state;
+
+ if (!skipSync) {
+ syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the locale
+ *
+ * @param locale the locale
+ * @param skipSync if true, don't sync folder configuration (typically because
+ * you are going to set other configuration parameters and you'll call
+ * {@link #syncFolderConfig()} once at the end)
+ */
+ public void setLocale(@NonNull Locale locale, boolean skipSync) {
+ mLocale = locale;
+
+ if (!skipSync) {
+ syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the rendering target
+ *
+ * @param target rendering target
+ * @param skipSync if true, don't sync folder configuration (typically because
+ * you are going to set other configuration parameters and you'll call
+ * {@link #syncFolderConfig()} once at the end)
+ */
+ public void setTarget(IAndroidTarget target, boolean skipSync) {
+ mTarget = target;
+
+ if (!skipSync) {
+ syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the night mode
+ *
+ * @param night the night mode
+ * @param skipSync if true, don't sync folder configuration (typically because
+ * you are going to set other configuration parameters and you'll call
+ * {@link #syncFolderConfig()} once at the end)
+ */
+ public void setNightMode(@NonNull NightMode night, boolean skipSync) {
+ mNightMode = night;
+
+ if (!skipSync) {
+ syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the UI mode
+ *
+ * @param uiMode the UI mode
+ * @param skipSync if true, don't sync folder configuration (typically because
+ * you are going to set other configuration parameters and you'll call
+ * {@link #syncFolderConfig()} once at the end)
+ */
+ public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) {
+ mUiMode = uiMode;
+
+ if (!skipSync) {
+ syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the theme style
+ *
+ * @param theme the theme
+ */
+ public void setTheme(String theme) {
+ mTheme = theme;
+ }
+
+ /**
+ * Updates the folder configuration such that it reflects changes in
+ * configuration state such as the device orientation, the UI mode, the
+ * rendering target, etc.
+ */
+ public void syncFolderConfig() {
+ Device device = getDevice();
+ if (device == null) {
+ return;
+ }
+
+ // get the device config from the device/state combos.
+ FolderConfiguration config = DeviceConfigHelper.getFolderConfig(getDeviceState());
+
+ // replace the config with the one from the device
+ mFullConfig.set(config);
+
+ // sync the selected locale
+ Locale locale = getLocale();
+ mFullConfig.setLanguageQualifier(locale.language);
+ mFullConfig.setRegionQualifier(locale.region);
+
+ // Replace the UiMode with the selected one, if one is selected
+ UiMode uiMode = getUiMode();
+ if (uiMode != null) {
+ mFullConfig.setUiModeQualifier(new UiModeQualifier(uiMode));
+ }
+
+ // Replace the NightMode with the selected one, if one is selected
+ NightMode nightMode = getNightMode();
+ if (nightMode != null) {
+ mFullConfig.setNightModeQualifier(new NightModeQualifier(nightMode));
+ }
+
+ // replace the API level by the selection of the combo
+ IAndroidTarget target = getTarget();
+ if (target == null && mConfigChooser != null) {
+ target = mConfigChooser.getProjectTarget();
+ }
+ if (target != null) {
+ int apiLevel = target.getVersion().getApiLevel();
+ mFullConfig.setVersionQualifier(new VersionQualifier(apiLevel));
+ }
+ }
+
+ /**
+ * Creates a string suitable for persistence, which can be initialized back
+ * to a configuration via {@link #initialize(String)}
+ *
+ * @return a persistent string
+ */
+ @NonNull
+ public String toPersistentString() {
+ StringBuilder sb = new StringBuilder(32);
+ Device device = getDevice();
+ if (device != null) {
+ sb.append(device.getName());
+ sb.append(SEP);
+ State state = getDeviceState();
+ if (state != null) {
+ sb.append(state.getName());
+ }
+ sb.append(SEP);
+ Locale locale = getLocale();
+ if (isLocaleSpecificLayout() && locale != null) {
+ // locale[0]/[1] can be null sometimes when starting Eclipse
+ sb.append(locale.language.getValue());
+ sb.append(SEP_LOCALE);
+ sb.append(locale.region.getValue());
+ }
+ sb.append(SEP);
+ // Need to escape the theme: if we write the full theme style, then
+ // we can end up with ":"'s in the string (as in @android:style/Theme) which
+ // can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}.
+ String theme = getTheme();
+ if (theme != null) {
+ String themeName = ResourceHelper.styleToTheme(theme);
+ if (theme.startsWith(STYLE_RESOURCE_PREFIX)) {
+ sb.append(MARKER_PROJECT);
+ } else if (theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
+ sb.append(MARKER_FRAMEWORK);
+ }
+ sb.append(themeName);
+ }
+ sb.append(SEP);
+ UiMode uiMode = getUiMode();
+ if (uiMode != null) {
+ sb.append(uiMode.getResourceValue());
+ }
+ sb.append(SEP);
+ NightMode nightMode = getNightMode();
+ if (nightMode != null) {
+ sb.append(nightMode.getResourceValue());
+ }
+ sb.append(SEP);
+
+ // We used to store the render target here in R9. Leave a marker
+ // to ensure that we don't reuse this slot; add new extra fields after it.
+ sb.append(SEP);
+ String activity = getActivity();
+ if (activity != null) {
+ sb.append(activity);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Initializes a string previously created with
+ * {@link #toPersistentString()}
+ *
+ * @param data the string to initialize back from
+ * @return true if the configuration was initialized
+ */
+ boolean initialize(String data) {
+ String[] values = data.split(SEP);
+ if (values.length >= 6 && values.length <= 8) {
+ for (Device d : mConfigChooser.getDeviceList()) {
+ if (d.getName().equals(values[0])) {
+ mDevice = d;
+ String stateName = null;
+ FolderConfiguration config = null;
+ if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$
+ stateName = values[1];
+ config = DeviceConfigHelper.getFolderConfig(mDevice, stateName);
+ } else if (mDevice.getAllStates().size() > 0) {
+ State first = mDevice.getAllStates().get(0);
+ stateName = first.getName();
+ config = DeviceConfigHelper.getFolderConfig(first);
+ }
+ mState = getState(mDevice, stateName);
+ if (config != null) {
+ // Load locale. Note that this can get overwritten by the
+ // project-wide settings read below.
+ LanguageQualifier language = Locale.ANY_LANGUAGE;
+ RegionQualifier region = Locale.ANY_REGION;
+ String locales[] = values[2].split(SEP_LOCALE);
+ if (locales.length >= 2) {
+ if (locales[0].length() > 0) {
+ language = new LanguageQualifier(locales[0]);
+ }
+ if (locales[1].length() > 0) {
+ region = new RegionQualifier(locales[1]);
+ }
+ mLocale = Locale.create(language, region);
+ }
+
+ // Decode the theme name: See {@link #getData}
+ mTheme = values[3];
+ if (mTheme.startsWith(MARKER_FRAMEWORK)) {
+ mTheme = ANDROID_STYLE_RESOURCE_PREFIX
+ + mTheme.substring(MARKER_FRAMEWORK.length());
+ } else if (mTheme.startsWith(MARKER_PROJECT)) {
+ mTheme = STYLE_RESOURCE_PREFIX
+ + mTheme.substring(MARKER_PROJECT.length());
+ }
+
+ mUiMode = UiMode.getEnum(values[4]);
+ if (mUiMode == null) {
+ mUiMode = UiMode.NORMAL;
+ }
+ mNightMode = NightMode.getEnum(values[5]);
+ if (mNightMode == null) {
+ mNightMode = NightMode.NOTNIGHT;
+ }
+
+ // element 7/values[6]: used to store render target in R9.
+ // No longer stored here. If adding more data, make
+ // sure you leave 7 alone.
+
+ Pair<Locale, IAndroidTarget> pair = loadRenderState(mConfigChooser);
+ if (pair != null) {
+ // We only use the "global" setting
+ if (!isLocaleSpecificLayout()) {
+ mLocale = pair.getFirst();
+ }
+ mTarget = pair.getSecond();
+ }
+
+ if (values.length == 8) {
+ mActivity = values[7];
+ }
+
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Loads the render state (the locale and the render target, which are shared among
+ * all the layouts meaning that changing it in one will change it in all) and returns
+ * the current project-wide locale and render target to be used.
+ *
+ * @param chooser the {@link ConfigurationChooser} providing information about
+ * loaded targets
+ * @return a pair of a locale and a render target
+ */
+ @Nullable
+ static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) {
+ IProject project = chooser.getProject();
+ if (!project.isAccessible()) {
+ return null;
+ }
+
+ try {
+ String data = project.getPersistentProperty(NAME_RENDER_STATE);
+ if (data != null) {
+ Locale locale = Locale.ANY;
+ IAndroidTarget target = null;
+
+ String[] values = data.split(SEP);
+ if (values.length == 2) {
+ LanguageQualifier language = Locale.ANY_LANGUAGE;
+ RegionQualifier region = Locale.ANY_REGION;
+ String locales[] = values[0].split(SEP_LOCALE);
+ if (locales.length >= 2) {
+ if (locales[0].length() > 0) {
+ language = new LanguageQualifier(locales[0]);
+ }
+ if (locales[1].length() > 0) {
+ region = new RegionQualifier(locales[1]);
+ }
+ }
+ locale = Locale.create(language, region);
+
+ target = stringToTarget(chooser, values[1]);
+
+ // See if we should "correct" the rendering target to a better version.
+ // If you're using a pre-release version of the render target, and a
+ // final release is available and installed, we should switch to that
+ // one instead.
+ if (target != null) {
+ AndroidVersion version = target.getVersion();
+ List<IAndroidTarget> targetList = chooser.getTargetList();
+ if (version.getCodename() != null && targetList != null) {
+ int targetApiLevel = version.getApiLevel() + 1;
+ for (IAndroidTarget t : targetList) {
+ if (t.getVersion().getApiLevel() == targetApiLevel
+ && t.isPlatform()) {
+ target = t;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return Pair.of(locale, target);
+ }
+
+ return Pair.of(Locale.ANY, ConfigurationMatcher.findDefaultRenderTarget(project));
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ /**
+ * Saves the render state (the current locale and render target settings) into the
+ * project wide settings storage
+ */
+ void saveRenderState() {
+ IProject project = mConfigChooser.getProject();
+ try {
+ // Generate a persistent string from locale+target
+ StringBuilder sb = new StringBuilder();
+ Locale locale = getLocale();
+ if (locale != null) {
+ // locale[0]/[1] can be null sometimes when starting Eclipse
+ sb.append(locale.language.getValue());
+ sb.append(SEP_LOCALE);
+ sb.append(locale.region.getValue());
+ }
+ sb.append(SEP);
+ IAndroidTarget target = getTarget();
+ if (target != null) {
+ sb.append(targetToString(target));
+ sb.append(SEP);
+ }
+
+ project.setPersistentProperty(NAME_RENDER_STATE, sb.toString());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Returns a String id to represent an {@link IAndroidTarget} which can be translated
+ * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id
+ * will never contain the {@link #SEP} character.
+ *
+ * @param target the target to return an id for
+ * @return an id for the given target; never null
+ */
+ @NonNull
+ private static String targetToString(@NonNull IAndroidTarget target) {
+ return target.getFullName().replace(SEP, ""); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns an {@link IAndroidTarget} that corresponds to the given id that was
+ * originally returned by {@link #targetToString}. May be null, if the platform is no
+ * longer available, or if the platform list has not yet been initialized.
+ *
+ * @param chooser the {@link ConfigurationChooser} providing information about
+ * loaded targets
+ * @param id the id that corresponds to the desired platform
+ * @return an {@link IAndroidTarget} that matches the given id, or null
+ */
+ @Nullable
+ private static IAndroidTarget stringToTarget(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull String id) {
+ List<IAndroidTarget> targetList = chooser.getTargetList();
+ if (targetList != null && targetList.size() > 0) {
+ for (IAndroidTarget target : targetList) {
+ if (id.equals(targetToString(target))) {
+ return target;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link State} by the given name for the given {@link Device}
+ *
+ * @param device the device
+ * @param name the name of the state
+ */
+ @Nullable
+ static State getState(@Nullable Device device, @Nullable String name) {
+ if (device == null) {
+ return null;
+ } else if (name != null) {
+ State state = device.getState(name);
+ if (state != null) {
+ return state;
+ }
+ }
+
+ return device.getDefaultState();
+ }
+
+ /**
+ * Returns the currently selected {@link Density}. This is guaranteed to be non null.
+ *
+ * @return the density
+ */
+ @NonNull
+ public Density getDensity() {
+ if (mFullConfig != null) {
+ DensityQualifier qual = mFullConfig.getDensityQualifier();
+ 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.
+ *
+ * @return the x dpi as a float
+ */
+ public float getXDpi() {
+ Device device = getDevice();
+ if (device != null) {
+ State currState = getDeviceState();
+ if (currState == null) {
+ currState = device.getDefaultState();
+ }
+ float dpi = (float) currState.getHardware().getScreen().getXdpi();
+ if (!Float.isNaN(dpi)) {
+ return dpi;
+ }
+ }
+
+ // get the pixel density as the density.
+ return getDensity().getDpiValue();
+ }
+
+ /**
+ * Returns the current device ydpi.
+ *
+ * @return the y dpi as a float
+ */
+ public float getYDpi() {
+ Device device = getDevice();
+ if (device != null) {
+ State currState = getDeviceState();
+ if (currState == null) {
+ currState = device.getDefaultState();
+ }
+ float dpi = (float) currState.getHardware().getScreen().getYdpi();
+ if (!Float.isNaN(dpi)) {
+ return dpi;
+ }
+ }
+
+ // get the pixel density as the density.
+ return getDensity().getDpiValue();
+ }
+
+ /**
+ * Returns the bounds of the screen
+ *
+ * @return the screen bounds
+ */
+ public Rect getScreenBounds() {
+ return getScreenBounds(mFullConfig);
+ }
+
+ /**
+ * Gets the orientation from the given configuration
+ *
+ * @param config the configuration to look up
+ * @return the bounds
+ */
+ @NonNull
+ public static Rect getScreenBounds(FolderConfiguration config) {
+ // get the orientation from the given device config
+ ScreenOrientationQualifier qual = config.getScreenOrientationQualifier();
+ ScreenOrientation orientation = ScreenOrientation.PORTRAIT;
+ if (qual != null) {
+ orientation = qual.getValue();
+ }
+
+ // get the device screen dimension
+ ScreenDimensionQualifier qual2 = config.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 Rect(0, 0, s2, s1);
+ case LANDSCAPE:
+ return new Rect(0, 0, s1, s2);
+ case SQUARE:
+ return new Rect(0, 0, s1, s1);
+ }
+ }
+
+ /**
+ * Get the next cyclical state after the given state
+ *
+ * @param from the state to start with
+ * @return the following state following
+ */
+ @Nullable
+ public State getNextDeviceState(@Nullable State from) {
+ Device device = getDevice();
+ if (device == null) {
+ return null;
+ }
+ List<State> states = device.getAllStates();
+ for (int i = 0; i < states.size(); i++) {
+ if (states.get(i) == from) {
+ return states.get((i + 1) % states.size());
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return toPersistentString();
+ }
+}
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
new file mode 100644
index 0000000..b512bcc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
@@ -0,0 +1,1945 @@
+/*
+ * 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.FD_RES_LAYOUT;
+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.ConfigurationClient.CHANGED_ALL;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_DEVICE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_DEVICE_CONFIG;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_FOLDER;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_LOCALE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_RENDER_TARGET;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_THEME;
+
+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.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.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.ide.common.resources.configuration.ResourceQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.common.sdk.LoadStatus;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+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.resources.ScreenSize;
+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.DevicesChangeListener;
+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.core.runtime.QualifiedName;
+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.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+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 DevicesChangeListener, DisposeListener {
+ /**
+ * Settings name for file-specific configuration preferences, such as which theme or
+ * device to render the current layout with
+ */
+ public final static QualifiedName NAME_CONFIG_STATE =
+ new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
+
+ 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 List<Device> mDeviceList = 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 | SWT.BOLD);
+ mConfigCombo.setImage(null);
+ 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(LocaleManager.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);
+ }
+
+ IFile getEditedFile() {
+ return mEditedFile;
+ }
+
+ IProject getProject() {
+ return mEditedFile.getProject();
+ }
+
+ ConfigurationClient getClient() {
+ return mClient;
+ }
+
+ 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
+ */
+ 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 List<Device> getDeviceList() {
+ return mDeviceList;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ mEditedFile = 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();
+ }
+ }
+ } 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) {
+ mEditedFile = 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 {
+ mConfiguration.setEditedConfig(FolderConfiguration.getConfig(
+ parent.getName().split(RES_QUALIFIER_SEP)));
+ }
+
+ 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(CHANGED_ALL);
+ }
+
+ Configuration oldConfiguration = mConfiguration;
+ mConfiguration = configuration;
+
+ if (mClient != null) {
+ mClient.changed(CHANGED_ALL);
+ }
+
+ 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) {
+ boolean accepted = mClient.changed(CHANGED_ALL);
+ if (!accepted) {
+ configuration = oldConfiguration;
+ selectTheme(configuration.getTheme());
+ selectLocale(configuration.getLocale());
+ selectDevice(configuration.getDevice());
+ selectDeviceState(configuration.getDeviceState());
+ selectTarget(configuration.getTarget());
+ selectActivity(configuration.getActivity());
+ return;
+ }
+ }
+
+ 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 {
+ // this is going to be followed by a call to onTargetLoaded.
+ // So we can only care about the layout devices in this case.
+ initDevices();
+ initTargets();
+ } 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) {
+ initDevices();
+ initTargets();
+ 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);
+ initTargets();
+ }
+
+ 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 {
+ mConfiguration.setEditedConfig(FolderConfiguration.getConfig(
+ parent.getName().split(RES_QUALIFIER_SEP)));
+ }
+ }
+
+ targetData = Sdk.getCurrent().getTargetData(mProjectTarget);
+
+ // get the file stored state
+ boolean loadedConfigData = false;
+ String data = AdtPlugin.getFileProperty(mEditedFile, NAME_CONFIG_STATE);
+ if (mInitialState != null) {
+ data = mInitialState;
+ mInitialState = null;
+ }
+
+ if (data != null) {
+ loadedConfigData = mConfiguration.initialize(data);
+ }
+
+ // 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
+ IProject p = mEditedFile.getProject();
+ IAndroidTarget target = ConfigurationMatcher.findDefaultRenderTarget(p);
+ if (target != null) {
+ targetData = Sdk.getCurrent().getTargetData(target);
+ selectTarget(target);
+ }
+ }
+
+ // 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();
+ }
+ } finally {
+ mDisableUpdates--;
+ }
+ }
+
+ return targetData;
+ }
+
+ /**
+ * 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(CHANGED_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) {
+ mDeviceList = sdk.getDevices();
+ DeviceManager manager = sdk.getDeviceManager();
+ // This method can be called more than once, so avoid duplicate entries
+ manager.unregisterListener(this);
+ manager.registerListener(this);
+ } else {
+ mDeviceList = new ArrayList<Device>();
+ }
+
+ // fill with the devices
+ if (!mDeviceList.isEmpty()) {
+ Device first = mDeviceList.get(0);
+ selectDevice(first);
+ List<State> states = first.getAllStates();
+ selectDeviceState(states.get(0));
+ } else {
+ selectDevice(null);
+ }
+ }
+
+ /**
+ * Loads the list of {@link IAndroidTarget} and inits the UI with it.
+ */
+ private void initTargets() {
+ mTargetList.clear();
+
+ IAndroidTarget renderingTarget = mConfiguration.getTarget();
+
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null) {
+ IAndroidTarget[] targets = currentSdk.getTargets();
+ IAndroidTarget match = null;
+ for (int i = 0 ; i < targets.length; i++) {
+ // FIXME: add check based on project minSdkVersion
+ if (targets[i].hasRenderingLibrary()) {
+ mTargetList.add(targets[i]);
+
+ 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(targets[i])) {
+ match = targets[i];
+ }
+ } else if (mProjectTarget == targets[i]) {
+ match = targets[i];
+ }
+ }
+ }
+
+ if (match == null) {
+ selectTarget(null);
+
+ // the rendering target is the same as the project.
+ renderingTarget = mProjectTarget;
+ } else {
+ selectTarget(match);
+
+ // set the rendering target to the new object.
+ renderingTarget = match;
+ }
+ }
+ }
+
+ /** 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();
+ AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, 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 LocaleManager.getGlobeIcon();
+ }
+
+ private void selectConfiguration(FolderConfiguration fileConfig) {
+ 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.language.getValue();
+ 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.region.getValue();
+ 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 DevicesChangeListener ----
+
+ @Override
+ public void onDevicesChange() {
+ final Sdk sdk = Sdk.getCurrent();
+ mDeviceList = sdk.getDevices();
+ }
+
+ // ---- 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
+ boolean accepted = mClient.changed(CHANGED_DEVICE | CHANGED_DEVICE_CONFIG);
+ if (!accepted) {
+ mConfiguration.setDevice(prevDevice, true);
+ mConfiguration.setDeviceState(prevState, true);
+ mConfiguration.syncFolderConfig();
+ selectDevice(prevDevice);
+ selectDeviceState(prevState);
+ return;
+ }
+
+ saveConstraints();
+ }
+
+ /**
+ * 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(CHANGED_DEVICE | CHANGED_DEVICE_CONFIG);
+ 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(CHANGED_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(CHANGED_THEME);
+ if (!accepted) {
+ mConfiguration.setTheme(prev);
+ selectTheme(prev);
+ return;
+ }
+ }
+
+ saveConstraints();
+ }
+
+ void notifyFolderConfigChanged() {
+ if (mDisableUpdates > 0 || mClient == null) {
+ return;
+ }
+
+ if (mClient.changed(CHANGED_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());
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ String preferred = activityThemes.get(activity);
+ 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 |= CHANGED_RENDER_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 |= CHANGED_THEME;
+ }
+
+ if (target != null) {
+ changeFlags |= CHANGED_RENDER_TARGET;
+ changeFlags |= CHANGED_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 |= CHANGED_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 |= CHANGED_RENDER_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 |= CHANGED_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);
+ 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;
+ }
+ }
+ }
+
+ // 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) {
+ LanguageQualifier langQual = new LanguageQualifier(language);
+
+ // find the matching regions and add them
+ SortedSet<String> regions = projectRes.getRegions(language);
+ for (String region : regions) {
+ RegionQualifier regionQual = new RegionQualifier(region);
+ mLocaleList.add(Locale.create(langQual, regionQual));
+ }
+
+ // now the entry for the other regions the language alone
+ // create a region qualifier that will never be matched by qualified resources.
+ mLocaleList.add(Locale.create(langQual));
+ }
+ }
+
+ // create language/region qualifier that will never be matched by qualified resources.
+ mLocaleList.add(Locale.ANY);
+
+ Locale locale = mConfiguration.getLocale();
+ setLocale(locale);
+ } finally {
+ mDisableUpdates--;
+ }
+ }
+
+ /** Returns the preferred theme, or null */
+ @Nullable
+ String computePreferredTheme() {
+ if (mClient == null) {
+ return null;
+ }
+
+ IProject project = mEditedFile.getProject();
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current state
+ ScreenSize screenSize = null;
+ Device device = mConfiguration.getDevice();
+ if (device != null) {
+ List<State> states = device.getAllStates();
+ for (State state : states) {
+ FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
+ if (folderConfig != null) {
+ ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+ }
+
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(mConfiguration.getTarget(), screenSize);
+
+ String preferred = defaultTheme;
+ if (mConfiguration.getTheme() == null) {
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+
+ String activity = mConfiguration.getActivity();
+ if (activity != null) {
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ mConfiguration.setTheme(preferred);
+ }
+
+ return preferred;
+ }
+
+ @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;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java
new file mode 100644
index 0000000..a7c26d4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java
@@ -0,0 +1,142 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.resources.NightMode;
+import com.android.resources.ResourceType;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+
+import java.util.Map;
+
+/**
+ * Interface implemented by clients who embed a {@link ConfigurationChooser}.
+ */
+public interface ConfigurationClient {
+ /** The {@link FolderConfiguration} in the configuration has changed */
+ public static final int CHANGED_FOLDER = 1 << 0;
+ /** The {@link Device} in the configuration has changed */
+ public static final int CHANGED_DEVICE = 1 << 1;
+ /** The {@link State} in the configuration has changed */
+ public static final int CHANGED_DEVICE_CONFIG = 1 << 2;
+ /** The theme in the configuration has changed */
+ public static final int CHANGED_THEME = 1 << 3;
+ /** The locale in the configuration has changed */
+ public static final int CHANGED_LOCALE = 1 << 4;
+ /** The rendering {@link IAndroidTarget} in the configuration has changed */
+ public static final int CHANGED_RENDER_TARGET = 1 << 5;
+ /** The {@link NightMode} in the configuration has changed */
+ public static final int CHANGED_NIGHT_MODE = 1 << 6;
+ /** The {@link UiMode} in the configuration has changed */
+ public static final int CHANGED_UI_MODE = 1 << 7;
+
+ /** Everything has changed */
+ public static final int CHANGED_ALL = 0xFFFF;
+
+ /**
+ * The configuration is about to be changed.
+ *
+ * @param flags details about what changed; consult the {@code CHANGED_} flags
+ * such as {@link #CHANGED_DEVICE}, {@link #CHANGED_LOCALE}, etc.
+ */
+ void aboutToChange(int flags);
+
+ /**
+ * The configuration has changed. If the client returns false, it means that
+ * the change was rejected. This typically means that changing the
+ * configuration in this particular way makes a configuration which has a
+ * better file match than the current client's file, so it will open that
+ * file to edit the new configuration -- and the current configuration
+ * should go back to editing the state prior to this change.
+ *
+ * @param flags details about what changed; consult the {@code CHANGED_} flags
+ * such as {@link #CHANGED_DEVICE}, {@link #CHANGED_LOCALE}, etc.
+ * @return true if the change was accepted, false if it was rejected.
+ */
+ boolean changed(int flags);
+
+ /**
+ * Compute the project resources
+ *
+ * @return the project resources as a {@link ResourceRepository}
+ */
+ @Nullable
+ ResourceRepository getProjectResources();
+
+ /**
+ * Compute the framework resources
+ *
+ * @return the project resources as a {@link ResourceRepository}
+ */
+ @Nullable
+ ResourceRepository getFrameworkResources();
+
+ /**
+ * Compute the framework resources for the given Android API target
+ *
+ * @param target the target to look up framework resources for
+ * @return the project resources as a {@link ResourceRepository}
+ */
+ @Nullable
+ ResourceRepository getFrameworkResources(@Nullable IAndroidTarget target);
+
+ /**
+ * Returns the configured project resources for the current file and
+ * configuration
+ *
+ * @return resource type maps to names to resource values
+ */
+ @NonNull
+ Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources();
+
+ /**
+ * Returns the configured framework resources for the current file and
+ * configuration
+ *
+ * @return resource type maps to names to resource values
+ */
+ @NonNull
+ Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources();
+
+ /**
+ * If the current layout is an included layout rendered within an outer layout,
+ * returns the outer layout.
+ *
+ * @return the outer including layout, or null
+ */
+ @Nullable
+ Reference getIncludedWithin();
+
+ /**
+ * Called when the "Create" button is clicked.
+ */
+ void createConfigFile();
+
+ /**
+ * Called when an associated activity is picked
+ *
+ * @param fqcn the fully qualified class name for the associated activity context
+ */
+ void setActivity(@NonNull String fqcn);
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
deleted file mode 100644
index 1dec5cd..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
+++ /dev/null
@@ -1,3418 +0,0 @@
-/*
- * Copyright (C) 2008 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.FD_RES_LAYOUT;
-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 com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.api.Rect;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-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.DensityQualifier;
-import com.android.ide.common.resources.configuration.DeviceConfigHelper;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.LanguageQualifier;
-import com.android.ide.common.resources.configuration.NightModeQualifier;
-import com.android.ide.common.resources.configuration.RegionQualifier;
-import com.android.ide.common.resources.configuration.ResourceQualifier;
-import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
-import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
-import com.android.ide.common.resources.configuration.UiModeQualifier;
-import com.android.ide.common.resources.configuration.VersionQualifier;
-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.layout.LayoutEditorDelegate;
-import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
-import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
-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.ide.eclipse.adt.internal.wizards.newxmlfile.AddTranslationDialog;
-import com.android.resources.Density;
-import com.android.resources.NightMode;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenSize;
-import com.android.resources.UiMode;
-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.DevicesChangeListener;
-import com.android.sdklib.devices.State;
-import com.android.sdklib.internal.avd.AvdInfo;
-import com.android.sdklib.internal.avd.AvdManager;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.util.SparseIntArray;
-import com.android.utils.Pair;
-import com.google.common.collect.Maps;
-
-import org.eclipse.core.resources.IContainer;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.QualifiedName;
-import org.eclipse.jdt.ui.ISharedImages;
-import org.eclipse.jdt.ui.JavaUI;
-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.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.Menu;
-import org.eclipse.swt.widgets.MenuItem;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.ToolBar;
-import org.eclipse.swt.widgets.ToolItem;
-import org.eclipse.ui.IEditorPart;
-import org.eclipse.ui.PartInitException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeMap;
-
-/**
- * A composite that displays the current configuration displayed in a Graphical Layout Editor.
- * <p/>
- * The composite has several entry points:<br>
- * - {@link #setFile(IFile)}<br>
- * Called after the constructor to set the file being edited. Nothing else is performed.<br>
- *<br>
- * - {@link #onXmlModelLoaded()}<br>
- * Called when the XML model is loaded, either the first time or when the Target/SDK changes.
- * This initializes the UI, either with the first compatible configuration found, or attempts
- * to restore a configuration if one is found to have been saved in the file persistent storage.
- * (see {@link #storeState()})<br>
- *<br>
- * - {@link #replaceFile(IFile)}<br>
- * Called when a file, representing the same resource but with a different config is opened<br>
- * by the user.<br>
- *<br>
- * - {@link #changeFileOnNewConfig(IFile)}<br>
- * Called when config change triggers the editing of a file with a different config.
- *<p/>
- * Additionally, the composite can handle the following events.<br>
- * - SDK reload. This is when the main SDK is finished loading.<br>
- * - Target reload. This is when the target used by the project is the edited file has finished<br>
- * loading.<br>
- */
-public class ConfigurationComposite extends Composite
- implements SelectionListener, DevicesChangeListener, DisposeListener {
- public static final String ATTR_CONTEXT = "context"; //$NON-NLS-1$
- 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_NEW_CONFIG = "newConfig"; //$NON-NLS-1$
- private static final String ICON_THEMES = "themes"; //$NON-NLS-1$
- private static final String ICON_ACTIVITY = "activity"; //$NON-NLS-1$
- private final static String SEP = ":"; //$NON-NLS-1$
- private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
- private final static String MARKER_FRAMEWORK = "-"; //$NON-NLS-1$
- private final static String MARKER_PROJECT = "+"; //$NON-NLS-1$
-
- /**
- * Setting name for project-wide setting controlling rendering target and locale which
- * is shared for all files
- */
- public final static QualifiedName NAME_RENDER_STATE =
- new QualifiedName(AdtPlugin.PLUGIN_ID, "render");//$NON-NLS-1$
-
- /**
- * Settings name for file-specific configuration preferences, such as which theme or
- * device to render the current layout with
- */
- public final static QualifiedName NAME_CONFIG_STATE =
- new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
-
- private final static int LOCALE_LANG = 0;
- private final static int LOCALE_REGION = 1;
-
- private ToolItem mDeviceCombo;
- private ToolItem mThemeCombo;
- private ToolItem mOrientationCombo;
- private ToolItem mLocaleCombo;
- private ToolItem mTargetCombo;
- private ToolItem mConfigCombo;
- private ToolItem mActivityCombo;
-
- /** updates are disabled if > 0 */
- private int mDisableUpdates = 0;
-
- private List<Device> mDeviceList = new ArrayList<Device>();
- private final List<IAndroidTarget> mTargetList = new ArrayList<IAndroidTarget>();
-
- private final List<String> mThemeList = new ArrayList<String>();
-
- private final List<ResourceQualifier[] > mLocaleList =
- new ArrayList<ResourceQualifier[]>();
-
- private final ConfigState mState = new ConfigState();
-
- private boolean mSdkChanged = false;
- private boolean mFirstXmlModelChange = true;
-
- /** The config listener given to the constructor. Never null. */
- private final IConfigListener mListener;
-
- /** The device menu listener, so we can remove it when the device lists are updated */
- private Listener mDeviceListener;
-
- /** The {@link FolderConfiguration} representing the state of the UI controls */
- private final FolderConfiguration mCurrentConfig = new FolderConfiguration();
-
- /** 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;
- /** The target of the project of the file being edited. */
- private IAndroidTarget mRenderingTarget;
- /** The {@link FolderConfiguration} being edited. */
- private FolderConfiguration mEditedConfig;
- /** Serialized state to use when initializing the configuration after the SDK is loaded */
- private String mInitialState;
-
- /**
- * Interface implemented by the part which owns a {@link ConfigurationComposite}.
- * This notifies the owners when the configuration change.
- * The owner must also provide methods to provide the configuration that will
- * be displayed.
- */
- public interface IConfigListener {
- /**
- * Called when the {@link FolderConfiguration} change. The new config can be queried
- * with {@link ConfigurationComposite#getCurrentConfig()}.
- */
- void onConfigurationChange();
-
- /**
- * Called after a device has changed (in addition to {@link #onConfigurationChange}
- * getting called)
- */
- void onDevicePostChange();
-
- /**
- * Called when the current theme changes. The theme can be queried with
- * {@link ConfigurationComposite#getThemeName()}.
- */
- void onThemeChange();
-
- /**
- * Called when the "Create" button is clicked.
- */
- void onCreate();
-
- /**
- * Called when an associated activity is picked
- *
- * @param fqcn the fully qualified class name for the associated activity context
- */
- void onSetActivity(String fqcn);
-
- /**
- * Called before the rendering target changes.
- * @param oldTarget the old rendering target
- */
- void onRenderingTargetPreChange(IAndroidTarget oldTarget);
-
- /**
- * Called after the rendering target changes.
- *
- * @param target the new rendering target
- */
- void onRenderingTargetPostChange(IAndroidTarget target);
-
- ResourceRepository getProjectResources();
- ResourceRepository getFrameworkResources();
- ResourceRepository getFrameworkResources(IAndroidTarget target);
- Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources();
- Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources();
- String getIncludedWithin();
- }
-
- /**
- * State of the current config. This is used during UI reset to attempt to return the
- * rendering to its original configuration.
- */
- private class ConfigState {
- Device device;
- String stateName;
- ResourceQualifier[] locale;
- String theme;
- // TODO: Need to know if it's the project theme or the framework theme!
- /** UI mode. Guaranteed to be non null */
- UiMode uiMode = UiMode.NORMAL;
- /** night mode. Guaranteed to be non null */
- NightMode night = NightMode.NOTNIGHT;
- /** the version being targeted for rendering */
- IAndroidTarget target;
- String activity;
-
- String getData() {
- StringBuilder sb = new StringBuilder();
- if (device != null) {
- sb.append(device.getName());
- sb.append(SEP);
- if (stateName == null) {
- State state= getSelectedDeviceState();
- if (state != null) {
- stateName = state.getName();
- }
- }
- if (stateName != null) {
- sb.append(stateName);
- }
- sb.append(SEP);
- if (isLocaleSpecificLayout() && locale != null) {
- if (locale[0] != null && locale[1] != null) {
- // locale[0]/[1] can be null sometimes when starting Eclipse
- sb.append(((LanguageQualifier) locale[0]).getValue());
- sb.append(SEP_LOCALE);
- sb.append(((RegionQualifier) locale[1]).getValue());
- }
- }
- sb.append(SEP);
- // Need to escape the theme: if we write the full theme style, then
- // we can end up with ":"'s in the string (as in @android:style/Theme) which
- // can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}.
- if (theme != null) {
- String themeName = ResourceHelper.styleToTheme(theme);
- if (theme.startsWith(STYLE_RESOURCE_PREFIX)) {
- sb.append(MARKER_PROJECT);
- } else if (theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
- sb.append(MARKER_FRAMEWORK);
- }
- sb.append(themeName);
- }
- sb.append(SEP);
- if (uiMode != null) {
- sb.append(uiMode.getResourceValue());
- }
- sb.append(SEP);
- if (night != null) {
- sb.append(night.getResourceValue());
- }
- sb.append(SEP);
-
- // We used to store the render target here in R9. Leave a marker
- // to ensure that we don't reuse this slot; add new extra fields after it.
- sb.append(SEP);
- if (activity != null) {
- sb.append(activity);
- }
- }
-
- return sb.toString();
- }
-
- boolean setData(String data) {
- String[] values = data.split(SEP);
- if (values.length >= 6 && values.length <= 8) {
- for (Device d : mDeviceList) {
- if (d.getName().equals(values[0])) {
- device = d;
- FolderConfiguration config = null;
- if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$
- stateName = values[1];
- config = DeviceConfigHelper.getFolderConfig(device, stateName);
- } else if (device.getAllStates().size() > 0) {
- State first = device.getAllStates().get(0);
- stateName = first.getName();
- config = DeviceConfigHelper.getFolderConfig(first);
- }
- if (config != null) {
- // Load locale. Note that this can get overwritten by the
- // project-wide settings read below.
- locale = new ResourceQualifier[2];
- String locales[] = values[2].split(SEP_LOCALE);
- if (locales.length >= 2) {
- if (locales[0].length() > 0) {
- locale[0] = new LanguageQualifier(locales[0]);
- }
- if (locales[1].length() > 0) {
- locale[1] = new RegionQualifier(locales[1]);
- }
- }
-
- // Decode the theme name: See {@link #getData}
- theme = values[3];
- if (theme.startsWith(MARKER_FRAMEWORK)) {
- theme = ANDROID_STYLE_RESOURCE_PREFIX
- + theme.substring(MARKER_FRAMEWORK.length());
- } else if (theme.startsWith(MARKER_PROJECT)) {
- theme = STYLE_RESOURCE_PREFIX
- + theme.substring(MARKER_PROJECT.length());
- }
-
- uiMode = UiMode.getEnum(values[4]);
- if (uiMode == null) {
- uiMode = UiMode.NORMAL;
- }
- night = NightMode.getEnum(values[5]);
- if (night == null) {
- night = NightMode.NOTNIGHT;
- }
-
- // element 7/values[6]: used to store render target in R9.
- // No longer stored here. If adding more data, make
- // sure you leave 7 alone.
-
- Pair<ResourceQualifier[], IAndroidTarget> pair = loadRenderState();
-
- // We only use the "global" setting
- if (!isLocaleSpecificLayout()) {
- locale = pair.getFirst();
- }
- target = pair.getSecond();
-
- if (values.length == 8) {
- activity = values[7];
- }
-
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- @Override
- public String toString() {
- return getData();
- }
- }
-
- /**
- * Returns a String id to represent an {@link IAndroidTarget} which can be translated
- * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id
- * will never contain the {@link #SEP} character.
- *
- * @param target the target to return an id for
- * @return an id for the given target; never null
- */
- private String targetToString(IAndroidTarget target) {
- return target.getFullName().replace(SEP, ""); //$NON-NLS-1$
- }
-
- /**
- * Returns an {@link IAndroidTarget} that corresponds to the given id that was
- * originally returned by {@link #targetToString}. May be null, if the platform is no
- * longer available, or if the platform list has not yet been initialized.
- *
- * @param id the id that corresponds to the desired platform
- * @return an {@link IAndroidTarget} that matches the given id, or null
- */
- private IAndroidTarget stringToTarget(String id) {
- if (mTargetList != null && mTargetList.size() > 0) {
- for (IAndroidTarget target : mTargetList) {
- if (id.equals(targetToString(target))) {
- return target;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Creates a new {@link ConfigurationComposite} 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 listener An {@link IConfigListener} that gets and sets configuration properties.
- * Mandatory, cannot be null.
- * @param parent The parent composite.
- * @param style The style of this composite.
- * @param initialState The initial state (serialized form) to use for the configuration
- */
- public ConfigurationComposite(IConfigListener listener,
- Composite parent, int style, String initialState) {
- super(parent, style);
- setVisible(false); // Delayed until the targets are loaded
-
- mListener = listener;
- 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 | SWT.BOLD);
- mConfigCombo.setImage(null);
- 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(LocaleManager.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");
-
- addConfigurationMenuListener(mConfigCombo);
- addActivityMenuListener(mActivityCombo);
- addLocaleMenuListener(mLocaleCombo);
- addDeviceMenuListener(mDeviceCombo);
- addTargetMenuListener(mTargetCombo);
- addThemeListener(mThemeCombo);
- addOrientationMenuListener(mOrientationCombo);
-
- addDisposeListener(this);
- }
-
- private void updateActivity() {
- if (mEditedFile != null) {
- String preferred = getPreferredActivity(mEditedFile);
- selectActivity(preferred);
- }
- }
-
- // ---- Dispose
-
- @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;
- }
-
- /**
- * 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
- * <p/>This will NOT trigger a redraw event (will not call
- * {@link IConfigListener#onConfigurationChange()}.)
- * @param file the file being opened.
- */
- public void replaceFile(IFile file) {
- // if there is no previous selection, revert to default mode.
- if (mState.device == null) {
- setFile(file); // onTargetChanged will be called later.
- return;
- }
-
- mEditedFile = file;
- IProject iProject = mEditedFile.getProject();
- mResources = ResourceManager.getInstance().getProjectResources(iProject);
-
- ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
- mEditedConfig = 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
- adaptConfigSelection(true /*needBestMatch*/);
-
- // compute the final current config
- computeCurrentConfig();
-
- // update the string showing the config value
- updateConfigDisplay(mEditedConfig);
-
- updateActivity();
- }
- }
- } 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) {
- mEditedFile = file;
- IProject iProject = mEditedFile.getProject();
- mResources = ResourceManager.getInstance().getProjectResources(iProject);
-
- ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
- mEditedConfig = resFolder.getConfiguration();
-
- // All that's needed is to update the string showing the config value
- // (since the config combo were chosen by the user).
- updateConfigDisplay(mEditedConfig);
- }
-
- /**
- * 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 {
- // this is going to be followed by a call to onTargetLoaded.
- // So we can only care about the layout devices in this case.
- initDevices();
- initTargets();
- } finally {
- mDisableUpdates--;
- }
- }
-
- /**
- * Answers 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 attempts 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 happened (but the method must be called
- * back when those are loaded).
- * <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).
- *
- * @see #storeState()
- * @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 || mFirstXmlModelChange) {
- initDevices();
- initTargets();
- mSdkChanged = false;
- }
-
- IProject iProject = mEditedFile.getProject();
-
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- mProjectTarget = currentSdk.getTarget(iProject);
- }
-
- LoadStatus targetStatus = LoadStatus.FAILED;
- if (mProjectTarget != null) {
- targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget, null);
- initTargets();
- }
-
- if (targetStatus == LoadStatus.LOADED) {
- setVisible(true);
- if (mResources == null) {
- mResources = ResourceManager.getInstance().getProjectResources(iProject);
- }
- if (mEditedConfig == null) {
- IFolder parent = (IFolder) mEditedFile.getParent();
- ResourceFolder resFolder = mResources.getResourceFolder(parent);
- if (resFolder != null) {
- mEditedConfig = resFolder.getConfiguration();
- } else {
- mEditedConfig = FolderConfiguration.getConfig(
- parent.getName().split(RES_QUALIFIER_SEP));
- }
- }
-
- targetData = Sdk.getCurrent().getTargetData(mProjectTarget);
-
- // get the file stored state
- boolean loadedConfigData = false;
- String data = AdtPlugin.getFileProperty(mEditedFile, NAME_CONFIG_STATE);
- if (mInitialState != null) {
- data = mInitialState;
- mInitialState = null;
- }
- if (data != null) {
- loadedConfigData = mState.setData(data);
- }
-
- 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.
- if (loadedConfigData) {
- // first make sure we have the config to adapt
- selectDevice(mState.device);
- selectState(mState.stateName);
-
- adaptConfigSelection(false /*needBestMatch*/);
-
- selectTarget(mState.target);
-
- targetData = Sdk.getCurrent().getTargetData(mState.target);
- } else {
- findAndSetCompatibleConfig(false /*favorCurrentConfig*/);
-
- // Default to modern layout lib
- IAndroidTarget target = findDefaultRenderTarget();
- if (target != null) {
- targetData = Sdk.getCurrent().getTargetData(target);
- selectTarget(target);
- }
- }
-
- // 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
- updateConfigDisplay(mEditedConfig);
-
- // compute the final current config
- computeCurrentConfig();
- }
- } finally {
- mDisableUpdates--;
- mFirstXmlModelChange = false;
- }
- }
-
- return targetData;
- }
-
- private void selectActivity(@Nullable String fqcn) {
- if (fqcn != null) {
- mActivityCombo.setData(fqcn);
- String label = getActivityLabel(fqcn, true);
- mActivityCombo.setText(label);
- } else {
- mActivityCombo.setText("(Select)");
- }
- resizeToolBar();
- }
-
- @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 (mState.activity != null) {
- return mState.activity;
- }
-
- 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;
- }
- }
- }
-
- mState.activity = activity;
- storeState();
- return activity;
- }
- }
- }
-
- // No, not available there: try to infer it from the code index
- String includedIn = mListener != null ? mListener.getIncludedWithin() : null;
-
- 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) {
- mState.activity = activity;
- storeState();
- 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;
- }
-
- private void onSelectActivity() {
- String activity = getSelectedActivity();
- mState.activity = activity;
- saveState();
- storeState();
-
- 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());
- Map<String, String> activityThemes = manifest.getActivityThemes();
- String preferred = activityThemes.get(activity);
- if (preferred != null) {
- // Yes, switch to it
- selectTheme(preferred);
- onThemeChange();
- }
-
- // Persist in XML
- if (mListener != null) {
- mListener.onSetActivity(activity);
- }
- }
-
-
- /** 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();
- }
-
- private 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;
- }
-
- String getSelectedActivity() {
- return (String) mActivityCombo.getData();
- }
-
- private void selectTarget(IAndroidTarget target) {
- mTargetCombo.setData(target);
- String label = getRenderingTargetLabel(target, true);
- mTargetCombo.setText(label);
- resizeToolBar();
- }
-
- private 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;
- }
-
- private String getLocaleLabel(ResourceQualifier[] qualifiers, boolean brief) {
- if (qualifiers == null) {
- return null;
- }
-
- LanguageQualifier language = (LanguageQualifier) qualifiers[LOCALE_LANG];
-
- if (language.hasFakeValue()) {
- if (brief) {
- // Just use the icon
- return "";
- }
-
- boolean hasLocale = false;
- ResourceRepository projectRes = mListener.getProjectResources();
- if (projectRes != null) {
- hasLocale = projectRes.getLanguages().size() > 0;
- }
-
- if (hasLocale) {
- return "Other";
- } else {
- return "Any";
- }
- }
-
- String languageCode = language.getValue();
- String languageName = LocaleManager.getLanguageName(languageCode);
-
- RegionQualifier region = (RegionQualifier) qualifiers[LOCALE_REGION];
- if (region.hasFakeValue()) {
- // 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 = region.getValue();
- 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);
- }
- }
-
- private void selectLocale(ResourceQualifier[] qualifiers) {
- mLocaleCombo.setData(qualifiers);
- String label = getLocaleLabel(qualifiers, true);
-
- mLocaleCombo.setText(label);
-
- Image image = getFlagImage(qualifiers);
- mLocaleCombo.setImage(image);
-
- resizeToolBar();
- }
-
- private ResourceQualifier[] getSelectedLocale() {
- return (ResourceQualifier[]) mLocaleCombo.getData();
- }
-
- private IAndroidTarget getSelectedTarget() {
- if (!mTargetCombo.isDisposed()) {
- return (IAndroidTarget) mTargetCombo.getData();
- }
-
- return null;
- }
-
- void selectTheme(String theme) {
- assert 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();
- }
-
- /** Return the default render target to use, or null if no strong preference */
- private IAndroidTarget findDefaultRenderTarget() {
- // Default to layoutlib version 5
- Sdk current = Sdk.getCurrent();
- if (current != null) {
- IAndroidTarget projectTarget = current.getTarget(mEditedFile.getProject());
- int minProjectApi = Integer.MAX_VALUE;
- if (projectTarget != null) {
- if (!projectTarget.isPlatform() && projectTarget.hasRenderingLibrary()) {
- // Renderable non-platform targets are all going to be adequate (they
- // will have at least version 5 of layoutlib) so use the project
- // target as the render target.
- return projectTarget;
- }
-
- if (projectTarget.getVersion().isPreview()
- && projectTarget.hasRenderingLibrary()) {
- // If the project target is a preview version, then just use it
- return projectTarget;
- }
-
- minProjectApi = projectTarget.getVersion().getApiLevel();
- }
-
- // We want to pick a render target that contains at least version 5 (and
- // preferably version 6) of the layout library. To do this, we go through the
- // targets and pick the -smallest- API level that is both simultaneously at
- // least as big as the project API level, and supports layoutlib level 5+.
- IAndroidTarget best = null;
- int bestApiLevel = Integer.MAX_VALUE;
-
- for (IAndroidTarget target : current.getTargets()) {
- // Non-platform targets are not chosen as the default render target
- if (!target.isPlatform()) {
- continue;
- }
-
- int apiLevel = target.getVersion().getApiLevel();
-
- // Ignore targets that have a lower API level than the minimum project
- // API level:
- if (apiLevel < minProjectApi) {
- continue;
- }
-
- // Look up the layout lib API level. This property is new so it will only
- // be defined for version 6 or higher, which means non-null is adequate
- // to see if this target is eligible:
- String property = target.getProperty(PkgProps.LAYOUTLIB_API);
- // In addition, Android 3.0 with API level 11 had version 5.0 which is adequate:
- if (property != null || apiLevel >= 11) {
- if (apiLevel < bestApiLevel) {
- bestApiLevel = apiLevel;
- best = target;
- }
- }
- }
-
- return best;
- }
-
- return null;
- }
-
- private static class ConfigBundle {
- FolderConfiguration config;
- int localeIndex;
- int dockModeIndex;
- int nightModeIndex;
-
- ConfigBundle() {
- config = new FolderConfiguration();
- localeIndex = 0;
- dockModeIndex = 0;
- nightModeIndex = 0;
- }
-
- ConfigBundle(ConfigBundle bundle) {
- config = new FolderConfiguration();
- config.set(bundle.config);
- localeIndex = bundle.localeIndex;
- dockModeIndex = bundle.dockModeIndex;
- nightModeIndex = bundle.nightModeIndex;
- }
- }
-
- private static class ConfigMatch {
- final FolderConfiguration testConfig;
- final Device device;
- final String name;
- final ConfigBundle bundle;
-
- public ConfigMatch(@NonNull FolderConfiguration testConfig, Device device, String name,
- ConfigBundle bundle) {
- this.testConfig = testConfig;
- this.device = device;
- this.name = name;
- this.bundle = bundle;
- }
-
- @Override
- public String toString() {
- return device.getName() + " - " + name;
- }
- }
-
- /**
- * Finds a device/config that can display {@link #mEditedConfig}.
- * <p/>Once found the device and config combos are set to the config.
- * <p/>If there is no compatible configuration, a custom one is created.
- * @param favorCurrentConfig if true, and no best match is found, don't change
- * the current config. This must only be true if the current config is compatible.
- */
- private void findAndSetCompatibleConfig(boolean favorCurrentConfig) {
- // list of compatible device/state/locale
- List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>();
-
- // list of actual best match (ie the file is a best match for the
- // device/state)
- List<ConfigMatch> bestMatches = new ArrayList<ConfigMatch>();
-
- // get a locale that match the host locale roughly (may not be exact match on the region.)
- int localeHostMatch = getLocaleMatch();
-
- // build a list of combinations of non standard qualifiers to add to each device's
- // qualifier set when testing for a match.
- // These qualifiers are: locale, night-mode, car dock.
- List<ConfigBundle> configBundles = new ArrayList<ConfigBundle>(200);
-
- // If the edited file has locales, then we have to select a matching locale from
- // the list.
- // However, if it doesn't, we don't randomly take the first locale, we take one
- // matching the current host locale (making sure it actually exist in the project)
- int start, max;
- if (mEditedConfig.getLanguageQualifier() != null || localeHostMatch == -1) {
- // add all the locales
- start = 0;
- max = mLocaleList.size();
- } else {
- // only add the locale host match
- start = localeHostMatch;
- max = localeHostMatch + 1; // test is <
- }
-
- for (int i = start ; i < max ; i++) {
- ResourceQualifier[] l = mLocaleList.get(i);
-
- ConfigBundle bundle = new ConfigBundle();
- bundle.config.setLanguageQualifier((LanguageQualifier) l[LOCALE_LANG]);
- bundle.config.setRegionQualifier((RegionQualifier) l[LOCALE_REGION]);
-
- bundle.localeIndex = i;
- configBundles.add(bundle);
- }
-
- // add the dock mode to the bundle combinations.
- addDockModeToBundles(configBundles);
-
- // add the night mode to the bundle combinations.
- addNightModeToBundles(configBundles);
-
- addRenderTargetToBundles(configBundles);
-
- for (Device device : mDeviceList) {
- for (State state : device.getAllStates()) {
-
- // loop on the list of config bundles to create full
- // configurations.
- FolderConfiguration stateConfig = DeviceConfigHelper.getFolderConfig(state);
- for (ConfigBundle bundle : configBundles) {
- // create a new config with device config
- FolderConfiguration testConfig = new FolderConfiguration();
- testConfig.set(stateConfig);
-
- // add on top of it, the extra qualifiers from the bundle
- testConfig.add(bundle.config);
-
- if (mEditedConfig.isMatchFor(testConfig)) {
- // this is a basic match. record it in case we don't
- // find a match
- // where the edited file is a best config.
- anyMatches
- .add(new ConfigMatch(testConfig, device, state.getName(), bundle));
-
- if (isCurrentFileBestMatchFor(testConfig)) {
- // this is what we want.
- bestMatches.add(new ConfigMatch(testConfig, device, state.getName(),
- bundle));
- }
- }
- }
- }
- }
-
- if (bestMatches.size() == 0) {
- if (favorCurrentConfig) {
- // quick check
- if (mEditedConfig.isMatchFor(mCurrentConfig) == false) {
- AdtPlugin.log(IStatus.ERROR,
- "favorCurrentConfig can only be true if the current config is compatible");
- }
-
- // just display the warning
- AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
- String.format(
- "'%1$s' is not a best match for any device/locale combination.",
- mEditedConfig.toDisplayString()),
- String.format(
- "Displaying it with '%1$s'",
- mCurrentConfig.toDisplayString()));
- } else if (anyMatches.size() > 0) {
- // select the best device anyway.
- ConfigMatch match = selectConfigMatch(anyMatches);
- selectDevice(mState.device = match.device);
- selectState(match.name);
- selectLocale(mLocaleList.get(match.bundle.localeIndex));
-
- mState.uiMode = UiMode.getByIndex(match.bundle.dockModeIndex);
- mState.night = NightMode.getByIndex(match.bundle.nightModeIndex);
-
- // TODO: display a better warning!
- computeCurrentConfig();
- AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
- String.format(
- "'%1$s' is not a best match for any device/locale combination.",
- mEditedConfig.toDisplayString()),
- String.format(
- "Displaying it with '%1$s' which is compatible, but will actually be displayed with another more specific version of the layout.",
- mCurrentConfig.toDisplayString()));
-
- } else {
- // 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 state,
- // and replace whatever qualifier required by the layout file.
- }
- } else {
- ConfigMatch match = selectConfigMatch(bestMatches);
- selectDevice(mState.device = match.device);
- selectState(match.name);
- selectLocale(mLocaleList.get(match.bundle.localeIndex));
- mState.uiMode = UiMode.getByIndex(match.bundle.dockModeIndex);
- mState.night = NightMode.getByIndex(match.bundle.nightModeIndex);
- }
- }
-
- /**
- * Note: this comparator imposes orderings that are inconsistent with equals.
- */
- private static class TabletConfigComparator implements Comparator<ConfigMatch> {
- @Override
- public int compare(ConfigMatch o1, ConfigMatch o2) {
- FolderConfiguration config1 = o1 != null ? o1.testConfig : null;
- FolderConfiguration config2 = o2 != null ? o2.testConfig : null;
- if (config1 == null) {
- if (config2 == null) {
- return 0;
- } else {
- return -1;
- }
- } else if (config2 == null) {
- return 1;
- }
-
- ScreenSizeQualifier size1 = config1.getScreenSizeQualifier();
- ScreenSizeQualifier size2 = config2.getScreenSizeQualifier();
- ScreenSize ss1 = size1 != null ? size1.getValue() : ScreenSize.NORMAL;
- ScreenSize ss2 = size2 != null ? size2.getValue() : ScreenSize.NORMAL;
-
- // X-LARGE is better than all others (which are considered identical)
- // if both X-LARGE, then LANDSCAPE is better than all others (which are identical)
-
- if (ss1 == ScreenSize.XLARGE) {
- if (ss2 == ScreenSize.XLARGE) {
- ScreenOrientationQualifier orientation1 =
- config1.getScreenOrientationQualifier();
- ScreenOrientation so1 = orientation1.getValue();
- if (so1 == null) {
- so1 = ScreenOrientation.PORTRAIT;
- }
- ScreenOrientationQualifier orientation2 =
- config2.getScreenOrientationQualifier();
- ScreenOrientation so2 = orientation2.getValue();
- if (so2 == null) {
- so2 = ScreenOrientation.PORTRAIT;
- }
-
- if (so1 == ScreenOrientation.LANDSCAPE) {
- if (so2 == ScreenOrientation.LANDSCAPE) {
- return 0;
- } else {
- return -1;
- }
- } else if (so2 == ScreenOrientation.LANDSCAPE) {
- return 1;
- } else {
- return 0;
- }
- } else {
- return -1;
- }
- } else if (ss2 == ScreenSize.XLARGE) {
- return 1;
- } else {
- return 0;
- }
- }
- }
-
- /**
- * Note: this comparator imposes orderings that are inconsistent with equals.
- */
- private static class PhoneConfigComparator implements Comparator<ConfigMatch> {
-
- private SparseIntArray mDensitySort = new SparseIntArray(4);
-
- public PhoneConfigComparator() {
- // put the sort order for the density.
- mDensitySort.put(Density.HIGH.getDpiValue(), 1);
- mDensitySort.put(Density.MEDIUM.getDpiValue(), 2);
- mDensitySort.put(Density.XHIGH.getDpiValue(), 3);
- mDensitySort.put(Density.LOW.getDpiValue(), 4);
- }
-
- @Override
- public int compare(ConfigMatch o1, ConfigMatch o2) {
- FolderConfiguration config1 = o1 != null ? o1.testConfig : null;
- FolderConfiguration config2 = o2 != null ? o2.testConfig : null;
- if (config1 == null) {
- if (config2 == null) {
- return 0;
- } else {
- return -1;
- }
- } else if (config2 == null) {
- return 1;
- }
-
- int dpi1 = Density.DEFAULT_DENSITY;
- int dpi2 = Density.DEFAULT_DENSITY;
-
- DensityQualifier dpiQualifier1 = config1.getDensityQualifier();
- if (dpiQualifier1 != null) {
- Density value = dpiQualifier1.getValue();
- dpi1 = value != null ? value.getDpiValue() : Density.DEFAULT_DENSITY;
- }
- dpi1 = mDensitySort.get(dpi1, 100 /* valueIfKeyNotFound*/);
-
- DensityQualifier dpiQualifier2 = config2.getDensityQualifier();
- if (dpiQualifier2 != null) {
- Density value = dpiQualifier2.getValue();
- dpi2 = value != null ? value.getDpiValue() : Density.DEFAULT_DENSITY;
- }
- dpi2 = mDensitySort.get(dpi2, 100 /* valueIfKeyNotFound*/);
-
- if (dpi1 == dpi2) {
- // portrait is better
- ScreenOrientation so1 = ScreenOrientation.PORTRAIT;
- ScreenOrientationQualifier orientationQualifier1 =
- config1.getScreenOrientationQualifier();
- if (orientationQualifier1 != null) {
- so1 = orientationQualifier1.getValue();
- if (so1 == null) {
- so1 = ScreenOrientation.PORTRAIT;
- }
- }
- ScreenOrientation so2 = ScreenOrientation.PORTRAIT;
- ScreenOrientationQualifier orientationQualifier2 =
- config2.getScreenOrientationQualifier();
- if (orientationQualifier2 != null) {
- so2 = orientationQualifier2.getValue();
- if (so2 == null) {
- so2 = ScreenOrientation.PORTRAIT;
- }
- }
-
- if (so1 == ScreenOrientation.PORTRAIT) {
- if (so2 == ScreenOrientation.PORTRAIT) {
- return 0;
- } else {
- return -1;
- }
- } else if (so2 == ScreenOrientation.PORTRAIT) {
- return 1;
- } else {
- return 0;
- }
- }
-
- return dpi1 - dpi2;
- }
- }
-
- private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) {
- // API 11-13: look for a x-large device
- int apiLevel = mProjectTarget.getVersion().getApiLevel();
- if (apiLevel >= 11 && apiLevel < 14) {
- // TODO: Maybe check the compatible-screen tag in the manifest to figure out
- // what kind of device should be used for display.
- Collections.sort(matches, new TabletConfigComparator());
- } else {
- // lets look for a high density device
- Collections.sort(matches, new PhoneConfigComparator());
- }
-
- // Look at the currently active editor to see if it's a layout editor, and if so,
- // look up its configuration and if the configuration is in our match list,
- // use it. This means we "preserve" the current configuration when you open
- // new layouts.
- IEditorPart activeEditor = AdtUtils.getActiveEditor();
- LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
- if (delegate != null
- && mEditedFile != null
- // (Only do this when the two files are in the same project)
- && delegate.getEditor().getProject() == mEditedFile.getProject()) {
- FolderConfiguration configuration = delegate.getGraphicalEditor().getConfiguration();
- if (configuration != null) {
- for (ConfigMatch match : matches) {
- if (configuration.equals(match.testConfig)) {
- return match;
- }
- }
- }
- }
-
- // the list has been sorted so that the first item is the best config
- return matches.get(0);
- }
-
- private void addRenderTargetToBundles(List<ConfigBundle> configBundles) {
- Pair<ResourceQualifier[], IAndroidTarget> state = loadRenderState();
- if (state != null) {
- IAndroidTarget target = state.getSecond();
- if (target != null) {
- int apiLevel = target.getVersion().getApiLevel();
- for (ConfigBundle bundle : configBundles) {
- bundle.config.setVersionQualifier(
- new VersionQualifier(apiLevel));
- }
- }
- }
- }
-
- private void addDockModeToBundles(List<ConfigBundle> addConfig) {
- ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>();
-
- // loop on each item and for each, add all variations of the dock modes
- for (ConfigBundle bundle : addConfig) {
- int index = 0;
- for (UiMode mode : UiMode.values()) {
- ConfigBundle b = new ConfigBundle(bundle);
- b.config.setUiModeQualifier(new UiModeQualifier(mode));
- b.dockModeIndex = index++;
- list.add(b);
- }
- }
-
- addConfig.clear();
- addConfig.addAll(list);
- }
-
- private void addNightModeToBundles(List<ConfigBundle> addConfig) {
- ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>();
-
- // loop on each item and for each, add all variations of the night modes
- for (ConfigBundle bundle : addConfig) {
- int index = 0;
- for (NightMode mode : NightMode.values()) {
- ConfigBundle b = new ConfigBundle(bundle);
- b.config.setNightModeQualifier(new NightModeQualifier(mode));
- b.nightModeIndex = index++;
- list.add(b);
- }
- }
-
- addConfig.clear();
- addConfig.addAll(list);
- }
-
- /**
- * Adapts the current device/config selection so that it's compatible with
- * {@link #mEditedConfig}.
- * <p/>If the current selection is compatible, nothing is changed.
- * <p/>If it's not compatible, configs from the current devices are tested.
- * <p/>If none are compatible, it reverts to
- * {@link #findAndSetCompatibleConfig(boolean)}
- */
- private void adaptConfigSelection(boolean needBestMatch) {
- // check the device config (ie sans locale)
- boolean needConfigChange = true; // if still true, we need to find another config.
- boolean currentConfigIsCompatible = false;
- State selectedState = getSelectedDeviceState();
- if (selectedState != null) {
- FolderConfiguration currentConfig = DeviceConfigHelper.getFolderConfig(selectedState);
- if (currentConfig != null && mEditedConfig.isMatchFor(currentConfig)) {
- currentConfigIsCompatible = true; // current config is compatible
- if (needBestMatch == false || isCurrentFileBestMatchFor(currentConfig)) {
- needConfigChange = false;
- }
- }
- }
-
- if (needConfigChange) {
- // if the current state/locale isn't a correct match, then
- // look for another state/locale in the same device.
- FolderConfiguration testConfig = new FolderConfiguration();
-
- // first look in the current device.
- String matchName = null;
- int localeIndex = -1;
- mainloop: for (State state : mState.device.getAllStates()) {
- testConfig.set(DeviceConfigHelper.getFolderConfig(state));
-
- // loop on the locales.
- for (int i = 0 ; i < mLocaleList.size() ; i++) {
- ResourceQualifier[] locale = mLocaleList.get(i);
-
- // update the test config with the locale qualifiers
- testConfig.setLanguageQualifier((LanguageQualifier)locale[LOCALE_LANG]);
- testConfig.setRegionQualifier((RegionQualifier)locale[LOCALE_REGION]);
-
- if (mEditedConfig.isMatchFor(testConfig) &&
- isCurrentFileBestMatchFor(testConfig)) {
- matchName = state.getName();
- localeIndex = i;
- break mainloop;
- }
- }
- }
-
- if (matchName != null) {
- selectState(matchName);
- selectLocale(mLocaleList.get(localeIndex));
- } else {
- // no match in current device with any state/locale
- // attempt to find another device that can display this
- // particular state.
- findAndSetCompatibleConfig(currentConfigIsCompatible);
- }
- }
- }
-
- /**
- * 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 setLocaleCombo(ResourceQualifier language, ResourceQualifier region) {
- boolean changed = false;
-
- // find the locale match. Since the locale list is based on the content of the
- // project resources there must be an exact match.
- // The only trick is that the region could be null in the fileConfig but in our
- // list of locales, this is represented as a RegionQualifier with value of
- // FAKE_LOCALE_VALUE.
- ResourceQualifier[] selectedLocale = getSelectedLocale();
- //changed = prevLanguage != language || region != prevRegion;
- if (selectedLocale != null) {
- ResourceQualifier prevLanguage = selectedLocale[LOCALE_LANG];
- ResourceQualifier prevRegion = selectedLocale[LOCALE_REGION];
- changed = !prevLanguage.equals(language) || !prevRegion.equals(region);
- }
-
- selectLocale(new ResourceQualifier[] { language, region});
-
- return changed;
- }
-
- private void updateConfigDisplay(FolderConfiguration fileConfig) {
- // Label currently hidden
- //String current = fileConfig.toDisplayString();
- //String current = fileConfig.getFolderName(ResourceFolderType.LAYOUT);
- 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();
- }
-
- private void saveState() {
- if (mDisableUpdates == 0) {
- State state = getSelectedDeviceState();
- String stateName = state != null ? state.getName() : null;
- mState.stateName = stateName;
-
- // since the locales are relative to the project, only keeping the index is enough
- mState.locale = getSelectedLocale();
- mState.theme = getSelectedTheme();
- mState.target = getRenderingTarget();
- mState.activity = getSelectedActivity();
- }
- }
-
- /**
- * Stores the current config selection into the edited file.
- */
- public void storeState() {
- AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, mState.getData());
- }
-
- private void addLocaleMenuListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
- ResourceQualifier[] current = getSelectedLocale();
-
- for (final ResourceQualifier[] qualifiers : mLocaleList) {
- String title = getLocaleLabel(qualifiers, false);
- MenuItem item = new MenuItem(menu, SWT.CHECK);
- item.setText(title);
- Image image = getFlagImage(qualifiers);
- item.setImage(image);
-
- boolean selected = current == qualifiers;
- if (selected) {
- item.setSelection(true);
- }
-
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- selectLocale(qualifiers);
- onLocaleChange();
- }
- });
- }
-
- @SuppressWarnings("unused")
- MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
-
- MenuItem item = new MenuItem(menu, SWT.PUSH);
- item.setText("Add New Translation...");
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- IProject project = mEditedFile.getProject();
- Shell shell = ConfigurationComposite.this.getShell();
- AddTranslationDialog dialog = new AddTranslationDialog(shell, project);
- dialog.open();
- }
- });
-
- Rectangle bounds = combo.getBounds();
- Point location = new Point(bounds.x, bounds.y + bounds.height);
- location = combo.getParent().toDisplay(location);
- menu.setLocation(location.x, location.y);
- menu.setVisible(true);
- }
- };
- combo.addListener(SWT.Selection, menuListener);
- }
-
- private Map<String, String> mCountryToLanguage;
-
- @SuppressWarnings("unused") // FIXME cleanup if really not used anymore?
- private String getCountry(String language, String region) {
- if (RegionQualifier.FAKE_REGION_VALUE.equals(region)) {
- region = "";
- }
-
- String country = region;
- if (country.isEmpty()) {
- // Special cases
- if (language.equals("ar")) { //$NON-NLS-1$
- country = "AE"; //$NON-NLS-1$
- } else if (language.equals("zh")) { //$NON-NLS-1$
- country = "CN"; //$NON-NLS-1$
- } else if (language.equals("en")) { //$NON-NLS-1$
- country = "US"; //$NON-NLS-1$
- } else if (language.equals("fa")) { //$NON-NLS-1$
- country = "IR"; //$NON-NLS-1$
- }
- }
-
- if (country.isEmpty()) {
- if (mCountryToLanguage == null) {
- Locale[] locales = Locale.getAvailableLocales();
- mCountryToLanguage = Maps.newHashMapWithExpectedSize(locales.length);
- Map<String, Locale> localeMap = Maps.newHashMapWithExpectedSize(locales.length);
- for (int i = 0; i < locales.length; i++) {
- Locale locale = locales[i];
- String localeLanguage = locale.getLanguage();
- String localeCountry = locale.getCountry();
- if (!localeCountry.isEmpty()) {
- localeCountry = localeCountry.toLowerCase(Locale.US);
- Locale old = localeMap.get(localeLanguage);
- if (old != null) {
- // For Italian for example it has both a locale with country = Italy
- // and one with country = Switzerland, so prefer the one where the
- // language code matches the country.
- if (!localeLanguage.equals(localeCountry)) {
- continue;
- }
- }
- mCountryToLanguage.put(localeLanguage, localeCountry);
- localeMap.put(localeLanguage, locale);
- }
- }
- }
-
- country = mCountryToLanguage.get(language);
- }
-
- return country;
- }
-
- @NonNull
- private Image getFlagImage(@NonNull ResourceQualifier[] qualifiers) {
- Image image = null;
- assert qualifiers.length == 2;
- String language = ((LanguageQualifier) qualifiers[LOCALE_LANG]).getValue();
- if (LanguageQualifier.FAKE_LANG_VALUE.equals(language)) {
- language = null;
- }
- String region = ((RegionQualifier) qualifiers[LOCALE_REGION]).getValue();
- if (RegionQualifier.FAKE_REGION_VALUE.equals(region)) {
- region = null;
- }
- LocaleManager icons = LocaleManager.get();
- if (language == null && region == null) {
- return LocaleManager.getGlobeIcon();
- } else {
- image = icons.getFlag(language, region);
- if (image == null) {
- image = LocaleManager.getEmptyIcon();
- }
-
- return image;
- }
- }
-
- private String getDeviceLabel(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;
- }
-
- private void addDeviceMenuListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- Device current = getSelectedDevice();
- Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
-
- AvdManager avdManager = Sdk.getCurrent().getAvdManager();
- AvdInfo[] avds = avdManager.getValidAvds();
- boolean separatorNeeded = false;
- for (AvdInfo avd : avds) {
- for (final Device d : mDeviceList) {
- if (d.getManufacturer().equals(avd.getDeviceManufacturer())
- && d.getName().equals(avd.getDeviceName())) {
- separatorNeeded = true;
- MenuItem item = new MenuItem(menu, SWT.CHECK);
- item.setText(avd.getName());
- item.setSelection(current == d);
-
- item.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- selectDevice(d);
- onDeviceChange(true /*recomputeLayout*/);
- }
- });
- }
- }
- }
-
- if (separatorNeeded) {
- @SuppressWarnings("unused")
- MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
- }
-
- // Group the devices by manufacturer, then put them in the menu
- if (!mDeviceList.isEmpty()) {
- Map<String, List<Device>> manufacturers = new TreeMap<String, List<Device>>();
- for (Device device : mDeviceList) {
- List<Device> devices;
- if(manufacturers.containsKey(device.getManufacturer())) {
- devices = manufacturers.get(device.getManufacturer());
- } else {
- devices = new ArrayList<Device>();
- manufacturers.put(device.getManufacturer(), devices);
- }
- devices.add(device);
- }
- for (List<Device> devices : manufacturers.values()) {
- Menu manufacturerMenu = menu;
- if (manufacturers.size() > 1) {
- MenuItem item = new MenuItem(menu, SWT.CASCADE);
- item.setText(devices.get(0).getManufacturer());
- manufacturerMenu = new Menu(menu);
- item.setMenu(manufacturerMenu);
- }
- for (final Device d : devices) {
- MenuItem deviceItem = new MenuItem(manufacturerMenu, SWT.CHECK);
- deviceItem.setText(d.getName());
- deviceItem.setSelection(current == d);
-
- deviceItem.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- selectDevice(d);
- onDeviceChange(true /*recomputeLayout*/);
- }
- });
- }
- }
- }
-
- // TODO - how do I dispose of this?
-
- Rectangle bounds = combo.getBounds();
- Point location = new Point(bounds.x, bounds.y + bounds.height);
- location = combo.getParent().toDisplay(location);
- menu.setLocation(location.x, location.y);
- menu.setVisible(true);
- }
- };
-
- if (mDeviceListener != null) {
- combo.removeListener(SWT.Selection, mDeviceListener);
- }
- mDeviceListener = menuListener;
- combo.addListener(SWT.Selection, menuListener);
- }
-
- private void addTargetMenuListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
- IAndroidTarget current = getSelectedTarget();
-
- for (final IAndroidTarget target : mTargetList) {
- String title = getRenderingTargetLabel(target, false);
- MenuItem item = new MenuItem(menu, SWT.CHECK);
- item.setText(title);
-
- boolean selected = current == target;
- if (selected) {
- item.setSelection(true);
- }
-
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- selectTarget(target);
- onRenderingTargetChange();
- }
- });
- }
-
- Rectangle bounds = combo.getBounds();
- Point location = new Point(bounds.x, bounds.y + bounds.height);
- location = combo.getParent().toDisplay(location);
- menu.setLocation(location.x, location.y);
- menu.setVisible(true);
- }
- };
- combo.addListener(SWT.Selection, menuListener);
- }
-
- private void addThemeListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- ThemeMenuAction.showThemeMenu(ConfigurationComposite.this, combo, mThemeList);
- }
- };
- combo.addListener(SWT.Selection, menuListener);
- }
-
- private void addOrientationMenuListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- if (event.detail == SWT.ARROW) {
- OrientationMenuAction.showMenu(ConfigurationComposite.this, combo);
- } else {
- gotoNextState();
- }
- }
- };
- combo.addListener(SWT.Selection, menuListener);
- }
-
- /** Move to the next device state, changing the icon if it changes orientation */
- private void gotoNextState() {
- State state = getSelectedDeviceState();
- State flipped = getNextDeviceState(state);
- if (flipped != state) {
- selectDeviceState(flipped);
- onDeviceConfigChange();
- }
- }
-
- /** Get the next cyclical state after the given state */
- @Nullable
- State getNextDeviceState(State state) {
- Device device = getSelectedDevice();
- List<State> states = device.getAllStates();
- for (int i = 0; i < states.size(); i++) {
- if (states.get(i) == state) {
- return states.get((i + 1) % states.size());
- }
- }
-
- return null;
- }
-
- protected 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;
- }
-
- private void addActivityMenuListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- // TODO: Allow using fragments here as well?
- Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
- ISharedImages sharedImages = JavaUI.getSharedImages();
- String current = getSelectedActivity();
-
- if (current != null) {
- MenuItem item = new MenuItem(menu, SWT.PUSH);
- String label = getActivityLabel(current, true);;
- item.setText( String.format("Open %1$s...", label));
- Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT);
- item.setImage(image);
-
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- String fqcn = getSelectedActivity();
- AdtPlugin.openJavaClass(mEditedFile.getProject(), fqcn);
- }
- });
-
- @SuppressWarnings("unused")
- MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
- }
-
- IProject project = mEditedFile.getProject();
- Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CLASS);
-
- // Add activities found to be relevant to this layout
- String layoutName = ResourceHelper.getLayoutName(mEditedFile);
- String pkg = ManifestInfo.get(project).getPackage();
- List<String> preferred = ManifestInfo.guessActivities(project, layoutName, pkg);
- current = addActivities(menu, current, image, preferred);
-
- // Add all activities
- List<String> activities = ManifestInfo.getProjectActivities(project);
- if (preferred.size() > 0) {
- // Filter out the activities we've already listed above
- List<String> filtered = new ArrayList<String>(activities.size());
- Set<String> remove = new HashSet<String>(preferred);
- for (String fqcn : activities) {
- if (!remove.contains(fqcn)) {
- filtered.add(fqcn);
- }
- }
- activities = filtered;
- }
-
- if (activities.size() > 0) {
- if (preferred.size() > 0) {
- @SuppressWarnings("unused")
- MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
- }
-
- addActivities(menu, current, image, activities);
- }
-
- Rectangle bounds = combo.getBounds();
- Point location = new Point(bounds.x, bounds.y + bounds.height);
- location = combo.getParent().toDisplay(location);
- menu.setLocation(location.x, location.y);
- menu.setVisible(true);
- }
-
- private String addActivities(Menu menu, String current, Image image,
- List<String> activities) {
- for (final String fqcn : activities) {
- String title = getActivityLabel(fqcn, false);
- MenuItem item = new MenuItem(menu, SWT.CHECK);
- item.setText(title);
- item.setImage(image);
-
- boolean selected = title.equals(current);
- if (selected) {
- item.setSelection(true);
- current = null; // Only show the first occurrence as selected
- // such that we don't show it selected again in the full activity list
- }
-
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- selectActivity(fqcn);
- onSelectActivity();
- }
- });
- }
-
- return current;
- }
- };
- combo.addListener(SWT.Selection, menuListener);
- }
-
- private void addConfigurationMenuListener(final ToolItem combo) {
- Listener menuListener = new Listener() {
- @Override
- public void handleEvent(Event event) {
- Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
-
- // Compute the set of layout files defining this layout resource
- String name = mEditedFile.getName();
- IContainer resFolder = mEditedFile.getParent().getParent();
- List<IFile> variations = new ArrayList<IFile>();
- try {
- for (IResource resource : resFolder.members()) {
- if (resource.getName().startsWith(FD_RES_LAYOUT)
- && resource instanceof IContainer) {
- IContainer layoutFolder = (IContainer) resource;
- IResource variation = layoutFolder.findMember(name);
- if (variation instanceof IFile) {
- variations.add((IFile) variation);
- }
- }
- }
- } catch (CoreException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
-
- ResourceManager manager = ResourceManager.getInstance();
- for (final IFile resource : variations) {
- MenuItem item = new MenuItem(menu, SWT.CHECK);
-
- IFolder parent = (IFolder) resource.getParent();
- ResourceFolder parentResource = manager.getResourceFolder(parent);
- FolderConfiguration configuration = parentResource.getConfiguration();
- String title = configuration.toDisplayString();
- item.setText(title);
-
- boolean selected = mEditedFile.equals(resource);
- if (selected) {
- item.setSelection(true);
- }
-
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- ConfigurationComposite.this.getDisplay().asyncExec(new Runnable() {
- @Override
- public void run() {
- try {
- AdtPlugin.openFile(resource, null, false);
- } catch (PartInitException ex) {
- AdtPlugin.log(ex, null);
- }
- }
- });
- }
- });
- }
-
- if (!mEditedConfig.equals(mCurrentConfig)) {
- if (variations.size() > 0) {
- @SuppressWarnings("unused")
- MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
- }
-
- // Add action for creating a new configuration
- MenuItem item = new MenuItem(menu, SWT.CHECK);
- item.setText("Create New...");
- item.setImage(IconFactory.getInstance().getIcon(ICON_NEW_CONFIG));
- //item.setToolTipText("Duplicate: Create new configuration for this layout");
-
- item.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- if (mListener != null) {
- mListener.onCreate();
- }
- }
- });
- }
-
- Rectangle bounds = combo.getBounds();
- Point location = new Point(bounds.x, bounds.y + bounds.height);
- location = combo.getParent().toDisplay(location);
- menu.setLocation(location.x, location.y);
- menu.setVisible(true);
- }
- };
- combo.addListener(SWT.Selection, menuListener);
- }
-
- /**
- * Updates the locale combo.
- * This must be called from the UI thread.
- */
- public void updateLocales() {
- if (mListener == null) {
- return; // can't do anything w/o it.
- }
-
- mDisableUpdates++;
-
- try {
- mLocaleList.clear();
-
- SortedSet<String> languages = null;
-
- // get the languages from the project.
- ResourceRepository projectRes = mListener.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) {
- LanguageQualifier langQual = new LanguageQualifier(language);
-
- // find the matching regions and add them
- SortedSet<String> regions = projectRes.getRegions(language);
- for (String region : regions) {
- RegionQualifier regionQual = new RegionQualifier(region);
- mLocaleList.add(new ResourceQualifier[] { langQual, regionQual });
- }
-
- // now the entry for the other regions the language alone
- // create a region qualifier that will never be matched by qualified resources.
- mLocaleList.add(new ResourceQualifier[] {
- langQual,
- new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)
- });
- }
- }
-
- // create language/region qualifier that will never be matched by qualified resources.
- mLocaleList.add(new ResourceQualifier[] {
- new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
- new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)
- });
-
- if (mState.locale != null) {
- // FIXME: this may fails if the layout was deleted (and was the last one to have
- // that local. (we have other problem in this case though)
- setLocaleCombo(mState.locale[LOCALE_LANG],
- mState.locale[LOCALE_REGION]);
- } else {
- //mLocaleCombo.select(0);
- selectLocale(mLocaleList.get(0));
- }
- } finally {
- mDisableUpdates--;
- }
- }
-
- private int getLocaleMatch() {
- Locale locale = Locale.getDefault();
- if (locale != null) {
- String currentLanguage = locale.getLanguage();
- String currentRegion = locale.getCountry();
-
- final int count = mLocaleList.size();
- for (int l = 0 ; l < count ; l++) {
- ResourceQualifier[] localeArray = mLocaleList.get(l);
- LanguageQualifier langQ = (LanguageQualifier)localeArray[LOCALE_LANG];
- RegionQualifier regionQ = (RegionQualifier)localeArray[LOCALE_REGION];
-
- // there's always a ##/Other or ##/Any (which is the same, the region
- // contains FAKE_REGION_VALUE). If we don't find a perfect region match
- // we take the fake region. Since it's last in the list, this makes the
- // test easy.
- if (langQ.getValue().equals(currentLanguage) &&
- (regionQ.getValue().equals(currentRegion) ||
- regionQ.getValue().equals(RegionQualifier.FAKE_REGION_VALUE))) {
- return l;
- }
- }
-
- // if no locale match the current local locale, it's likely that it is
- // the default one which is the last one.
- return count - 1;
- }
-
- return -1;
- }
-
- /**
- * Updates the theme combo.
- * This must be called from the UI thread.
- */
- private void updateThemes() {
- if (mListener == null) {
- return; // can't do anything w/o it.
- }
-
- ResourceRepository frameworkRes = mListener.getFrameworkResources(getRenderingTarget());
-
- mDisableUpdates++;
-
- try {
- if (mEditedFile != null) {
- if (mState.theme == null || mState.theme.isEmpty()
- || mListener.getIncludedWithin() != null) {
- mState.theme = null;
- getPreferredTheme();
- }
- assert mState.theme != null;
- }
-
- mThemeList.clear();
-
- ArrayList<String> themes = new ArrayList<String>();
- ResourceRepository projectRes = mListener.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 =
- mListener.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(getCurrentConfig());
-
- 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
- if (!mState.theme.startsWith(PREFIX_RESOURCE_REF)) {
- String projectStyle = STYLE_RESOURCE_PREFIX + mState.theme;
- String frameworkStyle = ANDROID_STYLE_RESOURCE_PREFIX + mState.theme;
- for (String theme : mThemeList) {
- if (theme.equals(projectStyle)) {
- mState.theme = projectStyle;
- break;
- } else if (theme.equals(frameworkStyle)) {
- mState.theme = frameworkStyle;
- break;
- }
- }
- }
-
- // TODO: Handle the case where you have a theme persisted that isn't available??
- // We could look up mState.theme and make sure it appears in the list! And if not,
- // picking one.
-
- selectTheme(mState.theme);
- } finally {
- mDisableUpdates--;
- }
- }
-
- /** Returns the preferred theme, or null */
- @Nullable
- String getPreferredTheme() {
- if (mListener == null) {
- return null;
- }
-
- IProject project = mEditedFile.getProject();
- ManifestInfo manifest = ManifestInfo.get(project);
-
- // Look up the screen size for the current state
- ScreenSize screenSize = null;
- if (mState.device != null) {
- List<State> states = mState.device.getAllStates();
- for (State state : states) {
- ScreenSizeQualifier qualifier =
- DeviceConfigHelper.getFolderConfig(state).getScreenSizeQualifier();
- screenSize = qualifier.getValue();
- break;
- }
- }
-
- // Look up the default/fallback theme to use for this project (which
- // depends on the screen size when no particular theme is specified
- // in the manifest)
- String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize);
-
- String preferred = defaultTheme;
- if (mState.theme == null) {
- // If we are rendering a layout in included context, pick the theme
- // from the outer layout instead
-
- String activity = getSelectedActivity();
- if (activity != null) {
- Map<String, String> activityThemes = manifest.getActivityThemes();
- preferred = activityThemes.get(activity);
- }
- if (preferred == null) {
- preferred = defaultTheme;
- }
- mState.theme = preferred;
- }
-
- return preferred;
- }
-
- // ---- getters for the config selection values ----
-
- public FolderConfiguration getEditedConfig() {
- return mEditedConfig;
- }
-
- 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) {
- DensityQualifier qual = mCurrentConfig.getDensityQualifier();
- 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 (mState.device != null) {
-
- State currState = mState.device.getState(mState.stateName);
- if (currState == null) {
- currState = mState.device.getDefaultState();
- }
- float dpi = (float) currState.getHardware().getScreen().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 (mState.device != null) {
-
- State currState = mState.device.getState(mState.stateName);
- if (currState == null) {
- currState = mState.device.getDefaultState();
- }
- float dpi = (float) currState.getHardware().getScreen().getYdpi();
- if (Float.isNaN(dpi) == false) {
- return dpi;
- }
- }
-
- // get the pixel density as the density.
- return getDensity().getDpiValue();
- }
-
- public Rect 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 Rect(0, 0, s2, s1);
- case LANDSCAPE:
- return new Rect(0, 0, s1, s2);
- case SQUARE:
- return new Rect(0, 0, s1, s1);
- }
- }
-
- /**
- * Returns the current theme, or null if the combo has no selection.
- *
- * @return the theme name, or null
- */
- @Nullable
- public String getThemeName() {
- String theme = getSelectedTheme();
- if (theme != null) {
- theme = ResourceHelper.styleToTheme(theme);
- }
-
- return theme;
- }
-
- @Nullable
- String getSelectedTheme() {
- return (String) mThemeCombo.getData();
- }
-
- /**
- * Returns the current device string, or null if the combo has no selection.
- *
- * @return the device name, or null
- */
- public String getDevice() {
- Device device = getSelectedDevice();
- if (device != null) {
- return device.getName();
- }
-
- return null;
- }
-
- /**
- * Returns whether the current theme selection is a project theme.
- * <p/>The returned value is meaningless if {@link #getThemeName()} returns <code>null</code>.
- * @return true for project theme, false for framework theme
- */
- public boolean isProjectTheme() {
- String theme = getSelectedTheme();
- if (theme != null) {
- assert theme.startsWith(STYLE_RESOURCE_PREFIX)
- || theme.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
-
- return ResourceHelper.isProjectStyle(theme);
- }
-
- return false;
- }
-
- @Nullable
- public IAndroidTarget getRenderingTarget() {
- return getSelectedTarget();
- }
-
- /**
- * Loads the list of {@link IAndroidTarget} and inits the UI with it.
- */
- private void initTargets() {
- mTargetList.clear();
-
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- IAndroidTarget[] targets = currentSdk.getTargets();
- IAndroidTarget match = null;
- for (int i = 0 ; i < targets.length; i++) {
- // FIXME: add check based on project minSdkVersion
- if (targets[i].hasRenderingLibrary()) {
- mTargetList.add(targets[i]);
-
- if (mRenderingTarget != null) {
- // use equals because the rendering could be from a previous SDK, so
- // it may not be the same instance.
- if (mRenderingTarget.equals(targets[i])) {
- match = targets[i];
- }
- } else if (mProjectTarget == targets[i]) {
- match = targets[i];
- }
- }
- }
-
- if (match == null) {
- selectTarget(null);
-
- // the rendering target is the same as the project.
- mRenderingTarget = mProjectTarget;
- } else {
- selectTarget(match);
-
- // set the rendering target to the new object.
- mRenderingTarget = match;
- }
- }
- }
-
- /**
- * Loads the list of {@link Device}s and inits the UI with it.
- */
- private void initDevices() {
- final Sdk sdk = Sdk.getCurrent();
- if (sdk != null) {
- mDeviceList = sdk.getDevices();
- DeviceManager manager = sdk.getDeviceManager();
- // This method can be called more than once, so avoid duplicate entries
- manager.unregisterListener(this);
- manager.registerListener(this);
- } else {
- mDeviceList = new ArrayList<Device>();
- }
-
- // fill with the devices
- if (!mDeviceList.isEmpty()) {
- Device first = mDeviceList.get(0);
- selectDevice(first);
- List<State> states = first.getAllStates();
- selectDeviceState(states.get(0));
- } else {
- selectDevice(null);
- }
- }
-
- @Override
- public void onDevicesChange() {
- final Sdk sdk = Sdk.getCurrent();
- mDeviceList = sdk.getDevices();
- Display.getDefault().asyncExec(new Runnable() {
- @Override
- public void run() {
- if (!mDeviceCombo.isDisposed()) {
- addDeviceMenuListener(mDeviceCombo);
- }
- }
- });
- }
-
- 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;
- }
-
- void selectDeviceState(@Nullable State state) {
- mOrientationCombo.setData(state);
-
- State nextState = getNextDeviceState(state);
- mOrientationCombo.setImage(getOrientationIcon(getOrientation(state),
- nextState != state));
- }
-
- State getSelectedDeviceState() {
- return (State) mOrientationCombo.getData();
- }
-
- /**
- * 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.
- */
- private boolean selectDevice(@Nullable Device device) {
- mDeviceCombo.setData(device);
- if (device != null) {
- mDeviceCombo.setText(getDeviceLabel(device, true));
- } else {
- mDeviceCombo.setText("Device");
- }
- resizeToolBar();
-
- return false;
- }
-
- @Nullable
- Device getSelectedDevice() {
- return (Device) mDeviceCombo.getData();
- }
-
- /**
- * Selects a state by name.
- * @param name the name of the state to select.
- */
- private void selectState(String name) {
- Device device = getSelectedDevice();
- State state = null;
- if (device != null) {
- state = device.getState(name);
- }
- selectDeviceState(state);
- }
-
- /**
- * Called when the selection of the device combo changes.
- * @param recomputeLayout
- */
- private void onDeviceChange(boolean recomputeLayout) {
- // because changing the content of a combo triggers a change event, respect the
- // mDisableUpdates flag
- if (mDisableUpdates > 0) {
- return;
- }
-
- String newConfigName = null;
-
- State prevState = getSelectedDeviceState();
- Device device = getSelectedDevice();
- if (mState.device != null && prevState != null && device != null) {
- // get the previous config, so that we can look for a close match
- FolderConfiguration oldConfig = DeviceConfigHelper.getFolderConfig(prevState);
- newConfigName = getClosestMatch(oldConfig, device.getAllStates());
- }
- mState.device = device;
-
- selectState(newConfigName);
-
- computeCurrentConfig();
-
- if (recomputeLayout) {
- onDeviceConfigChange();
- }
- }
-
- /**
- * Attempts to find a close state among a list
- * @param oldConfig the reference config.
- * @param states the list of states to search through
- * @return the name of the closest state match, or possibly null if no states are compatible
- * (this can only happen if the states don't have a single qualifier that is the same).
- */
- private String getClosestMatch(FolderConfiguration oldConfig, List<State> states) {
-
- // create 2 lists as we're going to go through one and put the
- // candidates in the other.
- ArrayList<State> list1 = new ArrayList<State>();
- ArrayList<State> list2 = new ArrayList<State>();
-
- list1.addAll(states);
-
- final int count = FolderConfiguration.getQualifierCount();
- for (int i = 0 ; i < count ; i++) {
- // compute the new candidate list by only taking states that have
- // the same i-th qualifier as the old state
- for (State s : list1) {
- ResourceQualifier oldQualifier = oldConfig.getQualifier(i);
-
- FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s);
- ResourceQualifier newQualifier = folderConfig.getQualifier(i);
-
- if (oldQualifier == null) {
- if (newQualifier == null) {
- list2.add(s);
- }
- } else if (oldQualifier.equals(newQualifier)) {
- list2.add(s);
- }
- }
-
- // at any moment if the new candidate list contains only one match, its name
- // is returned.
- if (list2.size() == 1) {
- return list2.get(0).getName();
- }
-
- // if the list is empty, then all the new states failed. It is considered ok, and
- // we move to the next qualifier anyway. This way, if a qualifier is different for
- // all new states 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 state and it doesn't matter,
- // we take the first one).
- if (list1.size() > 0) {
- return list1.get(0).getName();
- }
-
- return null;
- }
-
- /**
- * 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;
- }
-
- if (computeCurrentConfig() && mListener != null) {
- mListener.onConfigurationChange();
- mListener.onDevicePostChange();
- }
- }
-
- /**
- * Call back for language combo selection
- */
- private 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;
- }
-
- if (computeCurrentConfig() && mListener != null) {
- mListener.onConfigurationChange();
- }
-
- // Store locale project-wide setting
- saveRenderState();
- }
-
- /**
- * Call back for api level combo selection
- */
- private 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;
- }
-
- // tell the listener a new rendering target is being set. Need to do this before updating
- // mRenderingTarget.
- if (mListener != null && mRenderingTarget != null) {
- mListener.onRenderingTargetPreChange(mRenderingTarget);
- }
-
- mRenderingTarget = getRenderingTarget();
-
- boolean computeOk = computeCurrentConfig();
-
- // 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();
-
- // since the state is saved in computeCurrentConfig, we need to resave it since theme
- // change could have impacted it.
- saveState();
-
- if (mListener != null && mRenderingTarget != null) {
- mListener.onRenderingTargetPostChange(mRenderingTarget);
- }
-
- // Store project-wide render-target setting
- saveRenderState();
-
- if (computeOk && mListener != null) {
- mListener.onConfigurationChange();
- }
- }
-
- /**
- * Saves the current state and the current configuration
- *
- * @see #saveState()
- */
- private boolean computeCurrentConfig() {
- saveState();
-
- if (mState.device != null) {
- // get the device config from the device/state combos.
- FolderConfiguration config =
- DeviceConfigHelper.getFolderConfig(getSelectedDeviceState());
-
- // replace the config with the one from the device
- mCurrentConfig.set(config);
-
- // replace the locale qualifiers with the one coming from the locale combo
- ResourceQualifier[] localeQualifiers = getSelectedLocale();
- if (localeQualifiers != null) {
- mCurrentConfig.setLanguageQualifier(
- (LanguageQualifier)localeQualifiers[LOCALE_LANG]);
- mCurrentConfig.setRegionQualifier(
- (RegionQualifier)localeQualifiers[LOCALE_REGION]);
- }
-
- // Replace the UiMode with the selected one, if one is selected
- UiMode uiMode = getSelectedUiMode();
- if (uiMode != null) {
- mCurrentConfig.setUiModeQualifier(new UiModeQualifier(uiMode));
- }
-
- // Replace the NightMode with the selected one, if one is selected
- NightMode night = getSelectedNightMode();
- if (night != null) {
- mCurrentConfig.setNightModeQualifier(new NightModeQualifier(night));
- }
-
- // replace the API level by the selection of the combo
- IAndroidTarget target = getRenderingTarget();
- if (target == null) {
- target = mProjectTarget;
- }
- if (target != null) {
- mCurrentConfig.setVersionQualifier(
- new VersionQualifier(target.getVersion().getApiLevel()));
- }
-
- return true;
- }
-
- return false;
- }
-
- void onThemeChange() {
- saveState();
-
- String theme = getSelectedTheme();
- if (theme != null && mListener != null) {
- mListener.onThemeChange();
- }
- }
-
- /**
- * 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 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) == false) {
- 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;
- }
-
- /**
- * Checks whether the current edited file is the best match for a given config.
- * <p/>
- * This tests against other versions of the same layout in the project.
- * <p/>
- * The given config must be compatible with the current edited file.
- * @param config the config to test.
- * @return true if the current edited file is the best match in the project for the
- * given config.
- */
- private boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
- ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(),
- ResourceFolderType.LAYOUT, config);
-
- if (match != null) {
- return match.getFile().equals(mEditedFile);
- } else {
- // if we stop here that means the current file is not even a match!
- AdtPlugin.log(IStatus.ERROR, "Current file is not a match for the given config.");
- }
-
- return false;
- }
-
- /**
- * 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);
- mEditedConfig = null;
- onXmlModelLoaded();
- }
-
- /**
- * 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 (mEditedConfig == null) {
- // Startup; ignore
- return false;
- }
-
- boolean localeChanged = false;
- boolean renderTargetChanged = false;
-
- // When a page is re-activated, force the toggles to reflect the current project
- // state
-
- Pair<ResourceQualifier[], IAndroidTarget> pair = loadRenderState();
-
- // Only sync the locale if this layout is not already a locale-specific layout!
- if (pair != null && !isLocaleSpecificLayout()) {
- ResourceQualifier[] locale = pair.getFirst();
- if (locale != null) {
- localeChanged = setLocaleCombo(locale[0], locale[1]);
- }
- }
-
- // Sync render target
- IAndroidTarget target = pair != null ? pair.getSecond() : getSelectedTarget();
- if (target != null) {
- if (getRenderingTarget() != target) {
- selectTarget(target);
- renderTargetChanged = true;
- }
- }
-
- if (!renderTargetChanged && !localeChanged) {
- 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.
-
- if (renderTargetChanged) {
- if (mListener != null && mRenderingTarget != null) {
- mListener.onRenderingTargetPreChange(mRenderingTarget);
- }
- mRenderingTarget = target;
- }
-
- // Compute the new configuration; we want to do this both for locale changes
- // and for render targets.
- boolean computeOk = computeCurrentConfig();
-
- 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 (mListener != null && mRenderingTarget != null) {
- mListener.onRenderingTargetPostChange(mRenderingTarget);
- }
- }
-
- // For both locale and render target changes
- if (computeOk && mListener != null) {
- mListener.onConfigurationChange();
- }
-
- return true;
- }
-
- /**
- * Loads the render state (the locale and the render target, which are shared among
- * all the layouts meaning that changing it in one will change it in all) and returns
- * the current project-wide locale and render target to be used.
- *
- * @return a pair of locale resource qualifiers and render target
- */
- private Pair<ResourceQualifier[], IAndroidTarget> loadRenderState() {
- IProject project = mEditedFile.getProject();
- if (!project.isAccessible()) {
- return null;
- }
-
- try {
- String data = project.getPersistentProperty(NAME_RENDER_STATE);
- if (data != null) {
- ResourceQualifier[] locale = null;
- IAndroidTarget target = null;
-
- String[] values = data.split(SEP);
- if (values.length == 2) {
- locale = new ResourceQualifier[2];
- String locales[] = values[0].split(SEP_LOCALE);
- if (locales.length >= 2) {
- if (locales[0].length() > 0) {
- locale[0] = new LanguageQualifier(locales[0]);
- }
- if (locales[1].length() > 0) {
- locale[1] = new RegionQualifier(locales[1]);
- }
- }
- target = stringToTarget(values[1]);
-
- // See if we should "correct" the rendering target to a better version.
- // If you're using a pre-release version of the render target, and a
- // final release is available and installed, we should switch to that
- // one instead.
- if (target != null) {
- AndroidVersion version = target.getVersion();
- if (version.getCodename() != null && mTargetList != null) {
- int targetApiLevel = version.getApiLevel() + 1;
- for (IAndroidTarget t : mTargetList) {
- if (t.getVersion().getApiLevel() == targetApiLevel
- && t.isPlatform()) {
- target = t;
- break;
- }
- }
- }
- }
- }
-
- return Pair.of(locale, target);
- }
-
- ResourceQualifier[] any = new ResourceQualifier[] {
- new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
- new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)
- };
-
- return Pair.of(any, findDefaultRenderTarget());
- } catch (CoreException e) {
- AdtPlugin.log(e, null);
- }
-
- return null;
- }
-
- /** Returns true if the current layout is locale-specific */
- private boolean isLocaleSpecificLayout() {
- return mEditedConfig == null || mEditedConfig.getLanguageQualifier() != null;
- }
-
- /**
- * Saves the render state (the current locale and render target settings) into the
- * project wide settings storage
- */
- private void saveRenderState() {
- IProject project = mEditedFile.getProject();
- try {
- ResourceQualifier[] locale = getSelectedLocale();
- IAndroidTarget target = getRenderingTarget();
-
- // Generate a persistent string from locale+target
- StringBuilder sb = new StringBuilder();
- if (locale != null) {
- if (locale[0] != null && locale[1] != null) {
- // locale[0]/[1] can be null sometimes when starting Eclipse
- sb.append(((LanguageQualifier) locale[0]).getValue());
- sb.append(SEP_LOCALE);
- sb.append(((RegionQualifier) locale[1]).getValue());
- }
- }
- sb.append(SEP);
- if (target != null) {
- sb.append(targetToString(target));
- sb.append(SEP);
- }
-
- String data = sb.toString();
- project.setPersistentProperty(NAME_RENDER_STATE, data);
- } catch (CoreException e) {
- AdtPlugin.log(e, null);
- }
- }
-
- // ---- Implements SelectionListener ----
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- if (mDisableUpdates > 0) {
- return;
- }
-
- final Object source = e.getSource();
- if (source == mOrientationCombo) {
- gotoNextState();
- }
- }
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- }
-
- /** Returns the file whose rendering is being configured by this configuration composite */
- IFile getEditedFile() {
- return mEditedFile;
- }
-
- UiMode getSelectedUiMode() {
- return mState.uiMode;
- }
-
- NightMode getSelectedNightMode() {
- return mState.night;
- }
-
- void selectNightMode(NightMode night) {
- mState.night = night;
- if (computeCurrentConfig() && mListener != null) {
- mListener.onConfigurationChange();
- }
- }
-
- void selectUiMode(UiMode uiMode) {
- mState.uiMode = uiMode;
- if (computeCurrentConfig() && mListener != null) {
- mListener.onConfigurationChange();
- }
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
new file mode 100644
index 0000000..dc64b36
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
@@ -0,0 +1,797 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.ide.common.resources.configuration.DeviceConfigHelper;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.NightModeQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.ide.common.resources.configuration.ResourceQualifier;
+import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.common.resources.configuration.UiModeQualifier;
+import com.android.ide.common.resources.configuration.VersionQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+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.Sdk;
+import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.android.resources.Density;
+import com.android.resources.NightMode;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenSize;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.util.SparseIntArray;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.ui.IEditorPart;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/** Produces matches for configurations */
+public class ConfigurationMatcher {
+ private final ConfigurationChooser mConfigChooser;
+
+ ConfigurationMatcher(ConfigurationChooser chooser) {
+ mConfigChooser = chooser;
+ }
+
+ // ---- Finding matching configurations ----
+
+ private static class ConfigBundle {
+ private final FolderConfiguration config;
+ private int localeIndex;
+ private int dockModeIndex;
+ private int nightModeIndex;
+
+ private ConfigBundle() {
+ config = new FolderConfiguration();
+ }
+
+ private ConfigBundle(ConfigBundle bundle) {
+ config = new FolderConfiguration();
+ config.set(bundle.config);
+ localeIndex = bundle.localeIndex;
+ dockModeIndex = bundle.dockModeIndex;
+ nightModeIndex = bundle.nightModeIndex;
+ }
+ }
+
+ private static class ConfigMatch {
+ final FolderConfiguration testConfig;
+ final Device device;
+ final State state;
+ final ConfigBundle bundle;
+
+ public ConfigMatch(@NonNull FolderConfiguration testConfig, @NonNull Device device,
+ @NonNull State state, @NonNull ConfigBundle bundle) {
+ this.testConfig = testConfig;
+ this.device = device;
+ this.state = state;
+ this.bundle = bundle;
+ }
+
+ @Override
+ public String toString() {
+ return device.getName() + " - " + state.getName();
+ }
+ }
+
+ /**
+ * Checks whether the current edited file is the best match for a given config.
+ * <p>
+ * This tests against other versions of the same layout in the project.
+ * <p>
+ * The given config must be compatible with the current edited file.
+ * @param config the config to test.
+ * @return true if the current edited file is the best match in the project for the
+ * given config.
+ */
+ boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
+ ProjectResources resources = mConfigChooser.getResources();
+ IFile editedFile = mConfigChooser.getEditedFile();
+ ResourceFile match = resources.getMatchingFile(editedFile.getName(),
+ ResourceFolderType.LAYOUT, config);
+
+ if (match != null) {
+ return match.getFile().equals(editedFile);
+ } else {
+ // if we stop here that means the current file is not even a match!
+ AdtPlugin.log(IStatus.ERROR, "Current file is not a match for the given config.");
+ }
+
+ return false;
+ }
+
+ /**
+ * Adapts the current device/config selection so that it's compatible with
+ * the configuration.
+ * <p>
+ * If the current selection is compatible, nothing is changed.
+ * <p>
+ * If it's not compatible, configs from the current devices are tested.
+ * <p>
+ * If none are compatible, it reverts to
+ * {@link #findAndSetCompatibleConfig(boolean)}
+ */
+ void adaptConfigSelection(boolean needBestMatch) {
+ // check the device config (ie sans locale)
+ boolean needConfigChange = true; // if still true, we need to find another config.
+ boolean currentConfigIsCompatible = false;
+ Configuration configuration = mConfigChooser.getConfiguration();
+ State selectedState = configuration.getDeviceState();
+ FolderConfiguration editedConfig = configuration.getEditedConfig();
+ if (selectedState != null) {
+ FolderConfiguration currentConfig = DeviceConfigHelper.getFolderConfig(selectedState);
+ if (currentConfig != null && editedConfig.isMatchFor(currentConfig)) {
+ currentConfigIsCompatible = true; // current config is compatible
+ if (!needBestMatch || isCurrentFileBestMatchFor(currentConfig)) {
+ needConfigChange = false;
+ }
+ }
+ }
+
+ if (needConfigChange) {
+ List<Locale> localeList = mConfigChooser.getLocaleList();
+
+ // if the current state/locale isn't a correct match, then
+ // look for another state/locale in the same device.
+ FolderConfiguration testConfig = new FolderConfiguration();
+
+ // first look in the current device.
+ State matchState = null;
+ int localeIndex = -1;
+ Device device = configuration.getDevice();
+ if (device != null) {
+ mainloop: for (State state : device.getAllStates()) {
+ testConfig.set(DeviceConfigHelper.getFolderConfig(state));
+
+ // loop on the locales.
+ for (int i = 0 ; i < localeList.size() ; i++) {
+ Locale locale = localeList.get(i);
+
+ // update the test config with the locale qualifiers
+ testConfig.setLanguageQualifier(locale.language);
+ testConfig.setRegionQualifier(locale.region);
+
+ if (editedConfig.isMatchFor(testConfig) &&
+ isCurrentFileBestMatchFor(testConfig)) {
+ matchState = state;
+ localeIndex = i;
+ break mainloop;
+ }
+ }
+ }
+ }
+
+ if (matchState != null) {
+ configuration.setDeviceState(matchState, true);
+ Locale locale = localeList.get(localeIndex);
+ configuration.setLocale(locale, true);
+ mConfigChooser.selectDeviceState(matchState);
+ mConfigChooser.selectLocale(locale);
+ configuration.syncFolderConfig();
+ } else {
+ // no match in current device with any state/locale
+ // attempt to find another device that can display this
+ // particular state.
+ findAndSetCompatibleConfig(currentConfigIsCompatible);
+ }
+ }
+ }
+
+ /**
+ * Finds a device/config that can display a configuration.
+ * <p>
+ * Once found the device and config combos are set to the config.
+ * <p>
+ * If there is no compatible configuration, a custom one is created.
+ *
+ * @param favorCurrentConfig if true, and no best match is found, don't
+ * change the current config. This must only be true if the
+ * current config is compatible.
+ */
+ void findAndSetCompatibleConfig(boolean favorCurrentConfig) {
+ List<Locale> localeList = mConfigChooser.getLocaleList();
+ List<Device> deviceList = mConfigChooser.getDeviceList();
+ Configuration configuration = mConfigChooser.getConfiguration();
+ FolderConfiguration editedConfig = configuration.getEditedConfig();
+ FolderConfiguration currentConfig = configuration.getFullConfig();
+
+ // list of compatible device/state/locale
+ List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>();
+
+ // list of actual best match (ie the file is a best match for the
+ // device/state)
+ List<ConfigMatch> bestMatches = new ArrayList<ConfigMatch>();
+
+ // get a locale that match the host locale roughly (may not be exact match on the region.)
+ int localeHostMatch = getLocaleMatch();
+
+ // build a list of combinations of non standard qualifiers to add to each device's
+ // qualifier set when testing for a match.
+ // These qualifiers are: locale, night-mode, car dock.
+ List<ConfigBundle> configBundles = new ArrayList<ConfigBundle>(200);
+
+ // If the edited file has locales, then we have to select a matching locale from
+ // the list.
+ // However, if it doesn't, we don't randomly take the first locale, we take one
+ // matching the current host locale (making sure it actually exist in the project)
+ int start, max;
+ if (editedConfig.getLanguageQualifier() != null || localeHostMatch == -1) {
+ // add all the locales
+ start = 0;
+ max = localeList.size();
+ } else {
+ // only add the locale host match
+ start = localeHostMatch;
+ max = localeHostMatch + 1; // test is <
+ }
+
+ for (int i = start ; i < max ; i++) {
+ Locale l = localeList.get(i);
+
+ ConfigBundle bundle = new ConfigBundle();
+ bundle.config.setLanguageQualifier(l.language);
+ bundle.config.setRegionQualifier(l.region);
+
+ bundle.localeIndex = i;
+ configBundles.add(bundle);
+ }
+
+ // add the dock mode to the bundle combinations.
+ addDockModeToBundles(configBundles);
+
+ // add the night mode to the bundle combinations.
+ addNightModeToBundles(configBundles);
+
+ addRenderTargetToBundles(configBundles);
+
+ for (Device device : deviceList) {
+ for (State state : device.getAllStates()) {
+
+ // loop on the list of config bundles to create full
+ // configurations.
+ FolderConfiguration stateConfig = DeviceConfigHelper.getFolderConfig(state);
+ for (ConfigBundle bundle : configBundles) {
+ // create a new config with device config
+ FolderConfiguration testConfig = new FolderConfiguration();
+ testConfig.set(stateConfig);
+
+ // add on top of it, the extra qualifiers from the bundle
+ testConfig.add(bundle.config);
+
+ if (editedConfig.isMatchFor(testConfig)) {
+ // this is a basic match. record it in case we don't
+ // find a match
+ // where the edited file is a best config.
+ anyMatches.add(new ConfigMatch(testConfig, device, state, bundle));
+
+ if (isCurrentFileBestMatchFor(testConfig)) {
+ // this is what we want.
+ bestMatches.add(new ConfigMatch(testConfig, device, state, bundle));
+ }
+ }
+ }
+ }
+ }
+
+ if (bestMatches.size() == 0) {
+ if (favorCurrentConfig) {
+ // quick check
+ if (!editedConfig.isMatchFor(currentConfig)) {
+ AdtPlugin.log(IStatus.ERROR,
+ "favorCurrentConfig can only be true if the current config is compatible");
+ }
+
+ // just display the warning
+ AdtPlugin.printErrorToConsole(mConfigChooser.getProject(),
+ String.format(
+ "'%1$s' is not a best match for any device/locale combination.",
+ editedConfig.toDisplayString()),
+ String.format(
+ "Displaying it with '%1$s'",
+ currentConfig.toDisplayString()));
+ } else if (anyMatches.size() > 0) {
+ // select the best device anyway.
+ ConfigMatch match = selectConfigMatch(anyMatches);
+ configuration.setDevice(match.device, true);
+ configuration.setDeviceState(match.state, true);
+ configuration.setLocale(localeList.get(match.bundle.localeIndex), true);
+ configuration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
+ configuration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex),
+ true);
+
+ mConfigChooser.selectDevice(configuration.getDevice());
+ mConfigChooser.selectDeviceState(configuration.getDeviceState());
+ mConfigChooser.selectLocale(configuration.getLocale());
+
+ configuration.syncFolderConfig();
+
+ // TODO: display a better warning!
+ AdtPlugin.printErrorToConsole(mConfigChooser.getProject(),
+ String.format(
+ "'%1$s' is not a best match for any device/locale combination.",
+ editedConfig.toDisplayString()),
+ String.format(
+ "Displaying it with '%1$s' which is compatible, but will " +
+ "actually be displayed with another more specific version of " +
+ "the layout.",
+ currentConfig.toDisplayString()));
+
+ } else {
+ // 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 state,
+ // and replace whatever qualifier required by the layout file.
+ }
+ } else {
+ ConfigMatch match = selectConfigMatch(bestMatches);
+ configuration.setDevice(match.device, true);
+ configuration.setDeviceState(match.state, true);
+ configuration.setLocale(localeList.get(match.bundle.localeIndex), true);
+ configuration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
+ configuration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), true);
+
+ configuration.syncFolderConfig();
+
+ mConfigChooser.selectDevice(configuration.getDevice());
+ mConfigChooser.selectDeviceState(configuration.getDeviceState());
+ mConfigChooser.selectLocale(configuration.getLocale());
+ }
+ }
+
+ private void addRenderTargetToBundles(List<ConfigBundle> configBundles) {
+ Pair<Locale, IAndroidTarget> state = Configuration.loadRenderState(mConfigChooser);
+ if (state != null) {
+ IAndroidTarget target = state.getSecond();
+ if (target != null) {
+ int apiLevel = target.getVersion().getApiLevel();
+ for (ConfigBundle bundle : configBundles) {
+ bundle.config.setVersionQualifier(
+ new VersionQualifier(apiLevel));
+ }
+ }
+ }
+ }
+
+ private void addDockModeToBundles(List<ConfigBundle> addConfig) {
+ ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>();
+
+ // loop on each item and for each, add all variations of the dock modes
+ for (ConfigBundle bundle : addConfig) {
+ int index = 0;
+ for (UiMode mode : UiMode.values()) {
+ ConfigBundle b = new ConfigBundle(bundle);
+ b.config.setUiModeQualifier(new UiModeQualifier(mode));
+ b.dockModeIndex = index++;
+ list.add(b);
+ }
+ }
+
+ addConfig.clear();
+ addConfig.addAll(list);
+ }
+
+ private void addNightModeToBundles(List<ConfigBundle> addConfig) {
+ ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>();
+
+ // loop on each item and for each, add all variations of the night modes
+ for (ConfigBundle bundle : addConfig) {
+ int index = 0;
+ for (NightMode mode : NightMode.values()) {
+ ConfigBundle b = new ConfigBundle(bundle);
+ b.config.setNightModeQualifier(new NightModeQualifier(mode));
+ b.nightModeIndex = index++;
+ list.add(b);
+ }
+ }
+
+ addConfig.clear();
+ addConfig.addAll(list);
+ }
+
+ private int getLocaleMatch() {
+ java.util.Locale defaultLocale = java.util.Locale.getDefault();
+ if (defaultLocale != null) {
+ String currentLanguage = defaultLocale.getLanguage();
+ String currentRegion = defaultLocale.getCountry();
+
+ List<Locale> localeList = mConfigChooser.getLocaleList();
+ final int count = localeList.size();
+ for (int l = 0; l < count; l++) {
+ Locale locale = localeList.get(l);
+ LanguageQualifier langQ = locale.language;
+ RegionQualifier regionQ = locale.region;
+
+ // there's always a ##/Other or ##/Any (which is the same, the region
+ // contains FAKE_REGION_VALUE). If we don't find a perfect region match
+ // we take the fake region. Since it's last in the list, this makes the
+ // test easy.
+ if (langQ.getValue().equals(currentLanguage) &&
+ (regionQ.getValue().equals(currentRegion) ||
+ regionQ.getValue().equals(RegionQualifier.FAKE_REGION_VALUE))) {
+ return l;
+ }
+ }
+
+ // if no locale match the current local locale, it's likely that it is
+ // the default one which is the last one.
+ return count - 1;
+ }
+
+ return -1;
+ }
+
+ private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) {
+ // API 11-13: look for a x-large device
+ int apiLevel = mConfigChooser.getProjectTarget().getVersion().getApiLevel();
+ if (apiLevel >= 11 && apiLevel < 14) {
+ // TODO: Maybe check the compatible-screen tag in the manifest to figure out
+ // what kind of device should be used for display.
+ Collections.sort(matches, new TabletConfigComparator());
+ } else {
+ // lets look for a high density device
+ Collections.sort(matches, new PhoneConfigComparator());
+ }
+
+ // Look at the currently active editor to see if it's a layout editor, and if so,
+ // look up its configuration and if the configuration is in our match list,
+ // use it. This means we "preserve" the current configuration when you open
+ // new layouts.
+ IEditorPart activeEditor = AdtUtils.getActiveEditor();
+ LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
+ if (delegate != null
+ // (Only do this when the two files are in the same project)
+ && delegate.getEditor().getProject() == mConfigChooser.getProject()) {
+ FolderConfiguration configuration = delegate.getGraphicalEditor().getConfiguration();
+ if (configuration != null) {
+ for (ConfigMatch match : matches) {
+ if (configuration.equals(match.testConfig)) {
+ return match;
+ }
+ }
+ }
+ }
+
+ // the list has been sorted so that the first item is the best config
+ return matches.get(0);
+ }
+
+ /** Return the default render target to use, or null if no strong preference */
+ @Nullable
+ static IAndroidTarget findDefaultRenderTarget(@NonNull IProject project) {
+ // Default to layoutlib version 5
+ Sdk current = Sdk.getCurrent();
+ if (current != null) {
+ IAndroidTarget projectTarget = current.getTarget(project);
+ int minProjectApi = Integer.MAX_VALUE;
+ if (projectTarget != null) {
+ if (!projectTarget.isPlatform() && projectTarget.hasRenderingLibrary()) {
+ // Renderable non-platform targets are all going to be adequate (they
+ // will have at least version 5 of layoutlib) so use the project
+ // target as the render target.
+ return projectTarget;
+ }
+
+ if (projectTarget.getVersion().isPreview()
+ && projectTarget.hasRenderingLibrary()) {
+ // If the project target is a preview version, then just use it
+ return projectTarget;
+ }
+
+ minProjectApi = projectTarget.getVersion().getApiLevel();
+ }
+
+ // We want to pick a render target that contains at least version 5 (and
+ // preferably version 6) of the layout library. To do this, we go through the
+ // targets and pick the -smallest- API level that is both simultaneously at
+ // least as big as the project API level, and supports layoutlib level 5+.
+ IAndroidTarget best = null;
+ int bestApiLevel = Integer.MAX_VALUE;
+
+ for (IAndroidTarget target : current.getTargets()) {
+ // Non-platform targets are not chosen as the default render target
+ if (!target.isPlatform()) {
+ continue;
+ }
+
+ int apiLevel = target.getVersion().getApiLevel();
+
+ // Ignore targets that have a lower API level than the minimum project
+ // API level:
+ if (apiLevel < minProjectApi) {
+ continue;
+ }
+
+ // Look up the layout lib API level. This property is new so it will only
+ // be defined for version 6 or higher, which means non-null is adequate
+ // to see if this target is eligible:
+ String property = target.getProperty(PkgProps.LAYOUTLIB_API);
+ // In addition, Android 3.0 with API level 11 had version 5.0 which is adequate:
+ if (property != null || apiLevel >= 11) {
+ if (apiLevel < bestApiLevel) {
+ bestApiLevel = apiLevel;
+ best = target;
+ }
+ }
+ }
+
+ return best;
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to find a close state among a list
+ *
+ * @param oldConfig the reference config.
+ * @param states the list of states to search through
+ * @return the name of the closest state match, or possibly null if no states are compatible
+ * (this can only happen if the states don't have a single qualifier that is the same).
+ */
+ @Nullable
+ static String getClosestMatch(@NonNull FolderConfiguration oldConfig,
+ @NonNull List<State> states) {
+
+ // create 2 lists as we're going to go through one and put the
+ // candidates in the other.
+ List<State> list1 = new ArrayList<State>(states.size());
+ List<State> list2 = new ArrayList<State>(states.size());
+
+ list1.addAll(states);
+
+ final int count = FolderConfiguration.getQualifierCount();
+ for (int i = 0 ; i < count ; i++) {
+ // compute the new candidate list by only taking states that have
+ // the same i-th qualifier as the old state
+ for (State s : list1) {
+ ResourceQualifier oldQualifier = oldConfig.getQualifier(i);
+
+ FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s);
+ ResourceQualifier newQualifier =
+ folderConfig != null ? folderConfig.getQualifier(i) : null;
+
+ if (oldQualifier == null) {
+ if (newQualifier == null) {
+ list2.add(s);
+ }
+ } else if (oldQualifier.equals(newQualifier)) {
+ list2.add(s);
+ }
+ }
+
+ // at any moment if the new candidate list contains only one match, its name
+ // is returned.
+ if (list2.size() == 1) {
+ return list2.get(0).getName();
+ }
+
+ // if the list is empty, then all the new states failed. It is considered ok, and
+ // we move to the next qualifier anyway. This way, if a qualifier is different for
+ // all new states 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 state and it doesn't matter,
+ // we take the first one).
+ if (list1.size() > 0) {
+ return list1.get(0).getName();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the layout {@link IFile} which best matches the configuration
+ * selected in the given configuration chooser.
+ *
+ * @param chooser the associated configuration chooser holding project state
+ * @return the file which best matches the settings
+ */
+ @Nullable
+ public static IFile getBestFileMatch(ConfigurationChooser chooser) {
+ // get the resources of the file's project.
+ ResourceManager manager = ResourceManager.getInstance();
+ ProjectResources resources = manager.getProjectResources(chooser.getProject());
+ if (resources == null) {
+ return null;
+ }
+
+ // From the resources, look for a matching file
+ String name = chooser.getEditedFile().getName();
+ FolderConfiguration config = chooser.getConfiguration().getFullConfig();
+ ResourceFile match = resources.getMatchingFile(name, ResourceFolderType.LAYOUT, config);
+
+ if (match != null) {
+ // In Eclipse, the match's file is always an instance of IFileWrapper
+ return ((IFileWrapper) match.getFile()).getIFile();
+ }
+
+ return null;
+ }
+
+ /**
+ * Note: this comparator imposes orderings that are inconsistent with equals.
+ */
+ private static class TabletConfigComparator implements Comparator<ConfigMatch> {
+ @Override
+ public int compare(ConfigMatch o1, ConfigMatch o2) {
+ FolderConfiguration config1 = o1 != null ? o1.testConfig : null;
+ FolderConfiguration config2 = o2 != null ? o2.testConfig : null;
+ if (config1 == null) {
+ if (config2 == null) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else if (config2 == null) {
+ return 1;
+ }
+
+ ScreenSizeQualifier size1 = config1.getScreenSizeQualifier();
+ ScreenSizeQualifier size2 = config2.getScreenSizeQualifier();
+ ScreenSize ss1 = size1 != null ? size1.getValue() : ScreenSize.NORMAL;
+ ScreenSize ss2 = size2 != null ? size2.getValue() : ScreenSize.NORMAL;
+
+ // X-LARGE is better than all others (which are considered identical)
+ // if both X-LARGE, then LANDSCAPE is better than all others (which are identical)
+
+ if (ss1 == ScreenSize.XLARGE) {
+ if (ss2 == ScreenSize.XLARGE) {
+ ScreenOrientationQualifier orientation1 =
+ config1.getScreenOrientationQualifier();
+ ScreenOrientation so1 = orientation1.getValue();
+ if (so1 == null) {
+ so1 = ScreenOrientation.PORTRAIT;
+ }
+ ScreenOrientationQualifier orientation2 =
+ config2.getScreenOrientationQualifier();
+ ScreenOrientation so2 = orientation2.getValue();
+ if (so2 == null) {
+ so2 = ScreenOrientation.PORTRAIT;
+ }
+
+ if (so1 == ScreenOrientation.LANDSCAPE) {
+ if (so2 == ScreenOrientation.LANDSCAPE) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else if (so2 == ScreenOrientation.LANDSCAPE) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ } else if (ss2 == ScreenSize.XLARGE) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Note: this comparator imposes orderings that are inconsistent with equals.
+ */
+ private static class PhoneConfigComparator implements Comparator<ConfigMatch> {
+
+ private SparseIntArray mDensitySort = new SparseIntArray(4);
+
+ public PhoneConfigComparator() {
+ // put the sort order for the density.
+ mDensitySort.put(Density.HIGH.getDpiValue(), 1);
+ mDensitySort.put(Density.MEDIUM.getDpiValue(), 2);
+ mDensitySort.put(Density.XHIGH.getDpiValue(), 3);
+ mDensitySort.put(Density.LOW.getDpiValue(), 4);
+ }
+
+ @Override
+ public int compare(ConfigMatch o1, ConfigMatch o2) {
+ FolderConfiguration config1 = o1 != null ? o1.testConfig : null;
+ FolderConfiguration config2 = o2 != null ? o2.testConfig : null;
+ if (config1 == null) {
+ if (config2 == null) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else if (config2 == null) {
+ return 1;
+ }
+
+ int dpi1 = Density.DEFAULT_DENSITY;
+ int dpi2 = Density.DEFAULT_DENSITY;
+
+ DensityQualifier dpiQualifier1 = config1.getDensityQualifier();
+ if (dpiQualifier1 != null) {
+ Density value = dpiQualifier1.getValue();
+ dpi1 = value != null ? value.getDpiValue() : Density.DEFAULT_DENSITY;
+ }
+ dpi1 = mDensitySort.get(dpi1, 100 /* valueIfKeyNotFound*/);
+
+ DensityQualifier dpiQualifier2 = config2.getDensityQualifier();
+ if (dpiQualifier2 != null) {
+ Density value = dpiQualifier2.getValue();
+ dpi2 = value != null ? value.getDpiValue() : Density.DEFAULT_DENSITY;
+ }
+ dpi2 = mDensitySort.get(dpi2, 100 /* valueIfKeyNotFound*/);
+
+ if (dpi1 == dpi2) {
+ // portrait is better
+ ScreenOrientation so1 = ScreenOrientation.PORTRAIT;
+ ScreenOrientationQualifier orientationQualifier1 =
+ config1.getScreenOrientationQualifier();
+ if (orientationQualifier1 != null) {
+ so1 = orientationQualifier1.getValue();
+ if (so1 == null) {
+ so1 = ScreenOrientation.PORTRAIT;
+ }
+ }
+ ScreenOrientation so2 = ScreenOrientation.PORTRAIT;
+ ScreenOrientationQualifier orientationQualifier2 =
+ config2.getScreenOrientationQualifier();
+ if (orientationQualifier2 != null) {
+ so2 = orientationQualifier2.getValue();
+ if (so2 == null) {
+ so2 = ScreenOrientation.PORTRAIT;
+ }
+ }
+
+ if (so1 == ScreenOrientation.PORTRAIT) {
+ if (so2 == ScreenOrientation.PORTRAIT) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else if (so2 == ScreenOrientation.PORTRAIT) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ return dpi1 - dpi2;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
new file mode 100644
index 0000000..30f7dc2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
@@ -0,0 +1,157 @@
+/*
+ * 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.FD_RES_LAYOUT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceFolder;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.PartInitException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@linkplain ConfigurationMenuListener} class is responsible for
+ * generating the configuration menu in the {@link ConfigurationChooser}.
+ */
+class ConfigurationMenuListener extends SelectionAdapter {
+ private static final String ICON_NEW_CONFIG = "newConfig"; //$NON-NLS-1$
+ private static final int ACTION_SELECT_CONFIG = 1;
+ private static final int ACTION_CREATE_CONFIG_FILE = 2;
+
+ private final ConfigurationChooser mConfigChooser;
+ private final int mAction;
+ private final IFile mResource;
+
+ ConfigurationMenuListener(
+ @NonNull ConfigurationChooser configChooser,
+ int action,
+ @Nullable IFile resource) {
+ mConfigChooser = configChooser;
+ mAction = action;
+ mResource = resource;
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ switch (mAction) {
+ case ACTION_SELECT_CONFIG: {
+ try {
+ AdtPlugin.openFile(mResource, null, false);
+ } catch (PartInitException ex) {
+ AdtPlugin.log(ex, null);
+ }
+ break;
+ }
+ case ACTION_CREATE_CONFIG_FILE: {
+ ConfigurationClient client = mConfigChooser.getClient();
+ if (client != null) {
+ client.createConfigFile();
+ }
+ break;
+ }
+ default: assert false : mAction;
+ }
+ }
+
+ static void show(ConfigurationChooser chooser, ToolItem combo) {
+ Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+
+ // Compute the set of layout files defining this layout resource
+ IFile file = chooser.getEditedFile();
+ String name = file.getName();
+ IContainer resFolder = file.getParent().getParent();
+ List<IFile> variations = new ArrayList<IFile>();
+ try {
+ for (IResource resource : resFolder.members()) {
+ if (resource.getName().startsWith(FD_RES_LAYOUT)
+ && resource instanceof IContainer) {
+ IContainer layoutFolder = (IContainer) resource;
+ IResource variation = layoutFolder.findMember(name);
+ if (variation instanceof IFile) {
+ variations.add((IFile) variation);
+ }
+ }
+ }
+ } catch (CoreException e1) {
+ AdtPlugin.log(e1, null);
+ }
+
+ ResourceManager manager = ResourceManager.getInstance();
+ for (final IFile resource : variations) {
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+
+ IFolder parent = (IFolder) resource.getParent();
+ ResourceFolder parentResource = manager.getResourceFolder(parent);
+ FolderConfiguration configuration = parentResource.getConfiguration();
+ String title = configuration.toDisplayString();
+ item.setText(title);
+
+ boolean selected = file.equals(resource);
+ if (selected) {
+ item.setSelection(true);
+ item.setEnabled(false);
+ }
+
+ item.addSelectionListener(new ConfigurationMenuListener(chooser,
+ ACTION_SELECT_CONFIG, resource));
+ }
+
+ Configuration configuration = chooser.getConfiguration();
+ if (!configuration.getEditedConfig().equals(configuration.getFullConfig())) {
+ if (variations.size() > 0) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ // Add action for creating a new configuration
+ MenuItem item = new MenuItem(menu, SWT.PUSH);
+ item.setText("Create New...");
+ item.setImage(IconFactory.getInstance().getIcon(ICON_NEW_CONFIG));
+ //item.setToolTipText("Duplicate: Create new configuration for this layout");
+
+ item.addSelectionListener(
+ new ConfigurationMenuListener(chooser, ACTION_CREATE_CONFIG_FILE, null));
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java
new file mode 100644
index 0000000..8707c0e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java
@@ -0,0 +1,126 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.internal.avd.AvdInfo;
+import com.android.sdklib.internal.avd.AvdManager;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * The {@linkplain DeviceMenuListener} class is responsible for generating the device
+ * menu in the {@link ConfigurationChooser}.
+ */
+class DeviceMenuListener extends SelectionAdapter {
+ private final ConfigurationChooser mConfigChooser;
+ private final Device mDevice;
+
+ DeviceMenuListener(
+ @NonNull ConfigurationChooser configChooser,
+ @Nullable Device device) {
+ mConfigChooser = configChooser;
+ mDevice = device;
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ mConfigChooser.selectDevice(mDevice);
+ mConfigChooser.onDeviceChange();
+ }
+
+ static void show(final ConfigurationChooser chooser, ToolItem combo) {
+ Configuration configuration = chooser.getConfiguration();
+ Device current = configuration.getDevice();
+ Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+
+ AvdManager avdManager = Sdk.getCurrent().getAvdManager();
+ AvdInfo[] avds = avdManager.getValidAvds();
+ List<Device> deviceList = chooser.getDeviceList();
+ boolean separatorNeeded = false;
+ for (AvdInfo avd : avds) {
+ for (Device device : deviceList) {
+ if (device.getManufacturer().equals(avd.getDeviceManufacturer())
+ && device.getName().equals(avd.getDeviceName())) {
+ separatorNeeded = true;
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(avd.getName());
+ item.setSelection(current == device);
+
+ item.addSelectionListener(new DeviceMenuListener(chooser, device));
+ }
+ }
+ }
+
+ if (separatorNeeded) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ // Group the devices by manufacturer, then put them in the menu
+ if (!deviceList.isEmpty()) {
+ Map<String, List<Device>> manufacturers = new TreeMap<String, List<Device>>();
+ for (Device device : deviceList) {
+ List<Device> devices;
+ if (manufacturers.containsKey(device.getManufacturer())) {
+ devices = manufacturers.get(device.getManufacturer());
+ } else {
+ devices = new ArrayList<Device>();
+ manufacturers.put(device.getManufacturer(), devices);
+ }
+ devices.add(device);
+ }
+ for (List<Device> devices : manufacturers.values()) {
+ Menu manufacturerMenu = menu;
+ if (manufacturers.size() > 1) {
+ MenuItem item = new MenuItem(menu, SWT.CASCADE);
+ item.setText(devices.get(0).getManufacturer());
+ manufacturerMenu = new Menu(menu);
+ item.setMenu(manufacturerMenu);
+ }
+ for (final Device d : devices) {
+ MenuItem deviceItem = new MenuItem(manufacturerMenu, SWT.CHECK);
+ deviceItem.setText(d.getName());
+ deviceItem.setSelection(current == d);
+
+ deviceItem.addSelectionListener(new DeviceMenuListener(chooser, d));
+ }
+ }
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java
index 82bd054..97ff668 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java
@@ -50,6 +50,7 @@ public final class LayoutCreatorDialog extends GridDialog {
/**
* Creates a dialog, and init the UI from a {@link FolderConfiguration}.
* @param parentShell the parent {@link Shell}.
+ * @param fileName the filename associated with the configuration
* @param config The starting configuration.
*/
public LayoutCreatorDialog(Shell parentShell, String fileName, FolderConfiguration config) {
@@ -127,6 +128,11 @@ public final class LayoutCreatorDialog extends GridDialog {
resetStatus();
}
+ /**
+ * Sets the edited configuration on the given configuration parameter
+ *
+ * @param config the configuration to apply the current edits to
+ */
public void getConfiguration(FolderConfiguration config) {
config.set(mConfig);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Locale.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Locale.java
new file mode 100644
index 0000000..157c8c2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Locale.java
@@ -0,0 +1,169 @@
+/*
+ * 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.ide.common.resources.configuration.LanguageQualifier.FAKE_LANG_VALUE;
+import static com.android.ide.common.resources.configuration.RegionQualifier.FAKE_REGION_VALUE;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.google.common.base.Objects;
+
+import org.eclipse.swt.graphics.Image;
+
+/** A language,region pair */
+public class Locale {
+ /** A special marker region qualifier representing any region */
+ public static final RegionQualifier ANY_REGION = new RegionQualifier(FAKE_REGION_VALUE);
+
+ /** A special marker language qualifier representing any language */
+ public static final LanguageQualifier ANY_LANGUAGE = new LanguageQualifier(FAKE_LANG_VALUE);
+
+ /** A locale which matches any language and region */
+ public static final Locale ANY = new Locale(ANY_LANGUAGE, ANY_REGION);
+
+ /** The language qualifier, or {@link #ANY_LANGUAGE} if this locale matches any language */
+ @NonNull
+ public final LanguageQualifier language;
+
+ /** The language qualifier, or {@link #ANY_REGION} if this locale matches any region */
+ @NonNull
+ public final RegionQualifier region;
+
+ /**
+ * Constructs a new {@linkplain Locale} matching a given language in a given locale.
+ *
+ * @param language the language
+ * @param region the region
+ */
+ private Locale(@NonNull LanguageQualifier language, @NonNull RegionQualifier region) {
+ if (language.getValue().equals(FAKE_LANG_VALUE)) {
+ language = ANY_LANGUAGE;
+ }
+ if (region.getValue().equals(FAKE_REGION_VALUE)) {
+ region = ANY_REGION;
+ }
+ this.language = language;
+ this.region = region;
+ }
+
+ /**
+ * Constructs a new {@linkplain Locale} matching a given language in a given specific locale.
+ *
+ * @param language the language
+ * @param region the region
+ * @return a locale with the given language and region
+ */
+ @NonNull
+ public static Locale create(
+ @NonNull LanguageQualifier language,
+ @NonNull RegionQualifier region) {
+ return new Locale(language, region);
+ }
+
+ /**
+ * Constructs a new {@linkplain Locale} for the given language, matching any regions.
+ *
+ * @param language the language
+ * @return a locale with the given language and region
+ */
+ public static Locale create(@NonNull LanguageQualifier language) {
+ return new Locale(language, ANY_REGION);
+ }
+
+ /**
+ * Returns a flag image to use for this locale
+ *
+ * @return a flag image, or a default globe icon
+ */
+ @NonNull
+ public Image getFlagImage() {
+ Image image = null;
+ String languageCode = hasLanguage() ? language.getValue() : null;
+ String regionCode = hasRegion() ? region.getValue() : null;
+ LocaleManager icons = LocaleManager.get();
+ if (languageCode == null && regionCode == null) {
+ return LocaleManager.getGlobeIcon();
+ } else {
+ image = icons.getFlag(languageCode, regionCode);
+ if (image == null) {
+ image = LocaleManager.getEmptyIcon();
+ }
+
+ return image;
+ }
+ }
+
+ /**
+ * Returns true if this locale specifies a specific language. This is true
+ * for all locales except {@link #ANY}.
+ *
+ * @return true if this locale specifies a specific language
+ */
+ public boolean hasLanguage() {
+ return language != ANY_LANGUAGE;
+ }
+
+ /**
+ * Returns true if this locale specifies a specific region
+ *
+ * @return true if this locale specifies a region
+ */
+ public boolean hasRegion() {
+ return region != ANY_REGION;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((language == null) ? 0 : language.hashCode());
+ result = prime * result + ((region == null) ? 0 : region.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Locale other = (Locale) obj;
+ if (language == null) {
+ if (other.language != null)
+ return false;
+ } else if (!language.equals(other.language))
+ return false;
+ if (region == null) {
+ if (other.region != null)
+ return false;
+ } else if (!region.equals(other.region))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).omitNullValues()
+ .addValue(language.getValue())
+ .addValue(region.getValue())
+ .toString();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java
new file mode 100644
index 0000000..e85f21d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.AddTranslationDialog;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.List;
+
+/**
+ * The {@linkplain LocaleMenuListener} class is responsible for generating the locale
+ * menu in the {@link ConfigurationChooser}.
+ */
+class LocaleMenuListener extends SelectionAdapter {
+ private static final int ACTION_SET_LOCALE = 1;
+ private static final int ACTION_ADD_TRANSLATION = 2;
+
+ private final ConfigurationChooser mConfigChooser;
+ private final int mAction;
+ private final Locale mLocale;
+
+ LocaleMenuListener(
+ @NonNull ConfigurationChooser configChooser,
+ int action,
+ @Nullable Locale locale) {
+ mConfigChooser = configChooser;
+ mAction = action;
+ mLocale = locale;
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ switch (mAction) {
+ case ACTION_SET_LOCALE: {
+ mConfigChooser.selectLocale(mLocale);
+ mConfigChooser.onLocaleChange();
+ break;
+ }
+ case ACTION_ADD_TRANSLATION: {
+ IProject project = mConfigChooser.getProject();
+ Shell shell = mConfigChooser.getShell();
+ AddTranslationDialog dialog = new AddTranslationDialog(shell, project);
+ dialog.open();
+ break;
+ }
+ default: assert false : mAction;
+ }
+ }
+
+ static void show(final ConfigurationChooser chooser, ToolItem combo) {
+ Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+ Configuration configuration = chooser.getConfiguration();
+ List<Locale> locales = chooser.getLocaleList();
+ Locale current = configuration.getLocale();
+
+ for (Locale locale : locales) {
+ String title = ConfigurationChooser.getLocaleLabel(chooser, locale, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+ Image image = locale.getFlagImage();
+ item.setImage(image);
+
+ boolean selected = current == locale;
+ if (selected) {
+ item.setSelection(true);
+ }
+
+ LocaleMenuListener listener = new LocaleMenuListener(chooser, ACTION_SET_LOCALE,
+ locale);
+ item.addSelectionListener(listener);
+ }
+
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+ MenuItem item = new MenuItem(menu, SWT.PUSH);
+ item.setText("Add New Translation...");
+ LocaleMenuListener listener = new LocaleMenuListener(chooser,
+ ACTION_ADD_TRANSLATION, null);
+ item.addSelectionListener(listener);
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
index 0898cdf..5cad29a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
@@ -45,43 +45,46 @@ class OrientationMenuAction extends SubmenuAction {
private static final int MENU_NIGHTMODE = 1;
private static final int MENU_UIMODE = 2;
- private final ConfigurationComposite mConfiguration;
+ private final ConfigurationChooser mConfigChooser;
/** Type of menu; one of the constants {@link #MENU_NIGHTMODE} etc */
private final int mType;
- OrientationMenuAction(int type, String title, ConfigurationComposite configuration) {
+ OrientationMenuAction(int type, String title, ConfigurationChooser configuration) {
super(title);
mType = type;
- mConfiguration = configuration;
+ mConfigChooser = configuration;
}
- static void showMenu(ConfigurationComposite configuration, ToolItem combo) {
+ static void showMenu(ConfigurationChooser configChooser, ToolItem combo) {
MenuManager manager = new MenuManager();
- // Show toggles for all the available configurations
- State current = configuration.getSelectedDeviceState();
- Device device = configuration.getSelectedDevice();
+ // Show toggles for all the available states
+
+ Configuration configuration = configChooser.getConfiguration();
+ Device device = configuration.getDevice();
+ State current = configuration.getDeviceState();
if (device != null) {
List<State> states = device.getAllStates();
if (states.size() > 1 && current != null) {
State flip = configuration.getNextDeviceState(current);
- manager.add(new DeviceConfigAction(configuration,
- String.format("Switch to %1$s", flip.getName()), flip, false, true));
+ String flipName = flip != null ? flip.getName() : current.getName();
+ manager.add(new DeviceConfigAction(configChooser,
+ String.format("Switch to %1$s", flipName), flip, false, true));
manager.add(new Separator());
}
for (State config : states) {
- manager.add(new DeviceConfigAction(configuration, config.getName(),
+ manager.add(new DeviceConfigAction(configChooser, config.getName(),
config, config == current, false));
}
manager.add(new Separator());
}
- manager.add(new OrientationMenuAction(MENU_UIMODE, "UI Mode", configuration));
+ manager.add(new OrientationMenuAction(MENU_UIMODE, "UI Mode", configChooser));
manager.add(new Separator());
- manager.add(new OrientationMenuAction(MENU_NIGHTMODE, "Night Mode", configuration));
+ manager.add(new OrientationMenuAction(MENU_NIGHTMODE, "Night Mode", configChooser));
- Menu menu = manager.createContextMenu(configuration.getShell());
+ Menu menu = manager.createContextMenu(configChooser.getShell());
Rectangle bounds = combo.getBounds();
Point location = new Point(bounds.x, bounds.y + bounds.height);
location = combo.getParent().toDisplay(location);
@@ -93,7 +96,7 @@ class OrientationMenuAction extends SubmenuAction {
protected void addMenuItems(Menu menu) {
switch (mType) {
case MENU_NIGHTMODE: {
- NightMode selected = mConfiguration.getSelectedNightMode();
+ NightMode selected = mConfigChooser.getConfiguration().getNightMode();
for (NightMode mode : NightMode.values()) {
boolean checked = mode == selected;
SelectNightModeAction action = new SelectNightModeAction(mode, checked);
@@ -103,7 +106,7 @@ class OrientationMenuAction extends SubmenuAction {
break;
}
case MENU_UIMODE: {
- UiMode selected = mConfiguration.getSelectedUiMode();
+ UiMode selected = mConfigChooser.getConfiguration().getUiMode();
for (UiMode mode : UiMode.values()) {
boolean checked = mode == selected;
SelectUiModeAction action = new SelectUiModeAction(mode, checked);
@@ -114,6 +117,7 @@ class OrientationMenuAction extends SubmenuAction {
}
}
+
private class SelectNightModeAction extends Action {
private final NightMode mMode;
@@ -127,7 +131,9 @@ class OrientationMenuAction extends SubmenuAction {
@Override
public void run() {
- mConfiguration.selectNightMode(mMode);
+ Configuration configuration = mConfigChooser.getConfiguration();
+ configuration.setNightMode(mMode, false);
+ mConfigChooser.notifyFolderConfigChanged();
}
}
@@ -144,15 +150,16 @@ class OrientationMenuAction extends SubmenuAction {
@Override
public void run() {
- mConfiguration.selectUiMode(mMode);
+ Configuration configuration = mConfigChooser.getConfiguration();
+ configuration.setUiMode(mMode, false);
}
}
private static class DeviceConfigAction extends Action {
- private final ConfigurationComposite mConfiguration;
+ private final ConfigurationChooser mConfiguration;
private final State mState;
- private DeviceConfigAction(ConfigurationComposite configuration, String title,
+ private DeviceConfigAction(ConfigurationChooser configuration, String title,
State state, boolean checked, boolean flip) {
super(title, IAction.AS_RADIO_BUTTON);
mConfiguration = configuration;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
index a8f6504..d062849 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
@@ -27,10 +27,10 @@ import org.eclipse.jface.action.IAction;
* animation category
*/
class SelectThemeAction extends Action {
- private final ConfigurationComposite mConfiguration;
+ private final ConfigurationChooser mConfiguration;
private final String mTheme;
- public SelectThemeAction(ConfigurationComposite configuration, String title, String theme,
+ public SelectThemeAction(ConfigurationChooser configuration, String title, String theme,
boolean selected) {
super(title, IAction.AS_RADIO_BUTTON);
assert theme.startsWith(STYLE_RESOURCE_PREFIX)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java
new file mode 100644
index 0000000..cae6596
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.List;
+
+/**
+ * The {@linkplain TargetMenuListener} class is responsible for
+ * generating the rendering target menu in the {@link ConfigurationChooser}.
+ */
+class TargetMenuListener extends SelectionAdapter {
+ private final ConfigurationChooser mConfigChooser;
+ private final IAndroidTarget mTarget;
+
+ TargetMenuListener(
+ @NonNull ConfigurationChooser configChooser,
+ @Nullable IAndroidTarget target) {
+ mConfigChooser = configChooser;
+ mTarget = target;
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ mConfigChooser.selectTarget(mTarget);
+ mConfigChooser.onRenderingTargetChange();
+ }
+
+ static void show(ConfigurationChooser chooser, ToolItem combo) {
+ Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+ Configuration configuration = chooser.getConfiguration();
+ IAndroidTarget current = configuration.getTarget();
+ List<IAndroidTarget> targets = chooser.getTargetList();
+
+ for (final IAndroidTarget target : targets) {
+ String title = ConfigurationChooser.getRenderingTargetLabel(target, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+
+ boolean selected = current == target;
+ if (selected) {
+ item.setSelection(true);
+ }
+
+ item.addSelectionListener(new TargetMenuListener(chooser, target));
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java
deleted file mode 100644
index 262d317..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java
+++ /dev/null
@@ -1,163 +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 com.android.ide.eclipse.adt.AdtPlugin;
-
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
-
-/**
- * A dialog to let the user select a theme
- */
-public class ThemeChooser extends AbstractElementListSelectionDialog {
- /** The return code from the dialog for the user choosing "Clear" */
- public static final int CLEAR_RETURN_CODE = -5;
- /** The dialog button ID for the user choosing "Clear" */
- private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE;
-
- private String mCurrentResource;
- private String[] mThemes;
-
- private ThemeChooser(String[] themes, Shell parent) {
- super(parent, new LabelProvider());
- mThemes = themes;
-
- setTitle("Theme Chooser");
- setMessage(String.format("Choose a theme"));
- }
-
- @Override
- protected void createButtonsForButtonBar(Composite parent) {
- createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/);
- super.createButtonsForButtonBar(parent);
- }
-
- @Override
- protected void buttonPressed(int buttonId) {
- super.buttonPressed(buttonId);
-
- if (buttonId == CLEAR_BUTTON_ID) {
- assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL;
- setReturnCode(CLEAR_RETURN_CODE);
- close();
- }
- }
-
- private void setCurrentResource(String resource) {
- mCurrentResource = resource;
- }
-
- private String getCurrentResource() {
- return mCurrentResource;
- }
-
- @Override
- protected void computeResult() {
- computeResultFromSelection();
- }
-
- private void computeResultFromSelection() {
- if (getSelectionIndex() == -1) {
- mCurrentResource = null;
- return;
- }
-
- Object[] elements = getSelectedElements();
- if (elements.length == 1 && elements[0] instanceof String) {
- String item = (String) elements[0];
-
- mCurrentResource = item;
- }
- }
-
- @Override
- protected Control createDialogArea(Composite parent) {
- Composite top = (Composite)super.createDialogArea(parent);
-
- createMessageArea(top);
-
- createFilterText(top);
- createFilteredList(top);
-
- setupResourceList();
- selectResourceString(mCurrentResource);
-
- return top;
- }
-
- /**
- * Setups the current list.
- */
- private String[] setupResourceList() {
- setListElements(mThemes);
- fFilteredList.setEnabled(mThemes.length > 0);
-
- return mThemes;
- }
-
- /**
- * Select an item by its name, if possible.
- */
- private void selectItemName(String itemName, String[] items) {
- if (itemName == null || items == null) {
- return;
- }
- setSelection(new String[] { itemName });
- }
-
- /**
- * Select an item by its full resource string.
- * This also selects between project and system repository based on the resource string.
- */
- private void selectResourceString(String item) {
- String itemName = item;
-
- // Update the list
- String[] items = setupResourceList();
-
- // If we have a selection name, select it
- if (itemName != null) {
- selectItemName(itemName, items);
- }
- }
-
- public static String chooseResource(
- String[] themes,
- String currentTheme) {
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
- if (shell == null) {
- return null;
- }
-
- ThemeChooser dialog = new ThemeChooser(themes, shell);
- dialog.setCurrentResource(currentTheme);
-
- int result = dialog.open();
- if (result == ThemeChooser.CLEAR_RETURN_CODE) {
- return ""; //$NON-NLS-1$
- } else if (result == Window.OK) {
- return dialog.getCurrentResource();
- }
-
- return null;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
index 7d8c487..239f396 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
@@ -73,30 +73,31 @@ class ThemeMenuAction extends SubmenuAction {
private static final int MENU_DEVICE_LIGHT = 8;
private static final int MENU_ALL = 9;
- private final ConfigurationComposite mConfiguration;
+ private final ConfigurationChooser mConfigChooser;
private final List<String> mThemeList;
/** Type of menu; one of the constants {@link #MENU_ALL} etc */
private final int mType;
- ThemeMenuAction(int type, String title, ConfigurationComposite configuration,
+ ThemeMenuAction(int type, String title, ConfigurationChooser configuration,
List<String> themeList) {
super(title);
mType = type;
- mConfiguration = configuration;
+ mConfigChooser = configuration;
mThemeList = themeList;
}
- static void showThemeMenu(ConfigurationComposite configuration, ToolItem combo,
+ static void showThemeMenu(ConfigurationChooser configChooser, ToolItem combo,
List<String> themeList) {
MenuManager manager = new MenuManager();
// First show the currently selected theme (grayed out since you can't
// reselect it)
- String currentTheme = configuration.getSelectedTheme();
+ Configuration configuration = configChooser.getConfiguration();
+ String currentTheme = configuration.getTheme();
String currentName = null;
if (currentTheme != null) {
currentName = ResourceHelper.styleToTheme(currentTheme);
- SelectThemeAction action = new SelectThemeAction(configuration,
+ SelectThemeAction action = new SelectThemeAction(configChooser,
currentName,
currentTheme,
true /* selected */);
@@ -105,16 +106,16 @@ class ThemeMenuAction extends SubmenuAction {
manager.add(new Separator());
}
- String preferred = configuration.getPreferredTheme();
+ String preferred = configChooser.computePreferredTheme();
if (preferred != null && !preferred.equals(currentTheme)) {
- manager.add(new SelectThemeAction(configuration,
+ manager.add(new SelectThemeAction(configChooser,
ResourceHelper.styleToTheme(preferred),
preferred, false /* selected */));
manager.add(new Separator());
}
- IAndroidTarget target = configuration.getRenderingTarget();
- int apiLevel = target.getVersion().getApiLevel();
+ IAndroidTarget target = configuration.getTarget();
+ int apiLevel = target != null ? target.getVersion().getApiLevel() : 1;
boolean hasHolo = apiLevel >= 11; // Honeycomb
boolean hasDeviceDefault = apiLevel >= 14; // ICS
@@ -123,44 +124,44 @@ class ThemeMenuAction extends SubmenuAction {
// Theme.Holo.Wallpaper etc
manager.add(new ThemeMenuAction(MENU_PROJECT, "Project Themes",
- configuration, themeList));
+ configChooser, themeList));
manager.add(new ThemeMenuAction(MENU_MANIFEST, "Manifest Themes",
- configuration, themeList));
+ configChooser, themeList));
manager.add(new Separator());
if (hasHolo) {
manager.add(new ThemeMenuAction(MENU_HOLO, "Holo",
- configuration, themeList));
+ configChooser, themeList));
manager.add(new ThemeMenuAction(MENU_HOLO_LIGHT, "Holo.Light",
- configuration, themeList));
+ configChooser, themeList));
}
if (hasDeviceDefault) {
manager.add(new ThemeMenuAction(MENU_DEVICE, "DeviceDefault",
- configuration, themeList));
+ configChooser, themeList));
manager.add(new ThemeMenuAction(MENU_DEVICE_LIGHT, "DeviceDefault.Light",
- configuration, themeList));
+ configChooser, themeList));
}
manager.add(new ThemeMenuAction(MENU_THEME, "Theme",
- configuration, themeList));
+ configChooser, themeList));
manager.add(new ThemeMenuAction(MENU_THEME_LIGHT, "Theme.Light",
- configuration, themeList));
+ configChooser, themeList));
// TODO: Add generic types like Wallpaper, Dialog, Alert, etc here, with
// submenus for picking it within each theme category?
manager.add(new Separator());
manager.add(new ThemeMenuAction(MENU_ALL, "All",
- configuration, themeList));
+ configChooser, themeList));
if (currentTheme != null) {
assert currentName != null;
manager.add(new Separator());
String title = String.format("Open %1$s Declaration...", currentName);
- manager.add(new OpenThemeAction(title, configuration.getEditedFile(), currentTheme));
+ manager.add(new OpenThemeAction(title, configChooser.getEditedFile(), currentTheme));
}
- Menu menu = manager.createContextMenu(configuration.getShell());
+ Menu menu = manager.createContextMenu(configChooser.getShell());
Rectangle bounds = combo.getBounds();
Point location = new Point(bounds.x, bounds.y + bounds.height);
@@ -177,10 +178,11 @@ class ThemeMenuAction extends SubmenuAction {
break;
case MENU_MANIFEST: {
- IProject project = mConfiguration.getEditedFile().getProject();
+ IProject project = mConfigChooser.getEditedFile().getProject();
ManifestInfo manifest = ManifestInfo.get(project);
Map<String, String> activityThemes = manifest.getActivityThemes();
- String activity = mConfiguration.getSelectedActivity();
+ Configuration configuration = mConfigChooser.getConfiguration();
+ String activity = configuration.getActivity();
if (activity != null) {
String theme = activityThemes.get(activity);
if (theme != null) {
@@ -196,7 +198,7 @@ class ThemeMenuAction extends SubmenuAction {
}
List<String> sorted = new ArrayList<String>(allThemes);
Collections.sort(sorted);
- String current = mConfiguration.getSelectedTheme();
+ String current = configuration.getTheme();
for (String theme : sorted) {
boolean selected = theme.equals(current);
addMenuItem(menu, theme, selected);
@@ -268,19 +270,19 @@ class ThemeMenuAction extends SubmenuAction {
}
private void addMenuItems(Menu menu, List<String> themes) {
- String current = mConfiguration.getSelectedTheme();
+ String current = mConfigChooser.getConfiguration().getTheme();
for (String theme : themes) {
addMenuItem(menu, theme, theme.equals(current));
}
}
private boolean isSelectedTheme(String theme) {
- return theme.equals(mConfiguration.getSelectedTheme());
+ return theme.equals(mConfigChooser.getConfiguration().getTheme());
}
private void addMenuItem(Menu menu, String theme, boolean selected) {
String title = ResourceHelper.styleToTheme(theme);
- SelectThemeAction action = new SelectThemeAction(mConfiguration, title, theme, selected);
+ SelectThemeAction action = new SelectThemeAction(mConfigChooser, title, theme, selected);
new ActionContributionItem(action).fill(menu, -1);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java
new file mode 100644
index 0000000..f0698e6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java
@@ -0,0 +1,133 @@
+/*
+ * 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.gle2;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.resources.ResourceFolderType;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PartInitException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Job which creates a new layout file for a given configuration */
+class CreateNewConfigJob extends Job {
+ private final GraphicalEditorPart mEditor;
+ private final IFile mFromFile;
+ private final FolderConfiguration mConfig;
+
+ CreateNewConfigJob(
+ @NonNull GraphicalEditorPart editor,
+ @NonNull IFile fromFile,
+ @NonNull FolderConfiguration config) {
+ super("Create Alternate Layout");
+ mEditor = editor;
+ mFromFile = fromFile;
+ mConfig = config;
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ // get the folder name
+ String folderName = mConfig.getFolderName(ResourceFolderType.LAYOUT);
+ try {
+ // look to see if it exists.
+ // get the res folder
+ IFolder res = (IFolder) mFromFile.getParent().getParent();
+
+ IFolder newParentFolder = res.getFolder(folderName);
+ if (newParentFolder.exists()) {
+ // this should not happen since aapt would have complained
+ // before, but if one disables the automatic build, this could
+ // happen.
+ String message = String.format("File 'res/%1$s' already exists!",
+ folderName);
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
+ }
+
+ AdtUtils.ensureExists(newParentFolder);
+ final IFile file = newParentFolder.getFile(mFromFile.getName());
+
+ InputStream input = mFromFile.getContents();
+ file.create(input, false, monitor);
+ input.close();
+
+ // Ensure that the project resources updates itself to notice the new configuration.
+ // In theory, this shouldn't be necessary, but we need to make sure the
+ // resource manager knows about this immediately such that the call below
+ // to find the best configuration takes the new folder into account.
+ ResourceManager resourceManager = ResourceManager.getInstance();
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IFolder folder = root.getFolder(newParentFolder.getFullPath());
+ resourceManager.getResourceFolder(folder);
+
+ // Switch to the new file
+ Display display = mEditor.getConfigurationChooser().getDisplay();
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ // The given old layout has been forked into a new layout
+ // for a given configuration. This means that the old layout
+ // is no longer a match for the configuration, which is
+ // probably what it is still showing. We have to modify
+ // its configuration to no longer be an impossible
+ // configuration.
+ ConfigurationChooser chooser = mEditor.getConfigurationChooser();
+ chooser.onAlternateLayoutCreated();
+
+ // Finally open the new layout
+ try {
+ AdtPlugin.openFile(file, null, false);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ });
+ } catch (IOException e2) {
+ String message = String.format(
+ "Failed to create File 'res/%1$s/%2$s' : %3$s",
+ folderName, mFromFile.getName(), e2.getMessage());
+ AdtPlugin.displayError("Layout Creation", message);
+
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ message, e2);
+ } catch (CoreException e2) {
+ String message = String.format(
+ "Failed to create File 'res/%1$s/%2$s' : %3$s",
+ folderName, mFromFile.getName(), e2.getMessage());
+ AdtPlugin.displayError("Layout Creation", message);
+
+ return e2.getStatus();
+ }
+
+ return Status.OK_STATUS;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 0f1b373..726824f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -16,28 +16,31 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
-import static com.android.SdkConstants.FD_GEN_SOURCES;
+import static com.android.SdkConstants.ANDROID_PKG;
import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CONTEXT;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.FD_GEN_SOURCES;
import static com.android.SdkConstants.GRID_LAYOUT;
import static com.android.SdkConstants.SCROLL_VIEW;
import static com.android.SdkConstants.STRING_PREFIX;
import static com.android.SdkConstants.VALUE_FILL_PARENT;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
-import static com.android.SdkConstants.ANDROID_PKG;
+import static com.android.ide.eclipse.adt.AdtUtils.isUiThread;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser.NAME_CONFIG_STATE;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
-
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_OPEN;
import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.Rect;
import com.android.ide.common.layout.BaseLayoutRule;
import com.android.ide.common.rendering.LayoutLibrary;
@@ -48,7 +51,6 @@ import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.FolderConfiguration;
@@ -59,14 +61,17 @@ import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
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.LayoutReloadMonitor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationMatcher;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
@@ -76,13 +81,13 @@ import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFa
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
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.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.ide.eclipse.adt.io.IFileWrapper;
import com.android.resources.Density;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
@@ -91,7 +96,6 @@ import com.android.tools.lint.detector.api.LintUtils;
import com.android.utils.Pair;
import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
@@ -168,10 +172,6 @@ import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -196,11 +196,12 @@ import java.util.Set;
* @since GLE2
*/
public class GraphicalEditorPart extends EditorPart
- implements IPageImageProvider, INullSelectionListener, IFlyoutListener {
+ implements IPageImageProvider, INullSelectionListener, IFlyoutListener,
+ ConfigurationClient {
/*
* Useful notes:
- * To understand Drag'n'drop:
+ * To understand Drag & drop:
* http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
*
* To understand the site's selection listener, selection provider, and the
@@ -241,8 +242,8 @@ public class GraphicalEditorPart extends EditorPart
/** Reference to the file being edited. Can also be used to access the {@link IProject}. */
private IFile mEditedFile;
- /** The configuration composite at the top of the layout editor. */
- private ConfigurationComposite mConfigComposite;
+ /** The configuration chooser at the top of the layout editor. */
+ private ConfigurationChooser mConfigChooser;
/** The sash that splits the palette from the error view.
* The error view is shown only when needed. */
@@ -271,7 +272,6 @@ public class GraphicalEditorPart extends EditorPart
private ProjectCallback mProjectCallback;
private boolean mNeedsRecompute = false;
private TargetListener mTargetListener;
- private ConfigListener mConfigListener;
private ResourceResolver mResourceResolver;
private ReloadListener mReloadListener;
private int mMinSdkVersion;
@@ -291,7 +291,12 @@ public class GraphicalEditorPart extends EditorPart
*/
private boolean mActive;
- public GraphicalEditorPart(LayoutEditorDelegate editorDelegate) {
+ /**
+ * Constructs a new {@link GraphicalEditorPart}
+ *
+ * @param editorDelegate the associated XML editor delegate
+ */
+ public GraphicalEditorPart(@NonNull LayoutEditorDelegate editorDelegate) {
mEditorDelegate = editorDelegate;
setPartName("Graphical Layout");
}
@@ -341,8 +346,6 @@ public class GraphicalEditorPart extends EditorPart
parent.setLayout(gl);
gl.marginHeight = gl.marginWidth = 0;
- mConfigListener = new ConfigListener();
-
// Check whether somebody has requested an initial state for the newly opened file.
// The initial state is a serialized version of the state compatible with
// {@link ConfigurationComposite#CONFIG_STATE}.
@@ -404,9 +407,8 @@ public class GraphicalEditorPart extends EditorPart
gridLayout.marginHeight = 0;
layoutBarAndCanvas.setLayout(gridLayout);
- mConfigComposite = new ConfigurationComposite(mConfigListener, layoutBarAndCanvas,
- SWT.NONE /*SWT.BORDER*/, initialState);
- mConfigComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mConfigChooser = new ConfigurationChooser(this, layoutBarAndCanvas, initialState);
+ mConfigChooser.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this);
GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
@@ -513,12 +515,12 @@ public class GraphicalEditorPart extends EditorPart
/** Shows the embedded (within the layout editor) outline and or properties */
void showStructureViews(final boolean showOutline, final boolean showProperties,
final boolean updateLayout) {
- Display display = mConfigComposite.getDisplay();
+ Display display = mConfigChooser.getDisplay();
if (display.getThread() != Thread.currentThread()) {
display.asyncExec(new Runnable() {
@Override
public void run() {
- if (!mConfigComposite.isDisposed()) {
+ if (!mConfigChooser.isDisposed()) {
showStructureViews(showOutline, showProperties, updateLayout);
}
}
@@ -638,367 +640,236 @@ public class GraphicalEditorPart extends EditorPart
mCanvasViewer.getCanvas().getSelectionManager().select(xmlNode);
}
- /**
- * Listens to changes from the Configuration UI banner and triggers layout rendering when
- * changed. Also provide the Configuration UI with the list of resources/layout to display.
- */
- private class ConfigListener implements IConfigListener {
+ // ---- Implements ConfigurationClient ----
+ @Override
+ public void aboutToChange(int flags) {
+ if ((flags & CHANGED_RENDER_TARGET) != 0) {
+ IAndroidTarget oldTarget = mConfigChooser.getConfiguration().getTarget();
+ preRenderingTargetChangeCleanUp(oldTarget);
+ }
+ }
- /**
- * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
- * <p/>If there is no match, notify the user.
- */
- @Override
- public void onConfigurationChange() {
- mConfiguredFrameworkRes = mConfiguredProjectRes = null;
- mResourceResolver = null;
+ @Override
+ public boolean changed(int flags) {
+ mConfiguredFrameworkRes = mConfiguredProjectRes = null;
+ mResourceResolver = null;
- if (mEditedFile == null || mConfigComposite.getEditedConfig() == null) {
- return;
- }
+ if (mEditedFile == null) {
+ return true;
+ }
+
+ // Before doing the normal process, test for the following case.
+ // - the editor is being opened (or reset for a new input)
+ // - the file being opened is not the best match for any possible configuration
+ // - another random compatible config was chosen in the config composite.
+ // The result is that 'match' will not be the file being edited, but because this is not
+ // due to a config change, we should not trigger opening the actual best match (also,
+ // because the editor is still opening the MatchingStrategy woudln't answer true
+ // and the best match file would open in a different editor).
+ // So the solution is that if the editor is being created, we just call recomputeLayout
+ // without looking for a better matching layout file.
+ if (mEditorDelegate.getEditor().isCreatingPages()) {
+ recomputeLayout();
+ } else {
+ // get the resources of the file's project.
+ IFile best = ConfigurationMatcher.getBestFileMatch(mConfigChooser);
+ if (best != null) {
+ if (!best.equals(mEditedFile)) {
+ try {
+ // tell the editor that the next replacement file is due to a config
+ // change.
+ mEditorDelegate.setNewFileOnConfigChange(true);
+
+ boolean reuseEditor = AdtPrefs.getPrefs().isSharedLayoutEditor();
+ if (!reuseEditor) {
+ String data = AdtPlugin.getFileProperty(best, NAME_CONFIG_STATE);
+ if (data == null) {
+ // Not previously opened: duplicate the current state as
+ // much as possible
+ data = mConfigChooser.getConfiguration().toPersistentString();
+ AdtPlugin.setFileProperty(best, NAME_CONFIG_STATE, data);
+ }
+ }
- // Before doing the normal process, test for the following case.
- // - the editor is being opened (or reset for a new input)
- // - the file being opened is not the best match for any possible configuration
- // - another random compatible config was chosen in the config composite.
- // The result is that 'match' will not be the file being edited, but because this is not
- // due to a config change, we should not trigger opening the actual best match (also,
- // because the editor is still opening the MatchingStrategy woudln't answer true
- // and the best match file would open in a different editor).
- // So the solution is that if the editor is being created, we just call recomputeLayout
- // without looking for a better matching layout file.
- if (mEditorDelegate.getEditor().isCreatingPages()) {
- recomputeLayout();
- } else {
- // get the resources of the file's project.
- ProjectResources resources = ResourceManager.getInstance().getProjectResources(
- mEditedFile.getProject());
-
- // from the resources, look for a matching file
- ResourceFile match = null;
- if (resources != null) {
- match = resources.getMatchingFile(mEditedFile.getName(),
- ResourceFolderType.LAYOUT,
- mConfigComposite.getCurrentConfig());
- }
+ // ask the IDE to open the replacement file.
+ IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), best,
+ CommonXmlEditor.ID);
- if (match != null) {
- // since this is coming from Eclipse, this is always an instance of IFileWrapper
- IFileWrapper iFileWrapper = (IFileWrapper) match.getFile();
- IFile iFile = iFileWrapper.getIFile();
- if (iFile.equals(mEditedFile) == false) {
- try {
- // tell the editor that the next replacement file is due to a config
- // change.
- mEditorDelegate.setNewFileOnConfigChange(true);
-
- // ask the IDE to open the replacement file.
- IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), iFile);
-
- // we're done!
- return;
- } catch (PartInitException e) {
- // FIXME: do something!
- }
+ // we're done!
+ return reuseEditor;
+ } catch (PartInitException e) {
+ // FIXME: do something!
}
+ }
- // at this point, we have not opened a new file.
+ // at this point, we have not opened a new file.
- // Store the state in the current file
- mConfigComposite.storeState();
+ // Store the state in the current file
+ mConfigChooser.saveConstraints();
- // Even though the layout doesn't change, the config changed, and referenced
- // resources need to be updated.
- recomputeLayout();
- } else {
- // display the error.
- FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
- displayError(
- "No resources match the configuration\n" +
- " \n" +
- "\t%1$s\n" +
- " \n" +
- "Change the configuration or create:\n" +
- " \n" +
- "\tres/%2$s/%3$s\n" +
- " \n" +
- "You can also click the 'Create New...' item in the configuration dropdown menu above.",
- currentConfig.toDisplayString(),
- currentConfig.getFolderName(ResourceFolderType.LAYOUT),
- mEditedFile.getName());
- }
+ // Even though the layout doesn't change, the config changed, and referenced
+ // resources need to be updated.
+ recomputeLayout();
+ } else {
+ // display the error.
+ Configuration configuration = mConfigChooser.getConfiguration();
+ FolderConfiguration currentConfig = configuration.getFullConfig();
+ displayError(
+ "No resources match the configuration\n" +
+ " \n" +
+ "\t%1$s\n" +
+ " \n" +
+ "Change the configuration or create:\n" +
+ " \n" +
+ "\tres/%2$s/%3$s\n" +
+ " \n" +
+ "You can also click the 'Create New...' item in the configuration " +
+ "dropdown menu above.",
+ currentConfig.toDisplayString(),
+ currentConfig.getFolderName(ResourceFolderType.LAYOUT),
+ mEditedFile.getName());
+ }
+ }
+
+ if ((flags & CHANGED_RENDER_TARGET) != 0) {
+ Configuration configuration = mConfigChooser.getConfiguration();
+ IAndroidTarget target = configuration.getTarget();
+ Sdk current = Sdk.getCurrent();
+ if (current != null) {
+ AndroidTargetData targetData = current.getTargetData(target);
+ updateCapabilities(targetData);
+ }
+ }
+
+ if ((flags & (CHANGED_DEVICE | CHANGED_DEVICE_CONFIG)) != 0) {
+ // When the device changes, zoom the view to fit, but only up to 100% (e.g. zoom
+ // out to fit the content, or zoom back in if we were zoomed out more from the
+ // previous view, but only up to 100% such that we never blow up pixels
+ if (mActionBar.isZoomingAllowed()) {
+ getCanvasControl().setFitScale(true);
}
-
- reloadPalette();
}
- @Override
- public void onThemeChange() {
- // Store the state in the current file
- mConfigComposite.storeState();
- mResourceResolver = null;
+ reloadPalette();
- recomputeLayout();
+ return true;
+ }
- reloadPalette();
+ @Override
+ public void setActivity(@NonNull String activity) {
+ ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
+ String pkg = manifest.getPackage();
+ if (activity.startsWith(pkg) && activity.length() > pkg.length()
+ && activity.charAt(pkg.length()) == '.') {
+ activity = activity.substring(pkg.length());
}
+ CommonXmlEditor editor = getEditorDelegate().getEditor();
+ Element element = editor.getUiRootNode().getXmlDocument().getDocumentElement();
+ AdtUtils.setToolsAttribute(editor,
+ element, "Choose Activity", ATTR_CONTEXT,
+ activity, false /*reveal*/, false /*append*/);
+ }
- @Override
- public void onCreate() {
- LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(),
- mEditedFile.getName(), mConfigComposite.getCurrentConfig());
- if (dialog.open() == Window.OK) {
- final FolderConfiguration config = new FolderConfiguration();
- dialog.getConfiguration(config);
+ /**
+ * Returns a {@link ProjectResources} for the framework resources based on the current
+ * configuration selection.
+ * @return the framework resources or null if not found.
+ */
+ @Override
+ @Nullable
+ public ResourceRepository getFrameworkResources() {
+ return getFrameworkResources(getRenderingTarget());
+ }
- createAlternateLayout(config);
- }
- }
+ /**
+ * Returns a {@link ProjectResources} for the framework resources of a given
+ * target.
+ * @param target the target for which to return the framework resources.
+ * @return the framework resources or null if not found.
+ */
+ @Override
+ @Nullable
+ public ResourceRepository getFrameworkResources(@Nullable IAndroidTarget target) {
+ if (target != null) {
+ AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
- @Override
- public void onSetActivity(String activity) {
- ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
- String pkg = manifest.getPackage();
- if (activity.startsWith(pkg) && activity.length() > pkg.length()
- && activity.charAt(pkg.length()) == '.') {
- activity = activity.substring(pkg.length());
+ if (data != null) {
+ return data.getFrameworkResources();
}
- CommonXmlEditor editor = getEditorDelegate().getEditor();
- Element element = editor.getUiRootNode().getXmlDocument().getDocumentElement();
- AdtUtils.setToolsAttribute(editor,
- element, "Choose Activity", ConfigurationComposite.ATTR_CONTEXT,
- activity, false /*reveal*/, false /*append*/);
- }
-
- @Override
- public void onRenderingTargetPreChange(IAndroidTarget oldTarget) {
- preRenderingTargetChangeCleanUp(oldTarget);
}
- @Override
- public void onRenderingTargetPostChange(IAndroidTarget target) {
- AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
- updateCapabilities(targetData);
+ return null;
+ }
- mPalette.reloadPalette(target);
+ @Override
+ @Nullable
+ public ProjectResources getProjectResources() {
+ if (mEditedFile != null) {
+ ResourceManager manager = ResourceManager.getInstance();
+ return manager.getProjectResources(mEditedFile.getProject());
}
- @Override
- public Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources() {
- if (mConfiguredFrameworkRes == null && mConfigComposite != null) {
- ResourceRepository frameworkRes = getFrameworkResources();
-
- if (frameworkRes == null) {
- AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
- } else {
- // get the framework resource values based on the current config
- mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
- mConfigComposite.getCurrentConfig());
- }
- }
+ return null;
+ }
- return mConfiguredFrameworkRes;
- }
- @Override
- public Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources() {
- if (mConfiguredProjectRes == null && mConfigComposite != null) {
- ProjectResources project = getProjectResources();
+ @Override
+ @NonNull
+ public Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources() {
+ if (mConfiguredFrameworkRes == null && mConfigChooser != null) {
+ ResourceRepository frameworkRes = getFrameworkResources();
- // get the project resource values based on the current config
- mConfiguredProjectRes = project.getConfiguredResources(
- mConfigComposite.getCurrentConfig());
+ if (frameworkRes == null) {
+ AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
+ } else {
+ // get the framework resource values based on the current config
+ mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
+ mConfigChooser.getConfiguration().getFullConfig());
}
-
- return mConfiguredProjectRes;
- }
-
- /**
- * Returns a {@link ProjectResources} for the framework resources based on the current
- * configuration selection.
- * @return the framework resources or null if not found.
- */
- @Override
- public ResourceRepository getFrameworkResources() {
- return getFrameworkResources(getRenderingTarget());
}
- /**
- * Returns a {@link ProjectResources} for the framework resources of a given
- * target.
- * @param target the target for which to return the framework resources.
- * @return the framework resources or null if not found.
- */
- @Override
- public ResourceRepository getFrameworkResources(IAndroidTarget target) {
- if (target != null) {
- AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
+ return mConfiguredFrameworkRes;
+ }
- if (data != null) {
- return data.getFrameworkResources();
- }
- }
+ @Override
+ @NonNull
+ public Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources() {
+ if (mConfiguredProjectRes == null && mConfigChooser != null) {
+ ProjectResources project = getProjectResources();
- return null;
+ // get the project resource values based on the current config
+ mConfiguredProjectRes = project.getConfiguredResources(
+ mConfigChooser.getConfiguration().getFullConfig());
}
- @Override
- public ProjectResources getProjectResources() {
- if (mEditedFile != null) {
- ResourceManager manager = ResourceManager.getInstance();
- return manager.getProjectResources(mEditedFile.getProject());
- }
+ return mConfiguredProjectRes;
+ }
- return null;
+ @Override
+ public void createConfigFile() {
+ LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigChooser.getShell(),
+ mEditedFile.getName(), mConfigChooser.getConfiguration().getFullConfig());
+ if (dialog.open() != Window.OK) {
+ return;
}
- /**
- * Creates a new layout file from the specified {@link FolderConfiguration}.
- */
- private void createAlternateLayout(final FolderConfiguration config) {
- new Job("Create Alternate Resource") {
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- // get the folder name
- String folderName = config.getFolderName(ResourceFolderType.LAYOUT);
- try {
-
- // look to see if it exists.
- // get the res folder
- IFolder res = (IFolder)mEditedFile.getParent().getParent();
- String path = res.getLocation().toOSString();
-
- File newLayoutFolder = new File(path + File.separator + folderName);
- if (newLayoutFolder.isDirectory()) {
- // this should not happen since aapt would have complained
- // before, but if one disable the automatic build, this could
- // happen.
- String message = String.format("File 'res/%1$s' already exists!",
- folderName);
-
- return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
- } else if (newLayoutFolder.exists() == false) {
- // create it.
- newLayoutFolder.mkdir();
- }
-
- // now create the file
- File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +
- File.separator + mEditedFile.getName());
-
- newLayoutFile.createNewFile();
-
- InputStream input = mEditedFile.getContents();
-
- FileOutputStream fos = new FileOutputStream(newLayoutFile);
-
- byte[] data = new byte[512];
- int count;
- while ((count = input.read(data)) != -1) {
- fos.write(data, 0, count);
- }
-
- input.close();
- fos.close();
-
- // refreshes the res folder to show up the new
- // layout folder (if needed) and the file.
- // We use a progress monitor to catch the end of the refresh
- // to trigger the edit of the new file.
- res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {
- @Override
- public void done() {
- mConfigComposite.getDisplay().asyncExec(new Runnable() {
- @Override
- public void run() {
- onConfigurationChange();
- }
- });
- }
-
- @Override
- public void beginTask(String name, int totalWork) {
- // pass
- }
-
- @Override
- public void internalWorked(double work) {
- // pass
- }
-
- @Override
- public boolean isCanceled() {
- // pass
- return false;
- }
-
- @Override
- public void setCanceled(boolean value) {
- // pass
- }
-
- @Override
- public void setTaskName(String name) {
- // pass
- }
-
- @Override
- public void subTask(String name) {
- // pass
- }
+ FolderConfiguration config = new FolderConfiguration();
+ dialog.getConfiguration(config);
- @Override
- public void worked(int work) {
- // pass
- }
- });
-
- // Switch to the new file as well
- IFile file = AdtUtils.fileToIFile(newLayoutFile);
- if (file != null) {
- AdtPlugin.openFile(file, null, false);
- }
- } catch (IOException e2) {
- String message = String.format(
- "Failed to create File 'res/%1$s/%2$s' : %3$s",
- folderName, mEditedFile.getName(), e2.getMessage());
-
- AdtPlugin.displayError("Layout Creation", message);
-
- return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
- message, e2);
- } catch (CoreException e2) {
- String message = String.format(
- "Failed to create File 'res/%1$s/%2$s' : %3$s",
- folderName, mEditedFile.getName(), e2.getMessage());
-
- AdtPlugin.displayError("Layout Creation", message);
-
- return e2.getStatus();
- }
-
- return Status.OK_STATUS;
-
- }
- }.schedule();
- }
-
- /**
- * When the device changes, zoom the view to fit, but only up to 100% (e.g. zoom
- * out to fit the content, or zoom back in if we were zoomed out more from the
- * previous view, but only up to 100% such that we never blow up pixels
- */
- @Override
- public void onDevicePostChange() {
- if (mActionBar.isZoomingAllowed()) {
- getCanvasControl().setFitScale(true);
- }
- }
+ // Creates a new layout file from the specified {@link FolderConfiguration}.
+ CreateNewConfigJob job = new CreateNewConfigJob(this, mEditedFile, config);
+ job.schedule();
+ }
- @Override
- public String getIncludedWithin() {
- return mIncludedWithin != null ? mIncludedWithin.getName() : null;
- }
+ /**
+ * Returns the resource name of the file that is including this current layout, if any
+ * (may be null)
+ *
+ * @return the resource name of an including layout, or null
+ */
+ @Override
+ public Reference getIncludedWithin() {
+ return mIncludedWithin;
}
/**
@@ -1034,8 +905,8 @@ public class GraphicalEditorPart extends EditorPart
if (currentSdk != null) {
IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
if (target != null) {
- mConfigComposite.onSdkLoaded(target);
- mConfigListener.onConfigurationChange();
+ mConfigChooser.onSdkLoaded(target);
+ changed(CHANGED_FOLDER | CHANGED_RENDER_TARGET);
}
}
}
@@ -1159,7 +1030,7 @@ public class GraphicalEditorPart extends EditorPart
syncDockingState();
mActionBar.updateErrorIndicator();
- boolean changed = mConfigComposite.syncRenderState();
+ boolean changed = mConfigChooser.syncRenderState();
if (changed) {
// Will also force recomputeLayout()
return;
@@ -1236,7 +1107,7 @@ public class GraphicalEditorPart extends EditorPart
*/
public void openFile(IFile file) {
mEditedFile = file;
- mConfigComposite.setFile(mEditedFile);
+ mConfigChooser.setFile(mEditedFile);
if (mReloadListener == null) {
mReloadListener = new ReloadListener();
@@ -1273,7 +1144,7 @@ public class GraphicalEditorPart extends EditorPart
*/
public void replaceFile(IFile file) {
mEditedFile = file;
- mConfigComposite.replaceFile(mEditedFile);
+ mConfigChooser.replaceFile(mEditedFile);
computeSdkVersion();
}
@@ -1284,29 +1155,35 @@ public class GraphicalEditorPart extends EditorPart
*/
public void changeFileOnNewConfig(IFile file) {
mEditedFile = file;
- mConfigComposite.changeFileOnNewConfig(mEditedFile);
+ mConfigChooser.changeFileOnNewConfig(mEditedFile);
}
/**
* Responds to a target change for the project of the edited file
*/
public void onTargetChange() {
- AndroidTargetData targetData = mConfigComposite.onXmlModelLoaded();
+ AndroidTargetData targetData = mConfigChooser.onXmlModelLoaded();
updateCapabilities(targetData);
- mConfigListener.onConfigurationChange();
+ changed(CHANGED_FOLDER | CHANGED_RENDER_TARGET);
}
/** Updates the capabilities for the given target data (which may be null) */
private void updateCapabilities(AndroidTargetData targetData) {
if (targetData != null) {
LayoutLibrary layoutLib = targetData.getLayoutLibrary();
- if (mIncludedWithin != null && !layoutLib.supports(Capability.EMBEDDED_LAYOUT)) {
+ if (mIncludedWithin != null && !layoutLib.supports(Capability.EMBEDDED_LAYOUT)) {
showIn(null);
}
}
}
+ /**
+ * Returns the {@link CommonXmlDelegate} for this editor
+ *
+ * @return the {@link CommonXmlDelegate} for this editor
+ */
+ @NonNull
public LayoutEditorDelegate getEditorDelegate() {
return mEditorDelegate;
}
@@ -1332,6 +1209,11 @@ public class GraphicalEditorPart extends EditorPart
return null;
}
+ /**
+ * Returns the {@link UiDocumentNode} for the XML model edited by this editor
+ *
+ * @return the associated model
+ */
public UiDocumentNode getModel() {
return mEditorDelegate.getUiRootNode();
}
@@ -1361,6 +1243,9 @@ public class GraphicalEditorPart extends EditorPart
}
}
+ /**
+ * Recomputes the layout
+ */
public void recomputeLayout() {
try {
if (!ensureFileValid()) {
@@ -1401,6 +1286,9 @@ public class GraphicalEditorPart extends EditorPart
}
}
+ /**
+ * Reloads the palette
+ */
public void reloadPalette() {
if (mPalette != null) {
IAndroidTarget renderingTarget = getRenderingTarget();
@@ -1427,7 +1315,7 @@ public class GraphicalEditorPart extends EditorPart
* @return the bounds of the screen, never null
*/
public Rect getScreenBounds() {
- return mConfigComposite.getScreenBounds();
+ return mConfigChooser.getConfiguration().getScreenBounds();
}
/**
@@ -1437,7 +1325,8 @@ public class GraphicalEditorPart extends EditorPart
* @return the scale to multiple layout coordinates with to obtain the dip position
*/
public float getDipScale() {
- return Density.DEFAULT_DENSITY / (float) mConfigComposite.getDensity().getDpiValue();
+ float dpi = mConfigChooser.getConfiguration().getDensity().getDpiValue();
+ return Density.DEFAULT_DENSITY / dpi;
}
// --- private methods ---
@@ -1573,13 +1462,13 @@ public class GraphicalEditorPart extends EditorPart
return null;
}
- if (mConfigComposite.isDisposed()) {
+ if (mConfigChooser.isDisposed()) {
return null;
}
- assert mConfigComposite.getDisplay().getThread() == Thread.currentThread();
+ assert isUiThread();
// attempt to get a target from the configuration selector.
- IAndroidTarget renderingTarget = mConfigComposite.getRenderingTarget();
+ IAndroidTarget renderingTarget = mConfigChooser.getConfiguration().getTarget();
if (renderingTarget != null) {
return renderingTarget;
}
@@ -1707,19 +1596,19 @@ public class GraphicalEditorPart extends EditorPart
*/
public ResourceResolver getResourceResolver() {
if (mResourceResolver == null) {
- String theme = mConfigComposite.getThemeName();
+ String theme = mConfigChooser.getThemeName();
if (theme == null) {
displayError("Missing theme.");
return null;
}
- boolean isProjectTheme = mConfigComposite.isProjectTheme();
+ boolean isProjectTheme = mConfigChooser.getConfiguration().isProjectTheme();
Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes =
- mConfigListener.getConfiguredProjectResources();
+ getConfiguredProjectResources();
// Get the framework resources
Map<ResourceType, Map<String, ResourceValue>> frameworkResources =
- mConfigListener.getConfiguredFrameworkResources();
+ getConfiguredFrameworkResources();
if (configuredProjectRes == null) {
displayError("Missing project resources for current configuration.");
@@ -1796,10 +1685,10 @@ public class GraphicalEditorPart extends EditorPart
*/
@Override
public void reloadLayout(final ChangeFlags flags, final boolean libraryChanged) {
- if (mConfigComposite.isDisposed()) {
+ if (mConfigChooser.isDisposed()) {
return;
}
- Display display = mConfigComposite.getDisplay();
+ Display display = mConfigChooser.getDisplay();
display.asyncExec(new Runnable() {
@Override
public void run() {
@@ -1810,10 +1699,10 @@ public class GraphicalEditorPart extends EditorPart
/** Reload layout. <b>Must be called on the SWT thread</b> */
private void reloadLayoutSwt(ChangeFlags flags, boolean libraryChanged) {
- if (mConfigComposite.isDisposed()) {
+ if (mConfigChooser.isDisposed()) {
return;
}
- assert mConfigComposite.getDisplay().getThread() == Thread.currentThread();
+ assert mConfigChooser.getDisplay().getThread() == Thread.currentThread();
boolean recompute = false;
// we only care about the r class of the main project.
@@ -1836,7 +1725,7 @@ public class GraphicalEditorPart extends EditorPart
// However there's no recompute, as it could not be needed
// (for instance a new layout)
// If a resource that's not a layout changed this will trigger a recompute anyway.
- mConfigComposite.updateLocales();
+ mConfigChooser.updateLocales();
}
// if a resources was modified.
@@ -2809,23 +2698,13 @@ public class GraphicalEditorPart extends EditorPart
// Update configuration
if (file != null) {
- mConfigComposite.resetConfigFor(file);
+ mConfigChooser.resetConfigFor(file);
}
}
recomputeLayout();
}
/**
- * Returns the resource name of the file that is including this current layout, if any
- * (may be null)
- *
- * @return the resource name of an including layout, or null
- */
- public Reference getIncludedWithin() {
- return mIncludedWithin;
- }
-
- /**
* Return all resource names of a given type, either in the project or in the
* framework.
*
@@ -2851,7 +2730,7 @@ public class GraphicalEditorPart extends EditorPart
* @return the current configuration
*/
public FolderConfiguration getConfiguration() {
- return mConfigComposite.getCurrentConfig();
+ return mConfigChooser.getConfiguration().getFullConfig();
}
/**
@@ -2869,10 +2748,22 @@ public class GraphicalEditorPart extends EditorPart
return oldMinSdkVersion != mMinSdkVersion || oldTargetSdkVersion != mTargetSdkVersion;
}
- public ConfigurationComposite getConfigurationComposite() {
- return mConfigComposite;
+ /**
+ * Returns the associated configuration chooser
+ *
+ * @return the configuration chooser
+ */
+ @NonNull
+ public ConfigurationChooser getConfigurationChooser() {
+ return mConfigChooser;
}
+ /**
+ * Returns the associated layout actions bar
+ *
+ * @return the layout actions bar
+ */
+ @NonNull
public LayoutActionBar getLayoutActionBar() {
return mActionBar;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 285cba2..4368db4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -15,11 +15,9 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ID;
-
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.RuleAction;
@@ -29,7 +27,8 @@ import com.android.ide.common.api.RuleAction.Toggle;
import com.android.ide.common.layout.BaseViewRule;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
@@ -707,14 +706,14 @@ public class LayoutActionBar extends Composite {
boolean computeAndSetRealScale(boolean redraw) {
// compute average dpi of X and Y
- ConfigurationComposite config = mEditor.getConfigurationComposite();
+ ConfigurationChooser chooser = mEditor.getConfigurationChooser();
+ Configuration config = chooser.getConfiguration();
float dpi = (config.getXDpi() + config.getYDpi()) / 2.f;
// get the monitor dpi
float monitor = AdtPrefs.getPrefs().getMonitorDensity();
if (monitor == 0.f) {
- ResolutionChooserDialog dialog = new ResolutionChooserDialog(
- config.getShell());
+ ResolutionChooserDialog dialog = new ResolutionChooserDialog(chooser.getShell());
if (dialog.open() == Window.OK) {
monitor = dialog.getDensity();
AdtPrefs.getPrefs().setMonitorDensity(monitor);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 6eb5f27..86878ac 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -26,7 +26,7 @@ import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
@@ -1074,7 +1074,7 @@ public class LayoutCanvas extends Canvas {
try {
// Set initial state of a new file
// TODO: Only set rendering target portion of the state
- QualifiedName qname = ConfigurationComposite.NAME_CONFIG_STATE;
+ QualifiedName qname = ConfigurationChooser.NAME_CONFIG_STATE;
String state = AdtPlugin.getFileProperty(leavingFile, qname);
xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE,
state);
@@ -1528,10 +1528,10 @@ public class LayoutCanvas extends Canvas {
*/
public Margins getInsets(String fqcn) {
if (ViewMetadataRepository.INSETS_SUPPORTED) {
- ConfigurationComposite configComposite =
- mEditorDelegate.getGraphicalEditor().getConfigurationComposite();
+ ConfigurationChooser configComposite =
+ mEditorDelegate.getGraphicalEditor().getConfigurationChooser();
String theme = configComposite.getThemeName();
- Density density = configComposite.getDensity();
+ Density density = configComposite.getConfiguration().getDensity();
return ViewMetadataRepository.getInsets(fqcn, density, theme);
} else {
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
index a3be5cc..2fbd992 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
@@ -38,7 +38,7 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils
import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
@@ -375,9 +375,9 @@ public class PaletteControl extends Composite {
* @param target The target that has just been loaded
*/
public void reloadPalette(IAndroidTarget target) {
- ConfigurationComposite configuration = mEditor.getConfigurationComposite();
- String theme = configuration.getThemeName();
- String device = configuration.getDevice();
+ ConfigurationChooser configChooser = mEditor.getConfigurationChooser();
+ String theme = configChooser.getThemeName();
+ String device = configChooser.getDeviceName();
AndroidTargetData targetData =
target != null ? Sdk.getCurrent().getTargetData(target) : null;
if (target == mCurrentTarget && targetData == mCurrentTargetData
@@ -628,7 +628,8 @@ public class PaletteControl extends Composite {
} else if (mPaletteMode == PaletteMode.TINY_PREVIEW) {
scale = 0.5f;
}
- int dpi = mEditor.getConfigurationComposite().getDensity().getDpiValue();
+ ConfigurationChooser chooser = mEditor.getConfigurationChooser();
+ int dpi = chooser.getConfiguration().getDensity().getDpiValue();
while (dpi > 160) {
scale = scale / 2;
dpi = dpi / 2;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
index e68c47c..60e9920 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
@@ -16,12 +16,14 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.SdkConstants.DOT_PNG;
import static com.android.SdkConstants.FQCN_DATE_PICKER;
import static com.android.SdkConstants.FQCN_EXPANDABLE_LIST_VIEW;
import static com.android.SdkConstants.FQCN_LIST_VIEW;
import static com.android.SdkConstants.FQCN_TIME_PICKER;
-import static com.android.SdkConstants.DOT_PNG;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.api.Capability;
import com.android.ide.common.rendering.api.RenderSession;
@@ -477,10 +479,15 @@ public class PreviewIconFactory {
/**
* Cleans up a name by removing punctuation and whitespace etc to make
* it a better filename
- * @param name
- * @return
+ * @param name the name to clean
+ * @return a cleaned up name
*/
- private static String cleanup(String name) {
+ @NonNull
+ private static String cleanup(@Nullable String name) {
+ if (name == null) {
+ return "";
+ }
+
// Extract just the characters (no whitespace, parentheses, punctuation etc)
// to ensure that the filename is pretty portable
StringBuilder sb = new StringBuilder(name.length());
@@ -516,8 +523,11 @@ public class PreviewIconFactory {
if (themeName.startsWith(themeNamePrefix)) {
themeName = themeName.substring(themeNamePrefix.length());
}
- String dirName = String.format("palette-preview-r16b-%s-%s-%s", cleanup(targetName),
- cleanup(themeName), cleanup(mPalette.getCurrentDevice()));
+ targetName = cleanup(targetName);
+ themeName = cleanup(themeName);
+ String deviceName = cleanup(mPalette.getCurrentDevice());
+ String dirName = String.format("palette-preview-r16b-%s-%s-%s", targetName,
+ themeName, deviceName);
IPath dirPath = pluginState.append(dirName);
mImageDir = new File(dirPath.toOSString());
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
index 8f6eb56..e0c3add 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
@@ -38,7 +38,8 @@ import com.android.ide.eclipse.adt.internal.editors.layout.ContextPullParser;
import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
@@ -106,11 +107,12 @@ public class RenderService {
mProject = editor.getProject();
LayoutCanvas canvas = editor.getCanvasControl();
mImageFactory = canvas.getImageOverlay();
- ConfigurationComposite config = editor.getConfigurationComposite();
+ ConfigurationChooser chooser = editor.getConfigurationChooser();
+ Configuration config = chooser.getConfiguration();
mDensity = config.getDensity();
mXdpi = config.getXDpi();
mYdpi = config.getYDpi();
- mScreenSize = config.getCurrentConfig().getScreenSizeQualifier();
+ mScreenSize = chooser.getConfiguration().getFullConfig().getScreenSizeQualifier();
mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/);
mResourceResolver = editor.getResourceResolver();
mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java
index 2fb16ff..c5f976f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java
@@ -40,7 +40,7 @@ import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
@@ -531,15 +531,15 @@ class ClientRulesEngine implements IClientRulesEngine {
@Override
public int pxToDp(int px) {
- ConfigurationComposite config = mRulesEngine.getEditor().getConfigurationComposite();
- float dpi = config.getDensity().getDpiValue();
+ ConfigurationChooser chooser = mRulesEngine.getEditor().getConfigurationChooser();
+ float dpi = chooser.getConfiguration().getDensity().getDpiValue();
return (int) (px * 160 / dpi);
}
@Override
public int dpToPx(int dp) {
- ConfigurationComposite config = mRulesEngine.getEditor().getConfigurationComposite();
- float dpi = config.getDensity().getDpiValue();
+ ConfigurationChooser chooser = mRulesEngine.getEditor().getConfigurationChooser();
+ float dpi = chooser.getConfiguration().getDensity().getDpiValue();
return (int) (dp * dpi / 160);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
index 5d8d700..c2035f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
@@ -35,7 +35,7 @@ import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferen
import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
@@ -304,7 +304,7 @@ public abstract class VisualRefactoring extends Refactoring {
try {
// Duplicate the current state into the newly created file
- QualifiedName qname = ConfigurationComposite.NAME_CONFIG_STATE;
+ QualifiedName qname = ConfigurationChooser.NAME_CONFIG_STATE;
String state = AdtPlugin.getFileProperty(leavingFile, qname);
// TODO: Look for a ".NoTitleBar.Fullscreen" theme version of the current
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
index 3020851..d624cb7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
@@ -67,6 +67,7 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
public final static String PREFS_ATTRIBUTE_SORT = AdtPlugin.PLUGIN_ID + ".attrSort"; //$NON-NLS-1$
public final static String PREFS_LINT_SEVERITIES = AdtPlugin.PLUGIN_ID + ".lintSeverities"; //$NON-NLS-1$
public final static String PREFS_FIX_LEGACY_EDITORS = AdtPlugin.PLUGIN_ID + ".fixLegacyEditors"; //$NON-NLS-1$
+ public final static String PREFS_SHARED_LAYOUT_EDITOR = AdtPlugin.PLUGIN_ID + ".sharedLayoutEditor"; //$NON-NLS-1$
/** singleton instance */
private final static AdtPrefs sThis = new AdtPrefs();
@@ -97,6 +98,7 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
private boolean mLintOnSave;
private boolean mLintOnExport;
private AttributeSortOrder mAttributeSort;
+ private boolean mSharedLayoutEditor;
public static enum BuildVerbosity {
/** Build verbosity "Always". Those messages are always displayed, even in silent mode */
@@ -246,6 +248,11 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
if (property == null || PREFS_LINT_ON_EXPORT.equals(property)) {
mLintOnExport = mStore.getBoolean(PREFS_LINT_ON_EXPORT);
}
+
+ if (property == null || PREFS_SHARED_LAYOUT_EDITOR.equals(property)) {
+ mSharedLayoutEditor = mStore.getBoolean(PREFS_SHARED_LAYOUT_EDITOR);
+ }
+
}
/**
@@ -375,6 +382,32 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
store.setValue(PREFS_LINT_ON_EXPORT, on);
}
+ /**
+ * Returns whether the layout editor is sharing a single editor for all variations
+ * of a single resource. The default is false.
+ *
+ * @return true if the editor should be shared
+ */
+ public boolean isSharedLayoutEditor() {
+ return mSharedLayoutEditor;
+ }
+
+ /**
+ * Sets whether the layout editor should share a single editor for all variations
+ * of a single resource
+ *
+ * @param on if true, use a single editor
+ */
+ public void setSharedLayoutEditor(boolean on) {
+ mSharedLayoutEditor = on;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ store.setValue(PREFS_SHARED_LAYOUT_EDITOR, on);
+
+ // TODO: If enabling a shared editor, go and close all editors that are aliasing
+ // the same resource except for one of them.
+ }
+
+
public boolean getBuildForceErrorOnNativeLibInJar() {
return mBuildForceErrorOnNativeLibInJar;
}
@@ -457,6 +490,7 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
//store.setDefault(PREFS_USE_ECLIPSE_INDENT, false);
//store.setDefault(PREVS_REMOVE_EMPTY_LINES, false);
//store.setDefault(PREFS_FORMAT_ON_SAVE, false);
+ //store.setDefault(PREFS_SHARED_LAYOUT_EDITOR, false);
try {
store.setDefault(PREFS_DEFAULT_DEBUG_KEYSTORE,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
index 09261c2..0fcbaa0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
@@ -127,6 +127,10 @@ public class EditorsPage extends FieldEditorPreferencePage implements IWorkbench
"Format on Save",
parent));
+ addField(new BooleanFieldEditor(AdtPrefs.PREFS_SHARED_LAYOUT_EDITOR,
+ "Use a single layout editor for all configuration variations of a layout",
+ parent));
+
boolean enabled = getPreferenceStore().getBoolean(AdtPrefs.PREFS_USE_CUSTOM_XML_FORMATTER);
updateCustomFormattingOptions(enabled);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java
new file mode 100644
index 0000000..f55cce4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.ide.common.resources.configuration.LanguageQualifier.FAKE_LANG_VALUE;
+import static com.android.ide.common.resources.configuration.RegionQualifier.FAKE_REGION_VALUE;
+
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.resources.Density;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.DeviceManager;
+import com.android.utils.StdLogger;
+
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class ConfigurationTest extends TestCase {
+ private Configuration createConfiguration() throws Exception {
+ // Using reflection instead since we want to pass null to
+ // a constructor marked with @NonNull, so the test won't compile.
+ Constructor<Configuration> constructor =
+ Configuration.class.getDeclaredConstructor(ConfigurationChooser.class);
+ constructor.setAccessible(true);
+ ConfigurationChooser chooser = null;
+ return constructor.newInstance(chooser);
+ }
+
+ public void test() throws Exception {
+ Configuration configuration = createConfiguration();
+ assertNotNull(configuration);
+ configuration.setTheme("@style/Theme");
+ assertEquals("@style/Theme", configuration.getTheme());
+
+ DeviceManager deviceManager = new DeviceManager(new StdLogger(StdLogger.Level.VERBOSE));
+ List<Device> devices = deviceManager.getDefaultDevices();
+ assertNotNull(devices);
+ assertTrue(devices.size() > 0);
+ configuration.setDevice(devices.get(0), false);
+
+ // Check syncing
+ FolderConfiguration folderConfig = configuration.getFullConfig();
+ assertEquals(FAKE_LANG_VALUE, folderConfig.getLanguageQualifier().getValue());
+ assertEquals(FAKE_REGION_VALUE, folderConfig.getRegionQualifier().getValue());
+ assertEquals(Locale.ANY, configuration.getLocale());
+
+ Locale language = Locale.create(new LanguageQualifier("nb"));
+ configuration.setLocale(language, true /* skipSync */);
+ assertEquals(FAKE_LANG_VALUE, folderConfig.getLanguageQualifier().getValue());
+ assertEquals(FAKE_REGION_VALUE, folderConfig.getRegionQualifier().getValue());
+
+ configuration.setLocale(language, false /* skipSync */);
+ assertEquals(FAKE_REGION_VALUE, folderConfig.getRegionQualifier().getValue());
+ assertEquals("nb", folderConfig.getLanguageQualifier().getValue());
+
+ assertEquals("2.7in QVGA::nb-__:+Theme::notnight::", configuration.toPersistentString());
+
+ configuration.setActivity("foo.bar.FooActivity");
+ configuration.setTheme("@android:style/Theme.Holo.Light");
+
+ assertEquals("2.7in QVGA",
+ ConfigurationChooser.getDeviceLabel(configuration.getDevice(), true));
+ assertEquals("2.7in QVGA",
+ ConfigurationChooser.getDeviceLabel(configuration.getDevice(), false));
+ assertEquals("Light",
+ ConfigurationChooser.getThemeLabel(configuration.getTheme(), true));
+ assertEquals("Theme.Holo.Light",
+ ConfigurationChooser.getThemeLabel(configuration.getTheme(), false));
+ assertEquals("nb",
+ ConfigurationChooser.getLocaleLabel(null, configuration.getLocale(), true));
+ assertEquals("Norwegian Bokm\u00e5l (nb)",
+ ConfigurationChooser.getLocaleLabel(null, configuration.getLocale(), false));
+
+ assertEquals("FooActivity",
+ ConfigurationChooser.getActivityLabel(configuration.getActivity(), true));
+ assertEquals("foo.bar.FooActivity",
+ ConfigurationChooser.getActivityLabel(configuration.getActivity(), false));
+
+ assertEquals("2.7in QVGA::nb-__:-Theme.Holo.Light::notnight::foo.bar.FooActivity",
+ configuration.toPersistentString());
+
+ assertEquals(Density.MEDIUM, configuration.getDensity());
+ assertEquals(145.0f, configuration.getXDpi(), 0.001);
+ assertEquals(145.0f, configuration.getYDpi(), 0.001);
+ assertEquals(new Rect(0, 0, 320, 480), configuration.getScreenBounds());
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleTest.java
new file mode 100644
index 0000000..3dcf33a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class LocaleTest extends TestCase {
+ public void test() {
+ LanguageQualifier language1 = new LanguageQualifier("nb");
+ LanguageQualifier language2 = new LanguageQualifier("no");
+ RegionQualifier region1 = new RegionQualifier("NO");
+ RegionQualifier region2 = new RegionQualifier("SE");
+
+ assertEquals(Locale.ANY, Locale.ANY);
+ assertFalse(Locale.ANY.hasLanguage());
+ assertFalse(Locale.ANY.hasRegion());
+ assertFalse(Locale.create(new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
+ new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)).hasLanguage());
+ assertFalse(Locale.create(new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
+ new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE)).hasRegion());
+
+ assertEquals(Locale.create(language1), Locale.create(language1));
+ assertTrue(Locale.create(language1).hasLanguage());
+ assertFalse(Locale.create(language1).hasRegion());
+ assertTrue(Locale.create(language1, region1).hasLanguage());
+ assertTrue(Locale.create(language1, region1).hasRegion());
+
+ assertEquals(Locale.create(language1, region1), Locale.create(language1, region1));
+ assertEquals(Locale.create(language1), Locale.create(language1));
+ assertTrue(Locale.create(language1).equals(Locale.create(language1)));
+ assertTrue(Locale.create(language1, region1).equals(Locale.create(language1, region1)));
+ assertFalse(Locale.create(language1, region1).equals(Locale.create(language1, region2)));
+ assertFalse(Locale.create(language1).equals(Locale.create(language1, region1)));
+ assertFalse(Locale.create(language1).equals(Locale.create(language2)));
+ assertFalse(Locale.create(language1, region1).equals(Locale.create(language2, region1)));
+ assertEquals("Locale{nb, __}", Locale.create(language1).toString());
+ assertEquals("Locale{nb, NO}", Locale.create(language1, region1).toString());
+ }
+}