IDEA-54551 android: layout preview feature, migration to new libraries

This commit is contained in:
Eugene Kudelevsky
2011-07-25 16:27:15 +04:00
parent 88f2bdb755
commit 550a6f8ca1
47 changed files with 3730 additions and 116 deletions
+11 -3
View File
@@ -31,20 +31,28 @@
<root url="jar://$MODULE_DIR$/lib/androidprefs.jar!/" />
<root url="jar://$MODULE_DIR$/lib/jarutils.jar!/" />
<root url="jar://$MODULE_DIR$/lib/common.jar!/" />
<root url="jar://$MODULE_DIR$/lib/ide_common.jar!/" />
<root url="jar://$MODULE_DIR$/lib/ninepatch.jar!/" />
<root url="jar://$MODULE_DIR$/lib/layoutlib_api.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MODULE_DIR$/lib/src/ddmlib_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/sdklib_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/androidprefs_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/jarutils.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/ide_common_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/common_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/jarutils.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/layoutlib_api_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/androidprefs_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/sdklib_src.zip!/src" />
<root url="jar://$MODULE_DIR$/lib/src/ninepatch_src.zip!/src" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module" module-name="maven" />
<orderEntry type="module" module-name="java-impl" />
<orderEntry type="module" module-name="properties" />
<orderEntry type="library" name="xpp3-1.1.4-min" level="project" />
<orderEntry type="library" name="intermediate-java" level="project" />
</component>
</module>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

@@ -247,4 +247,14 @@ android.import.dependencies.import.module.task.title=Import library module {0} f
android.import.dependencies.dialog.title=Import Android dependencies from property files
android.import.dependencies.source.roots.dialog.title=Import source roots
android.import.dependencies.source.roots.dialog.label=Choose source roots to add to your project
sdk.broken.error=Cannot parse Android SDK. Please reconfigure it in Android facet settings
android.layout.preview.cannot.load.library.error=Failed to load platform rendering library
android.layout.preview.cannot.load.resources=Cannot load module resources.
android.layout.preview.editor.name=Preview
android.layout.preview.tool.window.title=Preview
android.layout.preview.default.error.message=Cannot perform rendering
android.layout.preview.zoom.in.action.text=Zoom In
android.layout.preview.zoom.out.action.text=Zoom Out
android.layout.preview.zoom.actual.action.text=Zoom to actual size
android.layout.preview.zoom.to.fit.action.text=Zoom to Fit
restart.adb.fix.text=Restart ADB service and try again
+4 -1
View File
@@ -16,6 +16,10 @@
<component>
<implementation-class>org.jetbrains.android.AndroidProjectComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowManager</implementation-class>
<skipForDefaultProject/>
</component>
</project-components>
<actions>
<!--<group>
@@ -52,7 +56,6 @@
<add-to-group group-id="EditorPopupMenu" anchor="last"/>
<add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
</action>
<action id="AndroidConnectDebuggerAction"
class="org.jetbrains.android.actions.AndroidConnectDebuggerAction"
text="Attach debugger to Android process"
@@ -15,6 +15,7 @@
*/
package org.jetbrains.android;
import com.android.AndroidConstants;
import com.android.sdklib.SdkConstants;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -39,7 +40,7 @@ import java.util.Map;
* @author Eugene.Kudelevsky
*/
public class AndroidIdIndex extends ScalarIndexExtension<String> {
public static final String[] RES_TYPES_CONTAINING_ID_DECLARATIONS = {SdkConstants.FD_LAYOUT, SdkConstants.FD_MENU};
public static final String[] RES_TYPES_CONTAINING_ID_DECLARATIONS = {AndroidConstants.FD_RES_LAYOUT, AndroidConstants.FD_RES_MENU};
public static final ID<String, Void> INDEX_ID = ID.create("android.id.index");
public static final String MARKER = "$";
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -35,6 +36,7 @@ public class AttributeDefinitions {
private Map<String, StyleableDefinition> myStyleables = new HashMap<String, StyleableDefinition>();
private final List<StyleableDefinition> myStateStyleables = new ArrayList<StyleableDefinition>();
private final Map<String, Map<String, Integer>> myEnumMap = new HashMap<String, Map<String, Integer>>();
public AttributeDefinitions() {
}
@@ -127,7 +129,7 @@ public class AttributeDefinitions {
return result;
}
private static void parseAndAddValues(AttributeDefinition def, XmlTag[] values) {
private void parseAndAddValues(AttributeDefinition def, XmlTag[] values) {
for (XmlTag value : values) {
final String valueName = value.getAttributeValue("name");
if (valueName == null) {
@@ -135,6 +137,23 @@ public class AttributeDefinitions {
}
else {
def.addValue(valueName);
final String strIntValue = value.getAttributeValue("value");
if (strIntValue != null) {
try {
int intValue = strIntValue.startsWith("0x")
? Integer.parseInt(strIntValue.substring(2), 16)
: Integer.parseInt(strIntValue);
Map<String, Integer> value2Int = myEnumMap.get(def.getName());
if (value2Int == null) {
value2Int = new HashMap<String, Integer>();
myEnumMap.put(def.getName(), value2Int);
}
value2Int.put(valueName, intValue);
}
catch (NumberFormatException ignored) {
}
}
}
}
}
@@ -207,4 +226,9 @@ public class AttributeDefinitions {
public StyleableDefinition[] getStateStyleables() {
return myStateStyleables.toArray(new StyleableDefinition[myStateStyleables.size()]);
}
@NotNull
public Map<String, Map<String, Integer>> getEnumMap() {
return myEnumMap;
}
}
@@ -35,5 +35,5 @@ public interface Style extends ResourceElement {
@Attribute("parent")
@Convert(ParentStyleConverter.class)
GenericAttributeValue<String> getParentStyle();
GenericAttributeValue<ResourceValue> getParentStyle();
}
@@ -42,6 +42,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
@@ -75,10 +76,7 @@ import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.resourceManagers.SystemResourceManager;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdk;
import org.jetbrains.android.sdk.AndroidSdkImpl;
import org.jetbrains.android.sdk.MessageBuildingSdkLog;
import org.jetbrains.android.sdk.*;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
@@ -227,20 +225,6 @@ public class AndroidFacet extends Facet<AndroidFacetConfiguration> {
return null;
}
private static int getIntAttrValue(@NotNull final XmlTag tag, @NotNull final String attrName) {
String value = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
public String compute() {
return tag.getAttributeValue(attrName, SdkConstants.NS_RESOURCES);
}
});
try {
return Integer.parseInt(value);
}
catch (NumberFormatException e) {
return -1;
}
}
// if baseTarget is null, then function return if application can be deployed on any target
public boolean isCompatibleBaseTarget(@Nullable IAndroidTarget baseTarget) {
@@ -272,9 +256,9 @@ public class AndroidFacet extends Facet<AndroidFacetConfiguration> {
if (manifestTag != null) {
XmlTag[] tags = manifestTag.findSubTags("uses-sdk");
for (XmlTag tag : tags) {
int candidate = getIntAttrValue(tag, "minSdkVersion");
int candidate = AndroidUtils.getIntAttrValue(tag, "minSdkVersion");
if (candidate >= 0) minSdkVersion = candidate;
candidate = getIntAttrValue(tag, "maxSdkVersion");
candidate = AndroidUtils.getIntAttrValue(tag, "maxSdkVersion");
if (candidate >= 0) maxSdkVersion = candidate;
}
}
@@ -378,7 +362,7 @@ public class AndroidFacet extends Facet<AndroidFacetConfiguration> {
});
getModule().getMessageBus().connect(this).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
private Sdk myPrevSdk;
private String[] myDependencies;
public void beforeRootsChange(final ModuleRootEvent event) {
@@ -388,22 +372,30 @@ public class AndroidFacet extends Facet<AndroidFacetConfiguration> {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (!isDisposed()) {
PsiDocumentManager.getInstance(getModule().getProject()).commitAllDocuments();
if (isDisposed()) {
return;
}
final PropertiesFile propertiesFile = AndroidUtils.findPropertyFile(getModule(), SdkConstants.FN_DEFAULT_PROPERTIES);
if (propertiesFile == null) {
return;
}
final Sdk newSdk = ModuleRootManager.getInstance(getModule()).getSdk();
if (newSdk != null && newSdk.getSdkType() instanceof AndroidSdkType && !newSdk.equals(myPrevSdk)) {
myPrevSdk = newSdk;
androidPlatformChanged();
}
updateTargetProperty(propertiesFile);
updateLibraryProperty(propertiesFile);
PsiDocumentManager.getInstance(getModule().getProject()).commitAllDocuments();
final String[] dependencies = collectDependencies();
if (myDependencies == null || !Comparing.equal(myDependencies, dependencies)) {
updateDependenciesInPropertyFile(propertiesFile, dependencies);
myDependencies = dependencies;
}
final PropertiesFile propertiesFile = AndroidUtils.findPropertyFile(getModule(), SdkConstants.FN_DEFAULT_PROPERTIES);
if (propertiesFile == null) {
return;
}
updateTargetProperty(propertiesFile);
updateLibraryProperty(propertiesFile);
final String[] dependencies = collectDependencies();
if (myDependencies == null || !Comparing.equal(myDependencies, dependencies)) {
updateDependenciesInPropertyFile(propertiesFile, dependencies);
myDependencies = dependencies;
}
}
});
@@ -567,9 +559,9 @@ public class AndroidFacet extends Facet<AndroidFacetConfiguration> {
@Nullable
public SystemResourceManager getSystemResourceManager() {
if (mySystemResourceManager == null) {
IAndroidTarget target = getConfiguration().getAndroidTarget();
if (target != null) {
mySystemResourceManager = new SystemResourceManager(getModule(), target);
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
if (platform != null) {
mySystemResourceManager = new SystemResourceManager(getModule(), platform);
}
}
return mySystemResourceManager;
@@ -16,6 +16,7 @@
package org.jetbrains.android.newProject;
import com.android.AndroidConstants;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.intellij.CommonBundle;
@@ -442,12 +443,12 @@ public class AndroidModuleBuilder extends JavaModuleBuilder {
createChildDirectoryIfNotExist(project, rootDir, SdkConstants.FD_ASSETS);
createChildDirectoryIfNotExist(project, rootDir, SdkConstants.FD_NATIVE_LIBS);
VirtualFile resDir = createChildDirectoryIfNotExist(project, rootDir, SdkConstants.FD_RES);
VirtualFile drawableDir = createChildDirectoryIfNotExist(project, resDir, SdkConstants.FD_DRAWABLE);
VirtualFile drawableDir = createChildDirectoryIfNotExist(project, resDir, AndroidConstants.FD_RES_DRAWABLE);
createFileFromResource(project, drawableDir, "icon.png", "/icons/androidLarge.png");
if (isHelloAndroid()) {
VirtualFile valuesDir = createChildDirectoryIfNotExist(project, resDir, SdkConstants.FD_VALUES);
VirtualFile valuesDir = createChildDirectoryIfNotExist(project, resDir, AndroidConstants.FD_RES_VALUES);
createFileFromResource(project, valuesDir, "strings.xml", "res/values/strings.xml");
VirtualFile layoutDir = AndroidUtils.createChildDirectoryIfNotExist(project, resDir, SdkConstants.FD_LAYOUT);
VirtualFile layoutDir = AndroidUtils.createChildDirectoryIfNotExist(project, resDir, AndroidConstants.FD_RES_LAYOUT);
createFileFromResource(project, layoutDir, "main.xml", "res/layout/main.xml");
}
}
@@ -16,7 +16,7 @@
package org.jetbrains.android.resourceManagers;
import com.android.sdklib.SdkConstants;
import com.android.AndroidConstants;
import com.intellij.CommonBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -126,13 +126,18 @@ public class LocalResourceManager extends ResourceManager {
@NotNull
public AttributeDefinitions getAttributeDefinitions() {
if (myAttrDefs == null) {
List<XmlFile> xmlResFiles = new ArrayList<XmlFile>();
for (PsiFile file : findResourceFiles("values")) {
if (file instanceof XmlFile) {
xmlResFiles.add((XmlFile)file);
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
List<XmlFile> xmlResFiles = new ArrayList<XmlFile>();
for (PsiFile file : findResourceFiles("values")) {
if (file instanceof XmlFile) {
xmlResFiles.add((XmlFile)file);
}
}
myAttrDefs = new AttributeDefinitions(xmlResFiles.toArray(new XmlFile[xmlResFiles.size()]));
}
}
myAttrDefs = new AttributeDefinitions(xmlResFiles.toArray(new XmlFile[xmlResFiles.size()]));
});
}
return myAttrDefs;
}
@@ -202,9 +207,9 @@ public class LocalResourceManager extends ResourceManager {
Messages.showErrorDialog(myModule.getProject(), AndroidBundle.message("check.resource.dir.error"), CommonBundle.getErrorTitle());
return null;
}
final VirtualFile valuesDir = findOrCreateChildDir(dir, SdkConstants.FD_VALUES);
final VirtualFile valuesDir = findOrCreateChildDir(dir, AndroidConstants.FD_RES_VALUES);
if (valuesDir == null) {
String errorMessage = AndroidBundle.message("android.directory.cannot.be.found.error", SdkConstants.FD_VALUES);
String errorMessage = AndroidBundle.message("android.directory.cannot.be.found.error", AndroidConstants.FD_RES_VALUES);
Messages.showErrorDialog(myModule.getProject(), errorMessage, CommonBundle.getErrorTitle());
return null;
}
@@ -252,11 +252,16 @@ public abstract class ResourceManager {
}
@NotNull
public List<ResourceElement> getValueResources(@NotNull String resourceType) {
List<ResourceElement> result = new ArrayList<ResourceElement>();
public List<ResourceElement> getValueResources(@NotNull final String resourceType) {
final List<ResourceElement> result = new ArrayList<ResourceElement>();
Collection<Resources> resourceFiles = getResourceElements();
for (Resources resources : resourceFiles) {
result.addAll(getValueResources(resourceType, resources));
for (final Resources resources : resourceFiles) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
result.addAll(getValueResources(resourceType, resources));
}
});
}
return result;
}
@@ -17,20 +17,18 @@ package org.jetbrains.android.resourceManagers;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.*;
import com.intellij.util.xml.ConvertContext;
import org.jetbrains.android.AndroidIdIndex;
import org.jetbrains.android.dom.attrs.AttributeDefinitions;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidTargetData;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,15 +39,13 @@ import java.util.*;
* @author coyote
*/
public class SystemResourceManager extends ResourceManager {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.resourceManagers.SystemResourceManager");
private volatile AttributeDefinitions definitions;
private volatile Map<String, List<PsiElement>> myIdMap;
private final IAndroidTarget myTarget;
private final AndroidPlatform myPlatform;
public SystemResourceManager(@NotNull Module module, @NotNull IAndroidTarget target) {
public SystemResourceManager(@NotNull Module module, @NotNull AndroidPlatform androidPlatform) {
super(module);
myTarget = target;
myPlatform = androidPlatform;
}
@NotNull
@@ -60,7 +56,7 @@ public class SystemResourceManager extends ResourceManager {
@Nullable
public VirtualFile getResourceDir() {
String resPath = myTarget.getPath(IAndroidTarget.RESOURCES);
String resPath = myPlatform.getTarget().getPath(IAndroidTarget.RESOURCES);
return LocalFileSystem.getInstance().findFileByPath(resPath);
}
@@ -72,15 +68,8 @@ public class SystemResourceManager extends ResourceManager {
@Nullable
public synchronized AttributeDefinitions getAttributeDefinitions() {
if (definitions == null) {
String attrsPath = myTarget.getPath(IAndroidTarget.ATTRIBUTES);
String attrsManifestPath = myTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES);
XmlFile[] files = findFiles(attrsPath, attrsManifestPath);
if (files != null) {
definitions = new AttributeDefinitions(files);
}
}
return definitions;
final AndroidTargetData targetData = myPlatform.getSdk().getTargetData(myPlatform.getTarget());
return targetData.getAttrDefs(myModule.getProject());
}
@Nullable
@@ -99,31 +88,6 @@ public class SystemResourceManager extends ResourceManager {
return myIdMap.keySet();
}
@Nullable
private XmlFile[] findFiles(final String... paths) {
XmlFile[] xmlFiles = new XmlFile[paths.length];
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
PsiFile psiFile = file != null ? ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
@Nullable
public PsiFile compute() {
return PsiManager.getInstance(myModule.getProject()).findFile(file);
}
}) : null;
if (psiFile == null) {
LOG.info("File " + path + " is not found");
return null;
}
if (!(psiFile instanceof XmlFile)) {
LOG.info("File " + path + " is not an xml psiFile");
return null;
}
xmlFiles[i] = (XmlFile)psiFile;
}
return xmlFiles;
}
@NotNull
public Map<String, List<PsiElement>> createIdMap() {
Map<String, List<PsiElement>> result = new HashMap<String, List<PsiElement>>();
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.run.CreateAvdDialog">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="7" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="395" height="383"/>
@@ -20,7 +20,7 @@
</component>
<vspacer id="4df42">
<constraints>
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="27135" class="javax.swing.JTextField" binding="myNameField">
@@ -76,7 +76,7 @@
</component>
<component id="7d5e0" class="javax.swing.JLabel" binding="myAvdInfoLink">
<constraints>
<grid row="4" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false">
<grid row="5" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="14"/>
</grid>
</constraints>
@@ -98,13 +98,10 @@
<grid id="60129" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="3" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="4" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="etched" title-resource-bundle="messages/AndroidBundle" title-key="android.create.avd.dialog.sdcard.title"/>
<clientProperties>
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory"/>
</clientProperties>
<children>
<component id="60ba2" class="javax.swing.JRadioButton" binding="mySdCardSizeRadioButton">
<constraints>
@@ -142,6 +139,21 @@
</component>
</children>
</grid>
<component id="c9f2d" class="javax.swing.JLabel">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="14658"/>
<text value="&amp;Abi Type:"/>
</properties>
</component>
<component id="14658" class="javax.swing.JComboBox" binding="myAbiCombo">
<constraints>
<grid row="3" column="1" row-span="1" col-span="3" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
<buttonGroups>
@@ -21,6 +21,7 @@ import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.avd.AvdManager;
import com.intellij.ide.BrowserUtil;
import com.intellij.ide.ui.ListCellRendererWrapper;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
@@ -54,6 +55,8 @@ import static com.android.prefs.AndroidLocation.FOLDER_AVD;
* Date: May 9, 2009
*/
public class CreateAvdDialog extends DialogWrapper {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.run.CreateAvdDialog");
private JTextField myNameField;
private JComboBox myTargetBox;
private JComboBox mySkinField;
@@ -65,6 +68,7 @@ public class CreateAvdDialog extends DialogWrapper {
private JSpinner mySdCardSizeSpinner;
private JComboBox mySdCardSizeUnitCombo;
private TextFieldWithBrowseButton mySdCardFileTextField;
private JComboBox myAbiCombo;
private final AvdManager myAvdManager;
private AvdManager.AvdInfo myCreatedAvd;
@@ -177,6 +181,8 @@ public class CreateAvdDialog extends DialogWrapper {
skinsToAdd.add(skin);
}
mySkinField.setModel(new CollectionComboBoxModel(skinsToAdd, null));
reloadAbiCombo();
}
}
});
@@ -251,6 +257,18 @@ public class CreateAvdDialog extends DialogWrapper {
mySdCardFileTextField
.addBrowseFolderListener(AndroidBundle.message("android.create.avd.dialog.sdcard.file.browser.title"), null, project,
new FileChooserDescriptor(true, false, false, false, false, false));
reloadAbiCombo();
}
private void reloadAbiCombo() {
final IAndroidTarget selectedTarget = (IAndroidTarget)myTargetBox.getSelectedItem();
if (selectedTarget != null) {
final String[] abis = selectedTarget.getAbiList();
myAbiCombo.setModel(new DefaultComboBoxModel(abis));
if (abis.length == 0) {
LOG.error("Abis not found for target " + selectedTarget.hashString());
}
}
}
private void updateSpinner() {
@@ -317,9 +335,10 @@ public class CreateAvdDialog extends DialogWrapper {
super.doOKAction();
IAndroidTarget selectedTarget = (IAndroidTarget)myTargetBox.getSelectedItem();
String skin = (String)mySkinField.getSelectedItem();
String abi = (String)myAbiCombo.getSelectedItem();
String sdCard = getSdCardParameter();
MessageBuildingSdkLog log = new MessageBuildingSdkLog();
myCreatedAvd = myAvdManager.createAvd(avdFolder, avdName, selectedTarget, skin, sdCard, null, true, log);
myCreatedAvd = myAvdManager.createAvd(avdFolder, avdName, selectedTarget, abi, skin, sdCard, null, true, false, false, log);
if (log.getErrorMessage().length() > 0) {
Messages.showErrorDialog(myProject, log.getErrorMessage(), AndroidBundle.message("android.avd.error.title"));
}
@@ -18,7 +18,9 @@ package org.jetbrains.android.sdk;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.vfs.JarFileSystem;
@@ -47,6 +49,20 @@ public class AndroidPlatform {
myTarget = target;
}
@Nullable
public static AndroidPlatform getInstance(@NotNull Module module) {
final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk == null || !(sdk.getSdkType() instanceof AndroidSdkType)) {
return null;
}
final AndroidSdkAdditionalData sdkAdditionalData = (AndroidSdkAdditionalData)sdk.getSdkAdditionalData();
if (sdkAdditionalData == null) {
return null;
}
return sdkAdditionalData.getAndroidPlatform();
}
@NotNull
public AndroidSdk getSdk() {
return mySdk;
@@ -33,6 +33,8 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.reference.SoftReference;
import com.intellij.util.containers.HashMap;
import org.jetbrains.android.actions.AndroidEnableDdmsAction;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
@@ -40,6 +42,7 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.util.Map;
import static org.jetbrains.android.util.AndroidUtils.ADB;
@@ -59,6 +62,9 @@ public abstract class AndroidSdk {
private static final Object myDdmsLock = new Object();
private final Map<IAndroidTarget, SoftReference<AndroidTargetData>> myTargetDatas =
new HashMap<IAndroidTarget, SoftReference<AndroidTargetData>>();
@NotNull
public abstract String getLocation();
@@ -137,6 +143,7 @@ public abstract class AndroidSdk {
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj.getClass() != getClass()) return false;
AndroidSdk sdk = (AndroidSdk)obj;
return FileUtil.pathsEqual(getLocation(), sdk.getLocation());
@@ -274,6 +281,17 @@ public abstract class AndroidSdk {
return result;
}
@Nullable
public AndroidTargetData getTargetData(@NotNull IAndroidTarget target) {
final SoftReference<AndroidTargetData> targetDataRef = myTargetDatas.get(target);
AndroidTargetData targetData = targetDataRef != null ? targetDataRef.get() : null;
if (targetData == null) {
targetData = new AndroidTargetData(this, target);
myTargetDatas.put(target, new SoftReference<AndroidTargetData>(targetData));
}
return targetData;
}
private static class MyInitializeDdmlibTask extends Task.Modal {
private final Object myLock = new Object();
private volatile boolean myFinished;
@@ -231,6 +231,16 @@ public class AndroidSdkImpl extends AndroidSdk {
return myWrapee.getUsbVendorId();
}
@Override
public String[] getAbiList() {
return myWrapee.getAbiList();
}
@Override
public String getImagePath(String abiType) {
return myWrapee.getImagePath(abiType);
}
@Override
public boolean canRunOn(IAndroidTarget target) {
return myWrapee.canRunOn(target);
@@ -21,15 +21,16 @@ import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.JavadocOrderRootType;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -41,9 +42,7 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* Created by IntelliJ IDEA.
@@ -372,4 +371,13 @@ public class AndroidSdkUtils {
setupPlatform(module);
}
}
public static void openModuleDependenciesConfigurable(final Module module) {
final ProjectStructureConfigurable projectStructureConfigurable = ProjectStructureConfigurable.getInstance(module.getProject());
ShowSettingsUtil.getInstance().editConfigurable(module.getProject(), projectStructureConfigurable, new Runnable() {
public void run() {
projectStructureConfigurable.select(module.getName(), ProjectBundle.message("modules.classpath.title"), true);
}
});
}
}
@@ -0,0 +1,98 @@
package org.jetbrains.android.sdk;
import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.containers.HashSet;
import org.jetbrains.android.dom.attrs.AttributeDefinitions;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.resourceManagers.SystemResourceManager;
import org.jetbrains.android.uipreview.RenderServiceFactory;
import org.jetbrains.android.uipreview.RenderingException;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidTargetData {
private final AndroidSdk mySdk;
private final IAndroidTarget myTarget;
private volatile AttributeDefinitions myAttrDefs;
private volatile RenderServiceFactory myRenderServiceFactory;
private volatile Set<String> myThemes;
public AndroidTargetData(@NotNull AndroidSdk sdk, @NotNull IAndroidTarget target) {
mySdk = sdk;
myTarget = target;
}
@Nullable
public AttributeDefinitions getAttrDefs(@NotNull final Project project) {
if (myAttrDefs == null) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final String attrsPath = myTarget.getPath(IAndroidTarget.ATTRIBUTES);
final String attrsManifestPath = myTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES);
final XmlFile[] files = AndroidUtils.findXmlFiles(project, attrsPath, attrsManifestPath);
if (files != null) {
myAttrDefs = new AttributeDefinitions(files);
}
}
});
}
return myAttrDefs;
}
@Nullable
public RenderServiceFactory getRenderServiceFactory(@NotNull Project project) throws RenderingException, IOException {
if (myRenderServiceFactory == null) {
final AttributeDefinitions attrDefs = getAttrDefs(project);
if (attrDefs == null) {
return null;
}
myRenderServiceFactory = RenderServiceFactory.create(myTarget, attrDefs.getEnumMap());
}
return myRenderServiceFactory;
}
public boolean areThemesCached() {
return myThemes != null;
}
@NotNull
public Set<String> getThemes(@NotNull Module module) {
if (myThemes == null) {
myThemes = new HashSet<String>();
final SystemResourceManager systemResourceManager = new SystemResourceManager(module, new AndroidPlatform(mySdk, myTarget));
final List<ResourceElement> styles = systemResourceManager.getValueResources(ResourceType.STYLE.getName());
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
for (ResourceElement style : styles) {
final String styleName = style.getName().getValue();
if (styleName != null && (styleName.equals("Theme") || styleName.startsWith("Theme."))) {
myThemes.add(styleName);
}
}
}
});
}
return myThemes;
}
@NotNull
public IAndroidTarget getTarget() {
return myTarget;
}
}
@@ -0,0 +1,212 @@
package org.jetbrains.android.uipreview;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.ui.HyperlinkLabel;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidLayoutPreviewPanel extends JPanel {
private static final double EPS = 0.0000001;
private static final double MAX_ZOOM_FACTOR = 2.0;
private static final double ZOOM_STEP = 1.25;
private RenderingErrorMessage myErrorMessage;
private BufferedImage myImage;
private final HyperlinkLabel myErrorLabel = new HyperlinkLabel("", Color.BLUE, getBackground(), Color.BLUE);
private double myZoomFactor = 1.0;
private boolean myZoomToFit = true;
private final JPanel myImagePanel = new JPanel() {
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (myImage == null) {
return;
}
final Dimension scaledDimension = getScaledDimension();
final Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawImage(myImage, 0, 0, scaledDimension.width, scaledDimension.height, 0, 0, myImage.getWidth(), myImage.getHeight(), null);
}
};
public AndroidLayoutPreviewPanel() {
super(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, true));
setBackground(Color.WHITE);
setOpaque(true);
myImagePanel.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY));
myErrorLabel.addHyperlinkListener(new HyperlinkListener() {
public void hyperlinkUpdate(final HyperlinkEvent e) {
final Runnable quickFix = myErrorMessage.myQuickFix;
if (quickFix != null && e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
quickFix.run();
}
}
});
myErrorLabel.setOpaque(false);
add(myErrorLabel);
add(new MyImagePanelWrapper());
}
public void setImage(@Nullable final BufferedImage image) {
myImage = image;
doRevalidate();
}
private void doRevalidate() {
revalidate();
updateImageSize();
repaint();
}
public void setErrorMessage(@Nullable RenderingErrorMessage errorMessage) {
myErrorMessage = errorMessage;
}
public void update() {
if (myErrorMessage != null) {
myErrorLabel.setHyperlinkText(myErrorMessage.myBeforeLinkText,
myErrorMessage.myLinkText,
myErrorMessage.myAfterLinkText);
myErrorLabel.setIcon(Messages.getErrorIcon());
myErrorLabel.setVisible(true);
}
else {
myErrorLabel.setVisible(false);
}
repaint();
}
void updateImageSize() {
if (myImage == null) {
myImagePanel.setSize(0, 0);
}
else {
myImagePanel.setSize(getScaledDimension());
}
}
private Dimension getScaledDimension() {
if (myZoomToFit) {
final Dimension panelSize = getParent().getSize();
if (myImage.getWidth() <= panelSize.width && myImage.getHeight() <= panelSize.height) {
return new Dimension(myImage.getWidth(), myImage.getHeight());
}
if (myImage.getWidth() <= panelSize.width) {
final double f = panelSize.getHeight() / myImage.getHeight();
return new Dimension((int)(myImage.getWidth() * f), (int)(myImage.getHeight() * f));
}
else if (myImage.getHeight() <= panelSize.height) {
final double f = panelSize.getWidth() / myImage.getWidth();
return new Dimension((int)(myImage.getWidth() * f), (int)(myImage.getHeight() * f));
}
double f = panelSize.getWidth() / myImage.getWidth();
int candidateWidth = (int)(myImage.getWidth() * f);
int candidateHeight = (int)(myImage.getHeight() * f);
if (candidateWidth <= panelSize.getWidth() && candidateHeight <= panelSize.getHeight()) {
return new Dimension(candidateWidth, candidateHeight);
}
f = panelSize.getHeight() / myImage.getHeight();
return new Dimension((int)(myImage.getWidth() * f), (int)(myImage.getHeight() * f));
}
return new Dimension((int)(myImage.getWidth() * myZoomFactor), (int)(myImage.getHeight() * myZoomFactor));
}
private void setZoomFactor(double zoomFactor) {
myZoomFactor = zoomFactor;
doRevalidate();
}
private double computeCurrentZoomFactor() {
if (myImage == null) {
return myZoomFactor;
}
return (double) myImagePanel.getWidth() / (double) myImage.getWidth();
}
private double getZoomFactor() {
return myZoomToFit ? computeCurrentZoomFactor() : myZoomFactor;
}
public void zoomOut() {
setZoomFactor(Math.max(getMinZoomFactor(), myZoomFactor / ZOOM_STEP));
}
public boolean canZoomOut() {
return myZoomFactor > getMinZoomFactor() + EPS;
}
private double getMinZoomFactor() {
return Math.min(1.0, (double) getParent().getWidth() / (double) myImage.getWidth());
}
public void zoomIn() {
if (myZoomToFit) {
myZoomToFit = false;
setZoomFactor(computeCurrentZoomFactor() * ZOOM_STEP);
return;
}
setZoomFactor(myZoomFactor * ZOOM_STEP);
}
public boolean canZoomIn() {
return getZoomFactor() * ZOOM_STEP < MAX_ZOOM_FACTOR - EPS;
}
public void zoomActual() {
if (myZoomToFit && myImagePanel.getWidth() >= myImage.getWidth() && myImagePanel.getHeight() >= myImage.getHeight()) {
return;
}
myZoomToFit = false;
setZoomFactor(1.0);
}
public void setZoomToFit(boolean zoomToFit) {
myZoomToFit = zoomToFit;
doRevalidate();
}
public boolean isZoomToFit() {
return myZoomToFit;
}
private class MyImagePanelWrapper extends JLayeredPane {
public MyImagePanelWrapper() {
add(myImagePanel);
}
private void centerComponents() {
Rectangle bounds = getBounds();
Point point = myImagePanel.getLocation();
point.x = (bounds.width - myImagePanel.getWidth()) / 2;
myImagePanel.setLocation(point);
}
public void invalidate() {
centerComponents();
super.invalidate();
}
public Dimension getPreferredSize() {
return myImagePanel.getSize();
}
}
}
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowForm">
<grid id="27dc6" binding="myContentPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="3" left="3" bottom="3" right="3"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<scrollpane id="f99f7" class="com.intellij.ui.components.JBScrollPane" binding="myScrollPane">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="7e0bb" class="org.jetbrains.android.uipreview.AndroidLayoutPreviewPanel" binding="myPreviewPanel">
<constraints/>
<properties/>
</component>
</children>
</scrollpane>
<grid id="fc823" binding="myComboPanel" layout-manager="GridBagLayout">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
</children>
</grid>
</form>
@@ -0,0 +1,789 @@
package org.jetbrains.android.uipreview;
import com.android.AndroidConstants;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.RegionQualifier;
import com.android.resources.DockMode;
import com.android.resources.NightMode;
import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.intellij.ide.ui.ListCellRendererWrapper;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.CollectionComboBoxModel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import org.jetbrains.android.dom.manifest.Activity;
import org.jetbrains.android.dom.manifest.Application;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.ResourceValue;
import org.jetbrains.android.dom.resources.Style;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.*;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidLayoutPreviewToolWindowForm implements Disposable {
private static final Icon ZOOM_TO_FIT_ICON = IconLoader.getIcon("/icons/zoomFit.png");
private static final Icon ZOOM_IN_ICON = IconLoader.getIcon("/icons/zoomIn.png");
private static final Icon ZOOM_OUT_ICON = IconLoader.getIcon("/icons/zoomOut.png");
private static final Icon ZOOM_ACTUAL_ICON = IconLoader.getIcon("/icons/zoomActual.png");
private JPanel myContentPanel;
private AndroidLayoutPreviewPanel myPreviewPanel;
private ComboBox myDevicesCombo;
private ComboBox myDeviceConfigurationsCombo;
private JBScrollPane myScrollPane;
private ComboBox myDockModeCombo;
private ComboBox myNightCombo;
private ComboBox myTargetCombo;
private ComboBox myLocaleCombo;
private ComboBox myThemeCombo;
private JPanel myComboPanel;
private PsiFile myFile;
private AndroidPlatform myPrevPlatform = null;
private LayoutDeviceManager myLayoutDeviceManager = new LayoutDeviceManager();
private final AndroidLayoutPreviewToolWindowManager myToolWindowManager;
private final ActionToolbar myActionToolBar;
public AndroidLayoutPreviewToolWindowForm(AndroidLayoutPreviewToolWindowManager toolWindowManager) {
myToolWindowManager = toolWindowManager;
final GridBagConstraints gb = new GridBagConstraints();
gb.fill = GridBagConstraints.HORIZONTAL;
gb.anchor = GridBagConstraints.CENTER;
gb.insets = new Insets(0, 2, 2, 2);
gb.gridy = 0;
gb.weightx = 1;
gb.gridx = 0;
gb.gridwidth = 1;
myDevicesCombo = new ComboBox();
myComboPanel.add(myDevicesCombo, gb);
gb.gridx++;
myDeviceConfigurationsCombo = new ComboBox();
myComboPanel.add(myDeviceConfigurationsCombo, gb);
gb.gridx++;
myTargetCombo = new ComboBox();
myComboPanel.add(myTargetCombo, gb);
gb.gridx = 0;
gb.gridy++;
myLocaleCombo = new ComboBox();
myComboPanel.add(myLocaleCombo, gb);
gb.gridx++;
myDockModeCombo = new ComboBox();
myComboPanel.add(myDockModeCombo, gb);
gb.gridx++;
myNightCombo = new ComboBox();
myComboPanel.add(myNightCombo, gb);
myDevicesCombo.setRenderer(new ListCellRendererWrapper(myDevicesCombo.getRenderer()) {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
if (value instanceof LayoutDevice) {
final LayoutDevice device = (LayoutDevice)value;
setText(device.getName());
}
else {
setText("<html><font color='red'>[none]</font></html>");
}
}
});
myDeviceConfigurationsCombo.setRenderer(new ListCellRendererWrapper(myDevicesCombo.getRenderer()) {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
if (value instanceof LayoutDeviceConfiguration) {
final LayoutDeviceConfiguration deviceConfiguration = (LayoutDeviceConfiguration)value;
setText(deviceConfiguration.getName());
}
else {
setText("<html><font color='red'>[none]</font></html>");
}
}
});
myDevicesCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final LayoutDevice newSelectedDevice = getSelectedDevice();
updateDeviceConfigurations(newSelectedDevice);
myToolWindowManager.render();
}
});
final DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(new MyZoomToFitAction());
actionGroup.add(new MyZoomActualAction());
actionGroup.add(new MyZoomInAction());
actionGroup.add(new MyZoomOutAction());
myActionToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actionGroup, true);
myActionToolBar.setReservePlaceAutoPopupIcon(false);
gb.gridx = 0;
gb.gridy++;
final JComponent toolbar = myActionToolBar.getComponent();
final JPanel toolBarWrapper = new JPanel(new BorderLayout());
toolBarWrapper.add(toolbar, BorderLayout.CENTER);
toolBarWrapper.setPreferredSize(new Dimension(10, toolbar.getPreferredSize().height));
toolBarWrapper.setMinimumSize(new Dimension(10, toolbar.getMinimumSize().height));
myComboPanel.add(toolBarWrapper, gb);
gb.fill = GridBagConstraints.HORIZONTAL;
myThemeCombo = new ComboBox();
gb.gridx++;
gb.gridwidth = 2;
myComboPanel.add(myThemeCombo, gb);
myContentPanel.addComponentListener(new ComponentListener() {
@Override
public void componentResized(ComponentEvent e) {
myPreviewPanel.updateImageSize();
}
@Override
public void componentMoved(ComponentEvent e) {
}
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
});
myScrollPane.getHorizontalScrollBar().setUnitIncrement(5);
myScrollPane.getVerticalScrollBar().setUnitIncrement(5);
myDockModeCombo.setModel(new DefaultComboBoxModel(DockMode.values()));
myDockModeCombo.setRenderer(new ListCellRendererWrapper(myDockModeCombo.getRenderer()) {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
setText(((DockMode)value).getLongDisplayValue());
}
});
final ActionListener renderingListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myToolWindowManager.render();
}
};
myNightCombo.setModel(new DefaultComboBoxModel(NightMode.values()));
myNightCombo.setRenderer(new ListCellRendererWrapper(myNightCombo.getRenderer()) {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
setText(((NightMode)value).getLongDisplayValue());
}
});
myTargetCombo.setRenderer(new ListCellRendererWrapper(myDevicesCombo.getRenderer()) {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
if (value instanceof IAndroidTarget) {
final IAndroidTarget target = (IAndroidTarget)value;
setText(target.getName());
}
else {
setText("<html><font color='red'>[none]</font></html>");
}
}
});
myTargetCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateThemes();
myToolWindowManager.render();
}
});
myDeviceConfigurationsCombo.addActionListener(renderingListener);
myDockModeCombo.addActionListener(renderingListener);
myNightCombo.addActionListener(renderingListener);
myLocaleCombo.addActionListener(renderingListener);
myThemeCombo.addActionListener(renderingListener);
myDeviceConfigurationsCombo.setMinimumAndPreferredWidth(10);
myDockModeCombo.setMinimumAndPreferredWidth(10);
myNightCombo.setMinimumAndPreferredWidth(10);
myDevicesCombo.setMinimumAndPreferredWidth(10);
myTargetCombo.setMinimumAndPreferredWidth(10);
myLocaleCombo.setMinimumAndPreferredWidth(10);
myThemeCombo.setMinimumAndPreferredWidth(10);
}
public JPanel getContentPanel() {
return myContentPanel;
}
public PsiFile getFile() {
return myFile;
}
public void setFile(@Nullable PsiFile file) {
myFile = file;
final AndroidPlatform newPlatform = getNewPlatform(file);
if (newPlatform == null || !newPlatform.equals(myPrevPlatform)) {
myPrevPlatform = newPlatform;
if (file != null) {
updateLocales();
updateDevicesAndTargets(newPlatform);
updateThemes();
}
}
}
@Override
public void dispose() {
}
@Nullable
private static AndroidPlatform getNewPlatform(PsiFile file) {
if (file == null) {
return null;
}
final Module module = ModuleUtil.findModuleForPsiElement(file);
if (module == null) {
return null;
}
final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk != null && sdk.getSdkType() instanceof AndroidSdkType) {
final AndroidSdkAdditionalData additionalData = (AndroidSdkAdditionalData)sdk.getSdkAdditionalData();
if (additionalData != null) {
return additionalData.getAndroidPlatform();
}
}
return null;
}
public void setErrorMessage(RenderingErrorMessage errorMessage) {
myPreviewPanel.setErrorMessage(errorMessage);
}
public void setImage(BufferedImage image) {
myPreviewPanel.setImage(image);
}
public void updatePreviewPanel() {
myPreviewPanel.update();
}
public void updateDevicesAndTargets(@Nullable AndroidPlatform platform) {
final AndroidSdk sdkObject = platform != null ? platform.getSdk() : null;
final LayoutDevice selectedDevice = getSelectedDevice();
final String selectedDeviceName = selectedDevice != null ? selectedDevice.getName() : null;
final List<LayoutDevice> devices;
if (sdkObject != null) {
myLayoutDeviceManager.loadDevices(sdkObject);
devices = myLayoutDeviceManager.getCombinedList();
}
else {
devices = Collections.emptyList();
}
LayoutDevice newSelectedDevice = devices.size() > 0 ? devices.get(0) : null;
if (selectedDeviceName != null) {
for (LayoutDevice device : devices) {
if (selectedDeviceName.equals(device.getName())) {
newSelectedDevice = device;
}
}
}
if (newSelectedDevice == null && devices.size() > 0) {
newSelectedDevice = devices.get(0);
}
myDevicesCombo.setModel(new CollectionComboBoxModel(devices, newSelectedDevice));
if (newSelectedDevice != null) {
updateDeviceConfigurations(newSelectedDevice);
}
final IAndroidTarget oldSelectedTarget = (IAndroidTarget)myTargetCombo.getSelectedItem();
final String selectedTargetHashString = oldSelectedTarget != null ? oldSelectedTarget.hashString() : null;
IAndroidTarget newSelectedTarget = platform != null ? platform.getTarget() : null;
final List<IAndroidTarget> targets;
if (sdkObject != null) {
targets = new ArrayList<IAndroidTarget>();
for (IAndroidTarget target : sdkObject.getTargets()) {
if (target.isPlatform()) {
if (target.hashString().equals(selectedTargetHashString)) {
newSelectedTarget = target;
}
targets.add(target);
}
}
}
else {
targets = Collections.emptyList();
}
if (newSelectedTarget == null && targets.size() > 0) {
newSelectedTarget = targets.get(0);
}
myTargetCombo.setModel(new CollectionComboBoxModel(targets, newSelectedTarget));
}
private void updateDeviceConfigurations(@Nullable LayoutDevice device) {
final LayoutDeviceConfiguration selectedConfiguration = getSelectedDeviceConfiguration();
final String selectedConfigurationName = selectedConfiguration != null
? selectedConfiguration.getName()
: null;
final List<LayoutDeviceConfiguration> configurations = device != null
? device.getConfigurations()
: Collections.<LayoutDeviceConfiguration>emptyList();
LayoutDeviceConfiguration newSelectedConfiguration = configurations.size() > 0
? configurations.get(0)
: null;
if (selectedConfigurationName != null) {
for (LayoutDeviceConfiguration configuration : configurations) {
if (selectedConfigurationName.equals(configuration.getName())) {
newSelectedConfiguration = configuration;
}
}
}
if (newSelectedConfiguration == null) {
newSelectedConfiguration = configurations.get(0);
}
myDeviceConfigurationsCombo.setModel(new CollectionComboBoxModel(configurations, newSelectedConfiguration));
}
public void updateLocales() {
if (myFile == null) {
return;
}
final LocaleData oldSelectedLocale = (LocaleData)myLocaleCombo.getSelectedItem();
myLocaleCombo.setModel(new DefaultComboBoxModel());
final AndroidFacet facet = AndroidFacet.getInstance(myFile);
if (facet == null) {
return;
}
final Map<String, Set<String>> language2Regions = new HashMap<String, Set<String>>();
final VirtualFile[] resourceDirs = facet.getLocalResourceManager().getAllResourceDirs();
for (VirtualFile resourceDir : resourceDirs) {
for (VirtualFile child : resourceDir.getChildren()) {
if (child.isDirectory()) {
final String resDirName = child.getName();
final String[] segments = resDirName.split(AndroidConstants.RES_QUALIFIER_SEP);
final List<String> languageQualifiers = new ArrayList<String>();
final List<String> regionQualifiers = new ArrayList<String>();
for (String segment : segments) {
final LanguageQualifier languageQualifier = LanguageQualifier.getQualifier(segment);
if (languageQualifier != null) {
languageQualifiers.add(languageQualifier.getValue());
}
final RegionQualifier regionQualifier = RegionQualifier.getQualifier(segment);
if (regionQualifier != null) {
regionQualifiers.add(regionQualifier.getValue());
}
}
for (String languageQualifier : languageQualifiers) {
Set<String> regions = language2Regions.get(languageQualifier);
if (regions == null) {
regions = new HashSet<String>();
language2Regions.put(languageQualifier, regions);
}
regions.addAll(regionQualifiers);
}
}
}
}
final List<LocaleData> locales = new ArrayList<LocaleData>();
for (String language : language2Regions.keySet()) {
final Set<String> regions = language2Regions.get(language);
for (String region : regions) {
final String presentation = String.format("%1$s / %2$s", language, region);
locales.add(new LocaleData(language, region, presentation));
}
final String presentation = regions.size() > 0
? String.format("%1$s / Other", language)
: String.format("%1$s / Any", language);
locales.add(new LocaleData(language, null, presentation));
}
locales.add(new LocaleData(null, null, language2Regions.size() > 0 ? "Other locale" : "Any locale"));
LocaleData newSelectedLocale = null;
for (LocaleData locale : locales) {
if (locale.equals(oldSelectedLocale)) {
newSelectedLocale = locale;
}
}
if (newSelectedLocale == null) {
final Locale defaultLocale = Locale.getDefault();
if (defaultLocale != null) {
for (LocaleData locale : locales) {
if (locale.equals(new LocaleData(defaultLocale.getLanguage(), defaultLocale.getCountry(), ""))) {
newSelectedLocale = locale;
}
}
}
}
Collections.sort(locales, new Comparator<LocaleData>() {
@Override
public int compare(LocaleData l1, LocaleData l2) {
return l1.toString().compareTo(l2.toString());
}
});
if (newSelectedLocale == null && locales.size() > 0) {
newSelectedLocale = locales.get(0);
}
myLocaleCombo.setModel(new CollectionComboBoxModel(locales, newSelectedLocale));
}
public void updateThemes() {
if (myFile == null) {
return;
}
final AndroidFacet facet = AndroidFacet.getInstance(myFile);
if (facet == null) {
return;
}
final List<ThemeData> themes = new ArrayList<ThemeData>();
final HashSet<ThemeData> addedThemes = new HashSet<ThemeData>();
collectThemesFromManifest(facet, themes, addedThemes);
collectProjectThemes(facet, themes, addedThemes);
final Module module = facet.getModule();
final AndroidPlatform androidPlatform = AndroidPlatform.getInstance(module);
if (androidPlatform != null) {
IAndroidTarget target = getSelectedTarget();
if (target == null) {
target = androidPlatform.getTarget();
}
final AndroidTargetData targetData = androidPlatform.getSdk().getTargetData(target);
if (targetData != null) {
if (targetData.areThemesCached()) {
collectFrameworkThemes(module, targetData, themes);
doApplyThemes(themes);
}
else {
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
collectFrameworkThemes(module, targetData, themes);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
doApplyThemes(themes);
myToolWindowManager.render();
}
});
}
});
}
}
}
}
private void doApplyThemes(List<ThemeData> themes) {
final ThemeData oldSelection = (ThemeData)myThemeCombo.getSelectedItem();
ThemeData selection = null;
for (ThemeData theme : themes) {
if (theme.equals(oldSelection)) {
selection = theme;
}
}
if (selection == null && themes.size() > 0) {
selection = themes.get(0);
}
myThemeCombo.setModel(new CollectionComboBoxModel(themes, selection));
}
private static void collectFrameworkThemes(Module module, AndroidTargetData targetData, List<ThemeData> themes) {
final List<String> frameworkThemeNames = new ArrayList<String>(targetData.getThemes(module));
Collections.sort(frameworkThemeNames);
for (String themeName : frameworkThemeNames) {
themes.add(new ThemeData(themeName, false));
}
}
private static void collectThemesFromManifest(AndroidFacet facet, List<ThemeData> resultList, Set<ThemeData> addedThemes) {
final Manifest manifest = facet.getManifest();
if (manifest == null) {
return;
}
final Application application = manifest.getApplication();
if (application == null) {
return;
}
final List<ThemeData> activityThemesList = new ArrayList<ThemeData>();
final XmlTag applicationTag = application.getXmlTag();
ThemeData applicationTheme = null;
if (applicationTag != null) {
final String applicationThemeRef = applicationTag.getAttributeValue("theme", SdkConstants.NS_RESOURCES);
if (applicationThemeRef != null) {
applicationTheme = getThemeByRef(applicationThemeRef);
}
}
for (Activity activity : application.getActivities()) {
final XmlTag activityTag = activity.getXmlTag();
if (activityTag != null) {
final String activityThemeRef = activityTag.getAttributeValue("theme", SdkConstants.NS_RESOURCES);
if (activityThemeRef != null) {
final ThemeData activityTheme = getThemeByRef(activityThemeRef);
if (addedThemes.add(activityTheme)) {
activityThemesList.add(activityTheme);
}
}
}
}
if (applicationTheme != null) {
if (addedThemes.add(applicationTheme)) {
resultList.add(applicationTheme);
}
}
Collections.sort(activityThemesList);
resultList.addAll(activityThemesList);
}
private static void collectProjectThemes(AndroidFacet facet, Collection<ThemeData> resultList, Set<ThemeData> addedThemes) {
final List<ThemeData> newThemes = new ArrayList<ThemeData>();
final Map<String, ResourceElement> styleMap = buildStyleMap(facet);
for (ResourceElement style : styleMap.values()) {
if (isTheme(style, styleMap, new HashSet<ResourceElement>())) {
final String themeName = style.getName().getValue();
if (themeName != null) {
final ThemeData theme = new ThemeData(themeName, true);
if (addedThemes.add(theme)) {
newThemes.add(theme);
}
}
}
}
Collections.sort(newThemes);
resultList.addAll(newThemes);
}
private static Map<String, ResourceElement> buildStyleMap(AndroidFacet facet) {
final Map<String, ResourceElement> result = new HashMap<String, ResourceElement>();
final List<ResourceElement> styles = facet.getLocalResourceManager().getValueResources(ResourceType.STYLE.getName());
for (ResourceElement style : styles) {
final String styleName = style.getName().getValue();
if (styleName != null) {
result.put(styleName, style);
}
}
return result;
}
private static boolean isTheme(ResourceElement resElement, Map<String, ResourceElement> styleMap, Set<ResourceElement> visitedElements) {
if (!visitedElements.add(resElement)) {
return false;
}
if (!(resElement instanceof Style)) {
return false;
}
final String styleName = resElement.getName().getValue();
if (styleName == null) {
return false;
}
final ResourceValue parentStyleRef = ((Style)resElement).getParentStyle().getValue();
String parentStyleName = null;
boolean frameworkStyle = false;
if (parentStyleRef != null) {
final String s = parentStyleRef.getResourceName();
if (s != null) {
parentStyleName = s;
frameworkStyle = AndroidUtils.SYSTEM_RESOURCE_PACKAGE.equals(parentStyleRef.getPackage());
}
}
if (parentStyleRef == null) {
final int index = styleName.indexOf('.');
if (index >= 0) {
parentStyleName = styleName.substring(0, index);
}
}
if (parentStyleRef != null) {
if (frameworkStyle) {
return parentStyleName.equals("Theme") || parentStyleName.startsWith("Theme.");
}
else {
final ResourceElement parentStyle = styleMap.get(parentStyleName);
if (parentStyle != null) {
return isTheme(parentStyle, styleMap, visitedElements);
}
}
}
return false;
}
@NotNull
private static ThemeData getThemeByRef(@NotNull String themeRef) {
if (themeRef.startsWith(ResourceResolver.PREFIX_STYLE)) {
themeRef = themeRef.substring(ResourceResolver.PREFIX_STYLE.length());
}
else if (themeRef.startsWith(ResourceResolver.PREFIX_ANDROID_STYLE)) {
themeRef = themeRef.substring(ResourceResolver.PREFIX_ANDROID_STYLE.length());
}
boolean isProjectTheme = !themeRef.startsWith(ResourceResolver.PREFIX_ANDROID_STYLE);
return new ThemeData(themeRef, isProjectTheme);
}
public LayoutDeviceConfiguration getSelectedDeviceConfiguration() {
return (LayoutDeviceConfiguration)myDeviceConfigurationsCombo.getSelectedItem();
}
public LayoutDevice getSelectedDevice() {
return (LayoutDevice)myDevicesCombo.getSelectedItem();
}
public DockMode getSelectedDockMode() {
return (DockMode)myDockModeCombo.getSelectedItem();
}
public NightMode getSelectedNightMode() {
return (NightMode)myNightCombo.getSelectedItem();
}
public IAndroidTarget getSelectedTarget() {
return (IAndroidTarget)myTargetCombo.getSelectedItem();
}
public LocaleData getSelectedLocaleData() {
return (LocaleData)myLocaleCombo.getSelectedItem();
}
public ThemeData getSelectedTheme() {
return (ThemeData)myThemeCombo.getSelectedItem();
}
private class MyZoomInAction extends AnAction {
MyZoomInAction() {
super(AndroidBundle.message("android.layout.preview.zoom.in.action.text"), null, ZOOM_IN_ICON);
}
@Override
public void actionPerformed(AnActionEvent e) {
myPreviewPanel.zoomIn();
myActionToolBar.updateActionsImmediately();
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(myPreviewPanel.canZoomIn());
}
}
private class MyZoomOutAction extends AnAction {
MyZoomOutAction() {
super(AndroidBundle.message("android.layout.preview.zoom.out.action.text"), null, ZOOM_OUT_ICON);
}
@Override
public void actionPerformed(AnActionEvent e) {
myPreviewPanel.zoomOut();
myActionToolBar.updateActionsImmediately();
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(!myPreviewPanel.isZoomToFit() && myPreviewPanel.canZoomOut());
}
}
private class MyZoomActualAction extends AnAction {
MyZoomActualAction() {
super(AndroidBundle.message("android.layout.preview.zoom.actual.action.text"), null, ZOOM_ACTUAL_ICON);
}
@Override
public void actionPerformed(AnActionEvent e) {
myPreviewPanel.zoomActual();
myActionToolBar.updateActionsImmediately();
}
}
private class MyZoomToFitAction extends ToggleAction {
MyZoomToFitAction() {
super(AndroidBundle.message("android.layout.preview.zoom.to.fit.action.text"), null, ZOOM_TO_FIT_ICON);
}
@Override
public boolean isSelected(AnActionEvent e) {
return myPreviewPanel.isZoomToFit();
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
myPreviewPanel.setZoomToFit(state);
myActionToolBar.updateActionsImmediately();
}
}
}
@@ -0,0 +1,421 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.resources.configuration.*;
import com.android.sdklib.IAndroidTarget;
import com.intellij.ProjectTopics;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlFile;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.containers.HashMap;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.android.dom.layout.LayoutDomFileDescription;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.sdk.*;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidSdkNotConfiguredException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidLayoutPreviewToolWindowManager implements ProjectComponent {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowManager");
private final MergingUpdateQueue myToolWindowUpdateQueue;
private final MergingUpdateQueue myRenderingQueue;
private final Project myProject;
private final FileEditorManager myFileEditorManager;
private AndroidLayoutPreviewToolWindowForm myToolWindowForm;
private ToolWindow myToolWindow;
private boolean myToolWindowReady = false;
private boolean myToolWindowDisposed = false;
private final VirtualFileListener myListener = new MyVirtualFileListener();
private static final Object RENDERING_LOCK = new Object();
public AndroidLayoutPreviewToolWindowManager(final Project project, final FileEditorManager fileEditorManager) {
myProject = project;
myFileEditorManager = fileEditorManager;
myToolWindowUpdateQueue = new MergingUpdateQueue("android.layout.preview", 300, true, null, project);
myRenderingQueue = new MergingUpdateQueue("android.layout.rendering", 300, true, null, project, null, false);
final MessageBusConnection connection = project.getMessageBus().connect(project);
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new MyFileEditorManagerListener());
connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyAndroidPlatformListener());
LocalFileSystem.getInstance().addVirtualFileListener(myListener);
PsiManager.getInstance(project).addPsiTreeChangeListener(new PsiTreeChangeAdapter() {
public void childrenChanged(PsiTreeChangeEvent event) {
update(event);
}
public void childRemoved(PsiTreeChangeEvent event) {
update(event);
}
public void childAdded(PsiTreeChangeEvent event) {
update(event);
}
public void childReplaced(PsiTreeChangeEvent event) {
update(event);
}
}, project);
}
private void update(PsiTreeChangeEvent event) {
if (myToolWindowForm != null) {
final PsiFile file = event.getFile();
if (file != null && myToolWindowForm.getFile() == file) {
render();
}
}
}
public void projectOpened() {
StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() {
public void run() {
myToolWindowReady = true;
}
});
}
private void initToolWindow() {
myToolWindowForm = new AndroidLayoutPreviewToolWindowForm(this);
myToolWindow = ToolWindowManager.getInstance(myProject)
.registerToolWindow(AndroidBundle.message("android.layout.preview.tool.window.title"), false, ToolWindowAnchor.RIGHT, myProject,
true);
final JPanel contentPanel = myToolWindowForm.getContentPanel();
final ContentManager contentManager = myToolWindow.getContentManager();
final Content content = contentManager.getFactory().createContent(contentPanel, null, false);
content.setDisposer(myToolWindowForm);
content.setCloseable(false);
content.setPreferredFocusableComponent(contentPanel);
contentManager.addContent(content);
contentManager.setSelectedContent(content, true);
myToolWindow.setAvailable(false, null);
}
public void projectClosed() {
if (myToolWindowForm != null) {
Disposer.dispose(myToolWindowForm);
myToolWindowForm = null;
myToolWindow = null;
myToolWindowDisposed = true;
LocalFileSystem.getInstance().removeVirtualFileListener(myListener);
}
}
@NotNull
@NonNls
public String getComponentName() {
return "AndroidLayoutPreviewToolWindowManager";
}
public void initComponent() {
}
public void disposeComponent() {
}
private void processFileEditorChange(final TextEditor newEditor) {
myToolWindowUpdateQueue.cancelAllUpdates();
myToolWindowUpdateQueue.queue(new Update("update") {
public void run() {
if (!myToolWindowReady || myToolWindowDisposed) {
return;
}
final Editor activeEditor = newEditor != null ? newEditor.getEditor() : null;
if (myToolWindow == null) {
if (activeEditor == null) {
return;
}
initToolWindow();
}
if (activeEditor == null) {
myToolWindowForm.setFile(null);
myToolWindow.setAvailable(false, null);
return;
}
final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(activeEditor.getDocument());
if (psiFile == null) {
myToolWindowForm.setFile(null);
myToolWindow.setAvailable(false, null);
return;
}
if (myToolWindowForm.getFile() != psiFile) {
ApplicationManager.getApplication().saveAll();
myToolWindowForm.setFile(psiFile);
render();
}
myToolWindow.setAvailable(true, null);
myToolWindow.show(null);
}
});
}
public void render() {
final PsiFile psiFile = myToolWindowForm.getFile();
if (psiFile == null) {
return;
}
final String layoutXmlText = psiFile.getText();
final AndroidFacet facet = AndroidFacet.getInstance(psiFile);
assert facet != null;
myRenderingQueue.queue(new Update("render") {
@Override
public void run() {
doRender(facet, layoutXmlText);
}
@Override
public boolean canEat(Update update) {
return true;
}
});
}
private void doRender(final AndroidFacet facet, String layoutXmlText) {
BufferedImage image = null;
RenderingErrorMessage errorMessage = null;
final String imgPath = FileUtil.getTempDirectory() + "/androidLayoutPreview.png";
try {
final LayoutDeviceConfiguration deviceConfiguration = myToolWindowForm.getSelectedDeviceConfiguration();
if (deviceConfiguration == null) {
throw new RenderingException("Device is not specified");
}
final FolderConfiguration config = new FolderConfiguration();
config.set(deviceConfiguration.getConfiguration());
config.setDockModeQualifier(new DockModeQualifier(myToolWindowForm.getSelectedDockMode()));
config.setNightModeQualifier(new NightModeQualifier(myToolWindowForm.getSelectedNightMode()));
final LocaleData localeData = myToolWindowForm.getSelectedLocaleData();
config.setLanguageQualifier(new LanguageQualifier(localeData.getLanguage()));
config.setRegionQualifier(new RegionQualifier(localeData.getRegion()));
final IAndroidTarget target = myToolWindowForm.getSelectedTarget();
final ThemeData theme = myToolWindowForm.getSelectedTheme();
synchronized (RENDERING_LOCK) {
if (target != null && theme != null &&
RenderUtil.renderLayout(myProject, layoutXmlText, imgPath, target, facet, config, theme)) {
final File input = new File(imgPath);
image = ImageIO.read(input);
}
}
}
catch (RenderingException e) {
LOG.debug(e);
final String message = e.getPresentableMessage();
errorMessage = new RenderingErrorMessage(message != null
? message
: AndroidBundle.message("android.layout.preview.default.error.message"));
}
catch (IOException e) {
LOG.info(e);
final String message = e.getMessage();
errorMessage = new RenderingErrorMessage("I/O error" + (message != null ? ": " + message : ""));
}
catch (AndroidSdkNotConfiguredException e) {
LOG.debug(e);
errorMessage = new RenderingErrorMessage("Please ", "configure", " Android SDK", new Runnable() {
@Override
public void run() {
AndroidSdkUtils.openModuleDependenciesConfigurable(facet.getModule());
}
});
}
final RenderingErrorMessage finalErrorMessage = errorMessage;
final BufferedImage finalImage = image;
if (!myRenderingQueue.isEmpty()) {
return;
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (!myToolWindowReady || myToolWindowDisposed) {
return;
}
myToolWindowForm.setErrorMessage(finalErrorMessage);
if (finalErrorMessage == null) {
myToolWindowForm.setImage(finalImage);
}
myToolWindowForm.updatePreviewPanel();
}
});
}
@Nullable
private TextEditor getActiveLayoutXmlEditor() {
FileEditor[] fileEditors = myFileEditorManager.getSelectedEditors();
if (fileEditors.length > 0 && fileEditors[0] instanceof TextEditor) {
final TextEditor textEditor = (TextEditor)fileEditors[0];
if (isApplicableEditor(textEditor)) {
return textEditor;
}
}
return null;
}
private boolean isApplicableEditor(TextEditor textEditor) {
final Document document = textEditor.getEditor().getDocument();
final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
return psiFile instanceof XmlFile &&
AndroidFacet.getInstance(psiFile) != null &&
LayoutDomFileDescription.isLayoutFile((XmlFile)psiFile);
}
public static AndroidLayoutPreviewToolWindowManager getInstance(Project project) {
return project.getComponent(AndroidLayoutPreviewToolWindowManager.class);
}
private class MyVirtualFileListener extends VirtualFileAdapter {
@Override
public void propertyChanged(VirtualFilePropertyEvent event) {
fileChanged(event);
}
@Override
public void fileCreated(VirtualFileEvent event) {
fileChanged(event);
}
@Override
public void fileDeleted(VirtualFileEvent event) {
fileChanged(event);
}
@Override
public void fileMoved(VirtualFileMoveEvent event) {
fileChanged(event);
}
private void fileChanged(VirtualFileEvent event) {
if (myToolWindowForm == null || !myToolWindowReady || myToolWindowDisposed) {
return;
}
final PsiFile layoutFile = myToolWindowForm.getFile();
if (layoutFile == null) {
return;
}
final VirtualFile changedFile = event.getFile();
final VirtualFile parent = changedFile.getParent();
if (parent != null && ResourceManager.isResourceDirectory(parent, myProject)) {
myToolWindowForm.updateLocales();
render();
}
}
}
private class MyAndroidPlatformListener implements ModuleRootListener {
private final Map<Module, Sdk> myModule2Sdk = new HashMap<Module, Sdk>();
@Override
public void beforeRootsChange(ModuleRootEvent event) {
}
@Override
public void rootsChanged(ModuleRootEvent event) {
if (myToolWindowForm == null || !myToolWindowReady || myToolWindowDisposed) {
return;
}
final PsiFile file = myToolWindowForm.getFile();
if (file != null) {
final Module module = ModuleUtil.findModuleForPsiElement(file);
if (module != null) {
final Sdk prevSdk = myModule2Sdk.get(module);
final Sdk newSdk = ModuleRootManager.getInstance(module).getSdk();
if (newSdk != null &&
(newSdk.getSdkType() instanceof AndroidSdkType || prevSdk.getSdkType() instanceof AndroidSdkType) &&
!newSdk.equals(prevSdk)) {
myModule2Sdk.put(module, newSdk);
final AndroidSdkAdditionalData additionalData = (AndroidSdkAdditionalData)newSdk.getSdkAdditionalData();
final AndroidPlatform newPlatform = additionalData != null ? additionalData.getAndroidPlatform() : null;
myToolWindowForm.updateDevicesAndTargets(newPlatform);
myToolWindowForm.updateThemes();
render();
}
}
}
}
}
private class MyFileEditorManagerListener implements FileEditorManagerListener {
public void fileOpened(FileEditorManager source, VirtualFile file) {
processFileEditorChange(getActiveLayoutXmlEditor());
}
public void fileClosed(FileEditorManager source, VirtualFile file) {
processFileEditorChange(getActiveLayoutXmlEditor());
}
public void selectionChanged(FileEditorManagerEvent event) {
final FileEditor newEditor = event.getNewEditor();
TextEditor layoutXmlEditor = null;
if (newEditor instanceof TextEditor) {
final TextEditor textEditor = (TextEditor)newEditor;
if (isApplicableEditor(textEditor)) {
layoutXmlEditor = textEditor;
}
}
processFileEditorChange(layoutXmlEditor);
}
}
}
@@ -0,0 +1,237 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.resources.configuration.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
class LayoutDevice {
private final String myName;
private final List<LayoutDeviceConfiguration> myConfigurations = new ArrayList<LayoutDeviceConfiguration>();
private List<LayoutDeviceConfiguration> mROList;
private float mXDpi = Float.NaN;
private float mYDpi = Float.NaN;
LayoutDevice(@NotNull String name) {
myName = name;
}
void saveTo(Document doc, Element parentNode) {
final Element deviceNode = createNode(doc, parentNode, "device");
deviceNode.setAttribute("name", myName);
final Element defaultNode = createNode(doc, deviceNode, "default");
if (!Float.isNaN(mXDpi)) {
final Element xdpiNode = createNode(doc, defaultNode, "xdpi");
xdpiNode.setTextContent(Float.toString(mXDpi));
}
if (!Float.isNaN(mYDpi)) {
final Element xdpiNode = createNode(doc, defaultNode, "ydpi");
xdpiNode.setTextContent(Float.toString(mYDpi));
}
synchronized (myConfigurations) {
for (LayoutDeviceConfiguration config : myConfigurations) {
saveConfigurations(doc, deviceNode, config.getName(), config.getConfiguration());
}
}
}
private static Element createNode(Document doc, Element parentNode, String name) {
Element newNode = doc.createElementNS(LayoutDeviceManager.LAYOUT_DEVICES_NAMESPACE, name);
newNode.setPrefix(doc.lookupPrefix(LayoutDeviceManager.LAYOUT_DEVICES_NAMESPACE));
parentNode.appendChild(newNode);
return newNode;
}
private static void saveConfigurations(Document doc, Element parent, String configName, FolderConfiguration config) {
final Element configNode = createNode(doc, parent, "config");
configNode.setAttribute("name", configName);
final CountryCodeQualifier countryCodeQualifier = config.getCountryCodeQualifier();
if (countryCodeQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.COUNTRY_CODE_TAG_NAME);
node.setTextContent(Integer.toString(countryCodeQualifier.getCode()));
}
final NetworkCodeQualifier networkCodeQualifier = config.getNetworkCodeQualifier();
if (networkCodeQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.NETWORK_CODE_TAG_NAME);
node.setTextContent(Integer.toString(networkCodeQualifier.getCode()));
}
final ScreenSizeQualifier screenSizeQualifier = config.getScreenSizeQualifier();
if (screenSizeQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.SCREEN_SIZE_TAG_NAME);
node.setTextContent(screenSizeQualifier.getFolderSegment());
}
final ScreenRatioQualifier screenRatioQualifier = config.getScreenRatioQualifier();
if (screenRatioQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.SCREEN_RATIO_TAG_NAME);
node.setTextContent(screenRatioQualifier.getFolderSegment());
}
final ScreenOrientationQualifier screenOrientationQualifier = config.getScreenOrientationQualifier();
if (screenOrientationQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.SCREEN_ORIENTATION_TAG_NAME);
node.setTextContent(screenOrientationQualifier.getFolderSegment());
}
final PixelDensityQualifier pixelDensityQualifier = config.getPixelDensityQualifier();
if (pixelDensityQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.PIXEL_DENSITY_TAG_NAME);
node.setTextContent(pixelDensityQualifier.getFolderSegment());
}
final TouchScreenQualifier touchTypeQualifier = config.getTouchTypeQualifier();
if (touchTypeQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.TOUCH_TYPE_TAG_NAME);
node.setTextContent(touchTypeQualifier.getFolderSegment());
}
final KeyboardStateQualifier keyboardStateQualifier = config.getKeyboardStateQualifier();
if (keyboardStateQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.KEYBOARD_STATE_TAG_NAME);
node.setTextContent(keyboardStateQualifier.getFolderSegment());
}
final TextInputMethodQualifier textInputMethodQualifier = config.getTextInputMethodQualifier();
if (textInputMethodQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.TEXT_INPUT_METHOD_TAG_NAME);
node.setTextContent(textInputMethodQualifier.getFolderSegment());
}
final NavigationStateQualifier navigationStateQualifier = config.getNavigationStateQualifier();
if (navigationStateQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.NAV_STATE_TAG_NAME);
node.setTextContent(navigationStateQualifier.getFolderSegment());
}
final NavigationMethodQualifier navigationMethodQualifier = config.getNavigationMethodQualifier();
if (navigationMethodQualifier != null) {
Element node = createNode(doc, configNode, LayoutDeviceHandler.NAV_METHOD_TAG_NAME);
node.setTextContent(navigationMethodQualifier.getFolderSegment());
}
final ScreenDimensionQualifier screenDimensionQualifier = config.getScreenDimensionQualifier();
if (screenDimensionQualifier != null) {
final Element screenDimensionNode = createNode(doc, configNode, LayoutDeviceHandler.SCREEN_DIMENSION_TAG_NAME);
final Element size1Node = createNode(doc, screenDimensionNode, LayoutDeviceHandler.SIZE_TAG_NAME);
size1Node.setTextContent(Integer.toString(screenDimensionQualifier.getValue1()));
final Element size2Node = createNode(doc, screenDimensionNode, LayoutDeviceHandler.SIZE_TAG_NAME);
size2Node.setTextContent(Integer.toString(screenDimensionQualifier.getValue2()));
}
}
public void addConfig(@NotNull String name, @NotNull FolderConfiguration config) {
synchronized (myConfigurations) {
doAddConfig(name, config);
seal();
}
}
void addConfigs(List<LayoutDeviceConfiguration> configs) {
synchronized (myConfigurations) {
for (LayoutDeviceConfiguration config : configs) {
for (LayoutDeviceConfiguration c : myConfigurations) {
if (c.getName().equals(config.getName())) {
myConfigurations.remove(c);
break;
}
}
myConfigurations.add(config);
}
seal();
}
}
void removeConfig(String name) {
synchronized (myConfigurations) {
for (LayoutDeviceConfiguration config : myConfigurations) {
if (config.getName().equals(name)) {
myConfigurations.remove(config);
seal();
return;
}
}
}
}
private void doAddConfig(@NotNull String name, @NotNull FolderConfiguration config) {
for (LayoutDeviceConfiguration c : myConfigurations) {
if (c.getName().equals(name)) {
myConfigurations.remove(c);
break;
}
}
myConfigurations.add(new LayoutDeviceConfiguration(name, config));
}
private void seal() {
mROList = Collections.unmodifiableList(myConfigurations);
}
void setXDpi(float xdpi) {
mXDpi = xdpi;
}
void setYDpi(float ydpi) {
mYDpi = ydpi;
}
@NotNull
public String getName() {
return myName;
}
public List<LayoutDeviceConfiguration> getConfigurations() {
synchronized (myConfigurations) {
return mROList;
}
}
@Nullable
public LayoutDeviceConfiguration getDeviceConfigByName(String name) {
synchronized (myConfigurations) {
for (LayoutDeviceConfiguration config : myConfigurations) {
if (config.getName().equals(name)) {
return config;
}
}
}
return null;
}
@Nullable
public FolderConfiguration getFolderConfigByName(String name) {
synchronized (myConfigurations) {
for (LayoutDeviceConfiguration config : myConfigurations) {
if (config.getName().equals(name)) {
return config.getConfiguration();
}
}
}
return null;
}
public float getXDpi() {
return mXDpi;
}
public float getYDpi() {
return mYDpi;
}
}
@@ -0,0 +1,28 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import org.jetbrains.annotations.NotNull;
/**
* @author Eugene.Kudelevsky
*/
class LayoutDeviceConfiguration {
private final String myName;
private final FolderConfiguration myConfiguration;
LayoutDeviceConfiguration(@NotNull String name, @NotNull FolderConfiguration configuration) {
myName = name;
myConfiguration = configuration;
}
@NotNull
public String getName() {
return myName;
}
@NotNull
public FolderConfiguration getConfiguration() {
return myConfiguration;
}
}
@@ -0,0 +1,160 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.resources.configuration.*;
import com.android.resources.*;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
class LayoutDeviceHandler extends DefaultHandler {
private static final String DEVICE_TAG_NAME = "device";
private static final String CONFIG_TAG_NAME = "config";
private static final String DEFAULT_TAG_NAME = "default";
static final String SCREEN_DIMENSION_TAG_NAME = "screen-dimension";
static final String COUNTRY_CODE_TAG_NAME = "country-code";
static final String NETWORK_CODE_TAG_NAME = "network-code";
static final String SCREEN_SIZE_TAG_NAME = "screen-size";
static final String SCREEN_RATIO_TAG_NAME = "screen-ratio";
static final String SCREEN_ORIENTATION_TAG_NAME = "screen-orientation";
static final String PIXEL_DENSITY_TAG_NAME = "pixel-density";
static final String TOUCH_TYPE_TAG_NAME = "touch-type";
static final String KEYBOARD_STATE_TAG_NAME = "keyboard-state";
static final String TEXT_INPUT_METHOD_TAG_NAME = "text-input-method";
static final String NAV_STATE_TAG_NAME = "nav-state";
static final String NAV_METHOD_TAG_NAME = "nav-method";
static final String XDPI_TAG_NAME = "xdpi";
static final String YDPI_TAG_NAME = "ydpi";
static final String SIZE_TAG_NAME = "size";
static final String NAME_ATTRIBUTE = "name";
private final List<LayoutDevice> myDevices = new ArrayList<LayoutDevice>();
private final StringBuilder myStringBuilder = new StringBuilder();
private LayoutDevice myCurrentDevice;
private FolderConfiguration myDefaultConfiguration;
private FolderConfiguration myCurrentConfiguration;
private String mySize1;
private String mySize2;
public List<LayoutDevice> getDevices() {
return myDevices;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if (DEVICE_TAG_NAME.equals(localName)) {
final String deviceName = attributes.getValue("", NAME_ATTRIBUTE);
if (deviceName != null) {
myCurrentDevice = new LayoutDevice(deviceName);
myDevices.add(myCurrentDevice);
}
}
else if (DEFAULT_TAG_NAME.equals(localName)) {
myDefaultConfiguration = myCurrentConfiguration = new FolderConfiguration();
}
else if (CONFIG_TAG_NAME.equals(localName)) {
myCurrentConfiguration = new FolderConfiguration();
if (myDefaultConfiguration != null) {
myCurrentConfiguration.set(myDefaultConfiguration);
}
final String deviceName = attributes.getValue("", NAME_ATTRIBUTE);
if (deviceName != null) {
myCurrentDevice.addConfig(deviceName, myCurrentConfiguration);
}
}
else if (SCREEN_DIMENSION_TAG_NAME.equals(localName)) {
mySize1 = null;
mySize2 = null;
}
myStringBuilder.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
myStringBuilder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
if (DEVICE_TAG_NAME.equals(localName)) {
myCurrentDevice = null;
myDefaultConfiguration = null;
}
else if (CONFIG_TAG_NAME.equals(localName)) {
//mCurrentConfig.updateScreenWidthAndHeight();
myCurrentConfiguration = null;
}
else if (COUNTRY_CODE_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setCountryCodeQualifier(new CountryCodeQualifier(
Integer.parseInt(myStringBuilder.toString())));
}
else if (NETWORK_CODE_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier(
Integer.parseInt(myStringBuilder.toString())));
}
else if (SCREEN_SIZE_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setScreenSizeQualifier(new ScreenSizeQualifier(
ScreenSize.getEnum(myStringBuilder.toString())));
}
else if (SCREEN_RATIO_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setScreenRatioQualifier(new ScreenRatioQualifier(
ScreenRatio.getEnum(myStringBuilder.toString())));
}
else if (SCREEN_ORIENTATION_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setScreenOrientationQualifier(new ScreenOrientationQualifier(
ScreenOrientation.getEnum(myStringBuilder.toString())));
}
else if (PIXEL_DENSITY_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setPixelDensityQualifier(new PixelDensityQualifier(
Density.getEnum(myStringBuilder.toString())));
}
else if (TOUCH_TYPE_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setTouchTypeQualifier(new TouchScreenQualifier(
TouchScreen.getEnum(myStringBuilder.toString())));
}
else if (KEYBOARD_STATE_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setKeyboardStateQualifier(new KeyboardStateQualifier(
KeyboardState.getEnum(myStringBuilder.toString())));
}
else if (TEXT_INPUT_METHOD_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setTextInputMethodQualifier(new TextInputMethodQualifier(
Keyboard.getEnum(myStringBuilder.toString())));
}
else if (NAV_STATE_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setNavigationStateQualifier(new NavigationStateQualifier(
NavigationState.getEnum(myStringBuilder.toString())));
}
else if (NAV_METHOD_TAG_NAME.equals(localName)) {
myCurrentConfiguration.setNavigationMethodQualifier(new NavigationMethodQualifier(
Navigation.getEnum(myStringBuilder.toString())));
}
else if (SCREEN_DIMENSION_TAG_NAME.equals(localName)) {
final ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(mySize1, mySize2);
if (qualifier != null) {
myCurrentConfiguration.setScreenDimensionQualifier(qualifier);
}
}
else if (XDPI_TAG_NAME.equals(localName)) {
myCurrentDevice.setXDpi(Float.parseFloat(myStringBuilder.toString()));
}
else if (YDPI_TAG_NAME.equals(localName)) {
myCurrentDevice.setYDpi(Float.parseFloat(myStringBuilder.toString()));
}
else if (SIZE_TAG_NAME.equals(localName)) {
if (mySize1 == null) {
mySize1 = myStringBuilder.toString();
}
else if (mySize2 == null) {
mySize2 = myStringBuilder.toString();
}
}
}
}
@@ -0,0 +1,306 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.android.sdk.AndroidSdk;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.XMLConstants;
import javax.xml.parsers.*;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
class LayoutDeviceManager {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.uipreview.LayoutDeviceManager");
private static final String LAYOUT_DEVICES_XSD = "layout-devices.xsd";
static final String LAYOUT_DEVICES_NAMESPACE = "http://schemas.android.com/sdk/android/layout-devices/1";
private final List<LayoutDevice> myUserLayoutDevices = new ArrayList<LayoutDevice>();
private final SAXParserFactory myParserFactory;
private List<LayoutDevice> myDefaultLayoutDevices = new ArrayList<LayoutDevice>();
private List<LayoutDevice> myAddOnLayoutDevices = new ArrayList<LayoutDevice>();
private List<LayoutDevice> myLayoutDevices;
LayoutDeviceManager() {
myParserFactory = SAXParserFactory.newInstance();
myParserFactory.setNamespaceAware(true);
}
public List<LayoutDevice> getCombinedList() {
return myLayoutDevices;
}
public List<LayoutDevice> getDefaultLayoutDevices() {
return myDefaultLayoutDevices;
}
public List<LayoutDevice> getAddOnLayoutDevice() {
return myAddOnLayoutDevices;
}
public List<LayoutDevice> getUserLayoutDevices() {
return myUserLayoutDevices;
}
@Nullable
public LayoutDevice getUserLayoutDevice(String name) {
for (LayoutDevice d : myUserLayoutDevices) {
if (d.getName().equals(name)) {
return d;
}
}
return null;
}
public LayoutDevice addUserDevice(@NotNull String name, float xdpi, float ydpi) {
final LayoutDevice device = new LayoutDevice(name);
device.setXDpi(xdpi);
device.setYDpi(ydpi);
myUserLayoutDevices.add(device);
combineLayoutDevices();
return device;
}
public void removeUserDevice(LayoutDevice device) {
if (myUserLayoutDevices.remove(device)) {
combineLayoutDevices();
}
}
public LayoutDevice replaceUserDevice(@NotNull LayoutDevice device, @NotNull String newName, float newXDpi, float newYDpi) {
if (device.getName().equals(newName) && device.getXDpi() == newXDpi && device.getYDpi() == newYDpi) {
return device;
}
final LayoutDevice newDevice = new LayoutDevice(newName);
newDevice.setXDpi(newXDpi);
newDevice.setYDpi(newYDpi);
final List<LayoutDeviceConfiguration> configs = device.getConfigurations();
newDevice.addConfigs(configs);
myUserLayoutDevices.remove(device);
myUserLayoutDevices.add(newDevice);
combineLayoutDevices();
return newDevice;
}
public void addUserConfiguration(@NotNull LayoutDevice device,
@NotNull String configurationName,
@NotNull FolderConfiguration configuration) {
if (myUserLayoutDevices.contains(device)) {
device.addConfig(configurationName, configuration);
}
}
public void replaceUserConfiguration(@NotNull LayoutDevice device,
@Nullable String oldConfigurationName,
@NotNull String newConfigurationName,
@NotNull FolderConfiguration configuration) {
if (myUserLayoutDevices.contains(device)) {
if (oldConfigurationName != null && !oldConfigurationName.equals(newConfigurationName)) {
device.removeConfig(oldConfigurationName);
}
device.addConfig(newConfigurationName, configuration);
}
}
public void removeUserConfiguration(LayoutDevice device, String configName) {
if (myUserLayoutDevices.contains(device)) {
device.removeConfig(configName);
}
}
public void saveUserDevices() {
try {
final String userFolder = AndroidLocation.getFolder();
final File deviceXmlFile = new File(userFolder, SdkConstants.FN_DEVICES_XML);
if (!deviceXmlFile.isDirectory()) {
write(deviceXmlFile, myUserLayoutDevices);
}
}
catch (AndroidLocationException e) {
LOG.info(e);
}
}
public void loadDevices(@NotNull AndroidSdk sdk) {
loadDefaultAndUserDevices(sdk);
loadAddOnLayoutDevices(sdk);
}
private void loadDefaultAndUserDevices(@NotNull AndroidSdk sdk) {
loadDefaultLayoutDevices(sdk.getLocation());
try {
final String userFolder = AndroidLocation.getFolder();
final File deviceXmlFile = new File(userFolder, SdkConstants.FN_DEVICES_XML);
if (deviceXmlFile.isFile()) {
parseLayoutDevices(deviceXmlFile, myUserLayoutDevices);
}
}
catch (AndroidLocationException e) {
LOG.info(e);
}
}
private void parseAddOnLayoutDevice(File deviceXml) {
parseLayoutDevices(deviceXml, myAddOnLayoutDevices);
}
private void sealAddonLayoutDevices() {
myAddOnLayoutDevices = Collections.unmodifiableList(myAddOnLayoutDevices);
combineLayoutDevices();
}
private void loadAddOnLayoutDevices(@NotNull AndroidSdk sdk) {
for (IAndroidTarget target : sdk.getTargets()) {
if (!target.isPlatform()) {
File deviceXml = new File(target.getLocation(), SdkConstants.FN_DEVICES_XML);
if (deviceXml.isFile()) {
parseAddOnLayoutDevice(deviceXml);
}
}
}
sealAddonLayoutDevices();
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) {
try {
final Source source = new StreamSource(new FileReader(deviceXml));
final MyErrorHandler errorHandler = new MyErrorHandler();
final Validator validator = getValidator(errorHandler);
validator.validate(source);
if (!errorHandler.foundError()) {
final LayoutDeviceHandler handler = new LayoutDeviceHandler();
final SAXParser parser = myParserFactory.newSAXParser();
parser.parse(new InputSource(new FileInputStream(deviceXml)), handler);
list.addAll(handler.getDevices());
}
}
catch (SAXException e) {
LOG.info(e);
}
catch (FileNotFoundException e) {
LOG.info(e);
}
catch (IOException e) {
LOG.info(e);
}
catch (ParserConfigurationException e) {
LOG.info(e);
}
}
private static Validator getValidator(ErrorHandler handler) throws SAXException {
final InputStream xsdStream = LayoutDeviceManager.class.getResourceAsStream(LAYOUT_DEVICES_XSD);
final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = factory.newSchema(new StreamSource(xsdStream));
final Validator validator = schema.newValidator();
if (handler != null) {
validator.setErrorHandler(handler);
}
return validator;
}
private void loadDefaultLayoutDevices(String sdkOsLocation) {
final List<LayoutDevice> devices = new ArrayList<LayoutDevice>();
final File toolsFolder = new File(sdkOsLocation, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER);
if (toolsFolder.isDirectory()) {
File deviceXml = new File(toolsFolder, SdkConstants.FN_DEVICES_XML);
if (deviceXml.isFile()) {
parseLayoutDevices(deviceXml, devices);
}
}
myDefaultLayoutDevices = Collections.unmodifiableList(devices);
}
private void combineLayoutDevices() {
final List<LayoutDevice> devices = new ArrayList<LayoutDevice>();
devices.addAll(myDefaultLayoutDevices);
devices.addAll(myAddOnLayoutDevices);
devices.addAll(myUserLayoutDevices);
myLayoutDevices = Collections.unmodifiableList(devices);
}
private static void write(File deviceXml, List<LayoutDevice> deviceList) {
try {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
final Document doc = docBuilder.newDocument();
final Element baseNode = doc.createElementNS(LAYOUT_DEVICES_NAMESPACE, "layout-devices");
baseNode.setPrefix("d");
doc.appendChild(baseNode);
for (LayoutDevice device : deviceList) {
device.saveTo(doc, baseNode);
}
final Source source = new DOMSource(doc);
final File file = new File(deviceXml.getAbsolutePath());
final Result result = new StreamResult(file);
final Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(source, result);
}
catch (Exception e) {
LOG.info(e);
}
}
private static class MyErrorHandler implements ErrorHandler {
private boolean myError = false;
MyErrorHandler() {
}
public boolean foundError() {
return myError;
}
public void error(SAXParseException ex) throws SAXException {
myError = true;
LOG.info(ex);
}
public void fatalError(SAXParseException ex) throws SAXException {
myError = true;
LOG.info(ex);
}
public void warning(SAXParseException ex) throws SAXException {
LOG.debug(ex);
}
}
}
@@ -0,0 +1,53 @@
package org.jetbrains.android.uipreview;
/**
* @author Eugene.Kudelevsky
*/
class LocaleData {
private final String myLanguage;
private final String myRegion;
private final String myPresentation;
LocaleData(String language, String region, String presentation) {
myPresentation = presentation;
myLanguage = language;
myRegion = region;
}
public String getLanguage() {
return myLanguage;
}
public String getRegion() {
return myRegion;
}
@Override
public String toString() {
return myPresentation;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LocaleData myLocale = (LocaleData)o;
if (myLanguage != null ? !myLanguage.equals(myLocale.myLanguage) : myLocale.myLanguage != null) {
return false;
}
if (myRegion != null ? !myRegion.equals(myLocale.myRegion) : myLocale.myRegion != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = myLanguage != null ? myLanguage.hashCode() : 0;
result = 31 * result + (myRegion != null ? myRegion.hashCode() : 0);
return result;
}
}
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.jetbrains.android.uipreview;
import com.android.ide.common.rendering.api.*;
import com.android.ide.common.rendering.legacy.LegacyCallback;
import com.android.resources.ResourceType;
import com.android.util.Pair;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* @author Eugene.Kudelevsky
*/
class ProjectCallback extends LegacyCallback implements IProjectCallback {
private final Map<ResourceType, Map<String, Integer>> myResource2Id = new HashMap<ResourceType, Map<String, Integer>>();
private final Map<Integer, Pair<ResourceType, String>> myId2Resource = new HashMap<Integer, Pair<ResourceType, String>>();
@Nullable
public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie, Object viewObject) {
return null;
}
@Nullable
public Object getAdapterItemValue(ResourceReference adapterView,
Object adapterCookie,
ResourceReference itemRef,
int fullPosition,
int positionPerType,
int fullParentPosition,
int parentPositionPerType,
ResourceReference viewRef,
ViewAttribute viewAttribute,
Object defaultValue) {
return null;
}
@Nullable
public String getNamespace() {
return null;
}
@Nullable
public ILayoutPullParser getParser(String layoutName) {
// don't support custom parser for included files.
return null;
}
public Integer getResourceId(ResourceType type, String name) {
Map<String, Integer> typeMap = myResource2Id.get(type);
if (typeMap == null) {
typeMap = new HashMap<String, Integer>();
myResource2Id.put(type, typeMap);
}
Integer value = typeMap.get(name);
if (value == null) {
value = typeMap.size() + 1;
typeMap.put(name, value);
myId2Resource.put(value, Pair.of(type, name));
}
return value;
}
@Nullable
public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
throws ClassNotFoundException, Exception {
// todo: support custom views
return null;
}
public Pair<ResourceType, String> resolveResourceId(int id) {
return myId2Resource.get(id);
}
@Nullable
public String resolveResourceId(int[] id) {
// this is needed only when custom views have custom styleable
return null;
}
}
@@ -0,0 +1,113 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.jetbrains.android.uipreview;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.Density;
import com.android.resources.ScreenOrientation;
import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.awt.*;
import java.io.FileNotFoundException;
import java.io.StringReader;
/**
* @author Eugene.Kudelevsky
*/
class RenderService {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.uipreview.RenderService");
private final IProjectCallback myProjectCallback;
private final ResourceResolver myResourceResolver;
private final LayoutLibrary myLayoutLib;
private final FolderConfiguration myConfig;
private final int myMinSdkVersion;
RenderService(LayoutLibrary layoutLibrary,
@NotNull ResourceResolver resourceResolver,
FolderConfiguration config,
IProjectCallback projectCallback,
int minSdkVersion) {
myLayoutLib = layoutLibrary;
myResourceResolver = resourceResolver;
myConfig = config;
myProjectCallback = projectCallback;
myMinSdkVersion = minSdkVersion;
}
@Nullable
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
public RenderSession createRenderSession(@NotNull String layoutXmlText, @NotNull String appLabel)
throws FileNotFoundException, XmlPullParserException {
final ILayoutPullParser parser = new XmlParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(new StringReader(layoutXmlText));
final Dimension dimension = getDimension();
if (dimension == null) {
return null;
}
final int minSdkVersion = myMinSdkVersion >= 0 ? myMinSdkVersion : myConfig.getVersionQualifier().getVersion();
final int targetSdkVersion = myConfig.getVersionQualifier().getVersion();
final Density density = myConfig.getPixelDensityQualifier().getValue();
final int xdpi = density.getDpiValue();
final int ydpi = density.getDpiValue();
final SessionParams params =
new SessionParams(parser, RenderingMode.NORMAL, this, dimension.width, dimension.height, density, xdpi, ydpi, myResourceResolver,
myProjectCallback, minSdkVersion, targetSdkVersion, new SimpleLogger(LOG));
params.setExtendedViewInfoMode(false);
params.setAppLabel(appLabel);
params.setConfigScreenSize(myConfig.getScreenSizeQualifier().getValue());
return myLayoutLib.createSession(params);
}
private Dimension getDimension() {
final int size1 = myConfig.getScreenDimensionQualifier().getValue1();
final int size2 = myConfig.getScreenDimensionQualifier().getValue2();
final ScreenOrientation orientation = myConfig.getScreenOrientationQualifier().getValue();
switch (orientation) {
case LANDSCAPE:
return new Dimension(size1 < size2 ? size2 : size1, size1 < size2 ? size1 : size2);
case PORTRAIT:
return new Dimension(size1 < size2 ? size1 : size2, size1 < size2 ? size2 : size1);
case SQUARE:
return new Dimension(size1, size1);
default:
LOG.error("Unknown screen orientation " + orientation);
return null;
}
}
}
@@ -0,0 +1,166 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.jetbrains.android.uipreview;
import com.android.ide.common.log.ILogger;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.api.IProjectCallback;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.FrameworkResources;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.*;
import com.android.ide.common.sdk.LoadStatus;
import com.android.io.FileWrapper;
import com.android.io.FolderWrapper;
import com.android.resources.*;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.project.ProjectProperties;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* @author Eugene.Kudelevsky
*/
public class RenderServiceFactory {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.uipreview.RenderServiceFactory");
private final Map<String,Map<String,Integer>> myEnumMap;
private LayoutLibrary myLibrary;
private FrameworkResources myResources;
@Nullable
public static RenderServiceFactory create(@NotNull IAndroidTarget target,
@NotNull Map<String, Map<String, Integer>> enumMap) throws RenderingException, IOException {
final RenderServiceFactory factory = new RenderServiceFactory(enumMap);
if (factory.loadLibrary(target)) {
return factory;
}
return null;
}
public static FolderConfiguration createConfig(int size1,
int size2,
ScreenSize screenSize,
ScreenRatio screenRatio,
ScreenOrientation orientation,
Density density,
TouchScreen touchScreen,
KeyboardState keyboardState,
Keyboard keyboard,
NavigationState navigationState,
Navigation navigation) {
final FolderConfiguration config = new FolderConfiguration();
config.addQualifier(new ScreenSizeQualifier(screenSize));
config.addQualifier(new ScreenRatioQualifier(screenRatio));
config.addQualifier(new ScreenOrientationQualifier(orientation));
config.addQualifier(new PixelDensityQualifier(density));
config.addQualifier(new TouchScreenQualifier(touchScreen));
config.addQualifier(new KeyboardStateQualifier(keyboardState));
config.addQualifier(new TextInputMethodQualifier(keyboard));
config.addQualifier(new NavigationStateQualifier(navigationState));
config.addQualifier(new NavigationMethodQualifier(navigation));
config.addQualifier(new ScreenDimensionQualifier(size1, size2));
return config;
}
public ResourceResolver createResourceResolver(FolderConfiguration config,
ResourceRepository projectResources,
String themeName,
boolean isProjectTheme) {
final Map<ResourceType, Map<String, ResourceValue>> configedProjectRes = projectResources.getConfiguredResources(config);
final Map<ResourceType, Map<String, ResourceValue>> configedFrameworkRes = myResources.getConfiguredResources(config);
return ResourceResolver.create(configedProjectRes, configedFrameworkRes, themeName, isProjectTheme);
}
public RenderService createService(ResourceResolver resources,
FolderConfiguration config,
IProjectCallback projectCallback,
int minSdkVersion) {
return new RenderService(myLibrary, resources, config, projectCallback, minSdkVersion);
}
private RenderServiceFactory(@NotNull Map<String, Map<String, Integer>> enumMap) {
myEnumMap = enumMap;
}
private boolean loadLibrary(@NotNull IAndroidTarget target) throws RenderingException, IOException {
final String layoutLibJarPath = target.getPath(IAndroidTarget.LAYOUT_LIB);
final VirtualFile layoutLibJar = LocalFileSystem.getInstance().findFileByPath(layoutLibJarPath);
if (layoutLibJar == null || layoutLibJar.isDirectory()) {
throw new RenderingException(AndroidBundle.message("android.file.not.exist.error", FileUtil.toSystemDependentName(layoutLibJarPath)));
}
final String resFolderPath = target.getPath(IAndroidTarget.RESOURCES);
final VirtualFile resFolder = LocalFileSystem.getInstance().findFileByPath(resFolderPath);
if (resFolder == null || !resFolder.isDirectory()) {
throw new RenderingException(
AndroidBundle.message("android.directory.cannot.be.found.error", FileUtil.toSystemDependentName(resFolderPath)));
}
final String fontFolderPath = target.getPath(IAndroidTarget.FONTS);
final VirtualFile fontFolder = LocalFileSystem.getInstance().findFileByPath(fontFolderPath);
if (fontFolder == null || !fontFolder.isDirectory()) {
throw new RenderingException(
AndroidBundle.message("android.directory.cannot.be.found.error", FileUtil.toSystemDependentName(fontFolderPath)));
}
final String platformFolderPath = target.isPlatform() ? target.getLocation() : target.getParent().getLocation();
final File platformFolder = new File(platformFolderPath);
if (!platformFolder.isDirectory()) {
throw new RenderingException(
AndroidBundle.message("android.directory.cannot.be.found.error", FileUtil.toSystemDependentName(platformFolderPath)));
}
final FileWrapper buildProp = new FileWrapper(platformFolder, SdkConstants.FN_BUILD_PROP);
if (!buildProp.isFile()) {
throw new RenderingException(
AndroidBundle.message("android.file.not.exist.error", FileUtil.toSystemDependentName(buildProp.getPath())));
}
final SimpleLogger logger = new SimpleLogger(LOG);
myLibrary = LayoutLibrary.load(layoutLibJar.getPath(), logger);
if (myLibrary.getStatus() != LoadStatus.LOADED) {
throw new RenderingException(myLibrary.getLoadMessage());
}
myResources = loadPlatformResources(new File(resFolder.getPath()), logger);
final Map<String, String> buildPropMap = ProjectProperties.parsePropertyFile(buildProp, logger);
return myLibrary.init(buildPropMap, new File(fontFolder.getPath()), myEnumMap, logger);
}
private static FrameworkResources loadPlatformResources(File resFolder, ILogger log) throws IOException {
final FrameworkResources resources = new FrameworkResources();
final FolderWrapper resFolderWrapper = new FolderWrapper(resFolder);
RenderUtil.loadResources(resources, resFolderWrapper);
resources.loadPublicResources(resFolderWrapper, log);
return resources;
}
}
@@ -0,0 +1,204 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.resources.*;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.io.FolderWrapper;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.IAbstractResource;
import com.android.sdklib.IAndroidTarget;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.HashMap;
import org.jetbrains.android.dom.manifest.Application;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdkAdditionalData;
import org.jetbrains.android.sdk.AndroidSdkType;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidSdkNotConfiguredException;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.xmlpull.v1.XmlPullParserException;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* @author Eugene.Kudelevsky
*/
class RenderUtil {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.uipreview.RenderUtil");
private static final Map<String, RenderServiceFactory> ourCachedFactories = new HashMap<String, RenderServiceFactory>();
private static final String DEFAULT_APP_LABEL = "Android application";
private RenderUtil() {
}
public static boolean renderLayout(@NotNull Project project,
@NotNull String layoutXmlText,
@NotNull String imgPath,
@NotNull IAndroidTarget target,
@NotNull AndroidFacet facet,
@NotNull FolderConfiguration config,
@NotNull ThemeData theme)
throws RenderingException, IOException, AndroidSdkNotConfiguredException {
final Sdk sdk = ModuleRootManager.getInstance(facet.getModule()).getSdk();
if (sdk == null || !(sdk.getSdkType() instanceof AndroidSdkType)) {
throw new AndroidSdkNotConfiguredException();
}
final AndroidSdkAdditionalData data = (AndroidSdkAdditionalData)sdk.getSdkAdditionalData();
if (data == null) {
throw new RenderingException(AndroidBundle.message("sdk.broken.error"));
}
final AndroidPlatform platform = data.getAndroidPlatform();
if (platform == null) {
throw new RenderingException(AndroidBundle.message("sdk.broken.error"));
}
config.setVersionQualifier(new VersionQualifier(target.getVersion().getApiLevel()));
final RenderServiceFactory factory = platform.getSdk().getTargetData(target).getRenderServiceFactory(project);
if (factory == null) {
throw new RenderingException(AndroidBundle.message("android.layout.preview.cannot.load.library.error"));
}
/*if (ourTargetHashString == null || ourCachedFactory == null || !ourTargetHashString.equals(target.hashString())) {
ourTargetHashString = target.hashString();
ourCachedFactory = RenderServiceFactory.create(target, enumMap);
if (ourCachedFactory == null) {
throw new RenderingException(AndroidBundle.message("android.layout.preview.cannot.load.library.error"));
}
}*/
final ResourceRepository repository = new ResourceRepository(false) {
@Override
protected ResourceItem createResourceItem(String name) {
return new ResourceItem(name);
}
};
final VirtualFile[] resourceDirs = facet.getLocalResourceManager().getAllResourceDirs();
final IAbstractFolder[] resFolders = toAbstractFolders(resourceDirs);
loadResources(repository, resFolders);
final ResourceResolver resources = factory.createResourceResolver(config, repository, theme.getName(), theme.isProjectTheme());
final int minSdkVersion = getMinSdkVersion(facet);
final RenderService renderService = factory.createService(resources, config, new ProjectCallback(), minSdkVersion);
final RenderSession session;
try {
session = renderService.createRenderSession(layoutXmlText, getAppLabelToShow(facet));
}
catch (XmlPullParserException e) {
throw new RenderingException(e);
}
if (session == null) {
return false;
}
final Result result = session.getResult();
if (!result.isSuccess()) {
final Throwable exception = result.getException();
if (exception != null) {
throw new RenderingException(exception);
}
final String message = result.getErrorMessage();
if (message != null) {
LOG.info(message);
throw new RenderingException();
}
return false;
}
final String format = FileUtil.getExtension(imgPath);
ImageIO.write(session.getImage(), format, new File(imgPath));
return true;
}
private static String getAppLabelToShow(final AndroidFacet facet) {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
public String compute() {
final Manifest manifest = facet.getManifest();
if (manifest != null) {
final Application application = manifest.getApplication();
if (application != null) {
final String label = application.getLabel().getStringValue();
if (label != null) {
return label;
}
}
}
return DEFAULT_APP_LABEL;
}
});
}
private static int getMinSdkVersion(final AndroidFacet facet) {
final XmlTag manifestTag = ApplicationManager.getApplication().runReadAction(new Computable<XmlTag>() {
@Override
public XmlTag compute() {
final Manifest manifest = facet.getManifest();
return manifest != null ? manifest.getXmlTag() : null;
}
});
if (manifestTag != null) {
for (XmlTag usesSdkTag : manifestTag.findSubTags("uses-sdk")) {
final int candidate = AndroidUtils.getIntAttrValue(usesSdkTag, "minSdkVersion");
if (candidate >= 0) {
return candidate;
}
}
}
return -1;
}
@NotNull
private static IAbstractFolder[] toAbstractFolders(@NotNull VirtualFile[] folders) {
final IAbstractFolder[] result = new IAbstractFolder[folders.length];
for (int i = 0; i < folders.length; i++) {
result[i] = new FolderWrapper(folders[i].getPath());
}
return result;
}
public static void loadResources(@NotNull ResourceRepository repository, @NotNull IAbstractFolder... rootFolders) throws IOException {
for (IAbstractFolder rootFolder : rootFolders) {
for (IAbstractResource file : rootFolder.listMembers()) {
if (!(file instanceof IAbstractFolder)) {
continue;
}
final IAbstractFolder folder = (IAbstractFolder)file;
final ResourceFolder resFolder = repository.processFolder(folder);
if (resFolder != null) {
for (IAbstractResource childRes : folder.listMembers()) {
if (childRes instanceof IAbstractFile) {
resFolder.processFile((IAbstractFile)childRes, ResourceDeltaKind.ADDED);
}
}
}
}
}
}
}
@@ -0,0 +1,28 @@
package org.jetbrains.android.uipreview;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Eugene.Kudelevsky
*/
class RenderingErrorMessage {
final String myBeforeLinkText;
final String myLinkText;
final String myAfterLinkText;
final Runnable myQuickFix;
RenderingErrorMessage(@NotNull String beforeLinkText,
@NotNull String linkText,
@NotNull String afterLinkText,
@Nullable Runnable quickFix) {
myBeforeLinkText = beforeLinkText;
myLinkText = linkText;
myAfterLinkText = afterLinkText;
myQuickFix = quickFix;
}
RenderingErrorMessage(@NotNull String message) {
this(message, "", "", null);
}
}
@@ -0,0 +1,32 @@
package org.jetbrains.android.uipreview;
/**
* @author Eugene.Kudelevsky
*/
public class RenderingException extends Exception {
private final String myPresentableMessage;
public RenderingException() {
super();
myPresentableMessage = null;
}
public RenderingException(String message) {
super(message);
myPresentableMessage = message;
}
public RenderingException(String message, Throwable cause) {
super(message, cause);
myPresentableMessage = message;
}
public RenderingException(Throwable cause) {
super(cause);
myPresentableMessage = null;
}
public String getPresentableMessage() {
return myPresentableMessage;
}
}
@@ -0,0 +1,55 @@
package org.jetbrains.android.uipreview;
import com.android.ide.common.log.ILogger;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.sdklib.ISdkLog;
import com.intellij.openapi.diagnostic.Logger;
/**
* @author Eugene.Kudelevsky
*/
class SimpleLogger extends LayoutLog implements ISdkLog, ILogger {
private final Logger myLog;
public SimpleLogger(Logger log) {
myLog = log;
}
@Override
public void error(String tag, String message, Object data) {
myLog.debug(tag + ": " + message);
}
@Override
public void error(String tag, String message, Throwable throwable, Object data) {
myLog.debug(throwable);
myLog.debug(tag + ": " + message);
}
@Override
public void warning(String tag, String message, Object data) {
myLog.debug(tag + ": " + message);
}
@Override
public void fidelityWarning(String tag, String message, Throwable throwable, Object data) {
myLog.debug(throwable);
myLog.debug(tag + ": " + message);
}
@Override
public void warning(String warningFormat, Object... args) {
myLog.debug(String.format(warningFormat, args));
}
@Override
public void error(Throwable t, String errorFormat, Object... args) {
myLog.debug(t);
myLog.debug(String.format(errorFormat, args));
}
@Override
public void printf(String msgFormat, Object... args) {
myLog.debug(String.format(msgFormat, args));
}
}
@@ -0,0 +1,55 @@
package org.jetbrains.android.uipreview;
import org.jetbrains.annotations.NotNull;
/**
* @author Eugene.Kudelevsky
*/
class ThemeData implements Comparable<ThemeData> {
private final String myName;
private final boolean myProjectTheme;
public ThemeData(@NotNull String name, boolean projectTheme) {
myName = name;
myProjectTheme = projectTheme;
}
@Override
public String toString() {
return myName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ThemeData myTheme = (ThemeData)o;
if (myProjectTheme != myTheme.myProjectTheme) return false;
if (!myName.equals(myTheme.myName)) return false;
return true;
}
@Override
public int hashCode() {
int result = myName.hashCode();
result = 31 * result + (myProjectTheme ? 1 : 0);
return result;
}
@Override
public int compareTo(ThemeData theme) {
return myName.compareTo(theme.myName);
}
@NotNull
public String getName() {
return myName;
}
public boolean isProjectTheme() {
return myProjectTheme;
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.jetbrains.android.uipreview;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.legacy.ILegacyPullParser;
import org.jetbrains.annotations.Nullable;
import org.kxml2.io.KXmlParser;
/**
* @author Eugene.Kudelevsky
*/
class XmlParser extends KXmlParser implements ILayoutPullParser, ILegacyPullParser {
@Nullable
public ILayoutPullParser getParser(String layoutName) {
return null;
}
@Nullable
public Object getViewCookie() {
return null;
}
@Nullable
@Override
public Object getViewKey() {
return null;
}
}
@@ -0,0 +1,345 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* Copyright (C) 2009 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.
-->
<xsd:schema
targetNamespace="http://schemas.android.com/sdk/android/layout-devices/1"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:c="http://schemas.android.com/sdk/android/layout-devices/1"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1">
<!-- The root element layout-devices defines a sequence of 0..n device elements. -->
<xsd:element name="layout-devices" type="c:layoutDevicesType"/>
<xsd:complexType name="layoutDevicesType">
<xsd:annotation>
<xsd:documentation xml:lang="en">
The "layout-devices" element is the root element of this schema.
It must contain zero or more "device" elements that each define the configurations
available for a given device.
These definitions are used in the Graphical Layout Editor in the
Android Development Tools (ADT) plugin for Eclipse.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<!-- layout-devices defines a sequence of 0..n device elements. -->
<xsd:element name="device" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation xml:lang="en">
A device element must contain at most one "default" element
followed by one or more "config" elements.
The "default" element defines all the default parameters
inherited by the following "config" elements.
Each "config" element can override the default values, if any.
A "device" element also has a required "name" attribute that
represents the user-interface name of this device.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<!-- device defines a choice of 0..1 default element
and 1..n config elements. -->
<xsd:sequence>
<xsd:element name="default" type="c:parametersType"
minOccurs="0" maxOccurs="1"/>
<xsd:element name="config" type="c:configType"
minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:normalizedString" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<!-- The type of a device>default element.
This is overridden by configType below for the device>config element.
-->
<xsd:complexType name="parametersType">
<xsd:annotation>
<xsd:documentation xml:lang="en">
The parametersType define all the parameters that can happen either in a
"default" element or in a named "config" element.
Each parameter element can appear once at most.
Parameters here are the same as those used to specify alternate Android
resources, as documented by
http://d.android.com/guide/topics/resources/resources-i18n.html#AlternateResources
</xsd:documentation>
</xsd:annotation>
<xsd:all>
<!-- parametersType says that 0..1 of each of these elements must be declared. -->
<xsd:element name="country-code" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the configuration is for a particular Mobile Country Code.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minInclusive value="100"/>
<xsd:maxInclusive value="999"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="network-code" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the configuration is for a particular Mobile Network Code.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minExclusive value="0"/>
<xsd:maxExclusive value="1000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="screen-size" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies that the configuration is for a particular class of screen.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="small"/>
<xsd:enumeration value="normal"/>
<xsd:enumeration value="large"/>
<xsd:enumeration value="xlarge"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="screen-ratio" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies that the configuration is for a taller/wider than traditional
screen. This is based purely on the aspect ration of the screen: QVGA,
HVGA, and VGA are notlong; WQVGA, WVGA, FWVGA are long. Note that long
may mean either wide or tall, depending on the current orientation.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="long"/>
<xsd:enumeration value="notlong"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="screen-orientation" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies that the configuration is for a screen that is tall (port) or
wide (land).
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="port"/>
<xsd:enumeration value="land"/>
<xsd:enumeration value="square"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="pixel-density" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the screen density the configuration is defined for. The medium
density of traditional HVGA screens (mdpi) is defined to be approximately
160dpi; low density (ldpi) is 120, and high density (hdpi) is 240. There
is thus a 4:3 scaling factor between each density, so a 9x9 bitmap in ldpi
would be 12x12 is mdpi and 16x16 in hdpi.
The special nodpi density that can be used in resource qualifiers is not
a valid keyword here.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="ldpi"/>
<xsd:enumeration value="mdpi"/>
<xsd:enumeration value="tvdpi"/>
<xsd:enumeration value="hdpi"/>
<xsd:enumeration value="xhdpi"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="touch-type" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the touch type the configuration is defined for.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="notouch"/>
<xsd:enumeration value="stylus"/>
<xsd:enumeration value="finger"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="keyboard-state" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
If your configuration uses a soft keyboard, use the keyssoft value.
If it doesn't and has a real keyboard, use keysexposed or keyshidden.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="keysexposed"/>
<xsd:enumeration value="keyshidden"/>
<xsd:enumeration value="keyssoft"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="text-input-method" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the primary text input method the configuration is designed for.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="nokeys"/>
<xsd:enumeration value="qwerty"/>
<xsd:enumeration value="12key"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="nav-state" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies whether the primary non-touchscreen navigation control is
exposed or hidden.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="navexposed"/>
<xsd:enumeration value="navhidden"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="nav-method" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the primary non-touchscreen navigation method the configuration
is designed for.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="dpad"/>
<xsd:enumeration value="trackball"/>
<xsd:enumeration value="wheel"/>
<xsd:enumeration value="nonav"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="screen-dimension" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the device screen resolution, in pixels.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence minOccurs="2" maxOccurs="2">
<xsd:element name="size">
<xsd:simpleType>
<xsd:restriction base="xsd:positiveInteger"/>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="xdpi" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the actual density in X of the device screen.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minExclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ydpi" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the actual density in Y of the device screen.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minExclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:all>
</xsd:complexType>
<!-- The type definition of a device>config element.
This type is basically all the element defined by parametersType and an extra
required "name" attribute for the user-interface configuration name.
-->
<xsd:complexType name="configType">
<xsd:annotation>
<xsd:documentation>
The configType defines the content of a "config" element in a "device" element.
A "config" element can have all the parameters elements defined by
"parameterType". It also has a required "name" attribute that indicates the
user-interface name for this configuration.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="c:parametersType">
<xsd:attribute name="name" type="xsd:normalizedString" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>
@@ -838,4 +838,43 @@ public class AndroidUtils {
}
return null;
}
public static int getIntAttrValue(@NotNull final XmlTag tag, @NotNull final String attrName) {
String value = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
public String compute() {
return tag.getAttributeValue(attrName, SdkConstants.NS_RESOURCES);
}
});
try {
return Integer.parseInt(value);
}
catch (NumberFormatException e) {
return -1;
}
}
@Nullable
public static XmlFile[] findXmlFiles(final Project project, final String... paths) {
XmlFile[] xmlFiles = new XmlFile[paths.length];
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
PsiFile psiFile = file != null ? ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
@Nullable
public PsiFile compute() {
return PsiManager.getInstance(project).findFile(file);
}
}) : null;
if (psiFile == null) {
LOG.info("File " + path + " is not found");
return null;
}
if (!(psiFile instanceof XmlFile)) {
LOG.info("File " + path + " is not an xml psiFile");
return null;
}
xmlFiles[i] = (XmlFile)psiFile;
}
return xmlFiles;
}
}