aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/dictionary.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/editPreview.pngbin0 -> 243 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/minimizePreview.pngbin0 -> 181 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/refreshPreview.pngbin0 -> 3817 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/renderError.pngbin0 -> 1077 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/restorePreview.pngbin0 -> 186 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-b.pngbin0 -> 195 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-bl.pngbin0 -> 277 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-br.pngbin0 -> 282 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-r.pngbin0 -> 192 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-tr.pngbin0 -> 286 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java57
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java346
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java77
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java149
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java359
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java136
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java217
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java305
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java32
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java33
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java33
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java25
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java218
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java63
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java166
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java1015
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java203
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java1161
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java68
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java33
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java17
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinderTest.java59
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java47
42 files changed, 4734 insertions, 233 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index d678c2d..d84f60d 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -348,6 +348,7 @@ wildcard
workflow
xdpi
xhdpi
+xlarge
xml
xmlns
ydpi
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editPreview.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editPreview.png
new file mode 100644
index 0000000..fd36133
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editPreview.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/minimizePreview.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/minimizePreview.png
new file mode 100644
index 0000000..4ddc540
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/minimizePreview.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/refreshPreview.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/refreshPreview.png
new file mode 100644
index 0000000..d103763
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/refreshPreview.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/renderError.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/renderError.png
new file mode 100644
index 0000000..95be641
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/renderError.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/restorePreview.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/restorePreview.png
new file mode 100644
index 0000000..d6b3f32
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/restorePreview.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-b.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-b.png
new file mode 100644
index 0000000..963973e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-b.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-bl.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-bl.png
new file mode 100644
index 0000000..7612487
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-bl.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-br.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-br.png
new file mode 100644
index 0000000..8e20252
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-br.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-r.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-r.png
new file mode 100644
index 0000000..8e026f1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-r.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-tr.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-tr.png
new file mode 100644
index 0000000..590373c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/shadow2-tr.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index d5fa567..4ef469d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -80,6 +80,7 @@ import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -1236,6 +1237,62 @@ public class AdtUtils {
}
/**
+ * Returns all resource variations for the given file
+ *
+ * @param file resource file, which should be an XML file in one of the
+ * various resource folders, e.g. res/layout, res/values-xlarge, etc.
+ * @param includeSelf if true, include the file itself in the list,
+ * otherwise exclude it
+ * @return a list of all the resource variations
+ */
+ public static List<IFile> getResourceVariations(@Nullable IFile file, boolean includeSelf) {
+ if (file == null) {
+ return Collections.emptyList();
+ }
+
+ // Compute the set of layout files defining this layout resource
+ List<IFile> variations = new ArrayList<IFile>();
+ String name = file.getName();
+ IContainer parent = file.getParent();
+ if (parent != null) {
+ IContainer resFolder = parent.getParent();
+ if (resFolder != null) {
+ String parentName = parent.getName();
+ String prefix = parentName;
+ int qualifiers = prefix.indexOf('-');
+
+ if (qualifiers != -1) {
+ parentName = prefix.substring(0, qualifiers);
+ prefix = prefix.substring(0, qualifiers + 1);
+ } else {
+ prefix = prefix + '-';
+ }
+ try {
+ for (IResource resource : resFolder.members()) {
+ String n = resource.getName();
+ if ((n.startsWith(prefix) || n.equals(parentName))
+ && resource instanceof IContainer) {
+ IContainer layoutFolder = (IContainer) resource;
+ IResource r = layoutFolder.findMember(name);
+ if (r instanceof IFile) {
+ IFile variation = (IFile) r;
+ if (!includeSelf && file.equals(variation)) {
+ continue;
+ }
+ variations.add(variation);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+
+ return variations;
+ }
+
+ /**
* Returns whether the current thread is the UI thread
*
* @return true if the current thread is the UI thread
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
index 1dd32c7..969d45a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
@@ -133,6 +133,44 @@ public class XmlPrettyPrinter {
}
/**
+ * Pretty prints the given node
+ *
+ * @param node the node, usually a document, to be printed
+ * @param prefs the formatting preferences
+ * @param style the formatting style to use
+ * @param lineSeparator the line separator to use, or null to use the
+ * default
+ * @return a formatted string
+ */
+ @NonNull
+ public static String prettyPrint(
+ @NonNull Node node,
+ @NonNull XmlFormatPreferences prefs,
+ @NonNull XmlFormatStyle style,
+ @Nullable String lineSeparator) {
+ XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, lineSeparator);
+ StringBuilder sb = new StringBuilder(1000);
+ printer.prettyPrint(-1, node, null, null, sb, false /*openTagOnly*/);
+ String xml = sb.toString();
+ if (node.getNodeType() == Node.DOCUMENT_NODE && !xml.startsWith("<?")) { //$NON-NLS-1$
+ xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + xml; //$NON-NLS-1$
+ }
+ return xml;
+ }
+
+ /**
+ * Pretty prints the given node using default styles
+ *
+ * @param node the node, usually a document, to be printed
+ * @return the resulting formatted string
+ */
+ @NonNull
+ public static String prettyPrint(@NonNull Node node) {
+ return prettyPrint(node, XmlFormatPreferences.create(), XmlFormatStyle.FILE,
+ SdkUtils.getLineSeparator());
+ }
+
+ /**
* Start pretty-printing at the given node, which must either be the
* startNode or contain it as a descendant.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java
new file mode 100644
index 0000000..cce5bc9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.resources.NightMode;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Hardware;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.State;
+
+import java.util.List;
+
+/**
+ * An {@linkplain ComplementingConfiguration} is a {@link Configuration} which
+ * inherits all of its values from a different configuration, except for one or
+ * more attributes where it overrides a custom value, and the overridden value
+ * will always <b>differ</b> from the inherited value!
+ * <p>
+ * For example, a {@linkplain ComplementingConfiguration} may state that it
+ * overrides the locale, and if the inherited locale is "en", then the returned
+ * locale from the {@linkplain ComplementingConfiguration} may be for example "nb",
+ * but never "en".
+ * <p>
+ * The configuration will attempt to make its changed inherited value to be as
+ * different as possible from the inherited value. Thus, a configuration which
+ * overrides the device will probably return a phone-sized screen if the
+ * inherited device is a tablet, or vice versa.
+ */
+public class ComplementingConfiguration extends NestedConfiguration {
+ /**
+ * If non zero, keep the display name up to date with the label for the
+ * given overridden attribute, according to the flag constants in
+ * {@link ConfigurationClient}
+ */
+ private int mUpdateDisplayName;
+
+ /** Variation version; see {@link #setVariation(int)} */
+ private int mVariation;
+
+ /** Variation version count; see {@link #setVariationCount(int)} */
+ private int mVariationCount;
+
+ /**
+ * Constructs a new {@linkplain ComplementingConfiguration}.
+ * Construct via
+ *
+ * @param chooser the associated chooser
+ * @param configuration the configuration to inherit from
+ */
+ private ComplementingConfiguration(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull Configuration configuration) {
+ super(chooser, configuration);
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration} which inherits values from the
+ * given parent {@linkplain Configuration}, possibly overriding some as
+ * well.
+ *
+ * @param chooser the associated chooser
+ * @param parent the configuration to inherit values from
+ * @return a new configuration
+ */
+ @NonNull
+ public static ComplementingConfiguration create(@NonNull ConfigurationChooser chooser,
+ @NonNull Configuration parent) {
+ return new ComplementingConfiguration(chooser, parent);
+ }
+
+ /**
+ * Sets the variation version for this
+ * {@linkplain ComplementingConfiguration}. There might be multiple
+ * {@linkplain ComplementingConfiguration} instances inheriting from a
+ * {@link Configuration}. The variation version allows them to choose
+ * different complementing values, so they don't all flip to the same other
+ * (out of multiple choices) value. The {@link #setVariationCount(int)}
+ * value can be used to determine how to partition the buckets of values.
+ * Also updates the variation count if necessary.
+ *
+ * @param variation variation version
+ */
+ public void setVariation(int variation) {
+ mVariation = variation;
+ mVariationCount = Math.max(mVariationCount, variation + 1);
+ }
+
+ /**
+ * Sets the number of {@link ComplementingConfiguration} variations mapped
+ * to the same parent configuration as this one. See
+ * {@link #setVariation(int)} for details.
+ *
+ * @param count the total number of variation versions
+ */
+ public void setVariationCount(int count) {
+ mVariationCount = count;
+ }
+
+ @Override
+ public void setOverrideDevice(boolean override) {
+ mUpdateDisplayName |= ConfigurationClient.CHANGED_DEVICE;
+ super.setOverrideDevice(override);
+ }
+
+ @Override
+ public void setOverrideDeviceState(boolean override) {
+ mUpdateDisplayName |= ConfigurationClient.CHANGED_DEVICE_CONFIG;
+ super.setOverrideDeviceState(override);
+ }
+
+ @Override
+ public void setOverrideLocale(boolean override) {
+ mUpdateDisplayName |= ConfigurationClient.CHANGED_LOCALE;
+ super.setOverrideLocale(override);
+ }
+
+ @Override
+ public void setOverrideTarget(boolean override) {
+ mUpdateDisplayName |= ConfigurationClient.CHANGED_RENDER_TARGET;
+ super.setOverrideTarget(override);
+ }
+
+ @Override
+ public void setOverrideNightMode(boolean override) {
+ mUpdateDisplayName |= ConfigurationClient.CHANGED_NIGHT_MODE;
+ super.setOverrideNightMode(override);
+ }
+
+ @Override
+ public void setOverrideUiMode(boolean override) {
+ mUpdateDisplayName |= ConfigurationClient.CHANGED_UI_MODE;
+ super.setOverrideUiMode(override);
+ }
+
+ @Override
+ @NonNull
+ public Locale getLocale() {
+ Locale locale = mParent.getLocale();
+ if (mOverrideLocale && locale != null) {
+ List<Locale> locales = mConfigChooser.getLocaleList();
+ for (Locale l : locales) {
+ // TODO: Try to be smarter about which one we pick; for example, try
+ // to pick a language that is substantially different from the inherited
+ // language, such as either with the strings of the largest or shortest
+ // length, or perhaps based on some geography or population metrics
+ if (!l.equals(locale)) {
+ locale = l;
+ break;
+ }
+ }
+
+ if ((mUpdateDisplayName & ConfigurationClient.CHANGED_LOCALE) != 0) {
+ setDisplayName(ConfigurationChooser.getLocaleLabel(mConfigChooser, locale, false));
+ }
+ }
+
+ return locale;
+ }
+
+ @Override
+ @Nullable
+ public IAndroidTarget getTarget() {
+ IAndroidTarget target = mParent.getTarget();
+ if (mOverrideTarget && target != null) {
+ List<IAndroidTarget> targets = mConfigChooser.getTargetList();
+ if (!targets.isEmpty()) {
+ // Pick a different target: if you're showing the most recent render target,
+ // then pick the lowest supported target, and vice versa
+ IAndroidTarget mostRecent = targets.get(targets.size() - 1);
+ if (target.equals(mostRecent)) {
+ // Find oldest supported
+ ManifestInfo info = ManifestInfo.get(mConfigChooser.getProject());
+ int minSdkVersion = info.getMinSdkVersion();
+ for (IAndroidTarget t : targets) {
+ if (t.getVersion().getApiLevel() >= minSdkVersion) {
+ target = t;
+ break;
+ }
+ }
+ } else {
+ target = mostRecent;
+ }
+ }
+
+ if ((mUpdateDisplayName & ConfigurationClient.CHANGED_RENDER_TARGET) != 0) {
+ setDisplayName(ConfigurationChooser.getRenderingTargetLabel(target, false));
+ }
+ }
+
+ return target;
+ }
+
+ @Override
+ @Nullable
+ public Device getDevice() {
+ Device device = mParent.getDevice();
+ if (mOverrideDevice && device != null) {
+ // Pick a different device
+ List<Device> devices = mConfigChooser.getDeviceList();
+
+
+ // Divide up the available devices into {@link #mVariationCount} + 1 buckets
+ // (the + 1 is for the bucket now taken up by the inherited value).
+ // Then assign buckets to each {@link #mVariation} version, and pick one
+ // from the bucket assigned to this current configuration's variation version.
+
+ // I could just divide up the device list count, but that would treat a lot of
+ // very similar phones as having the same kind of variety as the 7" and 10"
+ // tablets which are sitting right next to each other in the device list.
+ // Instead, do this by screen size.
+
+
+ double smallest = 100;
+ double biggest = 1;
+ for (Device d : devices) {
+ double size = getScreenSize(d);
+ if (size < 0) {
+ continue; // no data
+ }
+ if (size >= biggest) {
+ biggest = size;
+ }
+ if (size <= smallest) {
+ smallest = size;
+ }
+ }
+
+ int bucketCount = mVariationCount + 1;
+ double inchesPerBucket = (biggest - smallest) / bucketCount;
+
+ double overriddenSize = getScreenSize(device);
+ int overriddenBucket = (int) ((overriddenSize - smallest) / inchesPerBucket);
+ int bucket = (mVariation < overriddenBucket) ? mVariation : mVariation + 1;
+ double from = inchesPerBucket * bucket + smallest;
+ double to = from + inchesPerBucket;
+ if (biggest - to < 0.1) {
+ to = biggest + 0.1;
+ }
+
+ for (Device d : devices) {
+ double size = getScreenSize(d);
+ if (size >= from && size < to) {
+ device = d;
+ break;
+ }
+ }
+
+ if ((mUpdateDisplayName & ConfigurationClient.CHANGED_DEVICE) != 0) {
+ setDisplayName(ConfigurationChooser.getDeviceLabel(device, true));
+ }
+ }
+
+ return device;
+ }
+ private static double getScreenSize(@NonNull Device device) {
+ Hardware hardware = device.getDefaultHardware();
+ if (hardware != null) {
+ Screen screen = hardware.getScreen();
+ if (screen != null) {
+ return screen.getDiagonalLength();
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ @Nullable
+ public State getDeviceState() {
+ State state = mParent.getDeviceState();
+ if (mOverrideDeviceState && state != null) {
+ State alternate = getNextDeviceState(state);
+
+ if ((mUpdateDisplayName & ConfigurationClient.CHANGED_DEVICE_CONFIG) != 0) {
+ if (alternate != null) {
+ setDisplayName(alternate.getName());
+ }
+ }
+
+ return alternate;
+ } else {
+ if (mOverrideDevice && state != null) {
+ // If the device differs, I need to look up a suitable equivalent state
+ // on our device
+ Device device = getDevice();
+ if (device != null) {
+ return device.getState(state.getName());
+ }
+ }
+
+ return state;
+ }
+ }
+
+ @Override
+ @NonNull
+ public NightMode getNightMode() {
+ NightMode nightMode = mParent.getNightMode();
+ if (mOverrideNightMode && nightMode != null) {
+ nightMode = nightMode == NightMode.NIGHT ? NightMode.NOTNIGHT : NightMode.NIGHT;
+ if ((mUpdateDisplayName & ConfigurationClient.CHANGED_NIGHT_MODE) != 0) {
+ setDisplayName(nightMode.getLongDisplayValue());
+ }
+ return nightMode;
+ } else {
+ return nightMode;
+ }
+ }
+
+ @Override
+ @NonNull
+ public UiMode getUiMode() {
+ UiMode uiMode = mParent.getUiMode();
+ if (mOverrideUiMode && uiMode != null) {
+ // TODO: Use manifest's supports screen to decide which are most relevant
+ // (as well as which available configuration qualifiers are present in the
+ // layout)
+ UiMode[] values = UiMode.values();
+ uiMode = values[(uiMode.ordinal() + 1) % values.length];
+ if ((mUpdateDisplayName & ConfigurationClient.CHANGED_UI_MODE) != 0) {
+ setDisplayName(uiMode.getLongDisplayValue());
+ }
+ return uiMode;
+ } else {
+ return uiMode;
+ }
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
index 2b5589b..09adc64 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser.NAME_CONFIG_STATE;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -38,6 +39,7 @@ import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.Density;
import com.android.resources.NightMode;
@@ -50,6 +52,7 @@ import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.State;
import com.android.utils.Pair;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
@@ -120,6 +123,9 @@ public class Configuration {
@NonNull
private NightMode mNightMode = NightMode.NOTNIGHT;
+ /** The display name */
+ private String mDisplayName;
+
/**
* Creates a new {@linkplain Configuration}
*
@@ -141,6 +147,58 @@ public class Configuration {
}
/**
+ * Creates a configuration
+ * @param chooser the configuration chooser with device info, render target info, etc
+ * @param file the file to look up a configuration for
+ * @return a suitable configuration
+ */
+ @NonNull
+ public static Configuration create(@NonNull ConfigurationChooser chooser,
+ @NonNull IFile file) {
+ Configuration configuration = copy(chooser.getConfiguration());
+ String data = AdtPlugin.getFileProperty(file, NAME_CONFIG_STATE);
+ if (data != null) {
+ configuration.initialize(data);
+ } else {
+ ProjectResources resources = chooser.getResources();
+ ConfigurationMatcher matcher = new ConfigurationMatcher(chooser, configuration, file,
+ resources, false);
+ if (configuration.mEditedConfig == null) {
+ configuration.mEditedConfig = new FolderConfiguration();
+ }
+ matcher.adaptConfigSelection(true /*needBestMatch*/);
+ }
+
+ return configuration;
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration} that is a copy from a different configuration
+ *
+ * @param original the original to copy from
+ * @return a new configuration copied from the original
+ */
+ @NonNull
+ public static Configuration copy(@NonNull Configuration original) {
+ Configuration copy = create(original.mConfigChooser);
+ copy.mFullConfig.set(original.mFullConfig);
+ if (original.mEditedConfig != null) {
+ copy.mEditedConfig = new FolderConfiguration();
+ copy.mEditedConfig.set(original.mEditedConfig);
+ }
+ copy.mTarget = original.getTarget();
+ copy.mTheme = original.getTheme();
+ copy.mDevice = original.getDevice();
+ copy.mState = original.getDeviceState();
+ copy.mActivity = original.getActivity();
+ copy.mLocale = original.getLocale();
+ copy.mUiMode = original.getUiMode();
+ copy.mNightMode = original.getNightMode();
+
+ return copy;
+ }
+
+ /**
* Returns the associated activity
*
* @return the activity
@@ -221,6 +279,16 @@ public class Configuration {
}
/**
+ * Returns the display name to show for this configuration
+ *
+ * @return the display name, or null if none has been assigned
+ */
+ @Nullable
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
* Returns whether the configuration's theme is a project theme.
* <p/>
* The returned value is meaningless if {@link #getTheme()} returns
@@ -366,6 +434,15 @@ public class Configuration {
}
/**
+ * Sets the display name to be shown for this configuration.
+ *
+ * @param displayName the new display name
+ */
+ public void setDisplayName(@Nullable String displayName) {
+ mDisplayName = displayName;
+ }
+
+ /**
* Sets the night mode
*
* @param night the night mode
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
index 7412bf1..38f2e67 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
@@ -37,6 +37,7 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
@@ -55,6 +56,7 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
import com.android.sdklib.AndroidVersion;
@@ -203,8 +205,8 @@ public class ConfigurationChooser extends Composite
ToolBar toolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
toolBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN | SWT.BOLD);
- mConfigCombo.setImage(null);
+ mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN );
+ mConfigCombo.setImage(icons.getIcon("android_file")); //$NON-NLS-1$
mConfigCombo.setToolTipText("Configuration to render this layout with in Eclipse");
@SuppressWarnings("unused")
@@ -294,6 +296,9 @@ public class ConfigurationChooser extends Composite
mOrientationCombo.addSelectionListener(listener);
addDisposeListener(this);
+
+ initDevices();
+ initTargets();
}
/**
@@ -483,6 +488,7 @@ public class ConfigurationChooser extends Composite
*/
public void setFile(IFile file) {
mEditedFile = file;
+ initializeConfiguration();
}
/**
@@ -499,7 +505,7 @@ public class ConfigurationChooser extends Composite
return;
}
- mEditedFile = file;
+ setFile(file);
IProject project = mEditedFile.getProject();
mResources = ResourceManager.getInstance().getProjectResources(project);
@@ -543,7 +549,7 @@ public class ConfigurationChooser extends Composite
* @see #replaceFile(IFile)
*/
public void changeFileOnNewConfig(IFile file) {
- mEditedFile = file;
+ setFile(file);
IProject project = mEditedFile.getProject();
mResources = ResourceManager.getInstance().getProjectResources(project);
@@ -648,10 +654,8 @@ public class ConfigurationChooser extends Composite
mDisableUpdates++; // we do not want to trigger onXXXChange when setting
// new values in the widgets.
try {
- // this is going to be followed by a call to onTargetLoaded.
- // So we can only care about the layout devices in this case.
- initDevices();
- initTargets();
+ updateDevices();
+ updateTargets();
} finally {
mDisableUpdates--;
}
@@ -689,8 +693,8 @@ public class ConfigurationChooser extends Composite
try {
// init the devices if needed (new SDK or first time going through here)
if (mSdkChanged) {
- initDevices();
- initTargets();
+ updateDevices();
+ updateTargets();
mSdkChanged = false;
}
@@ -704,7 +708,7 @@ public class ConfigurationChooser extends Composite
LoadStatus targetStatus = LoadStatus.FAILED;
if (mProjectTarget != null) {
targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget, null);
- initTargets();
+ updateTargets();
}
if (targetStatus == LoadStatus.LOADED) {
@@ -726,16 +730,9 @@ public class ConfigurationChooser extends Composite
targetData = Sdk.getCurrent().getTargetData(mProjectTarget);
// get the file stored state
- boolean loadedConfigData = false;
- String data = AdtPlugin.getFileProperty(mEditedFile, NAME_CONFIG_STATE);
- if (mInitialState != null) {
- data = mInitialState;
- mInitialState = null;
- }
-
- if (data != null) {
- loadedConfigData = mConfiguration.initialize(data);
- }
+ initializeConfiguration();
+ boolean loadedConfigData = mConfiguration.getDevice() != null &&
+ mConfiguration.getDeviceState() != null;
// Load locale list. This must be run after we initialize the
// configuration above, since it attempts to sync the UI with
@@ -831,59 +828,81 @@ public class ConfigurationChooser extends Composite
} else {
mDeviceList = new ArrayList<Device>();
}
-
- // fill with the devices
- if (!mDeviceList.isEmpty()) {
- Device first = mDeviceList.get(0);
- selectDevice(first);
- List<State> states = first.getAllStates();
- selectDeviceState(states.get(0));
- } else {
- selectDevice(null);
- }
}
/**
* Loads the list of {@link IAndroidTarget} and inits the UI with it.
*/
- private void initTargets() {
+ private boolean initTargets() {
mTargetList.clear();
- IAndroidTarget renderingTarget = mConfiguration.getTarget();
-
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget[] targets = currentSdk.getTargets();
- IAndroidTarget match = null;
for (int i = 0 ; i < targets.length; i++) {
- // FIXME: add check based on project minSdkVersion
if (targets[i].hasRenderingLibrary()) {
mTargetList.add(targets[i]);
-
- if (renderingTarget != null) {
- // use equals because the rendering could be from a previous SDK, so
- // it may not be the same instance.
- if (renderingTarget.equals(targets[i])) {
- match = targets[i];
- }
- } else if (mProjectTarget == targets[i]) {
- match = targets[i];
- }
}
}
- if (match == null) {
- selectTarget(null);
+ return true;
+ }
- // the rendering target is the same as the project.
- renderingTarget = mProjectTarget;
- } else {
- selectTarget(match);
+ return false;
+ }
+
+ private void initializeConfiguration() {
+ if (mConfiguration.getDevice() == null) {
+ String data = AdtPlugin.getFileProperty(mEditedFile, NAME_CONFIG_STATE);
+ if (mInitialState != null) {
+ data = mInitialState;
+ mInitialState = null;
+ }
+ if (data != null) {
+ mConfiguration.initialize(data);
+ }
+ }
+ }
- // set the rendering target to the new object.
- renderingTarget = match;
+ private void updateDevices() {
+ if (mDeviceList.size() == 0) {
+ initDevices();
+ }
+ }
+
+ private void updateTargets() {
+ if (mTargetList.size() == 0) {
+ if (!initTargets()) {
+ return;
}
}
+
+ IAndroidTarget renderingTarget = mConfiguration.getTarget();
+
+ IAndroidTarget match = null;
+ for (IAndroidTarget target : mTargetList) {
+ if (renderingTarget != null) {
+ // use equals because the rendering could be from a previous SDK, so
+ // it may not be the same instance.
+ if (renderingTarget.equals(target)) {
+ match = target;
+ }
+ } else if (mProjectTarget == target) {
+ match = target;
+ }
+
+ }
+
+ if (match == null) {
+ // the rendering target is the same as the project.
+ renderingTarget = mProjectTarget;
+ } else {
+ // set the rendering target to the new object.
+ renderingTarget = match;
+ }
+
+ mConfiguration.setTarget(renderingTarget, true);
+ selectTarget(renderingTarget);
}
/** Update the toolbar whenever a label has changed, to not only
@@ -947,7 +966,9 @@ public class ConfigurationChooser extends Composite
*/
public void saveConstraints() {
String description = mConfiguration.toPersistentString();
- AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, description);
+ if (description != null && !description.isEmpty()) {
+ AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, description);
+ }
}
// ---- Setting the current UI state ----
@@ -1923,4 +1944,22 @@ public class ConfigurationChooser extends Composite
return false;
}
+
+ /**
+ * Returns true if this configuration chooser represents the best match for
+ * the given file
+ *
+ * @param file the file to test
+ * @param config the config to test
+ * @return true if the given config is the best match for the given file
+ */
+ public boolean isBestMatchFor(IFile file, FolderConfiguration config) {
+ ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(),
+ ResourceFolderType.LAYOUT, config);
+ if (match != null) {
+ return match.getFile().equals(mEditedFile);
+ }
+
+ return false;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java
new file mode 100644
index 0000000..47c139f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_THEME;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.configuration.DeviceConfigHelper;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.NightMode;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ScreenSize;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+import com.google.common.base.Splitter;
+
+import org.eclipse.core.resources.IProject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+import java.util.Map;
+
+/** A description of a configuration, used for persistence */
+public class ConfigurationDescription {
+ private static final String TAG_PREVIEWS = "previews"; //$NON-NLS-1$
+ private static final String TAG_PREVIEW = "preview"; //$NON-NLS-1$
+ private static final String ATTR_TARGET = "target"; //$NON-NLS-1$
+ private static final String ATTR_CONFIG = "config"; //$NON-NLS-1$
+ private static final String ATTR_LOCALE = "locale"; //$NON-NLS-1$
+ private static final String ATTR_ACTIVITY = "activity"; //$NON-NLS-1$
+ private static final String ATTR_DEVICE = "device"; //$NON-NLS-1$
+ private static final String ATTR_STATE = "devicestate"; //$NON-NLS-1$
+ private static final String ATTR_UIMODE = "ui"; //$NON-NLS-1$
+ private static final String ATTR_NIGHTMODE = "night"; //$NON-NLS-1$
+ private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
+
+ /** The project corresponding to this configuration's description */
+ public final IProject project;
+
+ /** The display name */
+ public String displayName;
+
+ /** The theme */
+ public String theme;
+
+ /** The target */
+ public IAndroidTarget target;
+
+ /** The display name */
+ public FolderConfiguration folder;
+
+ /** The locale */
+ public Locale locale = Locale.ANY;
+
+ /** The device */
+ public Device device;
+
+ /** The device state */
+ public State state;
+
+ /** The activity */
+ public String activity;
+
+ /** UI mode */
+ @NonNull
+ public UiMode uiMode = UiMode.NORMAL;
+
+ /** Night mode */
+ @NonNull
+ public NightMode nightMode = NightMode.NOTNIGHT;
+
+ private ConfigurationDescription(@Nullable IProject project) {
+ this.project = project;
+ }
+
+ /**
+ * Creates a description from a given configuration
+ *
+ * @param project the project for this configuration's description
+ * @param configuration the configuration to describe
+ * @return a new configuration
+ */
+ public static ConfigurationDescription fromConfiguration(
+ @Nullable IProject project,
+ @NonNull Configuration configuration) {
+ ConfigurationDescription description = new ConfigurationDescription(project);
+ description.displayName = configuration.getDisplayName();
+ description.theme = configuration.getTheme();
+ description.target = configuration.getTarget();
+ description.folder = new FolderConfiguration();
+ description.folder.set(configuration.getFullConfig());
+ description.locale = configuration.getLocale();
+ description.device = configuration.getDevice();
+ description.state = configuration.getDeviceState();
+ description.activity = configuration.getActivity();
+ return description;
+ }
+
+ /**
+ * Initializes a string previously created with
+ * {@link #toXml(Document)}
+ *
+ * @param project the project for this configuration's description
+ * @param element the element to read back from
+ * @param deviceList list of available devices
+ * @return true if the configuration was initialized
+ */
+ @Nullable
+ public static ConfigurationDescription fromXml(
+ @Nullable IProject project,
+ @NonNull Element element,
+ @NonNull List<Device> deviceList) {
+ ConfigurationDescription description = new ConfigurationDescription(project);
+
+ if (!TAG_PREVIEW.equals(element.getTagName())) {
+ return null;
+ }
+
+ String displayName = element.getAttribute(ATTR_NAME);
+ if (!displayName.isEmpty()) {
+ description.displayName = displayName;
+ }
+
+ String config = element.getAttribute(ATTR_CONFIG);
+ Iterable<String> segments = Splitter.on('-').split(config);
+ description.folder = FolderConfiguration.getConfig(segments);
+
+ String theme = element.getAttribute(ATTR_THEME);
+ if (!theme.isEmpty()) {
+ description.theme = theme;
+ }
+
+ String targetId = element.getAttribute(ATTR_TARGET);
+ if (!targetId.isEmpty()) {
+ IAndroidTarget target = Configuration.stringToTarget(targetId);
+ description.target = target;
+ }
+
+ String localeString = element.getAttribute(ATTR_LOCALE);
+ if (!localeString.isEmpty()) {
+ // Load locale. Note that this can get overwritten by the
+ // project-wide settings read below.
+ LanguageQualifier language = Locale.ANY_LANGUAGE;
+ RegionQualifier region = Locale.ANY_REGION;
+ String locales[] = localeString.split(SEP_LOCALE);
+ if (locales[0].length() > 0) {
+ language = new LanguageQualifier(locales[0]);
+ }
+ if (locales.length > 1 && locales[1].length() > 0) {
+ region = new RegionQualifier(locales[1]);
+ }
+ description.locale = Locale.create(language, region);
+ }
+
+ String activity = element.getAttribute(ATTR_ACTIVITY);
+ if (activity.isEmpty()) {
+ activity = null;
+ }
+
+ String deviceString = element.getAttribute(ATTR_DEVICE);
+ if (!deviceString.isEmpty()) {
+ for (Device d : deviceList) {
+ if (d.getName().equals(deviceString)) {
+ description.device = d;
+ String stateName = element.getAttribute(ATTR_STATE);
+ if (stateName.isEmpty() || stateName.equals("null")) {
+ description.state = Configuration.getState(d, stateName);
+ } else if (d.getAllStates().size() > 0) {
+ description.state = d.getAllStates().get(0);
+ }
+ break;
+ }
+ }
+ }
+
+ String uiModeString = element.getAttribute(ATTR_UIMODE);
+ if (!uiModeString.isEmpty()) {
+ description.uiMode = UiMode.getEnum(uiModeString);
+ if (description.uiMode == null) {
+ description.uiMode = UiMode.NORMAL;
+ }
+ }
+
+ String nightModeString = element.getAttribute(ATTR_NIGHTMODE);
+ if (!nightModeString.isEmpty()) {
+ description.nightMode = NightMode.getEnum(nightModeString);
+ if (description.nightMode == null) {
+ description.nightMode = NightMode.NOTNIGHT;
+ }
+ }
+
+
+ // Should I really be storing the FULL configuration? Might be trouble if
+ // you bring a different device
+
+ return description;
+ }
+
+ /**
+ * Write this description into the given document as a new element.
+ *
+ * @param document the document to add the description to
+ * @return the newly inserted element
+ */
+ @NonNull
+ public Element toXml(Document document) {
+ Element element = document.createElement(TAG_PREVIEW);
+
+ element.setAttribute(ATTR_NAME, displayName);
+ FolderConfiguration fullConfig = folder;
+ String folderName = fullConfig.getFolderName(ResourceFolderType.LAYOUT);
+ element.setAttribute(ATTR_CONFIG, folderName);
+ if (theme != null) {
+ element.setAttribute(ATTR_THEME, theme);
+ }
+ if (target != null) {
+ element.setAttribute(ATTR_TARGET, Configuration.targetToString(target));
+ }
+
+ if (locale != null && (locale.hasLanguage() || locale.hasRegion())) {
+ String value;
+ if (locale.hasRegion()) {
+ value = locale.language.getValue() + SEP_LOCALE + locale.region.getValue();
+ } else {
+ value = locale.language.getValue();
+ }
+ element.setAttribute(ATTR_LOCALE, value);
+ }
+
+ if (device != null) {
+ element.setAttribute(ATTR_DEVICE, device.getName());
+ if (state != null) {
+ element.setAttribute(ATTR_STATE, state.getName());
+ }
+ }
+
+ if (activity != null) {
+ element.setAttribute(ATTR_ACTIVITY, activity);
+ }
+
+ if (uiMode != null && uiMode != UiMode.NORMAL) {
+ element.setAttribute(ATTR_UIMODE, uiMode.getResourceValue());
+ }
+
+ if (nightMode != null && nightMode != NightMode.NOTNIGHT) {
+ element.setAttribute(ATTR_NIGHTMODE, nightMode.getResourceValue());
+ }
+
+ Element parent = document.getDocumentElement();
+ if (parent == null) {
+ parent = document.createElement(TAG_PREVIEWS);
+ document.appendChild(parent);
+ }
+ parent.appendChild(element);
+
+ return element;
+ }
+
+ /** Returns the preferred theme, or null */
+ @Nullable
+ String computePreferredTheme() {
+ if (project == null) {
+ return "Theme";
+ }
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current state
+ ScreenSize screenSize = null;
+ if (device != null) {
+ List<State> states = device.getAllStates();
+ for (State s : states) {
+ FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s);
+ if (folderConfig != null) {
+ ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+ }
+
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(target, screenSize);
+
+ String preferred = defaultTheme;
+ if (theme == null) {
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+
+ if (activity != null) {
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ theme = preferred;
+ }
+
+ return preferred;
+ }
+
+ private void checkThemePrefix() {
+ if (theme != null && !theme.startsWith(PREFIX_RESOURCE_REF)) {
+ if (theme.isEmpty()) {
+ computePreferredTheme();
+ return;
+ }
+
+ if (target != null) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ AndroidTargetData data = sdk.getTargetData(target);
+
+ if (data != null) {
+ ResourceRepository resources = data.getFrameworkResources();
+ if (resources != null
+ && resources.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) {
+ theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
+ return;
+ }
+ }
+ }
+ }
+
+ theme = STYLE_RESOURCE_PREFIX + theme;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
index dc64b36..a210bf7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
@@ -59,12 +59,35 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-/** Produces matches for configurations */
+/**
+ * Produces matches for configurations
+ * <p>
+ * See algorithm described here:
+ * http://developer.android.com/guide/topics/resources/providing-resources.html
+ */
public class ConfigurationMatcher {
private final ConfigurationChooser mConfigChooser;
+ private final Configuration mConfiguration;
+ private final IFile mEditedFile;
+ private final ProjectResources mResources;
+ private final boolean mUpdateUi;
ConfigurationMatcher(ConfigurationChooser chooser) {
+ this(chooser, chooser.getConfiguration(), chooser.getEditedFile(),
+ chooser.getResources(), true);
+ }
+
+ ConfigurationMatcher(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull Configuration configuration,
+ @Nullable IFile editedFile,
+ @Nullable ProjectResources resources,
+ boolean updateUi) {
mConfigChooser = chooser;
+ mConfiguration = configuration;
+ mEditedFile = editedFile;
+ mResources = resources;
+ mUpdateUi = updateUi;
}
// ---- Finding matching configurations ----
@@ -118,14 +141,12 @@ public class ConfigurationMatcher {
* @return true if the current edited file is the best match in the project for the
* given config.
*/
- boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
- ProjectResources resources = mConfigChooser.getResources();
- IFile editedFile = mConfigChooser.getEditedFile();
- ResourceFile match = resources.getMatchingFile(editedFile.getName(),
+ public boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
+ ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(),
ResourceFolderType.LAYOUT, config);
if (match != null) {
- return match.getFile().equals(editedFile);
+ return match.getFile().equals(mEditedFile);
} else {
// if we stop here that means the current file is not even a match!
AdtPlugin.log(IStatus.ERROR, "Current file is not a match for the given config.");
@@ -149,9 +170,8 @@ public class ConfigurationMatcher {
// check the device config (ie sans locale)
boolean needConfigChange = true; // if still true, we need to find another config.
boolean currentConfigIsCompatible = false;
- Configuration configuration = mConfigChooser.getConfiguration();
- State selectedState = configuration.getDeviceState();
- FolderConfiguration editedConfig = configuration.getEditedConfig();
+ State selectedState = mConfiguration.getDeviceState();
+ FolderConfiguration editedConfig = mConfiguration.getEditedConfig();
if (selectedState != null) {
FolderConfiguration currentConfig = DeviceConfigHelper.getFolderConfig(selectedState);
if (currentConfig != null && editedConfig.isMatchFor(currentConfig)) {
@@ -172,7 +192,7 @@ public class ConfigurationMatcher {
// first look in the current device.
State matchState = null;
int localeIndex = -1;
- Device device = configuration.getDevice();
+ Device device = mConfiguration.getDevice();
if (device != null) {
mainloop: for (State state : device.getAllStates()) {
testConfig.set(DeviceConfigHelper.getFolderConfig(state));
@@ -196,12 +216,14 @@ public class ConfigurationMatcher {
}
if (matchState != null) {
- configuration.setDeviceState(matchState, true);
+ mConfiguration.setDeviceState(matchState, true);
Locale locale = localeList.get(localeIndex);
- configuration.setLocale(locale, true);
- mConfigChooser.selectDeviceState(matchState);
- mConfigChooser.selectLocale(locale);
- configuration.syncFolderConfig();
+ mConfiguration.setLocale(locale, true);
+ if (mUpdateUi) {
+ mConfigChooser.selectDeviceState(matchState);
+ mConfigChooser.selectLocale(locale);
+ }
+ mConfiguration.syncFolderConfig();
} else {
// no match in current device with any state/locale
// attempt to find another device that can display this
@@ -225,9 +247,8 @@ public class ConfigurationMatcher {
void findAndSetCompatibleConfig(boolean favorCurrentConfig) {
List<Locale> localeList = mConfigChooser.getLocaleList();
List<Device> deviceList = mConfigChooser.getDeviceList();
- Configuration configuration = mConfigChooser.getConfiguration();
- FolderConfiguration editedConfig = configuration.getEditedConfig();
- FolderConfiguration currentConfig = configuration.getFullConfig();
+ FolderConfiguration editedConfig = mConfiguration.getEditedConfig();
+ FolderConfiguration currentConfig = mConfiguration.getFullConfig();
// list of compatible device/state/locale
List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>();
@@ -316,7 +337,7 @@ public class ConfigurationMatcher {
}
// just display the warning
- AdtPlugin.printErrorToConsole(mConfigChooser.getProject(),
+ AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
String.format(
"'%1$s' is not a best match for any device/locale combination.",
editedConfig.toDisplayString()),
@@ -326,21 +347,23 @@ public class ConfigurationMatcher {
} else if (anyMatches.size() > 0) {
// select the best device anyway.
ConfigMatch match = selectConfigMatch(anyMatches);
- configuration.setDevice(match.device, true);
- configuration.setDeviceState(match.state, true);
- configuration.setLocale(localeList.get(match.bundle.localeIndex), true);
- configuration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
- configuration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex),
+ mConfiguration.setDevice(match.device, true);
+ mConfiguration.setDeviceState(match.state, true);
+ mConfiguration.setLocale(localeList.get(match.bundle.localeIndex), true);
+ mConfiguration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
+ mConfiguration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex),
true);
- mConfigChooser.selectDevice(configuration.getDevice());
- mConfigChooser.selectDeviceState(configuration.getDeviceState());
- mConfigChooser.selectLocale(configuration.getLocale());
+ if (mUpdateUi) {
+ mConfigChooser.selectDevice(mConfiguration.getDevice());
+ mConfigChooser.selectDeviceState(mConfiguration.getDeviceState());
+ mConfigChooser.selectLocale(mConfiguration.getLocale());
+ }
- configuration.syncFolderConfig();
+ mConfiguration.syncFolderConfig();
// TODO: display a better warning!
- AdtPlugin.printErrorToConsole(mConfigChooser.getProject(),
+ AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
String.format(
"'%1$s' is not a best match for any device/locale combination.",
editedConfig.toDisplayString()),
@@ -357,17 +380,19 @@ public class ConfigurationMatcher {
}
} else {
ConfigMatch match = selectConfigMatch(bestMatches);
- configuration.setDevice(match.device, true);
- configuration.setDeviceState(match.state, true);
- configuration.setLocale(localeList.get(match.bundle.localeIndex), true);
- configuration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
- configuration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), true);
-
- configuration.syncFolderConfig();
-
- mConfigChooser.selectDevice(configuration.getDevice());
- mConfigChooser.selectDeviceState(configuration.getDeviceState());
- mConfigChooser.selectLocale(configuration.getLocale());
+ mConfiguration.setDevice(match.device, true);
+ mConfiguration.setDeviceState(match.state, true);
+ mConfiguration.setLocale(localeList.get(match.bundle.localeIndex), true);
+ mConfiguration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
+ mConfiguration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), true);
+
+ mConfiguration.syncFolderConfig();
+
+ if (mUpdateUi) {
+ mConfigChooser.selectDevice(mConfiguration.getDevice());
+ mConfigChooser.selectDeviceState(mConfiguration.getDeviceState());
+ mConfigChooser.selectLocale(mConfiguration.getLocale());
+ }
}
}
@@ -455,15 +480,24 @@ public class ConfigurationMatcher {
private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) {
// API 11-13: look for a x-large device
- int apiLevel = mConfigChooser.getProjectTarget().getVersion().getApiLevel();
- if (apiLevel >= 11 && apiLevel < 14) {
- // TODO: Maybe check the compatible-screen tag in the manifest to figure out
- // what kind of device should be used for display.
- Collections.sort(matches, new TabletConfigComparator());
- } else {
+ Comparator<ConfigMatch> comparator = null;
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ IAndroidTarget projectTarget = sdk.getTarget(mEditedFile.getProject());
+ if (projectTarget != null) {
+ int apiLevel = projectTarget.getVersion().getApiLevel();
+ if (apiLevel >= 11 && apiLevel < 14) {
+ // TODO: Maybe check the compatible-screen tag in the manifest to figure out
+ // what kind of device should be used for display.
+ comparator = new TabletConfigComparator();
+ }
+ }
+ }
+ if (comparator == null) {
// lets look for a high density device
- Collections.sort(matches, new PhoneConfigComparator());
+ comparator = new PhoneConfigComparator();
}
+ Collections.sort(matches, comparator);
// Look at the currently active editor to see if it's a layout editor, and if so,
// look up its configuration and if the configuration is in our match list,
@@ -473,7 +507,7 @@ public class ConfigurationMatcher {
LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
if (delegate != null
// (Only do this when the two files are in the same project)
- && delegate.getEditor().getProject() == mConfigChooser.getProject()) {
+ && delegate.getEditor().getProject() == mEditedFile.getProject()) {
FolderConfiguration configuration = delegate.getGraphicalEditor().getConfiguration();
if (configuration != null) {
for (ConfigMatch match : matches) {
@@ -636,7 +670,11 @@ public class ConfigurationMatcher {
}
// From the resources, look for a matching file
- String name = chooser.getEditedFile().getName();
+ IFile editedFile = chooser.getEditedFile();
+ if (editedFile == null) {
+ return null;
+ }
+ String name = editedFile.getName();
FolderConfiguration config = chooser.getConfiguration().getFullConfig();
ResourceFile match = resources.getMatchingFile(name, ResourceFolderType.LAYOUT, config);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
index 30f7dc2..81b4cd0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
@@ -16,21 +16,25 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
-
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewManager;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
@@ -39,9 +43,9 @@ import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -52,6 +56,15 @@ class ConfigurationMenuListener extends SelectionAdapter {
private static final String ICON_NEW_CONFIG = "newConfig"; //$NON-NLS-1$
private static final int ACTION_SELECT_CONFIG = 1;
private static final int ACTION_CREATE_CONFIG_FILE = 2;
+ private static final int ACTION_ADD = 3;
+ private static final int ACTION_GENERATE_DEFAULT = 4;
+ private static final int ACTION_DELETE_ALL = 5;
+ private static final int ACTION_PREVIEW_LOCALES = 6;
+ private static final int ACTION_PREVIEW_SCREENS = 7;
+ private static final int ACTION_PREVIEW_INCLUDED_IN = 8;
+ private static final int ACTION_PREVIEW_VARIATIONS = 9;
+ private static final int ACTION_PREVIEW_CUSTOM = 10;
+ private static final int ACTION_PREVIEW_NONE = 11;
private final ConfigurationChooser mConfigChooser;
private final int mAction;
@@ -75,77 +88,177 @@ class ConfigurationMenuListener extends SelectionAdapter {
} catch (PartInitException ex) {
AdtPlugin.log(ex, null);
}
- break;
+ return;
}
case ACTION_CREATE_CONFIG_FILE: {
ConfigurationClient client = mConfigChooser.getClient();
if (client != null) {
client.createConfigFile();
}
+ return;
+ }
+ }
+
+ IEditorPart activeEditor = AdtUtils.getActiveEditor();
+ LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
+ IFile editedFile = mConfigChooser.getEditedFile();
+
+ if (delegate == null || editedFile == null) {
+ return;
+ }
+ // (Only do this when the two files are in the same project)
+ IProject project = delegate.getEditor().getProject();
+ if (project == null ||
+ !project.equals(editedFile.getProject())) {
+ return;
+ }
+ LayoutCanvas canvas = delegate.getGraphicalEditor().getCanvasControl();
+ RenderPreviewManager previewManager = canvas.getPreviewManager();
+
+ switch (mAction) {
+ case ACTION_ADD: {
+ previewManager.addAsThumbnail();
+ break;
+ }
+ case ACTION_GENERATE_DEFAULT: {
+ previewManager.selectMode(RenderPreviewMode.DEFAULT);
+ break;
+ }
+ case ACTION_DELETE_ALL: {
+ previewManager.deleteManualPreviews();
+ break;
+ }
+ case ACTION_PREVIEW_LOCALES: {
+ previewManager.selectMode(RenderPreviewMode.LOCALES);
+ break;
+ }
+ case ACTION_PREVIEW_SCREENS: {
+ previewManager.selectMode(RenderPreviewMode.SCREENS);
+ break;
+ }
+ case ACTION_PREVIEW_INCLUDED_IN: {
+ previewManager.selectMode(RenderPreviewMode.INCLUDES);
+ break;
+ }
+ case ACTION_PREVIEW_VARIATIONS: {
+ previewManager.selectMode(RenderPreviewMode.VARIATIONS);
+ break;
+ }
+ case ACTION_PREVIEW_CUSTOM: {
+ previewManager.selectMode(RenderPreviewMode.CUSTOM);
+ break;
+ }
+ case ACTION_PREVIEW_NONE: {
+ previewManager.selectMode(RenderPreviewMode.NONE);
break;
}
default: assert false : mAction;
}
+ canvas.setFitScale(true /*onlyZoomOut*/, false /*allowZoomIn*/);
+ canvas.redraw();
}
static void show(ConfigurationChooser chooser, ToolItem combo) {
Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+ RenderPreviewMode mode = AdtPrefs.getPrefs().getRenderPreviewMode();
+
+ // Configuration Previews
+ create(menu, "Add As Thumbnail...",
+ new ConfigurationMenuListener(chooser, ACTION_ADD, null), SWT.PUSH, false);
+ if (mode == RenderPreviewMode.CUSTOM) {
+ create(menu, "Delete All Thumbnails",
+ new ConfigurationMenuListener(chooser, ACTION_DELETE_ALL, null), SWT.PUSH, false);
+ }
+
+ @SuppressWarnings("unused")
+ MenuItem configSeparator = new MenuItem(menu, SWT.SEPARATOR);
+
+ //create(menu, "Generate Default Thumbnails",
+ create(menu, "Preview Sample",
+ new ConfigurationMenuListener(chooser, ACTION_GENERATE_DEFAULT, null), SWT.RADIO,
+ mode == RenderPreviewMode.DEFAULT);
+ create(menu, "Preview All Screen Sizes",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_SCREENS, null), SWT.RADIO,
+ mode == RenderPreviewMode.SCREENS);
+
+ MenuItem localeItem = create(menu, "Preview All Locales",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_LOCALES, null), SWT.RADIO,
+ mode == RenderPreviewMode.LOCALES);
+ if (chooser.getLocaleList().size() <= 1) {
+ localeItem.setEnabled(false);
+ }
+
+ boolean canPreviewIncluded = false;
+ IProject project = chooser.getProject();
+ if (project != null) {
+ IncludeFinder finder = IncludeFinder.get(project);
+ final List<Reference> includedBy = finder.getIncludedBy(chooser.getEditedFile());
+ canPreviewIncluded = includedBy != null && !includedBy.isEmpty();
+ }
+ //if (!graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) {
+ // canPreviewIncluded = false;
+ //}
+ MenuItem includedItem = create(menu, "Preview Included",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_INCLUDED_IN, null),
+ SWT.RADIO, mode == RenderPreviewMode.INCLUDES);
+ if (!canPreviewIncluded) {
+ includedItem.setEnabled(false);
+ }
- // Compute the set of layout files defining this layout resource
IFile file = chooser.getEditedFile();
- String name = file.getName();
- IContainer resFolder = file.getParent().getParent();
- List<IFile> variations = new ArrayList<IFile>();
- try {
- for (IResource resource : resFolder.members()) {
- if (resource.getName().startsWith(FD_RES_LAYOUT)
- && resource instanceof IContainer) {
- IContainer layoutFolder = (IContainer) resource;
- IResource variation = layoutFolder.findMember(name);
- if (variation instanceof IFile) {
- variations.add((IFile) variation);
- }
- }
- }
- } catch (CoreException e1) {
- AdtPlugin.log(e1, null);
+ List<IFile> variations = AdtUtils.getResourceVariations(file, true);
+ MenuItem variationsItem = create(menu, "Preview Layout Versions",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_VARIATIONS, null),
+ SWT.RADIO, mode == RenderPreviewMode.VARIATIONS);
+ if (variations.size() <= 1) {
+ variationsItem.setEnabled(false);
}
- ResourceManager manager = ResourceManager.getInstance();
- for (final IFile resource : variations) {
- MenuItem item = new MenuItem(menu, SWT.CHECK);
+ create(menu, "Manual Previews",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_CUSTOM, null),
+ SWT.RADIO, mode == RenderPreviewMode.CUSTOM);
+ create(menu, "None",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_NONE, null),
+ SWT.RADIO, mode == RenderPreviewMode.NONE);
- IFolder parent = (IFolder) resource.getParent();
- ResourceFolder parentResource = manager.getResourceFolder(parent);
- FolderConfiguration configuration = parentResource.getConfiguration();
- String title = configuration.toDisplayString();
- item.setText(title);
+ if (variations.size() > 1) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
- boolean selected = file.equals(resource);
- if (selected) {
- item.setSelection(true);
- item.setEnabled(false);
- }
+ ResourceManager manager = ResourceManager.getInstance();
+ for (final IFile resource : variations) {
+ IFolder parent = (IFolder) resource.getParent();
+ ResourceFolder parentResource = manager.getResourceFolder(parent);
+ FolderConfiguration configuration = parentResource.getConfiguration();
+ String title = configuration.toDisplayString();
- item.addSelectionListener(new ConfigurationMenuListener(chooser,
- ACTION_SELECT_CONFIG, resource));
+ MenuItem item = create(menu, title,
+ new ConfigurationMenuListener(chooser, ACTION_SELECT_CONFIG, resource),
+ SWT.CHECK, false);
+
+ if (file != null) {
+ boolean selected = file.equals(resource);
+ if (selected) {
+ item.setSelection(true);
+ item.setEnabled(false);
+ }
+ }
+ }
}
Configuration configuration = chooser.getConfiguration();
- if (!configuration.getEditedConfig().equals(configuration.getFullConfig())) {
+ if (configuration.getEditedConfig() != null &&
+ !configuration.getEditedConfig().equals(configuration.getFullConfig())) {
if (variations.size() > 0) {
@SuppressWarnings("unused")
MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
}
// Add action for creating a new configuration
- MenuItem item = new MenuItem(menu, SWT.PUSH);
- item.setText("Create New...");
+ MenuItem item = create(menu, "Create New...",
+ new ConfigurationMenuListener(chooser, ACTION_CREATE_CONFIG_FILE, null),
+ SWT.PUSH, false);
item.setImage(IconFactory.getInstance().getIcon(ICON_NEW_CONFIG));
- //item.setToolTipText("Duplicate: Create new configuration for this layout");
-
- item.addSelectionListener(
- new ConfigurationMenuListener(chooser, ACTION_CREATE_CONFIG_FILE, null));
}
Rectangle bounds = combo.getBounds();
@@ -154,4 +267,16 @@ class ConfigurationMenuListener extends SelectionAdapter {
menu.setLocation(location.x, location.y);
menu.setVisible(true);
}
+
+ @NonNull
+ public static MenuItem create(@NonNull Menu menu, String title,
+ ConfigurationMenuListener listener, int style, boolean selected) {
+ MenuItem item = new MenuItem(menu, style);
+ item.setText(title);
+ item.addSelectionListener(listener);
+ if (selected) {
+ item.setSelection(true);
+ }
+ return item;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java
new file mode 100644
index 0000000..73d08f8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.NightMode;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+
+/**
+ * An {@linkplain NestedConfiguration} is a {@link Configuration} which inherits
+ * all of its values from a different configuration, except for one or more
+ * attributes where it overrides a custom value.
+ * <p>
+ * Unlike a {@link ComplementingConfiguration}, a {@linkplain NestedConfiguration}
+ * will always return the same overridden value, regardless of the inherited
+ * value.
+ * <p>
+ * For example, an {@linkplain NestedConfiguration} may fix the locale to always
+ * be "en", but otherwise inherit everything else.
+ */
+public class NestedConfiguration extends Configuration {
+ protected final Configuration mParent;
+ protected boolean mOverrideLocale;
+ protected boolean mOverrideTarget;
+ protected boolean mOverrideDevice;
+ protected boolean mOverrideDeviceState;
+ protected boolean mOverrideNightMode;
+ protected boolean mOverrideUiMode;
+
+ /**
+ * Constructs a new {@linkplain NestedConfiguration}.
+ * Construct via
+ *
+ * @param chooser the associated chooser
+ * @param configuration the configuration to inherit from
+ */
+ protected NestedConfiguration(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull Configuration configuration) {
+ super(chooser);
+ mParent = configuration;
+
+ mFullConfig.set(mParent.mFullConfig);
+ if (mParent.getEditedConfig() != null) {
+ mEditedConfig = new FolderConfiguration();
+ mEditedConfig.set(mParent.mEditedConfig);
+ }
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration} which inherits values from the
+ * given parent {@linkplain Configuration}, possibly overriding some as
+ * well.
+ *
+ * @param chooser the associated chooser
+ * @param parent the configuration to inherit values from
+ * @return a new configuration
+ */
+ @NonNull
+ public static NestedConfiguration create(@NonNull ConfigurationChooser chooser,
+ @NonNull Configuration parent) {
+ return new NestedConfiguration(chooser, parent);
+ }
+
+ @Override
+ @Nullable
+ public String getTheme() {
+ // Never overridden: this is a static attribute of a layout, not something which
+ // varies by configuration or at runtime
+ return mParent.getTheme();
+ }
+
+ @Override
+ public void setTheme(String theme) {
+ // Never overridden
+ mParent.setTheme(theme);
+ }
+
+ /**
+ * Sets whether the locale should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideLocale(boolean override) {
+ mOverrideLocale = override;
+ }
+
+ /**
+ * Returns true if the locale is overridden
+ *
+ * @return true if the locale is overridden
+ */
+ public boolean isOverridingLocale() {
+ return mOverrideLocale;
+ }
+
+ @Override
+ @NonNull
+ public Locale getLocale() {
+ if (mOverrideLocale) {
+ return super.getLocale();
+ } else {
+ return mParent.getLocale();
+ }
+ }
+
+ @Override
+ public void setLocale(@NonNull Locale locale, boolean skipSync) {
+ if (mOverrideLocale) {
+ super.setLocale(locale, skipSync);
+ } else {
+ mParent.setLocale(locale, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the rendering target should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideTarget(boolean override) {
+ mOverrideTarget = override;
+ }
+
+ @Override
+ @Nullable
+ public IAndroidTarget getTarget() {
+ if (mOverrideTarget) {
+ return super.getTarget();
+ } else {
+ return mParent.getTarget();
+ }
+ }
+
+ @Override
+ public void setTarget(IAndroidTarget target, boolean skipSync) {
+ if (mOverrideTarget) {
+ super.setTarget(target, skipSync);
+ } else {
+ mParent.setTarget(target, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the device should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideDevice(boolean override) {
+ mOverrideDevice = override;
+ }
+
+ @Override
+ @Nullable
+ public Device getDevice() {
+ if (mOverrideDevice) {
+ return super.getDevice();
+ } else {
+ return mParent.getDevice();
+ }
+ }
+
+ @Override
+ public void setDevice(Device device, boolean skipSync) {
+ if (mOverrideDevice) {
+ super.setDevice(device, skipSync);
+ } else {
+ mParent.setDevice(device, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the device state should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideDeviceState(boolean override) {
+ mOverrideDeviceState = override;
+ }
+
+ @Override
+ @Nullable
+ public State getDeviceState() {
+ if (mOverrideDeviceState) {
+ return super.getDeviceState();
+ } else {
+ State state = mParent.getDeviceState();
+ if (mOverrideDevice) {
+ // If the device differs, I need to look up a suitable equivalent state
+ // on our device
+ if (state != null) {
+ Device device = super.getDevice();
+ if (device != null) {
+ return device.getState(state.getName());
+ }
+ }
+ }
+
+ return state;
+ }
+ }
+
+ @Override
+ public void setDeviceState(State state, boolean skipSync) {
+ if (mOverrideDeviceState) {
+ super.setDeviceState(state, skipSync);
+ } else {
+ if (mOverrideDevice) {
+ Device device = super.getDevice();
+ if (device != null) {
+ State equivalentState = device.getState(state.getName());
+ if (equivalentState != null) {
+ state = equivalentState;
+ }
+ }
+ }
+ mParent.setDeviceState(state, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the night mode should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideNightMode(boolean override) {
+ mOverrideNightMode = override;
+ }
+
+ @Override
+ @NonNull
+ public NightMode getNightMode() {
+ if (mOverrideNightMode) {
+ return super.getNightMode();
+ } else {
+ return mParent.getNightMode();
+ }
+ }
+
+ @Override
+ public void setNightMode(@NonNull NightMode night, boolean skipSync) {
+ if (mOverrideNightMode) {
+ super.setNightMode(night, skipSync);
+ } else {
+ mParent.setNightMode(night, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the UI mode should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideUiMode(boolean override) {
+ mOverrideUiMode = override;
+ }
+
+ @Override
+ @NonNull
+ public UiMode getUiMode() {
+ if (mOverrideUiMode) {
+ return super.getUiMode();
+ } else {
+ return mParent.getUiMode();
+ }
+ }
+
+ @Override
+ public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) {
+ if (mOverrideUiMode) {
+ super.setUiMode(uiMode, skipSync);
+ } else {
+ mParent.setUiMode(uiMode, skipSync);
+ }
+ }
+
+ /**
+ * Returns the configuration this {@linkplain NestedConfiguration} is
+ * inheriting from
+ *
+ * @return the configuration this configuration is inheriting from
+ */
+ @NonNull
+ public Configuration getParent() {
+ return mParent;
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
index 5650772..717347f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
@@ -40,6 +40,9 @@ public class CanvasTransform {
/** Canvas image size (original, before zoom), in pixels. */
private int mImgSize;
+ /** Full size being scrolled (after zoom), in pixels */
+ private int mFullSize;;
+
/** Client size, in pixels. */
private int mClientSize;
@@ -83,6 +86,11 @@ public class CanvasTransform {
}
}
+ /** Recomputes the scrollbar and view port settings */
+ public void refresh() {
+ resizeScrollbar();
+ }
+
/**
* Returns current scaling factor.
*
@@ -110,13 +118,22 @@ public class CanvasTransform {
return (int) (mImgSize * mScale);
}
- /** Changes the size of the canvas image and the client size. Recomputes scrollbars. */
- public void setSize(int imgSize, int clientSize) {
+ /**
+ * Changes the size of the canvas image and the client size. Recomputes
+ * scrollbars.
+ *
+ * @param imgSize the size of the image being scaled
+ * @param fullSize the size of the full view area being scrolled
+ * @param clientSize the size of the view port
+ */
+ public void setSize(int imgSize, int fullSize, int clientSize) {
mImgSize = imgSize;
- setClientSize(clientSize);
+ mFullSize = fullSize;
+ mClientSize = clientSize;
+ mScrollbar.setPageIncrement(clientSize);
+ resizeScrollbar();
}
- /** Changes the size of the client size. Recomputes scrollbars. */
public void setClientSize(int clientSize) {
mClientSize = clientSize;
mScrollbar.setPageIncrement(clientSize);
@@ -125,7 +142,7 @@ public class CanvasTransform {
private void resizeScrollbar() {
// scaled image size
- int sx = (int) (mImgSize * mScale);
+ int sx = (int) (mScale * mFullSize);
// Adjust margin such that for zoomed out views
// we don't waste space (unless the viewport is
@@ -150,6 +167,11 @@ public class CanvasTransform {
mMargin = DEFAULT_MARGIN;
}
+ if (mCanvas.getPreviewManager().hasPreviews()) {
+ // Make more room for the previews
+ mMargin = 2;
+ }
+
// actual client area is always reduced by the margins
int cx = mClientSize - 2 * mMargin;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
index 1625195..b364f57 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
@@ -15,16 +15,13 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.TOOLS_URI;
-
-
import static org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML.ContentTypeID_XML;
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
@@ -61,6 +58,7 @@ import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
/**
* Various utility methods for manipulating DOM nodes.
@@ -828,6 +826,33 @@ public class DomUtilities {
}
/**
+ * Creates an empty non-Eclipse XML document.
+ * This is used when you need to use XML operations not supported by
+ * the Eclipse XML model (such as serialization).
+ * <p>
+ * The new document will not validate, will ignore comments, and will
+ * support namespace.
+ *
+ * @return the new document
+ */
+ @Nullable
+ public static Document createEmptyPlainDocument() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+ try {
+ builder = factory.newDocumentBuilder();
+ return builder.newDocument();
+ } catch (ParserConfigurationException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ /**
* Parses the given XML string as a DOM document, using the JDK parser.
* The parser does not validate, and is namespace aware.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
index 468d159..98bc25e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
@@ -39,6 +39,7 @@ import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Device;
@@ -433,7 +434,8 @@ public class GestureManager {
* Helper class which implements the {@link MouseMoveListener},
* {@link MouseListener} and {@link KeyListener} interfaces.
*/
- private class Listener implements MouseMoveListener, MouseListener, KeyListener {
+ private class Listener implements MouseMoveListener, MouseListener, MouseTrackListener,
+ KeyListener {
// --- MouseMoveListener ---
@@ -443,15 +445,16 @@ public class GestureManager {
mLastMouseY = e.y;
mLastStateMask = e.stateMask;
+ ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
if ((e.stateMask & SWT.BUTTON_MASK) != 0) {
if (mCurrentGesture != null) {
- ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
updateMouse(controlPoint, e);
mCanvas.redraw();
}
} else {
- updateCursor(ControlPoint.create(mCanvas, e));
+ updateCursor(controlPoint);
mCanvas.hover(e);
+ mCanvas.getPreviewManager().moved(controlPoint);
}
}
@@ -460,7 +463,13 @@ public class GestureManager {
@Override
public void mouseUp(MouseEvent e) {
ControlPoint mousePos = ControlPoint.create(mCanvas, e);
+
if (mCurrentGesture == null) {
+ // If clicking on a configuration preview, just process it there
+ if (mCanvas.getPreviewManager().click(mousePos)) {
+ return;
+ }
+
// Just a click, select
Pair<SelectionItem, SelectionHandle> handlePair =
mCanvas.getSelectionManager().findHandle(mousePos);
@@ -507,6 +516,24 @@ public class GestureManager {
}
}
+ // --- MouseTrackListener ---
+
+ @Override
+ public void mouseEnter(MouseEvent e) {
+ ControlPoint mousePos = ControlPoint.create(mCanvas, e);
+ mCanvas.getPreviewManager().enter(mousePos);
+ }
+
+ @Override
+ public void mouseExit(MouseEvent e) {
+ ControlPoint mousePos = ControlPoint.create(mCanvas, e);
+ mCanvas.getPreviewManager().exit(mousePos);
+ }
+
+ @Override
+ public void mouseHover(MouseEvent e) {
+ }
+
// --- KeyListener ---
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 726824f..891feea 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -30,7 +30,6 @@ import static com.android.SdkConstants.STRING_PREFIX;
import static com.android.SdkConstants.VALUE_FILL_PARENT;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
-import static com.android.ide.eclipse.adt.AdtUtils.isUiThread;
import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser.NAME_CONFIG_STATE;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
@@ -658,6 +657,8 @@ public class GraphicalEditorPart extends EditorPart
return true;
}
+ getCanvasControl().getPreviewManager().configurationChanged(flags);
+
// Before doing the normal process, test for the following case.
// - the editor is being opened (or reset for a new input)
// - the file being opened is not the best match for any possible configuration
@@ -746,7 +747,7 @@ public class GraphicalEditorPart extends EditorPart
// out to fit the content, or zoom back in if we were zoomed out more from the
// previous view, but only up to 100% such that we never blow up pixels
if (mActionBar.isZoomingAllowed()) {
- getCanvasControl().setFitScale(true);
+ getCanvasControl().setFitScale(true, true /*allowZoomIn*/);
}
}
@@ -1039,6 +1040,8 @@ public class GraphicalEditorPart extends EditorPart
if (mNeedsRecompute) {
recomputeLayout();
}
+
+ mCanvasViewer.getCanvas().syncPreviewMode();
}
}
@@ -1253,6 +1256,7 @@ public class GraphicalEditorPart extends EditorPart
}
UiDocumentNode model = getModel();
+ LayoutCanvas canvas = mCanvasViewer.getCanvas();
if (!ensureModelValid(model)) {
// Although we display an error, we still treat an empty document as a
// successful layout result so that we can drop new elements in it.
@@ -1260,7 +1264,7 @@ public class GraphicalEditorPart extends EditorPart
// For that purpose, create a special LayoutScene that has no image,
// no root view yet indicates success and then update the canvas with it.
- mCanvasViewer.getCanvas().setSession(
+ canvas.setSession(
new StaticRenderSession(
Result.Status.SUCCESS.createResult(),
null /*rootViewInfo*/, null /*image*/),
@@ -1278,6 +1282,8 @@ public class GraphicalEditorPart extends EditorPart
IProject project = mEditedFile.getProject();
renderWithBridge(project, model, layoutLib);
+
+ canvas.getPreviewManager().renderPreviews();
}
} finally {
// no matter the result, we are done doing the recompute based on the latest
@@ -1462,11 +1468,6 @@ public class GraphicalEditorPart extends EditorPart
return null;
}
- if (mConfigChooser.isDisposed()) {
- return null;
- }
- assert isUiThread();
-
// attempt to get a target from the configuration selector.
IAndroidTarget renderingTarget = mConfigChooser.getConfiguration().getTarget();
if (renderingTarget != null) {
@@ -1501,6 +1502,10 @@ public class GraphicalEditorPart extends EditorPart
private boolean ensureModelValid(UiDocumentNode model) {
// check there is actually a model (maybe the file is empty).
if (model.getUiChildren().size() == 0) {
+ if (mEditorDelegate.getEditor().isCreatingPages()) {
+ displayError("Loading editor");
+ return false;
+ }
displayError(
"No XML content. Please add a root view or layout to your document.");
return false;
@@ -1565,7 +1570,7 @@ public class GraphicalEditorPart extends EditorPart
} else if (missingClasses.size() > 0 || brokenClasses.size() > 0) {
displayFailingClasses(missingClasses, brokenClasses, false);
displayUserStackTrace(logger, true);
- } else {
+ } else if (session != null) {
// Nope, no missing or broken classes. Clear success, congrats!
hideError();
@@ -2798,7 +2803,7 @@ public class GraphicalEditorPart extends EditorPart
// Auto zoom the surface if you open or close flyout windows such as the palette
// or the property/outline views
if (newState == STATE_OPEN || newState == STATE_COLLAPSED && oldState == STATE_OPEN) {
- getCanvasControl().setFitScale(true /*onlyZoomOut*/);
+ getCanvasControl().setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/);
}
sDockingStateVersion++;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
index c55d0d8..8f7172e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
@@ -21,6 +21,12 @@ import static com.android.SdkConstants.DOT_GIF;
import static com.android.SdkConstants.DOT_JPG;
import static com.android.SdkConstants.DOT_PNG;
import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static java.awt.RenderingHints.KEY_ANTIALIASING;
+import static java.awt.RenderingHints.KEY_INTERPOLATION;
+import static java.awt.RenderingHints.KEY_RENDERING;
+import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
+import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
+import static java.awt.RenderingHints.VALUE_RENDER_QUALITY;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -34,7 +40,6 @@ import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
-import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
@@ -471,6 +476,7 @@ public class ImageUtils {
* Draws a drop shadow for the given rectangle into the given context. It
* will not draw anything if the rectangle is smaller than a minimum
* determined by the assets used to draw the shadow graphics.
+ * The size of the shadow is {@link #SHADOW_SIZE}.
*
* @param image the image to draw the shadow into
* @param x the left coordinate of the left hand side of the rectangle
@@ -489,12 +495,40 @@ public class ImageUtils {
}
/**
+ * Draws a small drop shadow for the given rectangle into the given context. It
+ * will not draw anything if the rectangle is smaller than a minimum
+ * determined by the assets used to draw the shadow graphics.
+ * The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+ *
+ * @param image the image to draw the shadow into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static final void drawSmallRectangleShadow(BufferedImage image,
+ int x, int y, int width, int height) {
+ Graphics gc = image.getGraphics();
+ try {
+ drawSmallRectangleShadow(gc, x, y, width, height);
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
* The width and height of the drop shadow painted by
* {@link #drawRectangleShadow(Graphics, int, int, int, int)}
*/
public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
/**
+ * The width and height of the drop shadow painted by
+ * {@link #drawSmallRectangleShadow(Graphics, int, int, int, int)}
+ */
+ public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+ /**
* Draws a drop shadow for the given rectangle into the given context. It
* will not draw anything if the rectangle is smaller than a minimum
* determined by the assets used to draw the shadow graphics.
@@ -569,6 +603,73 @@ public class ImageUtils {
null);
}
+ /**
+ * Draws a small drop shadow for the given rectangle into the given context. It
+ * will not draw anything if the rectangle is smaller than a minimum
+ * determined by the assets used to draw the shadow graphics.
+ * <p>
+ *
+ * @param gc the graphics context to draw into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static final void drawSmallRectangleShadow(Graphics gc,
+ int x, int y, int width, int height) {
+ if (sShadow2BottomLeft == null) {
+ // Shadow graphics. This was generated by creating a drop shadow in
+ // Gimp, using the parameters x offset=5, y offset=%, blur radius=5,
+ // color=black, and opacity=51. These values attempt to make a shadow
+ // that is legible both for dark and light themes, on top of the
+ // canvas background (rgb(150,150,150). Darker shadows would tend to
+ // blend into the foreground for a dark holo screen, and lighter shadows
+ // would be hard to spot on the canvas background. If you make adjustments,
+ // make sure to check the shadow with both dark and light themes.
+ //
+ // After making the graphics, I cut out the top right, bottom left
+ // and bottom right corners as 20x20 images, and these are reproduced by
+ // painting them in the corresponding places in the target graphics context.
+ // I then grabbed a single horizontal gradient line from the middle of the
+ // right edge,and a single vertical gradient line from the bottom. These
+ // are then painted scaled/stretched in the target to fill the gaps between
+ // the three corner images.
+ //
+ // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+ sShadow2BottomLeft = readImage("shadow2-bl.png"); //$NON-NLS-1$
+ sShadow2Bottom = readImage("shadow2-b.png"); //$NON-NLS-1$
+ sShadow2BottomRight = readImage("shadow2-br.png"); //$NON-NLS-1$
+ sShadow2Right = readImage("shadow2-r.png"); //$NON-NLS-1$
+ sShadow2TopRight = readImage("shadow2-tr.png"); //$NON-NLS-1$
+ assert sShadow2BottomLeft != null;
+ assert sShadow2BottomRight.getWidth() == SMALL_SHADOW_SIZE;
+ assert sShadow2BottomRight.getHeight() == SMALL_SHADOW_SIZE;
+ }
+
+ int blWidth = sShadow2BottomLeft.getWidth();
+ int trHeight = sShadow2TopRight.getHeight();
+ if (width < blWidth) {
+ return;
+ }
+ if (height < trHeight) {
+ return;
+ }
+
+ gc.drawImage(sShadow2BottomLeft, x, y + height, null);
+ gc.drawImage(sShadow2BottomRight, x + width, y + height, null);
+ gc.drawImage(sShadow2TopRight, x + width, y, null);
+ gc.drawImage(sShadow2Bottom,
+ x + sShadow2BottomLeft.getWidth(), y + height,
+ x + width, y + height + sShadow2Bottom.getHeight(),
+ 0, 0, sShadow2Bottom.getWidth(), sShadow2Bottom.getHeight(),
+ null);
+ gc.drawImage(sShadow2Right,
+ x + width, y + sShadow2TopRight.getHeight(),
+ x + width + sShadow2Right.getWidth(), y + height,
+ 0, 0, sShadow2Right.getWidth(), sShadow2Right.getHeight(),
+ null);
+ }
+
@Nullable
private static BufferedImage readImage(@NonNull String name) {
InputStream stream = ImageUtils.class.getResourceAsStream("/icons/" + name); //$NON-NLS-1$
@@ -589,12 +690,19 @@ public class ImageUtils {
return null;
}
+ // Normal drop shadow
private static BufferedImage sShadowBottomLeft;
private static BufferedImage sShadowBottom;
private static BufferedImage sShadowBottomRight;
private static BufferedImage sShadowRight;
private static BufferedImage sShadowTopRight;
+ // Small drop shadow
+ private static BufferedImage sShadow2BottomLeft;
+ private static BufferedImage sShadow2Bottom;
+ private static BufferedImage sShadow2BottomRight;
+ private static BufferedImage sShadow2Right;
+ private static BufferedImage sShadow2TopRight;
/**
* Returns a bounding rectangle for the given list of rectangles. If the list is
@@ -731,20 +839,102 @@ public class ImageUtils {
if (imageType == BufferedImage.TYPE_CUSTOM) {
imageType = BufferedImage.TYPE_INT_ARGB;
}
- BufferedImage scaled = new BufferedImage(
- destWidth + rightMargin, destHeight + bottomMargin, imageType);
- Graphics2D g2 = scaled.createGraphics();
- g2.setComposite(AlphaComposite.Src);
- g2.setColor(new Color(0, true));
- g2.fillRect(0, 0, destWidth + rightMargin, destHeight + bottomMargin);
- 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(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight, null);
- g2.dispose();
+ if (xScale > 0.5 && yScale > 0.5) {
+ BufferedImage scaled =
+ new BufferedImage(destWidth + rightMargin, destHeight + bottomMargin, imageType);
+ Graphics2D g2 = scaled.createGraphics();
+ g2.setComposite(AlphaComposite.Src);
+ g2.setColor(new Color(0, true));
+ g2.fillRect(0, 0, destWidth + rightMargin, destHeight + bottomMargin);
+ g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight,
+ null);
+ g2.dispose();
+ return scaled;
+ } else {
+ // When creating a thumbnail, using the above code doesn't work very well;
+ // you get some visible artifacts, especially for text. Instead use the
+ // technique of repeatedly scaling the image into half; this will cause
+ // proper averaging of neighboring pixels, and will typically (for the kinds
+ // of screen sizes used by this utility method in the layout editor) take
+ // about 3-4 iterations to get the result since we are logarithmically reducing
+ // the size. Besides, each successive pass in operating on much fewer pixels
+ // (a reduction of 4 in each pass).
+ //
+ // However, we may not be resizing to a size that can be reached exactly by
+ // successively diving in half. Therefore, once we're within a factor of 2 of
+ // the final size, we can do a resize to the exact target size.
+ // However, we can get even better results if we perform this final resize
+ // up front. Let's say we're going from width 1000 to a destination width of 85.
+ // The first approach would cause a resize from 1000 to 500 to 250 to 125, and
+ // then a resize from 125 to 85. That last resize can distort/blur a lot.
+ // Instead, we can start with the destination width, 85, and double it
+ // successfully until we're close to the initial size: 85, then 170,
+ // then 340, and finally 680. (The next one, 1360, is larger than 1000).
+ // So, now we *start* the thumbnail operation by resizing from width 1000 to
+ // width 680, which will preserve a lot of visual details such as text.
+ // Then we can successively resize the image in half, 680 to 340 to 170 to 85.
+ // We end up with the expected final size, but we've been doing an exact
+ // divide-in-half resizing operation at the end so there is less distortion.
+
+
+ int iterations = 0; // Number of halving operations to perform after the initial resize
+ int nearestWidth = destWidth; // Width closest to source width that = 2^x, x is integer
+ int nearestHeight = destHeight;
+ while (nearestWidth < sourceWidth / 2) {
+ nearestWidth *= 2;
+ nearestHeight *= 2;
+ iterations++;
+ }
- return scaled;
+ // If we're supposed to add in margins, we need to do it in the initial resizing
+ // operation if we don't have any subsequent resizing operations.
+ if (iterations == 0) {
+ nearestWidth += rightMargin;
+ nearestHeight += bottomMargin;
+ }
+
+ BufferedImage scaled = new BufferedImage(nearestWidth, nearestHeight, imageType);
+ Graphics2D g2 = scaled.createGraphics();
+ g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g2.drawImage(source, 0, 0, nearestWidth, nearestHeight,
+ 0, 0, sourceWidth, sourceHeight, null);
+ g2.dispose();
+
+ sourceWidth = nearestWidth;
+ sourceHeight = nearestHeight;
+ source = scaled;
+
+ for (int iteration = iterations - 1; iteration >= 0; iteration--) {
+ int halfWidth = sourceWidth / 2;
+ int halfHeight = sourceHeight / 2;
+ if (iteration == 0) { // Last iteration: Add margins in final image
+ scaled = new BufferedImage(halfWidth + rightMargin, halfHeight + bottomMargin,
+ imageType);
+ } else {
+ scaled = new BufferedImage(halfWidth, halfHeight, imageType);
+ }
+ g2 = scaled.createGraphics();
+ g2.setRenderingHint(KEY_INTERPOLATION,VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g2.drawImage(source, 0, 0,
+ halfWidth, halfHeight, 0, 0,
+ sourceWidth, sourceHeight,
+ null);
+ g2.dispose();
+
+ sourceWidth = halfWidth;
+ sourceHeight = halfHeight;
+ source = scaled;
+ iterations--;
+ }
+ return scaled;
+ }
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
index 0b8f784..7bab914 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
@@ -20,6 +20,8 @@ import static com.android.SdkConstants.ATTR_LAYOUT;
import static com.android.SdkConstants.EXT_XML;
import static com.android.SdkConstants.FD_RESOURCES;
import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_INCLUDE;
import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS;
import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
@@ -29,6 +31,8 @@ import static org.eclipse.core.resources.IResourceDelta.CHANGED;
import static org.eclipse.core.resources.IResourceDelta.CONTENT;
import static org.eclipse.core.resources.IResourceDelta.REMOVED;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
@@ -56,6 +60,7 @@ import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
@@ -117,8 +122,9 @@ public class IncludeFinder {
* Returns the {@link IncludeFinder} for the given project
*
* @param project the project the finder is associated with
- * @return an {@IncludeFinder} for the given project, never null
+ * @return an {@link IncludeFinder} for the given project, never null
*/
+ @NonNull
public static IncludeFinder get(IProject project) {
IncludeFinder finder = null;
try {
@@ -157,6 +163,7 @@ public class IncludeFinder {
* @param included the file that is included
* @return the files that are including the given file, or null or empty
*/
+ @Nullable
public List<Reference> getIncludedBy(IResource included) {
ensureInitialized();
String mapKey = getMapKey(included);
@@ -503,8 +510,10 @@ public class IncludeFinder {
* empty if the file does not include any include tags; it does this by only parsing
* if it detects the string &lt;include in the file.
*/
- private List<String> findIncludes(String xml) {
- int index = xml.indexOf("<include"); //$NON-NLS-1$
+ @VisibleForTesting
+ @NonNull
+ static List<String> findIncludes(@NonNull String xml) {
+ int index = xml.indexOf(ATTR_LAYOUT);
if (index != -1) {
return findIncludesInXml(xml);
}
@@ -518,7 +527,9 @@ public class IncludeFinder {
* @param xml layout XML content to be parsed for includes
* @return a list of included urls, or null
*/
- private List<String> findIncludesInXml(String xml) {
+ @VisibleForTesting
+ @NonNull
+ static List<String> findIncludesInXml(@NonNull String xml) {
Document document = DomUtilities.parseDocument(xml, false /*logParserErrors*/);
if (document != null) {
return findIncludesInDocument(document);
@@ -528,27 +539,52 @@ public class IncludeFinder {
}
/** Searches the given DOM document and returns the list of includes, if any */
- private List<String> findIncludesInDocument(Document document) {
- NodeList includes = document.getElementsByTagName(VIEW_INCLUDE);
- if (includes.getLength() > 0) {
- List<String> urls = new ArrayList<String>();
- for (int i = 0; i < includes.getLength(); i++) {
- Element element = (Element) includes.item(i);
- String url = element.getAttribute(ATTR_LAYOUT);
+ @NonNull
+ private static List<String> findIncludesInDocument(@NonNull Document document) {
+ List<String> includes = findIncludesInDocument(document, null);
+ if (includes == null) {
+ includes = Collections.emptyList();
+ }
+ return includes;
+ }
+
+ @Nullable
+ private static List<String> findIncludesInDocument(@NonNull Node node,
+ @Nullable List<String> urls) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ String tag = node.getNodeName();
+ boolean isInclude = tag.equals(VIEW_INCLUDE);
+ boolean isFragment = tag.equals(VIEW_FRAGMENT);
+ if (isInclude || isFragment) {
+ Element element = (Element) node;
+ String url;
+ if (isInclude) {
+ url = element.getAttribute(ATTR_LAYOUT);
+ } else {
+ url = element.getAttributeNS(TOOLS_URI, ATTR_LAYOUT);
+ }
if (url.length() > 0) {
String resourceName = urlToLocalResource(url);
if (resourceName != null) {
+ if (urls == null) {
+ urls = new ArrayList<String>();
+ }
urls.add(resourceName);
}
}
+
}
+ }
- return urls;
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ urls = findIncludesInDocument(children.item(i), urls);
}
- return Collections.emptyList();
+ return urls;
}
+
/**
* Returns the layout URL to a local resource name (provided the URL is a local
* resource, not something in @android etc.) Returns null otherwise.
@@ -628,6 +664,7 @@ public class IncludeFinder {
ResourceManager.getInstance().addListener(sListener);
}
+ /** Stop listening on project resources */
public static void stop() {
assert sListener != null;
ResourceManager.getInstance().addListener(sListener);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 4368db4..d5b46b4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -691,7 +691,7 @@ public class LayoutActionBar extends Composite {
* Reset the canvas scale to best fit (so content is as large as possible without scrollbars)
*/
void rescaleToFit(boolean onlyZoomOut) {
- mEditor.getCanvasControl().setFitScale(onlyZoomOut);
+ mEditor.getCanvasControl().setFitScale(onlyZoomOut, true /*allowZoomIn*/);
}
boolean rescaleToReal(boolean real) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 86878ac..81fd8d2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.Margins;
import com.android.ide.common.api.Point;
@@ -150,10 +151,10 @@ public class LayoutCanvas extends Canvas {
private final NodeFactory mNodeFactory = new NodeFactory(this);
/** Vertical scaling & scrollbar information. */
- private CanvasTransform mVScale;
+ private final CanvasTransform mVScale;
/** Horizontal scaling & scrollbar information. */
- private CanvasTransform mHScale;
+ private final CanvasTransform mHScale;
/** Drag source associated with this canvas. */
private DragSource mDragSource;
@@ -218,6 +219,9 @@ public class LayoutCanvas extends Canvas {
/** The overlay which paints masks hiding everything but included content. */
private IncludeOverlay mIncludeOverlay;
+ /** Configuration previews shown next to the layout */
+ private final RenderPreviewManager mPreviewManager;
+
/**
* Gesture Manager responsible for identifying mouse, keyboard and drag and
* drop events.
@@ -239,6 +243,14 @@ public class LayoutCanvas extends Canvas {
private Color mBackgroundColor;
+ /**
+ * Creates a new {@link LayoutCanvas} widget
+ *
+ * @param editorDelegate the associated editor delegate
+ * @param rulesEngine the rules engine
+ * @param parent parent SWT widget
+ * @param style the SWT style
+ */
public LayoutCanvas(LayoutEditorDelegate editorDelegate,
RulesEngine rulesEngine,
Composite parent,
@@ -253,6 +265,7 @@ public class LayoutCanvas extends Canvas {
mClipboardSupport = new ClipboardSupport(this, parent);
mHScale = new CanvasTransform(this, getHorizontalBar());
mVScale = new CanvasTransform(this, getVerticalBar());
+ mPreviewManager = new RenderPreviewManager(this);
// Unit test suite passes a null here; TODO: Replace with mocking
IFile file = editorDelegate != null ? editorDelegate.getEditor().getInputFile() : null;
@@ -314,9 +327,7 @@ public class LayoutCanvas extends Canvas {
}
}
- Rectangle clientArea = getClientArea();
- mHScale.setClientSize(clientArea.width);
- mVScale.setClientSize(clientArea.height);
+ updateScrollBars();
// Update the zoom level in the canvas when you toggle the zoom
if (coordinator != null) {
@@ -355,6 +366,37 @@ public class LayoutCanvas extends Canvas {
mLintTooltipManager.register();
}
+ void updateScrollBars() {
+ Rectangle clientArea = getClientArea();
+ Image image = mImageOverlay.getImage();
+ if (image != null) {
+ ImageData imageData = image.getImageData();
+ int clientWidth = clientArea.width;
+ int clientHeight = clientArea.height;
+
+ int imageWidth = imageData.width;
+ int imageHeight = imageData.height;
+
+ int fullWidth = imageWidth;
+ int fullHeight = imageHeight;
+
+ if (mPreviewManager.hasPreviews()) {
+ fullHeight = Math.max(fullHeight,
+ (int) (mPreviewManager.getHeight() / mHScale.getScale()));
+ }
+
+ if (clientWidth == 0) {
+ clientWidth = imageWidth;
+ }
+ if (clientHeight == 0) {
+ clientHeight = imageHeight;
+ }
+
+ mHScale.setSize(imageWidth, fullWidth, clientWidth);
+ mVScale.setSize(imageHeight, fullHeight, clientHeight);
+ }
+ }
+
private Runnable mZoomCheck = new Runnable() {
private Boolean mWasZoomed;
@@ -375,7 +417,7 @@ public class LayoutCanvas extends Canvas {
LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor()
.getLayoutActionBar();
if (actionBar.isZoomingAllowed()) {
- setFitScale(true /*onlyZoomOut*/);
+ setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/);
}
}
mWasZoomed = zoomed;
@@ -419,14 +461,22 @@ public class LayoutCanvas extends Canvas {
if (c == '1' && actionBar.isZoomingAllowed()) {
setScale(1, true);
} else if (c == '0' && actionBar.isZoomingAllowed()) {
- setFitScale(true);
+ setFitScale(true, true /*allowZoomIn*/);
} else if (e.keyCode == '0' && (e.stateMask & SWT.MOD2) != 0
&& actionBar.isZoomingAllowed()) {
- setFitScale(false);
- } else if (c == '+' && actionBar.isZoomingAllowed()) {
- actionBar.rescale(1);
+ setFitScale(false, true /*allowZoomIn*/);
+ } else if ((c == '+' || c == '=') && actionBar.isZoomingAllowed()) {
+ if ((e.stateMask & SWT.MOD1) != 0) {
+ mPreviewManager.zoomIn();
+ } else {
+ actionBar.rescale(1);
+ }
} else if (c == '-' && actionBar.isZoomingAllowed()) {
- actionBar.rescale(-1);
+ if ((e.stateMask & SWT.MOD1) != 0) {
+ mPreviewManager.zoomOut();
+ } else {
+ actionBar.rescale(-1);
+ }
}
}
}
@@ -507,9 +557,20 @@ public class LayoutCanvas extends Canvas {
mBackgroundColor = null;
}
+ mPreviewManager.disposePreviews();
mViewHierarchy.dispose();
}
+ /**
+ * Returns the configuration preview manager for this canvas
+ *
+ * @return the configuration preview manager for this canvas
+ */
+ @NonNull
+ public RenderPreviewManager getPreviewManager() {
+ return mPreviewManager;
+ }
+
/** Returns the Rules Engine, associated with the current project. */
/* package */ RulesEngine getRulesEngine() {
return mRulesEngine;
@@ -539,6 +600,8 @@ public class LayoutCanvas extends Canvas {
/**
* Returns the {@link LayoutEditorDelegate} associated with this canvas.
+ *
+ * @return the delegate
*/
public LayoutEditorDelegate getEditorDelegate() {
return mEditorDelegate;
@@ -670,15 +733,14 @@ public class LayoutCanvas extends Canvas {
mViewHierarchy.setSession(session, explodedNodes, layoutlib5);
if (mViewHierarchy.isValid() && session != null) {
- Image image = mImageOverlay.setImage(session.getImage(), session.isAlphaChannelImage());
+ Image image = mImageOverlay.setImage(session.getImage(),
+ session.isAlphaChannelImage());
mOutlinePage.setModel(mViewHierarchy.getRoot());
mEditorDelegate.getGraphicalEditor().setModel(mViewHierarchy.getRoot());
if (image != null) {
- Rectangle clientArea = getClientArea();
- mHScale.setSize(image.getImageData().width, clientArea.width);
- mVScale.setSize(image.getImageData().height, clientArea.height);
+ updateScrollBars();
if (mZoomFitNextImage) {
// Must be run asynchronously because getClientArea() returns 0 bounds
// when the editor is being initialized
@@ -691,6 +753,9 @@ public class LayoutCanvas extends Canvas {
}
});
}
+
+ // Ensure that if we have a a preview mode enabled, it's shown
+ syncPreviewMode();
}
}
@@ -703,7 +768,7 @@ public class LayoutCanvas extends Canvas {
LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor()
.getLayoutActionBar();
if (actionBar.isZoomingAllowed()) {
- setFitScale(true);
+ setFitScale(true, true /*allowZoomIn*/);
}
}
}
@@ -713,6 +778,13 @@ public class LayoutCanvas extends Canvas {
redraw();
}
+ /**
+ * Returns the zoom scale factor of the canvas (the amount the full
+ * resolution render of the device is zoomed before being shown on the
+ * canvas)
+ *
+ * @return the image scale
+ */
public double getScale() {
return mHScale.getScale();
}
@@ -746,8 +818,13 @@ public class LayoutCanvas extends Canvas {
* @param onlyZoomOut if true, then the zooming factor will never be larger than 1,
* which means that this function will zoom out if necessary to show the
* rendered image, but it will never zoom in.
+ * TODO: Rename this, it sounds like it conflicts with allowZoomIn,
+ * even though one is referring to the zoom level and one is referring
+ * to the overall act of scaling above/below 1.
+ * @param allowZoomIn if false, then if the computed zoom factor is smaller than
+ * the current zoom factor, it will be ignored.
*/
- void setFitScale(boolean onlyZoomOut) {
+ public void setFitScale(boolean onlyZoomOut, boolean allowZoomIn) {
ImageOverlay imageOverlay = getImageOverlay();
if (imageOverlay == null) {
return;
@@ -758,6 +835,13 @@ public class LayoutCanvas extends Canvas {
int canvasWidth = canvasSize.width;
int canvasHeight = canvasSize.height;
+ if (mPreviewManager.hasPreviews()) {
+ canvasWidth = 2 * canvasWidth / 3;
+ } else {
+ canvasWidth -= 4;
+ canvasHeight -= 4;
+ }
+
ImageData imageData = image.getImageData();
int sceneWidth = imageData.width;
int sceneHeight = imageData.height;
@@ -796,6 +880,10 @@ public class LayoutCanvas extends Canvas {
scale = Math.min(1.0, scale);
}
+ if (!allowZoomIn && scale > getScale()) {
+ return;
+ }
+
setScale(scale, true);
}
}
@@ -857,6 +945,8 @@ public class LayoutCanvas extends Canvas {
mImageOverlay.paint(gc);
}
+ mPreviewManager.paint(gc);
+
if (mShowOutline) {
if (mOutlineOverlay == null) {
mOutlineOverlay = new OutlineOverlay(mViewHierarchy, mHScale, mVScale);
@@ -1552,4 +1642,46 @@ public class LayoutCanvas extends Canvas {
mLintTooltipManager.hide();
}
}
+
+ /** @see #setPreview(RenderPreview) */
+ private RenderPreview mPreview;
+
+ /**
+ * Sets the {@link RenderPreview} associated with the currently rendering
+ * configuration.
+ * <p>
+ * A {@link RenderPreview} has various additional state beyond its rendering,
+ * such as its display name (which can be edited by the user). When you click on
+ * previews, the layout editor switches to show the given configuration preview.
+ * The preview is then no longer shown in the list of previews and is instead rendered
+ * in the main editor. However, when you then switch away to some other preview, we
+ * want to be able to restore the preview with all its state.
+ *
+ * @param preview the preview associated with the current canvas
+ */
+ public void setPreview(@Nullable RenderPreview preview) {
+ mPreview = preview;
+ }
+
+ /**
+ * Returns the {@link RenderPreview} associated with this layout canvas.
+ *
+ * @see #setPreview(RenderPreview)
+ * @return the {@link RenderPreview}
+ */
+ @Nullable
+ public RenderPreview getPreview() {
+ return mPreview;
+ }
+
+ /** Ensures that the configuration previews are up to date for this canvas */
+ public void syncPreviewMode() {
+ if (mImageOverlay != null && mImageOverlay.getImage() != null) {
+ if (mPreviewManager.recomputePreviews(false)) {
+ // Zoom when syncing modes
+ mZoomFitNextImage = true;
+ ensureZoomed();
+ }
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
index 119506b..ad4b94d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
@@ -378,6 +378,9 @@ public class PaletteControl extends Composite {
ConfigurationChooser configChooser = mEditor.getConfigurationChooser();
String theme = configChooser.getThemeName();
String device = configChooser.getDeviceName();
+ if (device == null) {
+ return;
+ }
AndroidTargetData targetData =
target != null ? Sdk.getCurrent().getTargetData(target) : null;
if (target == mCurrentTarget && targetData == mCurrentTargetData
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java
new file mode 100644
index 0000000..aafa69d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java
@@ -0,0 +1,1015 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_DEVICE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_FOLDER;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_LOCALE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_RENDER_TARGET;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_THEME;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
+import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.android.io.IAbstractFile;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Region;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.progress.UIJob;
+import org.w3c.dom.Document;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Represents a preview rendering of a given configuration
+ */
+public class RenderPreview implements IJobChangeListener {
+ private static final int HEADER_HEIGHT = 20;
+ static final boolean LARGE_SHADOWS = false;
+ private static final boolean DUMP_RENDER_DIAGNOSTICS = false;
+ private static final Image EDIT_ICON;
+ private static final Image ZOOM_IN_ICON;
+ private static final Image ZOOM_OUT_ICON;
+ private static final Image CLOSE_ICON;
+ private static final int EDIT_ICON_WIDTH;
+ private static final int ZOOM_IN_ICON_WIDTH;
+ private static final int ZOOM_OUT_ICON_WIDTH;
+ private static final int CLOSE_ICON_WIDTH;
+ static {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ IconFactory icons = IconFactory.getInstance();
+ CLOSE_ICON = sharedImages.getImage(ISharedImages.IMG_ETOOL_DELETE);
+ EDIT_ICON = icons.getIcon("editPreview"); //$NON-NLS-1$
+ ZOOM_IN_ICON = icons.getIcon("zoomplus"); //$NON-NLS-1$
+ ZOOM_OUT_ICON = icons.getIcon("zoomminus"); //$NON-NLS-1$
+ CLOSE_ICON_WIDTH = CLOSE_ICON.getImageData().width;
+ EDIT_ICON_WIDTH = EDIT_ICON.getImageData().width;
+ ZOOM_IN_ICON_WIDTH = ZOOM_IN_ICON.getImageData().width;
+ ZOOM_OUT_ICON_WIDTH = ZOOM_OUT_ICON.getImageData().width;
+ }
+
+ /** The configuration being previewed */
+ private final @NonNull Configuration mConfiguration;
+ /** The associated manager */
+ private final @NonNull RenderPreviewManager mManager;
+ private final @NonNull LayoutCanvas mCanvas;
+ private @Nullable ResourceResolver mResourceResolver;
+ private @Nullable RenderJob mJob;
+ private @Nullable Map<ResourceType, Map<String, ResourceValue>> mConfiguredFrameworkRes;
+ private @Nullable Map<ResourceType, Map<String, ResourceValue>> mConfiguredProjectRes;
+ private @Nullable Image mThumbnail;
+ private @Nullable String mDisplayName;
+ private int mWidth;
+ private int mHeight;
+ private int mX;
+ private int mY;
+ private double mScale = 1.0;
+ /** If non null, points to a separate file containing the source */
+ private @Nullable IFile mInput;
+ /** If included within another layout, the name of that outer layout */
+ private @Nullable Reference mIncludedWithin;
+ /** Whether the mouse is actively hovering over this preview */
+ private boolean mActive;
+ /** Whether this preview cannot be rendered because of a model error - such as
+ * an invalid configuration, a missing resource, an error in the XML markup, etc */
+ private boolean mError;
+ /**
+ * Whether this preview presents a file that has been "forked" (separate,
+ * not linked) from the primary layout.
+ * <p>
+ * TODO: Decide if this is redundant and I can just use {@link #mInput} != null
+ * instead.
+ */
+ private boolean mForked;
+ /** Whether in the current layout, this preview is visible */
+ private boolean mVisible;
+
+ /**
+ * Creates a new {@linkplain RenderPreview}
+ *
+ * @param manager the manager
+ * @param canvas canvas where preview is painted
+ * @param configuration the associated configuration
+ * @param width the initial width to use for the preview
+ * @param height the initial height to use for the preview
+ */
+ private RenderPreview(
+ @NonNull RenderPreviewManager manager,
+ @NonNull LayoutCanvas canvas,
+ @NonNull Configuration configuration,
+ int width,
+ int height) {
+ mManager = manager;
+ mCanvas = canvas;
+ mConfiguration = configuration;
+ mWidth = width;
+ mHeight = height;
+
+ updateForkStatus();
+ }
+
+ /**
+ * Gets the scale being applied to the thumbnail
+ *
+ * @return the scale being applied to the thumbnail
+ */
+ public double getScale() {
+ return mScale;
+ }
+
+ /**
+ * Sets the scale to apply to the thumbnail
+ *
+ * @param scale the factor to scale the thumbnail picture by
+ */
+ public void setScale(double scale) {
+ Image thumbnail = mThumbnail;
+ mThumbnail = null;
+ if (thumbnail != null) {
+ thumbnail.dispose();
+ }
+ mScale = scale;
+ }
+
+ /**
+ * Returns whether the preview is actively hovered
+ *
+ * @return whether the mouse is hovering over the preview
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * Sets whether the preview is actively hovered
+ *
+ * @param active if the mouse is hovering over the preview
+ */
+ public void setActive(boolean active) {
+ mActive = active;
+ }
+
+ /**
+ * Returns whether the preview is visible. Previews that are off
+ * screen are typically marked invisible during layout, which means we don't
+ * have to expend effort computing preview thumbnails etc
+ *
+ * @return true if the preview is visible
+ */
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ /**
+ * Sets whether this preview represents a forked layout (e.g. a layout which lives
+ * in a separate file and is not connected to the main layout)
+ *
+ * @param forked true if this preview represents a separate file
+ */
+ public void setForked(boolean forked) {
+ mForked = forked;
+ }
+
+ /**
+ * Returns whether this preview represents a forked layout
+ *
+ * @return true if this preview represents a separate file
+ */
+ public boolean isForked() {
+ return mForked;
+ }
+
+ /**
+ * Sets whether the preview is visible. Previews that are off
+ * screen are typically marked invisible during layout, which means we don't
+ * have to expend effort computing preview thumbnails etc
+ *
+ * @param visible whether this preview is visible
+ */
+ public void setVisible(boolean visible) {
+ mVisible = visible;
+ }
+
+ /**
+ * Sets the layout position relative to the top left corner of the preview
+ * area, in control coordinates
+ */
+ void setPosition(int x, int y) {
+ mX = x;
+ mY = y;
+ }
+
+ /**
+ * Gets the layout X position relative to the top left corner of the preview
+ * area, in control coordinates
+ */
+ int getX() {
+ return mX;
+ }
+
+ /**
+ * Gets the layout Y position relative to the top left corner of the preview
+ * area, in control coordinates
+ */
+ int getY() {
+ return mY;
+ }
+
+ /** Determine whether this configuration has a better match in a different layout file */
+ private void updateForkStatus() {
+ mForked = false;
+ mInput = null;
+ ConfigurationChooser chooser = mManager.getChooser();
+ IFile editedFile = chooser.getEditedFile();
+ if (editedFile != null) {
+ FolderConfiguration config = mConfiguration.getFullConfig();
+ if (!chooser.isBestMatchFor(editedFile, config)) {
+ ProjectResources resources = chooser.getResources();
+ if (resources != null) {
+ ResourceFile best = resources.getMatchingFile(editedFile.getName(),
+ ResourceFolderType.LAYOUT, config);
+ if (best != null) {
+ IAbstractFile file = best.getFile();
+ if (file instanceof IFileWrapper) {
+ mInput = ((IFileWrapper) file).getIFile();
+ } else if (file instanceof File) {
+ mInput = AdtUtils.fileToIFile(((File) file));
+ }
+ }
+ }
+ mForked = true;
+ }
+ }
+ }
+
+ /**
+ * Creates a new {@linkplain RenderPreview}
+ *
+ * @param manager the manager
+ * @param configuration the associated configuration
+ * @return a new configuration
+ */
+ @NonNull
+ public static RenderPreview create(
+ @NonNull RenderPreviewManager manager,
+ @NonNull Configuration configuration) {
+ LayoutCanvas canvas = manager.getCanvas();
+
+ Image image = canvas.getImageOverlay().getImage();
+
+ // Image size
+ int screenWidth = 0;
+ int screenHeight = 0;
+ FolderConfiguration myconfig = configuration.getFullConfig();
+ ScreenDimensionQualifier dimension = myconfig.getScreenDimensionQualifier();
+ if (dimension != null) {
+ screenWidth = dimension.getValue1();
+ screenHeight = dimension.getValue2();
+ ScreenOrientationQualifier orientation = myconfig.getScreenOrientationQualifier();
+ if (orientation != null) {
+ ScreenOrientation value = orientation.getValue();
+ if (value == ScreenOrientation.PORTRAIT) {
+ int temp = screenWidth;
+ screenWidth = screenHeight;
+ screenHeight = temp;
+ }
+ }
+ } else {
+ if (image != null) {
+ screenWidth = image.getImageData().width;
+ screenHeight = image.getImageData().height;
+ }
+ }
+ int width = RenderPreviewManager.getMaxWidth();
+ int height = RenderPreviewManager.getMaxHeight();
+ if (screenWidth > 0) {
+ double scale = getScale(screenWidth, screenHeight);
+ width = (int) (screenWidth * scale);
+ height = (int) (screenHeight * scale);
+ }
+
+ return new RenderPreview(manager, canvas,
+ configuration, width, height);
+ }
+
+ /**
+ * Throws away this preview: cancels any pending rendering jobs and disposes
+ * of image resources etc
+ */
+ public void dispose() {
+ if (mThumbnail != null) {
+ mThumbnail.dispose();
+ mThumbnail = null;
+ }
+
+ if (mJob != null) {
+ mJob.cancel();
+ mJob = null;
+ }
+ }
+
+ /**
+ * Returns the display name of this preview
+ *
+ * @return the name of the preview
+ */
+ @NonNull
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ String displayName = getConfiguration().getDisplayName();
+ if (displayName == null) {
+ // No display name: this must be the configuration used by default
+ // for the view which is originally displayed (before adding thumbnails),
+ // and you've switched away to something else; now we need to display a name
+ // for this original configuration. For now, just call it "Original"
+ return "Original";
+ }
+
+ return displayName;
+ }
+
+ return mDisplayName;
+ }
+
+ /**
+ * Sets the display name of this preview. By default, the display name is
+ * the display name of the configuration, but it can be overridden by calling
+ * this setter (which only sets the preview name, without editing the configuration.)
+ *
+ * @param displayName the new display name
+ */
+ public void setDisplayName(@NonNull String displayName) {
+ mDisplayName = displayName;
+ }
+
+ /**
+ * Sets an inclusion context to use for this layout, if any. This will render
+ * the configuration preview as the outer layout with the current layout
+ * embedded within.
+ *
+ * @param includedWithin a reference to a layout which includes this one
+ */
+ public void setIncludedWithin(Reference includedWithin) {
+ mIncludedWithin = includedWithin;
+ }
+
+ /**
+ * Request a new render after the given delay
+ *
+ * @param delay the delay to wait before starting the render job
+ */
+ public void render(long delay) {
+ RenderJob job = mJob;
+ if (job != null) {
+ job.cancel();
+ }
+ job = new RenderJob();
+ job.schedule(delay);
+ job.addJobChangeListener(this);
+ mJob = job;
+ }
+
+ /** Render immediately */
+ private void renderSync() {
+ if (mThumbnail != null) {
+ mThumbnail.dispose();
+ mThumbnail = null;
+ }
+
+ GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ ResourceResolver resolver = getResourceResolver();
+ FolderConfiguration config = mConfiguration.getFullConfig();
+ RenderService renderService = RenderService.create(editor, config, resolver);
+ ScreenSizeQualifier screenSize = config.getScreenSizeQualifier();
+ renderService.setScreen(screenSize, mConfiguration.getXDpi(), mConfiguration.getYDpi());
+
+ if (mIncludedWithin != null) {
+ renderService.setIncludedWithin(mIncludedWithin);
+ }
+
+ if (mInput != null) {
+ IAndroidTarget target = editor.getRenderingTarget();
+ AndroidTargetData data = null;
+ if (target != null) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ data = sdk.getTargetData(target);
+ }
+ }
+
+ // Construct UI model from XML
+ DocumentDescriptor documentDescriptor;
+ if (data == null) {
+ documentDescriptor = new DocumentDescriptor("temp", null);//$NON-NLS-1$
+ } else {
+ documentDescriptor = data.getLayoutDescriptors().getDescriptor();
+ }
+ UiDocumentNode model = (UiDocumentNode) documentDescriptor.createUiNode();
+ model.setEditor(mCanvas.getEditorDelegate().getEditor());
+ model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider());
+
+ Document document = DomUtilities.getDocument(mInput);
+ if (document == null) {
+ mError = true;
+ return;
+ }
+ model.loadFromXmlNode(document);
+ renderService.setModel(model);
+ } else {
+ renderService.setModel(editor.getModel());
+ }
+ Rect rect = Configuration.getScreenBounds(config);
+ renderService.setSize(rect.w, rect.h);
+ RenderLogger log = new RenderLogger(getDisplayName());
+ renderService.setLog(log);
+ RenderSession session = renderService.createRenderSession();
+ Result render = session.render(1000);
+
+ if (DUMP_RENDER_DIAGNOSTICS) {
+ if (log.hasProblems() || !render.isSuccess()) {
+ Throwable exception = render.getException();
+ System.out.println("Found problems rendering preview " + getDisplayName());
+ System.out.println(render.getErrorMessage());
+ System.out.println(log.getProblems(false));
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ }
+ }
+
+ mError = !render.isSuccess();
+
+ if (render.getStatus() == Status.ERROR_TIMEOUT) {
+ // TODO: Special handling? schedule update again later
+ return;
+ }
+ if (render.isSuccess()) {
+ BufferedImage image = session.getImage();
+ if (image != null) {
+ setFullImage(image);
+ }
+ }
+ }
+
+ private ResourceResolver getResourceResolver() {
+ if (mResourceResolver != null) {
+ return mResourceResolver;
+ }
+
+ GraphicalEditorPart graphicalEditor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ String theme = mConfiguration.getTheme();
+ if (theme == null) {
+ return null;
+ }
+
+ mConfiguredFrameworkRes = mConfiguredProjectRes = null;
+ mResourceResolver = null;
+
+ FolderConfiguration config = mConfiguration.getFullConfig();
+ IAndroidTarget target = graphicalEditor.getRenderingTarget();
+ ResourceRepository frameworkRes = null;
+ if (target != null) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk == null) {
+ return null;
+ }
+ AndroidTargetData data = sdk.getTargetData(target);
+
+ if (data != null) {
+ // TODO: SHARE if possible
+ frameworkRes = data.getFrameworkResources();
+ mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(config);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ assert mConfiguredFrameworkRes != null;
+
+
+ // get the resources of the file's project.
+ ProjectResources projectRes = ResourceManager.getInstance().getProjectResources(
+ graphicalEditor.getProject());
+ mConfiguredProjectRes = projectRes.getConfiguredResources(config);
+
+ if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+ if (frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) {
+ theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
+ } else {
+ theme = STYLE_RESOURCE_PREFIX + theme;
+ }
+ }
+
+ mResourceResolver = ResourceResolver.create(
+ mConfiguredProjectRes, mConfiguredFrameworkRes,
+ ResourceHelper.styleToTheme(theme),
+ ResourceHelper.isProjectStyle(theme));
+
+ return mResourceResolver;
+ }
+
+ /**
+ * Sets the new image of the preview and generates a thumbnail
+ *
+ * @param image the full size image
+ */
+ void setFullImage(BufferedImage image) {
+ if (image == null) {
+ mThumbnail = null;
+ return;
+ }
+
+ //double scale = getScale(image);
+ double scale = getWidth() / (double) image.getWidth();
+ if (scale < 1.0) {
+ if (LARGE_SHADOWS) {
+ image = ImageUtils.scale(image, scale, scale,
+ SHADOW_SIZE, SHADOW_SIZE);
+ ImageUtils.drawRectangleShadow(image, 0, 0,
+ image.getWidth() - SHADOW_SIZE,
+ image.getHeight() - SHADOW_SIZE);
+ } else {
+ image = ImageUtils.scale(image, scale, scale,
+ SMALL_SHADOW_SIZE, SMALL_SHADOW_SIZE);
+ ImageUtils.drawSmallRectangleShadow(image, 0, 0,
+ image.getWidth() - SMALL_SHADOW_SIZE,
+ image.getHeight() - SMALL_SHADOW_SIZE);
+ }
+ }
+
+ // Adjust size; for different aspect ratios the height might get adjusted etc
+ /*
+ if (LARGE_SHADOWS) {
+ mWidth = image.getWidth() - SMALL_SHADOW_SIZE;
+ mHeight = image.getHeight() - SMALL_SHADOW_SIZE;
+ } else {
+ mWidth = image.getWidth() - SHADOW_SIZE;
+ mHeight = image.getHeight() - SHADOW_SIZE;
+ }*/
+
+ mThumbnail = SwtUtils.convertToSwt(mCanvas.getDisplay(), image,
+ true /* transferAlpha */, -1);
+ }
+
+ private static double getScale(int width, int height) {
+ int maxWidth = RenderPreviewManager.getMaxWidth();
+ int maxHeight = RenderPreviewManager.getMaxHeight();
+ if (width > 0 && height > 0
+ && (width > maxWidth || height > maxHeight)) {
+ if (width >= height) { // landscape
+ return maxWidth / (double) width;
+ } else { // portrait
+ return maxHeight / (double) height;
+ }
+ }
+
+ return 1.0;
+ }
+
+ /**
+ * Returns the width of the preview, in pixels
+ *
+ * @return the width in pixels
+ */
+ public int getWidth() {
+ return (int) (mScale * mWidth);
+ }
+
+ /**
+ * Returns the height of the preview, in pixels
+ *
+ * @return the height in pixels
+ */
+ public int getHeight() {
+ return (int) (mScale * mHeight);
+ }
+
+ /**
+ * Handles clicks within the preview (x and y are positions relative within the
+ * preview
+ *
+ * @param x the x coordinate within the preview where the click occurred
+ * @param y the y coordinate within the preview where the click occurred
+ * @return true if this preview handled (and therefore consumed) the click
+ */
+ public boolean click(int x, int y) {
+ if (y < RenderPreview.HEADER_HEIGHT) {
+ int left = 0;
+ left += CLOSE_ICON_WIDTH;
+ if (x <= left) {
+ // Delete
+ mManager.deletePreview(this);
+ return true;
+ }
+ left += ZOOM_IN_ICON_WIDTH;
+ if (x <= left) {
+ // Zoom in
+ mScale = mScale * (1 / 0.5);
+ if (Math.abs(mScale-1.0) < 0.0001) {
+ mScale = 1.0;
+ }
+
+ render(0);
+ mManager.layout(true);
+ mCanvas.redraw();
+ return true;
+ }
+ left += ZOOM_OUT_ICON_WIDTH;
+ if (x <= left) {
+ // Zoom out
+ mScale = mScale * (0.5 / 1);
+ if (Math.abs(mScale-1.0) < 0.0001) {
+ mScale = 1.0;
+ }
+ render(0);
+
+ mManager.layout(true);
+ mCanvas.redraw();
+ return true;
+ }
+ left += EDIT_ICON_WIDTH;
+ if (x <= left) {
+ // Edit. For now, just rename
+ InputDialog d = new InputDialog(
+ AdtPlugin.getDisplay().getActiveShell(),
+ "Rename Preview", // title
+ "Name:",
+ getDisplayName(),
+ null);
+ if (d.open() == Window.OK) {
+ String newName = d.getValue();
+ mConfiguration.setDisplayName(newName);
+ mCanvas.redraw();
+ }
+
+ return true;
+ }
+
+ // Clicked anywhere else on header
+ // Perhaps open Edit dialog here?
+ }
+
+ mManager.switchTo(this);
+ return true;
+ }
+
+ /**
+ * Paints the preview at the given x/y position
+ *
+ * @param gc the graphics context to paint it into
+ * @param x the x coordinate to paint the preview at
+ * @param y the y coordinate to paint the preview at
+ */
+ void paint(GC gc, int x, int y) {
+ if (mThumbnail != null) {
+ gc.drawImage(mThumbnail, x, y);
+
+ if (mActive) {
+ int oldWidth = gc.getLineWidth();
+ gc.setLineWidth(3);
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_LIST_SELECTION));
+ gc.drawRectangle(x - 1, y - 1, getWidth() + 2, getHeight() + 2);
+ gc.setLineWidth(oldWidth);
+ }
+ } else if (mError) {
+ gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BORDER));
+ gc.drawRectangle(x, y, getWidth(), getHeight());
+ Image icon = IconFactory.getInstance().getIcon("renderError"); //$NON-NLS-1$
+ ImageData data = icon.getImageData();
+ int prevAlpha = gc.getAlpha();
+ gc.setAlpha(128-32);
+ gc.drawImage(icon, x + (getWidth() - data.width) / 2,
+ y + (getHeight() - data.height) / 2);
+ gc.setAlpha(prevAlpha);
+ } else {
+ gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BORDER));
+ gc.drawRectangle(x, y, getWidth(), getHeight());
+ Image icon = IconFactory.getInstance().getIcon("refreshPreview"); //$NON-NLS-1$
+ ImageData data = icon.getImageData();
+ int prevAlpha = gc.getAlpha();
+ gc.setAlpha(128-32);
+ gc.drawImage(icon, x + (getWidth() - data.width) / 2,
+ y + (getHeight() - data.height) / 2);
+ gc.setAlpha(prevAlpha);
+ }
+
+ if (mActive) {
+ int left = x ;
+ int prevAlpha = gc.getAlpha();
+ gc.setAlpha(128+32);
+ Color bg = mCanvas.getDisplay().getSystemColor(SWT.COLOR_WHITE);
+ gc.setBackground(bg);
+ gc.fillRectangle(left, y, x + getWidth() - left,
+ RenderPreview.HEADER_HEIGHT);
+ gc.setAlpha(prevAlpha);
+
+ // Paint icons
+ gc.drawImage(CLOSE_ICON, left, y);
+ left += CLOSE_ICON_WIDTH;
+
+ gc.drawImage(ZOOM_IN_ICON, left, y);
+ left += ZOOM_IN_ICON_WIDTH;
+
+ gc.drawImage(ZOOM_OUT_ICON, left, y);
+ left += ZOOM_OUT_ICON_WIDTH;
+
+ gc.drawImage(EDIT_ICON, left, y);
+ left += EDIT_ICON_WIDTH;
+ }
+
+ paintTitle(gc, x, y);
+ }
+
+ /**
+ * Paints the preview title at the given position
+ *
+ * @param gc the graphics context to paint into
+ * @param x the left edge of the preview rectangle
+ * @param y the top edge of the preview rectangle
+ */
+ void paintTitle(GC gc, int x, int y) {
+ String displayName = getDisplayName();
+ if (displayName != null && displayName.length() > 0) {
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE));
+
+ int width = getWidth();
+ int height = getHeight();
+ Point extent = gc.textExtent(displayName);
+ int labelLeft = Math.max(x, x + (width - extent.x) / 2);
+ int labelTop = y + height + 1;
+ Image flagImage = null;
+ Locale locale = mConfiguration.getLocale();
+ if (locale != null && (locale.hasLanguage() || locale.hasRegion())
+ && (!(mConfiguration instanceof NestedConfiguration)
+ || ((NestedConfiguration) mConfiguration).isOverridingLocale())) {
+ flagImage = locale.getFlagImage();
+ }
+
+ gc.setClipping(x, y, width, height + 100);
+ if (flagImage != null) {
+ int flagWidth = flagImage.getImageData().width;
+ int flagHeight = flagImage.getImageData().height;
+ gc.drawImage(flagImage, labelLeft - flagWidth / 2 - 1, labelTop);
+ labelLeft += flagWidth / 2 + 1;
+ gc.drawText(displayName, labelLeft,
+ labelTop - (extent.y - flagHeight) / 2, true);
+ } else {
+ gc.drawText(displayName, labelLeft, labelTop, true);
+ }
+
+ if (mForked && mInput != null) {
+ // Draw file flag, and parent folder name
+ labelTop += extent.y;
+ String fileName = mInput.getParent().getName() + File.separator + mInput.getName();
+ extent = gc.textExtent(fileName);
+ flagImage = IconFactory.getInstance().getIcon("android_file"); //$NON-NLS-1$
+ int flagWidth = flagImage.getImageData().width;
+ int flagHeight = flagImage.getImageData().height;
+
+ labelLeft = Math.max(x, x + (width - extent.x - flagWidth - 1) / 2);
+
+ gc.drawImage(flagImage, labelLeft, labelTop);
+
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
+ labelLeft += flagWidth + 1;
+ labelTop -= (extent.y - flagHeight) / 2;
+ gc.drawText(fileName, labelLeft, labelTop, true);
+ }
+
+ gc.setClipping((Region) null);
+ }
+ }
+
+ /**
+ * Notifies that the preview's configuration has changed.
+ *
+ * @param flags the change flags, a bitmask corresponding to the
+ * {@code CHANGE_} constants in {@link ConfigurationClient}
+ */
+ public void configurationChanged(int flags) {
+ if ((flags & (CHANGED_FOLDER | CHANGED_THEME | CHANGED_DEVICE
+ | CHANGED_RENDER_TARGET | CHANGED_LOCALE)) != 0) {
+ mResourceResolver = null;
+ // Handle inheritance
+ mConfiguration.syncFolderConfig();
+ updateForkStatus();
+ }
+
+ FolderConfiguration folderConfig = mConfiguration.getFullConfig();
+ ScreenOrientationQualifier qualifier = folderConfig.getScreenOrientationQualifier();
+ ScreenOrientation orientation = qualifier == null
+ ? ScreenOrientation.PORTRAIT : qualifier.getValue();
+ if (orientation == ScreenOrientation.LANDSCAPE
+ || orientation == ScreenOrientation.SQUARE) {
+ orientation = ScreenOrientation.PORTRAIT;
+ } else {
+ orientation = ScreenOrientation.LANDSCAPE;
+ }
+
+ if ((mWidth < mHeight && orientation == ScreenOrientation.PORTRAIT)
+ || (mWidth > mHeight && orientation == ScreenOrientation.LANDSCAPE)) {
+ Image thumbnail = mThumbnail;
+ mThumbnail = null;
+ if (thumbnail != null) {
+ thumbnail.dispose();
+ }
+
+ // Flip icon size
+ int temp = mHeight;
+ mHeight = mWidth;
+ mWidth = temp;
+ }
+ }
+
+ /**
+ * Returns the configuration associated with this preview
+ *
+ * @return the configuration
+ */
+ @NonNull
+ public Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ // ---- Implements IJobChangeListener ----
+
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void awake(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ mJob = null;
+ }
+
+ @Override
+ public void running(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void scheduled(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void sleeping(IJobChangeEvent event) {
+ }
+
+ // ---- Delayed Rendering ----
+
+ private final class RenderJob extends UIJob {
+ public RenderJob() {
+ super("RenderPreview");
+ setSystem(true);
+ setUser(false);
+ }
+
+ /* TODO: Make this job work in the background. Need to make the render service
+ * not read UI thread properties out of the configuration composite.
+ * SEPTEMBER 2012: The config composite work should be done now, check.
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ if (mCanvas.isDisposed()) {
+ return org.eclipse.core.runtime.Status.CANCEL_STATUS;
+ }
+
+ renderSync();
+
+ // Update display
+ mCanvas.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ mCanvas.redraw();
+ }
+ });
+ return org.eclipse.core.runtime.Status.OK_STATUS;
+ }
+ */
+
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ mJob = null;
+ if (!mCanvas.isDisposed()) {
+ renderSync();
+ mCanvas.redraw();
+ return org.eclipse.core.runtime.Status.OK_STATUS;
+ }
+
+ return org.eclipse.core.runtime.Status.CANCEL_STATUS;
+ }
+
+ @Override
+ public Display getDisplay() {
+ if (mCanvas.isDisposed()) {
+ return null;
+ }
+ return mCanvas.getDisplay();
+ }
+ }
+
+ /**
+ * Sets the input file to use for rendering. If not set, this will just be
+ * the same file as the configuration chooser. This is used to render other
+ * layouts, such as variations of the currently edited layout, which are
+ * not kept in sync with the main layout.
+ *
+ * @param file the file to set as input
+ */
+ public void setInput(@Nullable IFile file) {
+ mInput = file;
+ }
+
+ /** Corresponding description for this preview if it is a manually added preview */
+ private @Nullable ConfigurationDescription mDescription;
+
+ /**
+ * Sets the description of this preview, if this preview is a manually added preview
+ *
+ * @param description the description of this preview
+ */
+ public void setDescription(@Nullable ConfigurationDescription description) {
+ mDescription = description;
+ }
+
+ /**
+ * Returns the description of this preview, if this preview is a manually added preview
+ *
+ * @return the description
+ */
+ @Nullable
+ public ConfigurationDescription getDescription() {
+ return mDescription;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java
new file mode 100644
index 0000000..1d48f7b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
+import com.android.sdklib.devices.Device;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** A list of render previews */
+class RenderPreviewList {
+ /** Name of file saved in project directory storing previews */
+ private static final String PREVIEW_FILE_NAME = "previews.xml"; //$NON-NLS-1$
+
+ /** Qualified name for the per-project persistent property include-map */
+ private final static QualifiedName PREVIEW_LIST = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "previewlist");//$NON-NLS-1$
+
+ private final IProject mProject;
+ private final List<ConfigurationDescription> mList = Lists.newArrayList();
+
+ private RenderPreviewList(@NonNull IProject project) {
+ mProject = project;
+ }
+
+ /**
+ * Returns the {@link RenderPreviewList} for the given project
+ *
+ * @param project the project the list is associated with
+ * @return a {@link RenderPreviewList} for the given project, never null
+ */
+ @NonNull
+ public static RenderPreviewList get(@NonNull IProject project) {
+ RenderPreviewList list = null;
+ try {
+ list = (RenderPreviewList) project.getSessionProperty(PREVIEW_LIST);
+ } catch (CoreException e) {
+ // Not a problem; we will just create a new one
+ }
+
+ if (list == null) {
+ list = new RenderPreviewList(project);
+ try {
+ project.setSessionProperty(PREVIEW_LIST, list);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ return list;
+ }
+
+ private File getManualFile() {
+ return new File(AdtUtils.getAbsolutePath(mProject).toFile(), PREVIEW_FILE_NAME);
+ }
+
+ void load(List<Device> deviceList) throws IOException {
+ File file = getManualFile();
+ if (file.exists()) {
+ load(file, deviceList);
+ }
+ }
+
+ void save() throws IOException {
+ delete();
+ if (!mList.isEmpty()) {
+ File file = getManualFile();
+ save(file);
+ }
+ }
+
+ private void save(File file) throws IOException {
+ //Document document = DomUtilities.createEmptyPlainDocument();
+ Document document = DomUtilities.createEmptyDocument();
+ if (document != null) {
+ for (ConfigurationDescription description : mList) {
+ description.toXml(document);
+ }
+ String xml = XmlPrettyPrinter.prettyPrint(document);
+ Files.write(xml, file, Charsets.UTF_8);
+ }
+ }
+
+ void load(File file, List<Device> deviceList) throws IOException {
+ mList.clear();
+
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = DomUtilities.parseDocument(xml, true);
+ if (document == null || document.getDocumentElement() == null) {
+ return;
+ }
+ List<Element> elements = DomUtilities.getChildren(document.getDocumentElement());
+ for (Element element : elements) {
+ ConfigurationDescription description = ConfigurationDescription.fromXml(
+ mProject, element, deviceList);
+ if (description != null) {
+ mList.add(description);
+ }
+ }
+ }
+
+ /**
+ * Create a list of previews for the given canvas that matches the internal
+ * configuration preview list
+ *
+ * @param canvas the associated canvas
+ * @return a new list of previews linked to the given canvas
+ */
+ @NonNull
+ List<RenderPreview> createPreviews(LayoutCanvas canvas) {
+ if (mList.isEmpty()) {
+ return new ArrayList<RenderPreview>();
+ }
+ List<RenderPreview> previews = Lists.newArrayList();
+ RenderPreviewManager manager = canvas.getPreviewManager();
+ ConfigurationChooser chooser = canvas.getEditorDelegate().getGraphicalEditor()
+ .getConfigurationChooser();
+
+ for (ConfigurationDescription description : mList) {
+ Configuration configuration = Configuration.create(chooser);
+ configuration.getFullConfig().set(description.folder);
+ if (description.target != null) {
+ // TODO: Make sure this layout isn't in some v-folder which is incompatible
+ // with this target!
+ configuration.setTarget(description.target, true);
+ }
+
+ if (description.theme != null) {
+ configuration.setTheme(description.theme);
+ }
+
+ RenderPreview preview = RenderPreview.create(manager, configuration);
+ if (description.displayName != null) {
+ preview.setDisplayName(description.displayName);
+ }
+
+ preview.setDescription(description);
+ previews.add(preview);
+ }
+
+ return previews;
+ }
+
+ void remove(@NonNull RenderPreview preview) {
+ ConfigurationDescription description = preview.getDescription();
+ if (description != null) {
+ mList.remove(description);
+ }
+ }
+
+ boolean isEmpty() {
+ return mList.isEmpty();
+ }
+
+ void add(@NonNull RenderPreview preview) {
+ Configuration configuration = preview.getConfiguration();
+ ConfigurationDescription description =
+ ConfigurationDescription.fromConfiguration(mProject, configuration);
+ // RenderPreviews can have display names that aren't reflected in the configuration
+ description.displayName = preview.getDisplayName();
+ mList.add(description);
+ preview.setDescription(description);
+ }
+
+ void delete() {
+ mList.clear();
+ File file = getManualFile();
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java
new file mode 100644
index 0000000..9fc0681
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java
@@ -0,0 +1,1161 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreview.LARGE_SHADOWS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.ide.common.resources.configuration.DeviceConfigHelper;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ComplementingConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.resources.Density;
+import com.android.resources.ScreenSize;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.State;
+import com.google.common.collect.Lists;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.ScrollBar;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manager for the configuration previews, which handles layout computations,
+ * managing the image buffer cache, etc
+ */
+public class RenderPreviewManager {
+ private static double sScale = 1.0;
+ private static final int RENDER_DELAY = 100;
+ private static final int PREVIEW_VGAP = 18;
+ private static final int PREVIEW_HGAP = 12;
+ private static final int MAX_WIDTH = 200;
+ private static final int MAX_HEIGHT = MAX_WIDTH;
+ private @Nullable List<RenderPreview> mPreviews;
+ private @Nullable RenderPreviewList mManualList;
+ private final @NonNull LayoutCanvas mCanvas;
+ private final @NonNull CanvasTransform mVScale;
+ private final @NonNull CanvasTransform mHScale;
+ private int mPrevCanvasWidth;
+ private int mPrevCanvasHeight;
+ private int mPrevImageWidth;
+ private int mPrevImageHeight;
+ private @NonNull RenderPreviewMode mMode = RenderPreviewMode.NONE;
+ private @Nullable RenderPreview mActivePreview;
+ private @Nullable ScrollBarListener mListener;
+ private int mLayoutHeight;
+ private int mMaxVisibleY;
+
+ /**
+ * Creates a {@link RenderPreviewManager} associated with the given canvas
+ *
+ * @param canvas the canvas to manage previews for
+ */
+ public RenderPreviewManager(@NonNull LayoutCanvas canvas) {
+ mCanvas = canvas;
+ mHScale = canvas.getHorizontalTransform();
+ mVScale = canvas.getVerticalTransform();
+ }
+
+ /**
+ * Returns the associated chooser
+ *
+ * @return the associated chooser
+ */
+ @NonNull
+ ConfigurationChooser getChooser() {
+ GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ return editor.getConfigurationChooser();
+ }
+
+ /**
+ * Returns the associated canvas
+ *
+ * @return the canvas
+ */
+ @NonNull
+ public LayoutCanvas getCanvas() {
+ return mCanvas;
+ }
+
+ /** Zooms in (grows all previews) */
+ public void zoomIn() {
+ sScale = sScale * (1 / 0.9);
+ if (Math.abs(sScale-1.0) < 0.0001) {
+ sScale = 1.0;
+ }
+
+ updatedZoom();
+ }
+
+ /** Zooms out (shrinks all previews) */
+ public void zoomOut() {
+ sScale = sScale * (0.9 / 1);
+ if (Math.abs(sScale-1.0) < 0.0001) {
+ sScale = 1.0;
+ }
+ updatedZoom();
+ }
+
+ private void updatedZoom() {
+ if (hasPreviews()) {
+ for (RenderPreview preview : mPreviews) {
+ preview.setScale(sScale);
+ }
+ RenderPreview preview = mCanvas.getPreview();
+ if (preview != null) {
+ preview.setScale(sScale);
+ }
+ }
+
+ renderPreviews();
+ layout(true);
+ mCanvas.redraw();
+ }
+
+ static int getMaxWidth() {
+ return (int) sScale * MAX_WIDTH;
+ }
+
+ static int getMaxHeight() {
+ return (int) sScale * MAX_HEIGHT;
+ }
+
+ /** Delete all the previews */
+ public void deleteManualPreviews() {
+ disposePreviews();
+ selectMode(RenderPreviewMode.NONE);
+ mCanvas.setFitScale(true /* onlyZoomOut */, true /*allowZoomIn*/);
+
+ if (mManualList != null) {
+ mManualList.delete();
+ }
+ }
+
+ /** Dispose all the previews */
+ public void disposePreviews() {
+ if (mPreviews != null) {
+ List<RenderPreview> old = mPreviews;
+ mPreviews = null;
+ for (RenderPreview preview : old) {
+ preview.dispose();
+ }
+ }
+ }
+
+ /**
+ * Deletes the given preview
+ *
+ * @param preview the preview to be deleted
+ */
+ public void deletePreview(RenderPreview preview) {
+ mPreviews.remove(preview);
+ preview.dispose();
+ layout(true);
+ mCanvas.redraw();
+
+ if (mManualList != null) {
+ mManualList.remove(preview);
+ saveList();
+ }
+ }
+
+ /**
+ * Compute the total width required for the previews, including internal padding
+ *
+ * @return total width in pixels
+ */
+ public int computePreviewWidth() {
+ int maxPreviewWidth = 0;
+ if (hasPreviews()) {
+ for (RenderPreview preview : mPreviews) {
+ maxPreviewWidth = Math.max(maxPreviewWidth, preview.getWidth());
+ }
+
+ if (maxPreviewWidth > 0) {
+ maxPreviewWidth += 2 * PREVIEW_HGAP; // 2x for left and right side
+ maxPreviewWidth += LARGE_SHADOWS ? SHADOW_SIZE : SMALL_SHADOW_SIZE;
+ }
+
+ return maxPreviewWidth;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Layout Algorithm. This sets the {@link RenderPreview#getX()} and
+ * {@link RenderPreview#getY()} coordinates of all the previews. It also marks
+ * previews as visible or invisible via {@link RenderPreview#setVisible(boolean)}
+ * according to their position and the current visible view port in the layout canvas.
+ * Finally, it also sets the {@code mMaxVisibleY} and {@code mLayoutHeight} fields,
+ * such that the scrollbars can compute the right scrolled area, and that scrolling
+ * can cause render refreshes on views that are made visible.
+ *
+ * <p>
+ * Two shapes to fill. The screen is typically wide. When showing a phone,
+ * I should use all the vertical space; I then show thumbnails on the right.
+ * When showing the tablet, I need to do something in between. I reserve at least
+ * 200 pixels either on the right or on the bottom and use the remainder.
+ * TODO: Look up better algorithms. Optimal space division algorithm. Can prune etc.
+ * <p>
+ * This is not a traditional bin packing problem, because the objects to be packaged
+ * do not have a fixed size; we can scale them up and down in order to provide an
+ * "optimal" size.
+ * <p>
+ * See http://en.wikipedia.org/wiki/Packing_problem
+ * See http://en.wikipedia.org/wiki/Bin_packing_problem
+ * <p>
+ * Returns true if the layout changed (so a redraw is desired)
+ */
+ boolean layout(boolean refresh) {
+ if (mPreviews == null || mPreviews.isEmpty()) {
+ return false;
+ }
+
+ if (mListener == null) {
+ mListener = new ScrollBarListener();
+ mCanvas.getVerticalBar().addSelectionListener(mListener);
+ }
+
+ // TODO: Separate layout heuristics for portrait and landscape orientations (though
+ // it also depends on the dimensions of the canvas window, which determines the
+ // shape of the leftover space)
+
+ int scaledImageWidth = mHScale.getScaledImgSize();
+ int scaledImageHeight = mVScale.getScaledImgSize();
+ Rectangle clientArea = mCanvas.getClientArea();
+
+ if (!refresh &&
+ (scaledImageWidth == mPrevImageWidth
+ && scaledImageHeight == mPrevImageHeight
+ && clientArea.width == mPrevCanvasWidth
+ && clientArea.height == mPrevCanvasHeight)) {
+ // No change
+ return false;
+ }
+
+ mPrevImageWidth = scaledImageWidth;
+ mPrevImageHeight = scaledImageHeight;
+ mPrevCanvasWidth = clientArea.width;
+ mPrevCanvasHeight = clientArea.height;
+
+ int availableWidth = clientArea.x + clientArea.width - getX();
+ int availableHeight = clientArea.y + clientArea.height - getY();
+ int maxVisibleY = clientArea.y + clientArea.height;
+
+ int bottomBorder = scaledImageHeight;
+ int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
+ int nextY = 0;
+
+ // First lay out images across the top right hand side
+ int x = rightHandSide;
+ int y = 0;
+ boolean wrapped = false;
+
+ int vgap = PREVIEW_VGAP;
+ for (RenderPreview preview : mPreviews) {
+ // If we have forked previews, double the vgap to allow space for two labels
+ if (preview.isForked()) {
+ vgap *= 2;
+ break;
+ }
+ }
+
+ for (RenderPreview preview : mPreviews) {
+ if (x > 0 && x + preview.getWidth() > availableWidth) {
+ x = rightHandSide;
+ int prevY = y;
+ y = nextY;
+ if ((prevY <= bottomBorder ||
+ y <= bottomBorder)
+ && Math.max(nextY, y + preview.getHeight()) > bottomBorder) {
+ // If there's really no visible room below, don't bother
+ // Similarly, don't wrap individually scaled views
+ if (bottomBorder < availableHeight - 40 && preview.getScale() < 1.2) {
+ // If it's closer to the top row than the bottom, just
+ // mark the next row for left justify instead
+ if (bottomBorder - y > y + preview.getHeight() - bottomBorder) {
+ rightHandSide = 0;
+ wrapped = true;
+ } else if (!wrapped) {
+ y = nextY = Math.max(nextY, bottomBorder + vgap);
+ x = rightHandSide = 0;
+ wrapped = true;
+ }
+ }
+ }
+ }
+ if (x > 0 && y <= bottomBorder
+ && Math.max(nextY, y + preview.getHeight()) > bottomBorder) {
+ if (clientArea.height - bottomBorder < preview.getHeight()) {
+ // No room below the device on the left; just continue on the
+ // bottom row
+ } else if (preview.getScale() < 1.2) {
+ if (bottomBorder - y > y + preview.getHeight() - bottomBorder) {
+ rightHandSide = 0;
+ wrapped = true;
+ } else {
+ y = nextY = Math.max(nextY, bottomBorder + vgap);
+ x = rightHandSide = 0;
+ wrapped = true;
+ }
+ }
+ }
+
+ preview.setPosition(x, y);
+
+ if (y > maxVisibleY) {
+ preview.setVisible(false);
+ } else if (!preview.isVisible()) {
+ preview.render(RENDER_DELAY);
+ preview.setVisible(true);
+ }
+
+ x += preview.getWidth();
+ x += PREVIEW_HGAP;
+ nextY = Math.max(nextY, y + preview.getHeight() + vgap);
+ }
+
+ mLayoutHeight = nextY;
+ mMaxVisibleY = maxVisibleY;
+ mCanvas.updateScrollBars();
+
+ return true;
+ }
+
+ /**
+ * Paints the configuration previews
+ *
+ * @param gc the graphics context to paint into
+ */
+ void paint(GC gc) {
+ if (hasPreviews()) {
+ // Ensure up to date at all times; consider moving if it's too expensive
+ layout(false);
+ int rootX = getX();
+ int rootY = getY();
+
+ for (RenderPreview preview : mPreviews) {
+ if (preview.isVisible()) {
+ int x = rootX + preview.getX();
+ int y = rootY + preview.getY();
+ preview.paint(gc, x, y);
+ }
+ }
+
+ RenderPreview preview = mCanvas.getPreview();
+ if (preview != null) {
+ CanvasTransform hi = mHScale;
+ CanvasTransform vi = mVScale;
+
+ int destX = hi.translate(0);
+ int destY = vi.translate(0);
+ int destWidth = hi.getScaledImgSize();
+ int destHeight = vi.getScaledImgSize();
+
+ int x = destX + destWidth / 2 - preview.getWidth() / 2;
+ int y = destY + destHeight - preview.getHeight();
+ preview.paintTitle(gc, x, y);
+ }
+ } else if (mMode == RenderPreviewMode.CUSTOM) {
+ int rootX = getX();
+ rootX += mHScale.getScaledImgSize();
+ rootX += 2 * PREVIEW_HGAP;
+ int rootY = getY();
+ rootY += 20;
+ gc.setFont(mCanvas.getFont());
+ gc.setForeground(mCanvas.getDisplay().getSystemColor(SWT.COLOR_BLACK));
+ gc.drawText("Add previews with \"Add as Thumbnail\"\nin the configuration menu",
+ rootX, rootY, true);
+ }
+ }
+
+ private void addPreview(@NonNull RenderPreview preview) {
+ if (mPreviews == null) {
+ mPreviews = Lists.newArrayList();
+ }
+ mPreviews.add(preview);
+ }
+
+ /** Adds the current configuration as a new configuration preview */
+ public void addAsThumbnail() {
+ ConfigurationChooser chooser = getChooser();
+ String name = chooser.getConfiguration().getDisplayName();
+ if (name == null || name.isEmpty()) {
+ name = getUniqueName();
+ }
+ InputDialog d = new InputDialog(
+ AdtPlugin.getDisplay().getActiveShell(),
+ "Add as Thumbnail Preview", // title
+ "Name of thumbnail:",
+ name,
+ null);
+ if (d.open() == Window.OK) {
+ selectMode(RenderPreviewMode.CUSTOM);
+
+ String newName = d.getValue();
+ // Create a new configuration from the current settings in the composite
+ Configuration configuration = Configuration.copy(chooser.getConfiguration());
+ configuration.setDisplayName(newName);
+
+ RenderPreview preview = RenderPreview.create(this, configuration);
+ addPreview(preview);
+
+ layout(true);
+ preview.render(RENDER_DELAY);
+ mCanvas.setFitScale(true /* onlyZoomOut */, false /*allowZoomIn*/);
+
+ if (mManualList == null) {
+ loadList();
+ }
+ if (mManualList != null) {
+ mManualList.add(preview);
+ saveList();
+ }
+ }
+ }
+
+ /**
+ * Computes a unique new name for a configuration preview that represents
+ * the current, default configuration
+ *
+ * @return a unique name
+ */
+ private String getUniqueName() {
+ if (mPreviews == null || mPreviews.isEmpty()) {
+ // NO, not for the first preview!
+ return "Config1";
+ }
+
+ Set<String> names = new HashSet<String>(mPreviews.size());
+ for (RenderPreview preview : mPreviews) {
+ names.add(preview.getDisplayName());
+ }
+
+ int index = 2;
+ while (true) {
+ String name = String.format("Config%1$d", index);
+ if (!names.contains(name)) {
+ return name;
+ }
+ index++;
+ }
+ }
+
+ /** Generates a bunch of default configuration preview thumbnails */
+ public void addDefaultPreviews() {
+ ConfigurationChooser chooser = getChooser();
+ Configuration parent = chooser.getConfiguration();
+ if (parent instanceof NestedConfiguration) {
+ parent = ((NestedConfiguration) parent).getParent();
+ }
+ if (mCanvas.getImageOverlay().getImage() != null) {
+ // Create Language variation
+ createLocaleVariation(chooser, parent);
+
+ // Vary screen size
+ // TODO: Be smarter here: Pick a screen that is both as differently as possible
+ // from the current screen as well as also supported. So consider
+ // things like supported screens, targetSdk etc.
+ createScreenVariations(parent);
+
+ // Vary orientation
+ createStateVariation(chooser, parent);
+
+ // Vary render target
+ createRenderTargetVariation(chooser, parent);
+ }
+
+ // Make a placeholder preview for the current screen, in case we switch from it
+ RenderPreview preview = RenderPreview.create(this, parent);
+ mCanvas.setPreview(preview);
+
+ sortPreviewsByOrientation();
+ }
+
+ private void createRenderTargetVariation(ConfigurationChooser chooser, Configuration parent) {
+ /* This is disabled for now: need to load multiple versions of layoutlib.
+ When I did this, there seemed to be some drug interactions between
+ them, and I would end up with NPEs in layoutlib code which normally works.
+ ComplementingConfiguration configuration =
+ ComplementingConfiguration.create(chooser, parent);
+ configuration.setOverrideTarget(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+ */
+ }
+
+ private void createStateVariation(ConfigurationChooser chooser, Configuration parent) {
+ State currentState = parent.getDeviceState();
+ State nextState = parent.getNextDeviceState(currentState);
+ if (nextState != currentState) {
+ ComplementingConfiguration configuration =
+ ComplementingConfiguration.create(chooser, parent);
+ configuration.setOverrideDeviceState(true);
+ configuration.setDeviceState(nextState, false);
+ addPreview(RenderPreview.create(this, configuration));
+ }
+ }
+
+ private void createLocaleVariation(ConfigurationChooser chooser, Configuration parent) {
+ LanguageQualifier currentLanguage = parent.getLocale().language;
+ for (Locale locale : chooser.getLocaleList()) {
+ LanguageQualifier language = locale.language;
+ if (!language.equals(currentLanguage)) {
+ ComplementingConfiguration configuration =
+ ComplementingConfiguration.create(chooser, parent);
+ configuration.setOverrideLocale(true);
+ Locale otherLanguage = Locale.create(language);
+ configuration.setLocale(otherLanguage, false);
+ addPreview(RenderPreview.create(this, configuration));
+ break;
+ }
+ }
+ }
+
+ private void createScreenVariations(Configuration parent) {
+ ConfigurationChooser chooser = getChooser();
+ ComplementingConfiguration configuration;
+
+ configuration = ComplementingConfiguration.create(chooser, parent);
+ configuration.setVariation(0);
+ configuration.setOverrideDevice(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+
+ configuration = ComplementingConfiguration.create(chooser, parent);
+ configuration.setVariation(1);
+ configuration.setOverrideDevice(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+ }
+
+ /**
+ * Returns the current mode as seen by this {@link RenderPreviewManager}.
+ * Note that it may not yet have been synced with the global mode kept in
+ * {@link AdtPrefs#getRenderPreviewMode()}.
+ *
+ * @return the current preview mode
+ */
+ @NonNull
+ public RenderPreviewMode getMode() {
+ return mMode;
+ }
+
+ /**
+ * Update the set of previews for the current mode
+ *
+ * @param force force a refresh even if the preview type has not changed
+ * @return true if the views were recomputed, false if the previews were
+ * already showing and the mode not changed
+ */
+ public boolean recomputePreviews(boolean force) {
+ RenderPreviewMode newMode = AdtPrefs.getPrefs().getRenderPreviewMode();
+ if (newMode == mMode) {
+ if (!force || mMode == RenderPreviewMode.CUSTOM) {
+ return false;
+ }
+ }
+
+ mMode = newMode;
+
+ sScale = 1.0;
+ disposePreviews();
+
+ switch (mMode) {
+ case DEFAULT:
+ addDefaultPreviews();
+ break;
+ case INCLUDES:
+ addIncludedInPreviews();
+ break;
+ case LOCALES:
+ addLocalePreviews();
+ break;
+ case SCREENS:
+ addScreenSizePreviews();
+ break;
+ case VARIATIONS:
+ addVariationPreviews();
+ break;
+ case CUSTOM:
+ addManualPreviews();
+ break;
+ case NONE:
+ break;
+ default:
+ assert false : mMode;
+ }
+
+ layout(true);
+ renderPreviews();
+ boolean allowZoomIn = mMode == RenderPreviewMode.NONE;
+ mCanvas.setFitScale(true /*onlyZoomOut*/, allowZoomIn);
+ mCanvas.updateScrollBars();
+
+ return true;
+ }
+
+ /**
+ * Sets the new render preview mode to use
+ *
+ * @param mode the new mode
+ */
+ public void selectMode(@NonNull RenderPreviewMode mode) {
+ if (mode != mMode) {
+ AdtPrefs.getPrefs().setPreviewMode(mode);
+ recomputePreviews(false);
+ }
+ }
+
+ /** Similar to {@link #addDefaultPreviews()} but for locales */
+ public void addLocalePreviews() {
+
+ ConfigurationChooser chooser = getChooser();
+ List<Locale> locales = chooser.getLocaleList();
+ Configuration parent = chooser.getConfiguration();
+
+ for (Locale locale : locales) {
+ if (!locale.hasLanguage() && !locale.hasRegion()) {
+ continue;
+ }
+ NestedConfiguration configuration = NestedConfiguration.create(chooser, parent);
+ configuration.setOverrideLocale(true);
+ configuration.setLocale(locale, false);
+
+ String displayName = ConfigurationChooser.getLocaleLabel(chooser, locale, true);
+ assert displayName != null; // it's never non null when locale is non null
+ configuration.setDisplayName(displayName);
+
+ addPreview(RenderPreview.create(this, configuration));
+ }
+
+ // Make a placeholder preview for the current screen, in case we switch from it
+ Configuration configuration = parent;
+ Locale locale = configuration.getLocale();
+ String label = ConfigurationChooser.getLocaleLabel(chooser, locale, true);
+ if (label == null) {
+ label = "default";
+ }
+ configuration.setDisplayName(label);
+ RenderPreview preview = RenderPreview.create(this, parent);
+ if (preview != null) {
+ mCanvas.setPreview(preview);
+ }
+
+ // No need to sort: they should all be identical
+ }
+
+ /** Similar to {@link #addDefaultPreviews()} but for screen sizes */
+ public void addScreenSizePreviews() {
+ ConfigurationChooser chooser = getChooser();
+ List<Device> devices = chooser.getDeviceList();
+ Configuration configuration = chooser.getConfiguration();
+
+ // Rearrange the devices a bit such that the most interesting devices bubble
+ // to the front
+ // 10" tablet, 7" tablet, reference phones, tiny phone, and in general the first
+ // version of each seen screen size
+ List<Device> sorted = new ArrayList<Device>(devices);
+ Set<ScreenSize> seenSizes = new HashSet<ScreenSize>();
+ State currentState = configuration.getDeviceState();
+ String currentStateName = currentState != null ? currentState.getName() : "";
+
+ for (int i = 0, n = sorted.size(); i < n; i++) {
+ Device device = sorted.get(i);
+ boolean interesting = false;
+
+ State state = device.getState(currentStateName);
+ if (state == null) {
+ state = device.getAllStates().get(0);
+ }
+
+ if (device.getName().startsWith("Nexus ") //$NON-NLS-1$
+ || device.getName().endsWith(" Nexus")) { //$NON-NLS-1$
+ // Not String#contains("Nexus") because that would also pick up all the generic
+ // entries ("3.7in WVGA (Nexus One)") so we'd have them duplicated
+ interesting = true;
+ }
+
+ FolderConfiguration c = DeviceConfigHelper.getFolderConfig(state);
+ if (c != null) {
+ ScreenSizeQualifier sizeQualifier = c.getScreenSizeQualifier();
+ if (sizeQualifier != null) {
+ ScreenSize size = sizeQualifier.getValue();
+ if (!seenSizes.contains(size)) {
+ seenSizes.add(size);
+ interesting = true;
+ }
+ }
+
+ // Omit LDPI, not really used anymore
+ DensityQualifier density = c.getDensityQualifier();
+ if (density != null) {
+ Density d = density.getValue();
+ if (Density.LOW.equals(d)) {
+ interesting = false;
+ }
+ }
+ }
+
+ if (interesting) {
+ NestedConfiguration screenConfig = NestedConfiguration.create(chooser,
+ configuration);
+ screenConfig.setOverrideDevice(true);
+ screenConfig.setDevice(device, true);
+ screenConfig.syncFolderConfig();
+ screenConfig.setDisplayName(ConfigurationChooser.getDeviceLabel(device, true));
+ addPreview(RenderPreview.create(this, screenConfig));
+ }
+ }
+
+ // Sorted by screen size, in decreasing order
+ sortPreviewsByScreenSize();
+ }
+
+ /**
+ * Previews this layout as included in other layouts
+ */
+ public void addIncludedInPreviews() {
+ ConfigurationChooser chooser = getChooser();
+ IProject project = chooser.getProject();
+ if (project == null) {
+ return;
+ }
+ IncludeFinder finder = IncludeFinder.get(project);
+
+ final List<Reference> includedBy = finder.getIncludedBy(chooser.getEditedFile());
+
+ if (includedBy == null || includedBy.isEmpty()) {
+ // TODO: Generate some useful defaults, such as including it in a ListView
+ // as the list item layout?
+ return;
+ }
+
+ for (final Reference reference : includedBy) {
+ String title = reference.getDisplayName();
+ Configuration config = Configuration.create(chooser, reference.getFile());
+ RenderPreview preview = RenderPreview.create(this, config);
+ preview.setDisplayName(title);
+ preview.setIncludedWithin(reference);
+
+ addPreview(preview);
+ }
+
+ sortPreviewsByOrientation();
+ }
+
+ /**
+ * Previews this layout as included in other layouts
+ */
+ public void addVariationPreviews() {
+ ConfigurationChooser chooser = getChooser();
+
+ IFile file = chooser.getEditedFile();
+ List<IFile> variations = AdtUtils.getResourceVariations(file, false /*includeSelf*/);
+
+ // Sort by parent folder
+ Collections.sort(variations, new Comparator<IFile>() {
+ @Override
+ public int compare(IFile file1, IFile file2) {
+ return file1.getParent().getName().compareTo(file2.getParent().getName());
+ }
+ });
+
+ for (IFile variation : variations) {
+ String title = variation.getParent().getName();
+ Configuration config = Configuration.create(chooser, variation);
+ RenderPreview preview = RenderPreview.create(this, config);
+ preview.setDisplayName(title);
+ preview.setInput(variation);
+
+ addPreview(preview);
+ }
+
+ sortPreviewsByOrientation();
+ }
+
+ /**
+ * Previews this layout using a custom configured set of layouts
+ */
+ public void addManualPreviews() {
+ if (mManualList == null) {
+ loadList();
+ } else {
+ mPreviews = mManualList.createPreviews(mCanvas);
+ }
+ }
+
+ private void loadList() {
+ IProject project = getChooser().getProject();
+ if (project == null) {
+ return;
+ }
+
+ if (mManualList == null) {
+ mManualList = RenderPreviewList.get(project);
+ }
+
+ try {
+ mManualList.load(getChooser().getDeviceList());
+ mPreviews = mManualList.createPreviews(mCanvas);
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ private void saveList() {
+ if (mManualList != null) {
+ try {
+ mManualList.save();
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+
+ /**
+ * Notifies that the main configuration has changed.
+ *
+ * @param flags the change flags, a bitmask corresponding to the
+ * {@code CHANGE_} constants in {@link ConfigurationClient}
+ */
+ public void configurationChanged(int flags) {
+ // Similar to renderPreviews, but only acts on incomplete previews
+ if (hasPreviews()) {
+ long delay = 0;
+ // Do zoomed images first
+ for (RenderPreview preview : mPreviews) {
+ if (preview.getScale() > 1.2) {
+ preview.configurationChanged(flags);
+ delay += RENDER_DELAY;
+ preview.render(delay);
+ }
+ }
+ for (RenderPreview preview : mPreviews) {
+ if (preview.getScale() <= 1.2) {
+ preview.configurationChanged(flags);
+ delay += RENDER_DELAY;
+ preview.render(delay);
+ }
+ }
+ RenderPreview preview = mCanvas.getPreview();
+ if (preview != null) {
+ preview.configurationChanged(flags);
+ preview.dispose();
+ }
+ layout(true);
+ mCanvas.redraw();
+ }
+ }
+
+ /** Updates the configuration preview thumbnails */
+ public void renderPreviews() {
+ if (hasPreviews()) {
+ long delay = 0;
+ // Do zoomed images first
+ for (RenderPreview preview : mPreviews) {
+ if (preview.getScale() > 1.2 && preview.isVisible()) {
+ delay += RENDER_DELAY;
+ preview.render(delay);
+ }
+ }
+ // Non-zoomed images
+ for (RenderPreview preview : mPreviews) {
+ if (preview.getScale() <= 1.2 && preview.isVisible()) {
+ delay += RENDER_DELAY;
+ preview.render(delay);
+ }
+ }
+ }
+ }
+
+ /**
+ * Switch to the given configuration preview
+ *
+ * @param preview the preview to switch to
+ */
+ public void switchTo(@NonNull RenderPreview preview) {
+ GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ ConfigurationChooser chooser = editor.getConfigurationChooser();
+
+ RenderPreview newPreview = mCanvas.getPreview();
+ if (newPreview == null) {
+ newPreview = RenderPreview.create(this, chooser.getConfiguration());
+ }
+
+ // Replace clicked preview with preview of the formerly edited main configuration
+ if (newPreview != null) {
+ // This doesn't work yet because the image overlay has had its image
+ // replaced by the configuration previews! I should make a list of them
+ //newPreview.setFullImage(mImageOverlay.getAwtImage());
+
+ for (int i = 0, n = mPreviews.size(); i < n; i++) {
+ if (preview == mPreviews.get(i)) {
+ mPreviews.set(i, newPreview);
+ break;
+ }
+ }
+ //newPreview.setPosition(preview.getX(), preview.getY());
+ }
+
+ // Switch main editor to the clicked configuration preview
+ mCanvas.setPreview(preview);
+ chooser.setConfiguration(preview.getConfiguration());
+ editor.recomputeLayout();
+ mCanvas.getVerticalBar().setSelection(mCanvas.getVerticalBar().getMinimum());
+ mCanvas.setFitScale(true /*onlyZoomOut*/, false /*allowZoomIn*/);
+ layout(true);
+ mCanvas.redraw();
+ }
+
+ /**
+ * Gets the preview at the given location, or null if none. This is
+ * currently deeply tied to where things are painted in onPaint().
+ */
+ RenderPreview getPreview(ControlPoint mousePos) {
+ if (hasPreviews()) {
+ int rootX = getX();
+ if (mousePos.x < rootX) {
+ return null;
+ }
+ int rootY = getY();
+
+ for (RenderPreview preview : mPreviews) {
+ int x = rootX + preview.getX();
+ int y = rootY + preview.getY();
+ if (mousePos.x >= x && mousePos.x <= x + preview.getWidth()) {
+ if (mousePos.y >= y && mousePos.y <= y + preview.getHeight()) {
+ return preview;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private int getX() {
+ return mHScale.translate(0);
+ }
+
+ private int getY() {
+ return mVScale.translate(0);
+ }
+
+ /**
+ * Returns the height of the layout
+ *
+ * @return the height
+ */
+ public int getHeight() {
+ return mLayoutHeight;
+ }
+
+ /**
+ * Notifies that preview manager that the mouse cursor has moved to the
+ * given control position within the layout canvas
+ *
+ * @param mousePos the mouse position, relative to the layout canvas
+ */
+ public void moved(ControlPoint mousePos) {
+ RenderPreview hovered = getPreview(mousePos);
+ if (hovered != mActivePreview) {
+ if (mActivePreview != null) {
+ mActivePreview.setActive(false);
+ }
+ mActivePreview = hovered;
+ if (mActivePreview != null) {
+ mActivePreview.setActive(true);
+ }
+ mCanvas.redraw();
+ }
+ }
+
+ /**
+ * Notifies that preview manager that the mouse cursor has entered the layout canvas
+ *
+ * @param mousePos the mouse position, relative to the layout canvas
+ */
+ public void enter(ControlPoint mousePos) {
+ moved(mousePos);
+ }
+
+ /**
+ * Notifies that preview manager that the mouse cursor has exited the layout canvas
+ *
+ * @param mousePos the mouse position, relative to the layout canvas
+ */
+ public void exit(ControlPoint mousePos) {
+ if (mActivePreview != null) {
+ mActivePreview.setActive(false);
+ }
+ mActivePreview = null;
+ mCanvas.redraw();
+ }
+
+ /**
+ * Process a mouse click, and return true if it was handled by this manager
+ * (e.g. the click was on a preview)
+ *
+ * @param mousePos the mouse position where the click occurred
+ * @return true if the click occurred over a preview and was handled, false otherwise
+ */
+ public boolean click(ControlPoint mousePos) {
+ RenderPreview preview = getPreview(mousePos);
+ if (preview != null) {
+ boolean handled = preview.click(mousePos.x - getX() - preview.getX(),
+ mousePos.y - getY() - preview.getY());
+ if (handled) {
+ // In case layout was performed, there could be a new preview
+ // under this coordinate now, so make sure it's hover etc
+ // shows up
+ moved(mousePos);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if there are thumbnail previews
+ *
+ * @return true if thumbnails are being shown
+ */
+ public boolean hasPreviews() {
+ return mPreviews != null && !mPreviews.isEmpty();
+ }
+
+
+ private void sortPreviewsByScreenSize() {
+ if (mPreviews != null) {
+ Collections.sort(mPreviews, new Comparator<RenderPreview>() {
+ @Override
+ public int compare(RenderPreview preview1, RenderPreview preview2) {
+ Configuration config1 = preview1.getConfiguration();
+ Configuration config2 = preview2.getConfiguration();
+ Device device1 = config1.getDevice();
+ Device device2 = config1.getDevice();
+ if (device1 != null && device2 != null) {
+ Screen screen1 = device1.getDefaultHardware().getScreen();
+ Screen screen2 = device2.getDefaultHardware().getScreen();
+ if (screen1 != null && screen2 != null) {
+ double delta = screen1.getDiagonalLength()
+ - screen2.getDiagonalLength();
+ if (delta != 0.0) {
+ return (int) Math.signum(delta);
+ } else {
+ if (screen1.getPixelDensity() != screen2.getPixelDensity()) {
+ return screen1.getPixelDensity().compareTo(
+ screen2.getPixelDensity());
+ }
+ }
+ }
+
+ }
+ State state1 = config1.getDeviceState();
+ State state2 = config2.getDeviceState();
+ if (state1 != state2 && state1 != null && state2 != null) {
+ return state1.getName().compareTo(state2.getName());
+ }
+
+ return preview1.getDisplayName().compareTo(preview2.getDisplayName());
+ }
+ });
+ }
+ }
+
+ private void sortPreviewsByOrientation() {
+ if (mPreviews != null) {
+ Collections.sort(mPreviews, new Comparator<RenderPreview>() {
+ @Override
+ public int compare(RenderPreview preview1, RenderPreview preview2) {
+ Configuration config1 = preview1.getConfiguration();
+ Configuration config2 = preview2.getConfiguration();
+ State state1 = config1.getDeviceState();
+ State state2 = config2.getDeviceState();
+ if (state1 != state2 && state1 != null && state2 != null) {
+ return state1.getName().compareTo(state2.getName());
+ }
+
+ return preview1.getDisplayName().compareTo(preview2.getDisplayName());
+ }
+ });
+ }
+ }
+
+ /**
+ * Vertical scrollbar listener which updates render previews which are not
+ * visible and triggers a redraw
+ */
+ private class ScrollBarListener implements SelectionListener {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mPreviews == null) {
+ return;
+ }
+
+ ScrollBar bar = mCanvas.getVerticalBar();
+ int selection = bar.getSelection();
+ int thumb = bar.getThumb();
+ int maxY = selection + thumb;
+ if (maxY > mMaxVisibleY) {
+ }
+ for (RenderPreview preview : mPreviews) {
+ if (!preview.isVisible() && preview.getY() <= maxY) {
+ preview.render(RENDER_DELAY);
+ preview.setVisible(true);
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java
new file mode 100644
index 0000000..0f06d7f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+/**
+ * The {@linkplain RenderPreviewMode} records what type of configurations to
+ * render in the layout editor
+ */
+public enum RenderPreviewMode {
+ /** Generate a set of default previews with maximum variation */
+ DEFAULT,
+
+ /** Preview all the locales */
+ LOCALES,
+
+ /** Preview all the screen sizes */
+ SCREENS,
+
+ /** Preview layout as included in other layouts */
+ INCLUDES,
+
+ /** Preview all the variations of this layout */
+ VARIATIONS,
+
+ /** Show a manually configured set of previews */
+ CUSTOM,
+
+ /** No previews */
+ NONE;
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
index e0c3add..ccf4068 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
@@ -32,6 +32,8 @@ import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.ContextPullParser;
@@ -82,9 +84,9 @@ public class RenderService {
private final LayoutLibrary mLayoutLib;
private final IImageFactory mImageFactory;
private final Density mDensity;
- private final float mXdpi;
- private final float mYdpi;
- private final ScreenSizeQualifier mScreenSize;
+ private float mXdpi;
+ private float mYdpi;
+ private ScreenSizeQualifier mScreenSize;
// The following fields are optional or configurable using the various chained
// setters:
@@ -120,6 +122,51 @@ public class RenderService {
mTargetSdkVersion = editor.getTargetSdkVersion();
}
+ private RenderService(GraphicalEditorPart editor, FolderConfiguration configuration,
+ ResourceResolver resourceResolver) {
+ mEditor = editor;
+
+ mProject = editor.getProject();
+ LayoutCanvas canvas = editor.getCanvasControl();
+ mImageFactory = canvas.getImageOverlay();
+ Configuration config = editor.getConfigurationChooser().getConfiguration();
+ mXdpi = config.getXDpi();
+ mYdpi = config.getYDpi();
+ mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/);
+ mResourceResolver = resourceResolver != null ? resourceResolver : editor.getResourceResolver();
+ mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib);
+ mMinSdkVersion = editor.getMinSdkVersion();
+ mTargetSdkVersion = editor.getTargetSdkVersion();
+
+ // TODO: Look up device etc and offer additional configuration options here?
+ Density density = Density.MEDIUM;
+ DensityQualifier densityQualifier = configuration.getDensityQualifier();
+ if (densityQualifier != null) {
+ // just a sanity check
+ Density d = densityQualifier.getValue();
+ if (d != Density.NODPI) {
+ density = d;
+ }
+ }
+ mDensity = density;
+ mScreenSize = configuration.getScreenSizeQualifier();
+ }
+
+ /**
+ * Sets the screen size and density to use for rendering
+ *
+ * @param screenSize the screen size
+ * @param xdpi the x density
+ * @param ydpi the y density
+ * @return this, for constructor chaining
+ */
+ public RenderService setScreen(ScreenSizeQualifier screenSize, float xdpi, float ydpi) {
+ mXdpi = xdpi;
+ mYdpi = ydpi;
+ mScreenSize = screenSize;
+ return this;
+ }
+
/**
* Creates a new {@link RenderService} associated with the given editor.
*
@@ -133,6 +180,21 @@ public class RenderService {
}
/**
+ * Creates a new {@link RenderService} associated with the given editor.
+ *
+ * @param editor the editor to provide configuration data such as the render target
+ * @param configuration the configuration to use (and fallback to editor for the rest)
+ * @param resolver a resource resolver to use to look up resources
+ * @return a {@link RenderService} which can perform rendering services
+ */
+ public static RenderService create(GraphicalEditorPart editor,
+ FolderConfiguration configuration, ResourceResolver resolver) {
+ RenderService renderService = new RenderService(editor, configuration, resolver);
+
+ return renderService;
+ }
+
+ /**
* Renders the given model, using this editor's theme and screen settings, and returns
* the result as a {@link RenderSession}.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
index 586da12..45d0644 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
@@ -37,6 +37,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewEleme
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.resources.Density;
import com.android.utils.Pair;
+import com.google.common.io.Closeables;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -140,6 +141,8 @@ public class ViewMetadataRepository {
} catch (Exception e) {
AdtPlugin.log(e, "Parsing palette file failed");
return null;
+ } finally {
+ Closeables.closeQuietly(paletteStream);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java
index 8f678c1..99a6c81 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java
@@ -353,14 +353,8 @@ public class UseCompoundDrawableRefactoring extends VisualRefactoring {
}
}
- XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
- XmlPrettyPrinter printer = new XmlPrettyPrinter(formatPrefs, XmlFormatStyle.LAYOUT,
- null /*lineSeparator*/);
- StringBuilder sb = new StringBuilder(300);
- printer.prettyPrint(-1, tempDocument, null, null, sb, false /*openTagOnly*/);
- String xml = sb.toString();
-
-
+ String xml = XmlPrettyPrinter.prettyPrint(tempDocument, XmlFormatPreferences.create(),
+ XmlFormatStyle.LAYOUT, null);
TextEdit replace = new ReplaceEdit(mSelectionStart, mSelectionEnd - mSelectionStart, xml);
rootEdit.addChild(replace);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
index 8526ad9..8a2877e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
@@ -17,9 +17,11 @@
package com.android.ide.eclipse.adt.internal.preferences;
+import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.internal.build.DebugKeyProvider;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
@@ -30,6 +32,7 @@ import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.PropertyChangeEvent;
import java.io.File;
+import java.util.Locale;
public final class AdtPrefs extends AbstractPreferenceInitializer {
public final static String PREFS_SDK_DIR = AdtPlugin.PLUGIN_ID + ".sdk"; //$NON-NLS-1$
@@ -69,6 +72,7 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
public final static String PREFS_LINT_SEVERITIES = AdtPlugin.PLUGIN_ID + ".lintSeverities"; //$NON-NLS-1$
public final static String PREFS_FIX_LEGACY_EDITORS = AdtPlugin.PLUGIN_ID + ".fixLegacyEditors"; //$NON-NLS-1$
public final static String PREFS_SHARED_LAYOUT_EDITOR = AdtPlugin.PLUGIN_ID + ".sharedLayoutEditor"; //$NON-NLS-1$
+ public final static String PREFS_PREVIEWS = AdtPlugin.PLUGIN_ID + ".previews"; //$NON-NLS-1$
/** singleton instance */
private final static AdtPrefs sThis = new AdtPrefs();
@@ -99,6 +103,7 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
private boolean mLintOnExport;
private AttributeSortOrder mAttributeSort;
private boolean mSharedLayoutEditor;
+ private RenderPreviewMode mPreviewMode = RenderPreviewMode.NONE;
private int mPreferXmlEditor;
public static enum BuildVerbosity {
@@ -254,6 +259,17 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
mSharedLayoutEditor = mStore.getBoolean(PREFS_SHARED_LAYOUT_EDITOR);
}
+ if (property == null || PREFS_PREVIEWS.equals(property)) {
+ mPreviewMode = RenderPreviewMode.NONE;
+ String previewMode = mStore.getString(PREFS_PREVIEWS);
+ if (previewMode != null && !previewMode.isEmpty()) {
+ try {
+ mPreviewMode = RenderPreviewMode.valueOf(previewMode.toUpperCase(Locale.US));
+ } catch (IllegalArgumentException iae) {
+ // Ignore: Leave it as RenderPreviewMode.NONE
+ }
+ }
+ }
}
/**
@@ -538,4 +554,29 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
store.setValue(PREFS_PREFER_XML, xml);
}
}
+
+ /**
+ * Gets the {@link RenderPreviewMode}
+ *
+ * @return the preview mode
+ */
+ @NonNull
+ public RenderPreviewMode getRenderPreviewMode() {
+ return mPreviewMode;
+ }
+
+ /**
+ * Sets the {@link RenderPreviewMode}
+ *
+ * @param previewMode the preview mode
+ */
+ public void setPreviewMode(@NonNull RenderPreviewMode previewMode) {
+ mPreviewMode = previewMode;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ if (previewMode != RenderPreviewMode.NONE) {
+ store.setValue(PREFS_PREVIEWS, previewMode.name().toLowerCase(Locale.US));
+ } else {
+ store.setToDefault(PREFS_PREVIEWS);
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
index 434384c..d78fa03 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -660,7 +660,9 @@ class TemplateHandler {
}
Document currentManifest = DomUtilities.parseStructuredDocument(currentXml);
+ assert currentManifest != null : currentXml;
Document fragment = DomUtilities.parseStructuredDocument(xml);
+ assert fragment != null : xml;
XmlFormatStyle formatStyle = XmlFormatStyle.MANIFEST;
boolean modified;
@@ -686,11 +688,8 @@ class TemplateHandler {
String contents = null;
if (ok) {
if (modified) {
- XmlPrettyPrinter printer = new XmlPrettyPrinter(
+ contents = XmlPrettyPrinter.prettyPrint(currentManifest,
XmlFormatPreferences.create(), formatStyle, null);
- StringBuilder sb = new StringBuilder(2 );
- printer.prettyPrint(-1, currentManifest, null, null, sb, false /*openTagOnly*/);
- contents = sb.toString();
}
} else {
// Just insert into file along with comment, using the "standard" conflict
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java
index f55cce4..c14e7f7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationTest.java
@@ -101,4 +101,37 @@ public class ConfigurationTest extends TestCase {
assertEquals(145.0f, configuration.getYDpi(), 0.001);
assertEquals(new Rect(0, 0, 320, 480), configuration.getScreenBounds());
}
+
+ public void testCopy() throws Exception {
+ Configuration configuration = createConfiguration();
+ assertNotNull(configuration);
+ configuration.setTheme("@style/Theme");
+ assertEquals("@style/Theme", configuration.getTheme());
+ DeviceManager deviceManager = new DeviceManager(new StdLogger(StdLogger.Level.VERBOSE));
+ List<Device> devices = deviceManager.getDefaultDevices();
+ assertNotNull(devices);
+ assertTrue(devices.size() > 0);
+ configuration.setDevice(devices.get(0), false);
+ configuration.setActivity("foo.bar.FooActivity");
+ configuration.setTheme("@android:style/Theme.Holo.Light");
+ Locale locale = Locale.create(new LanguageQualifier("nb"));
+ configuration.setLocale(locale, false /* skipSync */);
+
+ Configuration copy = Configuration.copy(configuration);
+ assertEquals(locale, copy.getLocale());
+ assertEquals("foo.bar.FooActivity", copy.getActivity());
+ assertEquals("@android:style/Theme.Holo.Light", copy.getTheme());
+ assertEquals(devices.get(0), copy.getDevice());
+
+ // Make sure edits to master does not affect the child
+ configuration.setLocale(Locale.ANY, false);
+ configuration.setTheme("@android:style/Theme.Holo");
+ configuration.setDevice(devices.get(1), true);
+
+ assertTrue(copy.getFullConfig().getLanguageQualifier().equals(locale.language));
+ assertEquals(locale, copy.getLocale());
+ assertEquals("foo.bar.FooActivity", copy.getActivity());
+ assertEquals("@android:style/Theme.Holo.Light", copy.getTheme());
+ assertEquals(devices.get(0), copy.getDevice());
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
index 9e9c734..d1c56c2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
@@ -30,6 +30,7 @@ import java.util.List;
import junit.framework.TestCase;
+@SuppressWarnings("javadoc")
public class ImageUtilsTest extends TestCase {
public void testCropBlank() throws Exception {
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE);
@@ -339,6 +340,21 @@ public class ImageUtilsTest extends TestCase {
assertEquals(0xFF00FF00, scaled.getRGB(48, 48));
assertEquals(0xFFFF0000, scaled.getRGB(100, 100));
assertEquals(0xFF00FF00, scaled.getRGB(199, 199));
+
+ scaled = ImageUtils.scale(image, 0.25, 0.25);
+ assertEquals(25, scaled.getWidth());
+ assertEquals(25, scaled.getHeight());
+ assertEquals(0xFF00FF00, scaled.getRGB(0, 0));
+ assertEquals(0xFF00FF00, scaled.getRGB(24, 24));
+ assertEquals(0xFFFF0000, scaled.getRGB(13, 13));
+
+ scaled = ImageUtils.scale(image, 0.25, 0.25, 75, 95);
+ assertEquals(100, scaled.getWidth());
+ assertEquals(120, scaled.getHeight());
+ assertEquals(0xFF00FF00, scaled.getRGB(0, 0));
+ assertEquals(0xFF00FF00, scaled.getRGB(24, 24));
+ assertEquals(0xFFFF0000, scaled.getRGB(13, 13));
+
}
public void testCreateColoredImage() throws Exception {
@@ -349,5 +365,4 @@ public class ImageUtilsTest extends TestCase {
assertEquals(0xFFFEFDFC, image.getRGB(50, 50));
assertEquals(0xFFFEFDFC, image.getRGB(119, 109));
}
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinderTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinderTest.java
index 24fa0ae..c86623c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinderTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinderTest.java
@@ -17,9 +17,11 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import junit.framework.TestCase;
+@SuppressWarnings("javadoc")
public class IncludeFinderTest extends TestCase {
public void testEncodeDecode1() throws Exception {
// Test ending with just a key
@@ -66,4 +68,61 @@ public class IncludeFinderTest extends TestCase {
finder.setIncluded("baz", Collections.<String>emptyList(), false);
assertEquals(Collections.emptyList(), finder.getIncludedBy("foo"));
}
+
+ public void testFindIncludes() throws Exception {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <RadioButton\n" +
+ " android:id=\"@+id/radioButton1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"RadioButton\" />\n" +
+ "\n" +
+ " <include\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " layout=\"@layout/layout3\" />\n" +
+ "\n" +
+ " <include\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " layout=\"@layout/layout4\" />\n" +
+ "\n" +
+ "</LinearLayout>";
+ List<String> includes = IncludeFinder.findIncludes(xml);
+ Collections.sort(includes);
+ assertEquals(Arrays.asList("layout3", "layout4"), includes);
+ }
+
+ public void testFindFragments() throws Exception {
+ String xml =
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:context=\".MainActivity\" >\n" +
+ "\n" +
+ " <fragment\n" +
+ " android:id=\"@+id/fragment1\"\n" +
+ " android:name=\"android.app.ListFragment\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_alignParentTop=\"true\"\n" +
+ " android:layout_marginLeft=\"58dp\"\n" +
+ " android:layout_marginTop=\"74dp\"\n" +
+ " tools:layout=\"@layout/myfragment\" />\n" +
+ "\n" +
+ "</RelativeLayout>";
+ List<String> includes = IncludeFinder.findIncludes(xml);
+ Collections.sort(includes);
+ assertEquals(Arrays.asList("myfragment"), includes);
+ }
+
+
}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java b/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
index e2fe767..a513c1f 100644
--- a/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
@@ -22,6 +22,7 @@ import com.android.resources.ResourceFolderType;
import com.android.resources.ScreenOrientation;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
@@ -105,6 +106,52 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
}
/**
+ * Creates a {@link FolderConfiguration} matching the folder segments.
+ * @param folderSegments The segments of the folder name. The first segments should contain
+ * the name of the folder
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ * @see FolderConfiguration#getConfig(String[])
+ */
+ public static FolderConfiguration getConfig(Iterable<String>folderSegments) {
+ FolderConfiguration config = new FolderConfiguration();
+
+ // we are going to loop through the segments, and match them with the first
+ // available qualifier. If the segment doesn't match we try with the next qualifier.
+ // Because the order of the qualifier is fixed, we do not reset the first qualifier
+ // after each successful segment.
+ // If we run out of qualifier before processing all the segments, we fail.
+
+ int qualifierIndex = 0;
+ int qualifierCount = DEFAULT_QUALIFIERS.length;
+
+ Iterator<String> iterator = folderSegments.iterator();
+ if (iterator.hasNext()) {
+ // Skip the first segment: it should be just the base folder, such as "values" or
+ // "layout"
+ iterator.next();
+ }
+ while (iterator.hasNext()) {
+ String seg = iterator.next();
+ if (seg.length() > 0) {
+ while (qualifierIndex < qualifierCount &&
+ DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config) == false) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == qualifierCount) {
+ return null;
+ }
+
+ } else {
+ return null;
+ }
+ }
+
+ return config;
+ }
+
+ /**
* Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
*/
public static int getQualifierCount() {