mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'master' of git@git.labs.intellij.net:idea/community
This commit is contained in:
@@ -17,11 +17,13 @@ abstract class IntToIntBtree {
|
||||
final BtreeIndexNodeView root;
|
||||
private int maxStepsSearched;
|
||||
private int pagesCount;
|
||||
private int size;
|
||||
private int count;
|
||||
private int movedMembersCount;
|
||||
|
||||
private final byte[] buffer;
|
||||
private boolean isLarge = true;
|
||||
private final ISimpleStorage storage;
|
||||
private boolean offloadToSiblingsBeforeSplit = false; // TODO till effective insertion to page
|
||||
|
||||
public IntToIntBtree(int _pageSize, int rootAddress, ISimpleStorage _storage, boolean initial) {
|
||||
pageSize = _pageSize;
|
||||
@@ -66,7 +68,7 @@ abstract class IntToIntBtree {
|
||||
int index = currentIndexNode.locate(key, true);
|
||||
|
||||
if (index < 0) {
|
||||
++size;
|
||||
++count;
|
||||
currentIndexNode.insert(key, value, -index - 1);
|
||||
} else {
|
||||
currentIndexNode.setAddressAt(index, value);
|
||||
@@ -101,6 +103,18 @@ abstract class IntToIntBtree {
|
||||
this.pagesCount = pagesCount;
|
||||
}
|
||||
|
||||
public int getMovedMembersCount() {
|
||||
return movedMembersCount;
|
||||
}
|
||||
|
||||
public void setMovedMembersCount(int movedMembersCount) {
|
||||
this.movedMembersCount = movedMembersCount;
|
||||
}
|
||||
|
||||
void dumpStatistics() {
|
||||
IOStatistics.dump("pagecount:" + pagesCount + ", height:" + maxStepsSearched + ", movedMembers:"+movedMembersCount);
|
||||
}
|
||||
|
||||
void doFlush() {
|
||||
//pagesCache.clear();
|
||||
}
|
||||
@@ -212,6 +226,7 @@ abstract class IntToIntBtree {
|
||||
static class BtreeIndexNodeView extends BtreePage {
|
||||
private boolean isIndexLeaf;
|
||||
private boolean isIndexLeafSet;
|
||||
private static final int LARGE_MOVE_THRESHOLD = 5;
|
||||
|
||||
BtreeIndexNodeView(IntToIntBtree btree) {
|
||||
super(btree);
|
||||
@@ -325,63 +340,8 @@ abstract class IntToIntBtree {
|
||||
if (parentAddress != 0) {
|
||||
parent = new BtreeIndexNodeView(btree);
|
||||
parent.setAddress(parentAddress);
|
||||
int indexInParent = isIndexLeaf() ? parent.search(keyAt(0)) : -1;
|
||||
BtreeIndexNodeView sibling = new BtreeIndexNodeView(btree);
|
||||
|
||||
if (indexInParent > 0) {
|
||||
if (doSanityCheck) {
|
||||
myAssert(parent.keyAt(indexInParent) == keyAt(0));
|
||||
myAssert(parent.addressAt(indexInParent + 1) == -address);
|
||||
}
|
||||
|
||||
int siblingAddress = parent.addressAt(indexInParent);
|
||||
sibling.setAddress(-siblingAddress);
|
||||
|
||||
if (!sibling.isFull() && sibling.getChildrenCount() + 1 != sibling.getMaxChildrenCount()) {
|
||||
if (doSanityCheck) {
|
||||
sibling.dump("Offloading to left sibling");
|
||||
parent.dump("parent before");
|
||||
}
|
||||
|
||||
sibling.insert(keyAt(0), addressAt(0), sibling.getChildrenCount());
|
||||
if (doSanityCheck) {
|
||||
sibling.dump("Left sibling after");
|
||||
}
|
||||
|
||||
parent.setKeyAt(indexInParent, keyAt(1));
|
||||
|
||||
int indexOflastChildToMove = getChildrenCount() - 1;
|
||||
if (btree.isLarge) {
|
||||
final int bytesToMove = indexOflastChildToMove * INTERIOR_SIZE;
|
||||
getBytes(indexToOffset(1), btree.buffer, 0, bytesToMove);
|
||||
putBytes(indexToOffset(0), btree.buffer, 0, bytesToMove);
|
||||
} else {
|
||||
for(int i = 0; i < indexOflastChildToMove; ++i) {
|
||||
setAddressAt(i, addressAt(i + 1));
|
||||
setKeyAt(i, keyAt(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
setChildrenCount((short)indexOflastChildToMove);
|
||||
} else if (indexInParent + 1 < parent.getChildrenCount()) {
|
||||
insertToRightSibling(parent, indexInParent + 1, sibling);
|
||||
}
|
||||
// TODO: move members in non leaf level + handle cases below
|
||||
} /*else if (indexInParent == -1) {
|
||||
insertToRightSibling(parent, 0, sibling);
|
||||
} else {
|
||||
int a = 1;
|
||||
}*/
|
||||
|
||||
if (!isFull()) {
|
||||
sync();
|
||||
parent.sync();
|
||||
|
||||
if (doSanityCheck) {
|
||||
dump("old node after split:");
|
||||
parent.dump("Parent node after split");
|
||||
}
|
||||
return parentAddress;
|
||||
if (btree.offloadToSiblingsBeforeSplit) {
|
||||
if (doOffloadToSiblings(parent)) return parentAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +408,7 @@ abstract class IntToIntBtree {
|
||||
}
|
||||
int newRootAddress = btree.nextPage();
|
||||
if (doSanityCheck) {
|
||||
System.out.println("Pages:"+btree.pagesCount+", elements:"+btree.size + ", average:" + (btree.maxStepsSearched + 1));
|
||||
System.out.println("Pages:"+btree.pagesCount+", elements:"+btree.count + ", average:" + (btree.maxStepsSearched + 1));
|
||||
}
|
||||
btree.setRootAddress(newRootAddress);
|
||||
parentAddress = newRootAddress;
|
||||
@@ -472,6 +432,70 @@ abstract class IntToIntBtree {
|
||||
return parentAddress;
|
||||
}
|
||||
|
||||
private boolean doOffloadToSiblings(BtreeIndexNodeView parent) {
|
||||
int indexInParent = isIndexLeaf() ? parent.search(keyAt(0)) : -1;
|
||||
BtreeIndexNodeView sibling = new BtreeIndexNodeView(btree);
|
||||
|
||||
if (indexInParent > 0) {
|
||||
if (doSanityCheck) {
|
||||
myAssert(parent.keyAt(indexInParent) == keyAt(0));
|
||||
myAssert(parent.addressAt(indexInParent + 1) == -address);
|
||||
}
|
||||
|
||||
int siblingAddress = parent.addressAt(indexInParent);
|
||||
sibling.setAddress(-siblingAddress);
|
||||
|
||||
if (!sibling.isFull() && sibling.getChildrenCount() + 1 != sibling.getMaxChildrenCount()) {
|
||||
if (doSanityCheck) {
|
||||
sibling.dump("Offloading to left sibling");
|
||||
parent.dump("parent before");
|
||||
}
|
||||
|
||||
sibling.insert(keyAt(0), addressAt(0), sibling.getChildrenCount());
|
||||
if (doSanityCheck) {
|
||||
sibling.dump("Left sibling after");
|
||||
}
|
||||
|
||||
parent.setKeyAt(indexInParent, keyAt(1));
|
||||
|
||||
int indexOflastChildToMove = getChildrenCount() - 1;
|
||||
if (btree.isLarge) {
|
||||
final int bytesToMove = indexOflastChildToMove * INTERIOR_SIZE;
|
||||
getBytes(indexToOffset(1), btree.buffer, 0, bytesToMove);
|
||||
putBytes(indexToOffset(0), btree.buffer, 0, bytesToMove);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < indexOflastChildToMove; ++i) {
|
||||
setAddressAt(i, addressAt(i + 1));
|
||||
setKeyAt(i, keyAt(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
setChildrenCount((short)indexOflastChildToMove);
|
||||
}
|
||||
else if (indexInParent + 1 < parent.getChildrenCount()) {
|
||||
insertToRightSibling(parent, indexInParent + 1, sibling);
|
||||
}
|
||||
// TODO: move members in non leaf level + handle cases below
|
||||
} /*else if (indexInParent == -1) {
|
||||
insertToRightSibling(parent, 0, sibling);
|
||||
} else {
|
||||
int a = 1;
|
||||
}*/
|
||||
|
||||
if (!isFull()) {
|
||||
sync();
|
||||
parent.sync();
|
||||
|
||||
if (doSanityCheck) {
|
||||
dump("old node after split:");
|
||||
parent.dump("Parent node after split");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void insertToRightSibling(BtreeIndexNodeView parent, int indexInParent, BtreeIndexNodeView sibling) {
|
||||
int siblingAddress;
|
||||
siblingAddress = parent.addressAt(indexInParent + 1);
|
||||
@@ -553,10 +577,13 @@ abstract class IntToIntBtree {
|
||||
if (doSanityCheck) myAssert(recordCount < getMaxChildrenCount());
|
||||
setChildrenCount((short)(recordCount + 1));
|
||||
|
||||
final int itemsToMove = recordCount - index;
|
||||
btree.movedMembersCount += itemsToMove;
|
||||
|
||||
// TODO Clever books tell us to use Btree for cheaper elements shifting within page
|
||||
if (isIndexLeaf()) {
|
||||
if (btree.isLarge) {
|
||||
final int bytesToMove = (recordCount - index) * INTERIOR_SIZE;
|
||||
if (btree.isLarge && itemsToMove > LARGE_MOVE_THRESHOLD) {
|
||||
final int bytesToMove = itemsToMove * INTERIOR_SIZE;
|
||||
getBytes(indexToOffset(index), btree.buffer, 0, bytesToMove);
|
||||
putBytes(indexToOffset(index + 1), btree.buffer, 0, bytesToMove);
|
||||
} else {
|
||||
@@ -571,7 +598,7 @@ abstract class IntToIntBtree {
|
||||
// <address> (<key><address>) {record_count - 1}
|
||||
//
|
||||
setAddressAt(recordCount + 1, addressAt(recordCount));
|
||||
if (btree.isLarge) {
|
||||
if (btree.isLarge && itemsToMove > LARGE_MOVE_THRESHOLD) {
|
||||
int elementsAfterIndex = recordCount - index - 1;
|
||||
if (elementsAfterIndex > 0) {
|
||||
int bytesToMove = elementsAfterIndex * INTERIOR_SIZE;
|
||||
|
||||
@@ -13,9 +13,9 @@ final class MappedFileSimpleStorage implements ISimpleStorage {
|
||||
storage = new ResizeableMappedFile(file, initialSize, PersistentEnumeratorBase.ourLock);
|
||||
}
|
||||
|
||||
public MappedFileSimpleStorage(File file, int initialSize, int pageSize, float growFactor, boolean zeroPagesWhenExpand) throws IOException {
|
||||
public MappedFileSimpleStorage(File file, int initialSize, int pageSize, boolean zeroPagesWhenExpand) throws IOException {
|
||||
FileUtil.createIfDoesntExist(file);
|
||||
storage = new ResizeableMappedFile(file, initialSize, PersistentEnumeratorBase.ourLock, pageSize, growFactor, zeroPagesWhenExpand);
|
||||
storage = new ResizeableMappedFile(file, initialSize, PersistentEnumeratorBase.ourLock, pageSize, zeroPagesWhenExpand);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,7 +45,7 @@ public class PersistentBTreeEnumerator<Data> extends PersistentEnumeratorBase<Da
|
||||
private final boolean myInlineKeysNoMapping;
|
||||
|
||||
public PersistentBTreeEnumerator(File file, KeyDescriptor<Data> dataDescriptor, int initialSize) throws IOException {
|
||||
super(file, new MappedFileSimpleStorage(file, initialSize, 1024 * 1024, 10f, false), dataDescriptor, initialSize);
|
||||
super(file, new MappedFileSimpleStorage(file, initialSize, 1024 * 1024, false), dataDescriptor, initialSize);
|
||||
|
||||
myInlineKeysNoMapping = myDataDescriptor instanceof InlineKeyDescriptor && !wantInlineKeyMapping();
|
||||
myBuffer = new byte[getRecordSize()];
|
||||
@@ -96,6 +96,7 @@ public class PersistentBTreeEnumerator<Data> extends PersistentEnumeratorBase<Da
|
||||
if (btree != null) {
|
||||
btree.setMaxStepsSearched(store(DATA_START + 36, btree.getMaxStepsSearched(), toDisk));
|
||||
btree.setPagesCount(store(DATA_START + 40, btree.getPageCount(), toDisk));
|
||||
btree.setMovedMembersCount(store(DATA_START + 44, btree.getMovedMembersCount(), toDisk));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,11 +257,8 @@ public class PersistentBTreeEnumerator<Data> extends PersistentEnumeratorBase<Da
|
||||
", values " +
|
||||
valuesCount +
|
||||
", storage size:" +
|
||||
myStorage.length() +
|
||||
", pagecount:" +
|
||||
btree.getPageCount() +
|
||||
", height:" +
|
||||
btree.getMaxStepsSearched());
|
||||
myStorage.length());
|
||||
btree.dumpStatistics();
|
||||
}
|
||||
|
||||
if (collisionAddress != NULL_ID) {
|
||||
@@ -354,4 +352,4 @@ public class PersistentBTreeEnumerator<Data> extends PersistentEnumeratorBase<Da
|
||||
btree.doFlush();
|
||||
super.doFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +440,8 @@ abstract class PersistentEnumeratorBase<Data> implements Forceable, Closeable {
|
||||
}
|
||||
|
||||
protected final void markDirty(boolean dirty) throws IOException {
|
||||
if (dirty && isDirty()) return;
|
||||
//assert Thread.holdsLock(this) || Thread.holdsLock(ourLock); // we hold one lock or another so can access myDirty
|
||||
if (dirty && myDirty) return;
|
||||
synchronized (ourLock) {
|
||||
if (myDirty) {
|
||||
if (!dirty) {
|
||||
|
||||
@@ -29,9 +29,8 @@ public class ResizeableMappedFile implements Forceable {
|
||||
|
||||
private long myLogicalSize;
|
||||
private final PagedFileStorage myStorage;
|
||||
private final float myGrowFactor;
|
||||
|
||||
public ResizeableMappedFile(final File file, int initialSize, PagedFileStorage.StorageLock lock, int pageSize, float growFactor, boolean zeroWhenExpand) throws IOException {
|
||||
public ResizeableMappedFile(final File file, int initialSize, PagedFileStorage.StorageLock lock, int pageSize, boolean zeroWhenExpand) throws IOException {
|
||||
myStorage = new PagedFileStorage(file, lock, pageSize, zeroWhenExpand);
|
||||
if (!file.exists() || file.length() == 0) {
|
||||
writeLength(0);
|
||||
@@ -43,13 +42,10 @@ public class ResizeableMappedFile implements Forceable {
|
||||
resize(initialSize);
|
||||
}
|
||||
}
|
||||
|
||||
assert growFactor > 8f:"growFactor / 8 should be > 1";
|
||||
myGrowFactor = growFactor;
|
||||
}
|
||||
|
||||
public ResizeableMappedFile(final File file, int initialSize, PagedFileStorage.StorageLock lock) throws IOException {
|
||||
this(file, initialSize, lock, PagedFileStorage.DEFAULT_BUFFER_SIZE, 13f, true);
|
||||
this(file, initialSize, lock, PagedFileStorage.DEFAULT_BUFFER_SIZE, true);
|
||||
}
|
||||
|
||||
public long length() {
|
||||
@@ -78,7 +74,7 @@ public class ResizeableMappedFile implements Forceable {
|
||||
}
|
||||
|
||||
private void expand() {
|
||||
final long newSize = Math.min(Integer.MAX_VALUE, ((long)((realSize() + 1) * myGrowFactor)) >> 3);
|
||||
final long newSize = Math.min(Integer.MAX_VALUE, ((realSize() + 1) * 13) >> 3);
|
||||
resize((int)newSize);
|
||||
}
|
||||
|
||||
|
||||
@@ -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!/" />
|
||||
<root url="jar://$MODULE_DIR$/lib/kxml2-2.3.0.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" />
|
||||
</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.
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>
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.jetbrains.android.util;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidSdkNotConfiguredException extends Exception {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.intellij.openapi.roots.WatchedRootsProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -26,12 +27,16 @@ public class MvcWatchedRootProvider implements WatchedRootsProvider {
|
||||
|
||||
@NotNull
|
||||
public Set<String> getRootsToWatch() {
|
||||
final Set<String> result = new HashSet<String>();
|
||||
if (!myProject.isInitialized()) return Collections.emptySet();
|
||||
|
||||
Set<String> result = null;
|
||||
|
||||
for (Module module : ModuleManager.getInstance(myProject).getModules()) {
|
||||
final MvcFramework framework = MvcModuleStructureSynchronizer.getFramework(module);
|
||||
if (framework == null) continue;
|
||||
|
||||
if (result == null) result = new HashSet<String>();
|
||||
|
||||
final File sdkWorkDir = framework.getCommonPluginsDir(module);
|
||||
if (sdkWorkDir != null) {
|
||||
result.add(sdkWorkDir.getAbsolutePath());
|
||||
@@ -43,7 +48,7 @@ public class MvcWatchedRootProvider implements WatchedRootsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result == null ? Collections.<String>emptySet() : result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user