aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-09-12 12:11:47 -0700
committerTor Norbye <tnorbye@google.com>2012-09-17 07:43:29 -0700
commitfe51dba2aa25e559786e5da315d4db714ffe7559 (patch)
tree058d343ffda252403da6b47813d6ab1eac1af711
parentd38457bd813f7591e13a58bf91655192d761d81a (diff)
downloadsdk-fe51dba2aa25e559786e5da315d4db714ffe7559.zip
sdk-fe51dba2aa25e559786e5da315d4db714ffe7559.tar.gz
sdk-fe51dba2aa25e559786e5da315d4db714ffe7559.tar.bz2
Support separate layout editors for a single layout resource
This changeset changes the "reuse" behavior of the layout editor to no longer reuse the same layout editor when you are switching between alternate layout files for the same layout resource, such as layout/foo.xml and layout-land/foo.xml. This lets you more quickly switch back and forth and inspect differences between the layouts, etc. There is also an option in the Android > Editors panel to turn on single editor sharing again. The biggest part of the changeset, by far, is a cleanup of the ConfigurationComposite class and associated code. This was necessary not just to support the above feature (where we need to "back out" UI changes when you've made a configuration edit which results in a different file getting opened), but it's also an important preparation for multi configuration editing, where we need to be able to switch configuration settings in and out of a single configuration editor, etc. The configuration data itself is now in a separate Configuration class; the UI is in ConfigurationChooser, and the configuration matching code is in ConfigurationMatcher. There's also a new Locale class to track language/region pairs instead of using 2-element ResourceQualifier arrays. The various menu listeners are also in separate UI classes now. While there are new classes, most of the configuration matching algorithm is unchanged, just moved to a new class and the UI syncing and configuration data lookup replaced. Bitmasks are used to handle configuration changes, such that updating multiple related attributes (e.g. a rendering target change also causes a theme change if say Holo isn't available) can now be processed just once with a single change call. (Various other cleanup too.) Change-Id: I04ac969f46824321be3db0c487ef077c03cc6012
-rw-r--r--common/src/com/android/SdkConstants.java10
-rw-r--r--eclipse/dictionary.txt2
-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
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java5
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java8
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java4
41 files changed, 5436 insertions, 4090 deletions
diff --git a/common/src/com/android/SdkConstants.java b/common/src/com/android/SdkConstants.java
index d2f1421..53277c5 100644
--- a/common/src/com/android/SdkConstants.java
+++ b/common/src/com/android/SdkConstants.java
@@ -26,9 +26,15 @@ import java.io.File;
* <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
* <li><code>FN_</code> File name constant.</li>
* <li><code>FD_</code> Folder name constant.</li>
+ * <li><code>TAG_</code> XML element tag name</li>
+ * <li><code>ATTR_</code> XML attribute name</li>
+ * <li><code>VALUE_</code> XML attribute value</li>
+ * <li><code>CLASS_</code> Class name</li>
+ * <li><code>DOT_</code> File name extension, including the dot </li>
+ * <li><code>EXT_</code> File name extension, without the dot </li>
* </ul>
- *
*/
+@SuppressWarnings("javadoc") // Not documenting all the fields here
public final class SdkConstants {
public static final int PLATFORM_UNKNOWN = 0;
public static final int PLATFORM_LINUX = 1;
@@ -690,7 +696,7 @@ public final class SdkConstants {
public static final String ATTR_LAYOUT_RESOURCE_PREFIX = "layout_"; //$NON-NLS-1$
public static final String ATTR_CLASS = "class"; //$NON-NLS-1$
public static final String ATTR_STYLE = "style"; //$NON-NLS-1$
-
+ public static final String ATTR_CONTEXT = "context"; //$NON-NLS-1$
public static final String ATTR_ID = "id"; //$NON-NLS-1$
public static final String ATTR_TEXT = "text"; //$NON-NLS-1$
public static final String ATTR_TEXT_SIZE = "textSize"; //$NON-NLS-1$
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index e2c4340..d678c2d 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -240,6 +240,7 @@ registry
reindent
reindenting
remap
+renderable
reparse
reparses
rescales
@@ -306,6 +307,7 @@ timestamp
timestamps
tmp
toolbar
+toolbars
tooltip
tooltips
traceview
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());
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java
index dcd82a2..cb712f0 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java
@@ -287,4 +287,9 @@ public final class Device {
hash = 31 * hash + mDefaultState.hashCode();
return hash;
}
+
+ @Override
+ public String toString() {
+ return mName;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java
index d5297d3..3662c26 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java
@@ -246,8 +246,10 @@ public class DeviceManager {
try {
userDevicesFile = new File(AndroidLocation.getFolder(),
SdkConstants.FN_DEVICES_XML);
- mUserDevices.addAll(DeviceParser.parse(userDevicesFile));
- notifyListeners();
+ if (userDevicesFile.exists()) {
+ mUserDevices.addAll(DeviceParser.parse(userDevicesFile));
+ notifyListeners();
+ }
} catch (AndroidLocationException e) {
mLog.warning("Couldn't load user devices: %1$s", e.getMessage());
} catch (SAXException e) {
@@ -263,8 +265,6 @@ public class DeviceManager {
userDevicesFile.getAbsolutePath(), renamedConfig.getAbsolutePath());
userDevicesFile.renameTo(renamedConfig);
}
- } catch (FileNotFoundException e) {
- mLog.warning("No user devices found");
} catch (ParserConfigurationException e) {
mLog.error(null, "Error parsing %1$s",
userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java
index 1dc6961..27e5448 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java
@@ -138,4 +138,8 @@ public class State {
return hash;
}
+ @Override
+ public String toString() {
+ return mName;
+ }
}