mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-54551 android: layout preview feature, migration to new libraries
This commit is contained in:
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+14
-9
@@ -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;
|
||||
}
|
||||
|
||||
+8
-44
@@ -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="&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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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>
|
||||
+789
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+421
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user