instanceof
instantiatable
int
+iterable
javac
javadoc
keystore
themed
tmp
tooltip
+tooltips
traceview
translucency
ui
continue;
}
String childId = child.getStringAttr(ANDROID_URI, ATTR_ID);
+ if (childId == null) {
+ continue;
+ }
childId = normalizeId(childId);
if (id.equals(childId)) {
Set<String> linkedIds = getLinkedIds(child, cachedLinkIds);
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
}
/**
+ * Writes the given content out to the given {@link File}. The file will be deleted if
+ * it already exists.
+ *
+ * @param file the target file
+ * @param content the content to be written into the file
+ */
+ public static void writeFile(File file, String content) {
+ if (file.exists()) {
+ file.delete();
+ }
+ FileWriter fw = null;
+ try {
+ fw = new FileWriter(file);
+ fw.write(content);
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (fw != null) {
+ try {
+ fw.close();
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+ }
+
+ /**
* Returns true iff the given file contains the given String.
*
* @param file the file to look for the string in
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
- pingUsageServer(); //$NON-NLS-1$
+ pingUsageServer();
return Status.OK_STATUS;
} catch (Throwable t) {
public final static String DOT_DEX = DOT + EXT_DEX;
/** Dot-Extension for temporary resource files, ie "ap_ */
public final static String DOT_RES = DOT + EXT_RES;
+ /** Dot-Extension for PNG files, i.e. ".png" */
+ public static final String DOT_PNG = ".png"; //$NON-NLS-1$
/** Name of the android sources directory */
public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
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.LayoutDevice;
+import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.resources.Density;
import com.android.sdklib.resources.DockMode;
/**
* Returns the current theme, or null if the combo has no selection.
+ *
+ * @return the theme name, or null
*/
public String getTheme() {
int themeIndex = mThemeCombo.getSelectionIndex();
}
/**
+ * Returns the current device string, or null if the combo has no selection.
+ *
+ * @return the device name, or null
+ */
+ public String getDevice() {
+ int deviceIndex = mDeviceCombo.getSelectionIndex();
+ if (deviceIndex != -1) {
+ return mDeviceCombo.getItem(deviceIndex);
+ }
+
+ return null;
+ }
+
+ /**
* Returns whether the current theme selection is a project theme.
* <p/>The returned value is meaningless if {@link #getTheme()} returns <code>null</code>.
* @return true for project theme, false for framework theme
--- /dev/null
+/*
+ * Copyright (C) 2011 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.ide.eclipse.adt.internal.editors.IconFactory;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CLabel;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The accordion control allows a series of labels with associated content that can be
+ * shown. For more details on accordions, see http://en.wikipedia.org/wiki/Accordion_(GUI)
+ * <p>
+ * This control allows the children to be created lazily. You can also customize the
+ * composite which is created to hold the children items, to for example allow multiple
+ * columns of items rather than just the default vertical stack.
+ * <p>
+ * The visual appearance of the headers is built in; it uses a mild gradient, with a
+ * heavier gradient during mouse-overs. It also uses a bold label along with the eclipse
+ * folder icons.
+ * <p>
+ * The control can be configured to enforce a single category open at any time (the
+ * default), or allowing multiple categories open (where they share the available space).
+ * The control can also be configured to fill the available vertical space for the open
+ * category/categories.
+ */
+public abstract class AccordionControl extends Composite {
+ /** Pixel spacing between header items */
+ private static final int HEADER_SPACING = 0;
+
+ /** Pixel spacing between items in the content area */
+ private static final int ITEM_SPACING = 0;
+
+ private static final String KEY_CONTENT = "content"; //$NON-NLS-1$
+ private static final String KEY_HEADER = "header"; //$NON-NLS-1$
+
+ private Image mClosed;
+ private Image mOpen;
+ private boolean mSingle = true;
+ private boolean mWrap;
+
+ /**
+ * Creates the container which will hold the items in a category; this can be
+ * overridden to lay out the children with a different layout than the default
+ * vertical RowLayout
+ */
+ protected Composite createChildContainer(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ if (mWrap) {
+ RowLayout layout = new RowLayout(SWT.HORIZONTAL);
+ layout.center = true;
+ composite.setLayout(layout);
+ } else {
+ RowLayout layout = new RowLayout(SWT.VERTICAL);
+ layout.spacing = ITEM_SPACING;
+ layout.marginHeight = 0;
+ layout.marginWidth = 0;
+ layout.marginLeft = 0;
+ layout.marginTop = 0;
+ layout.marginRight = 0;
+ layout.marginBottom = 0;
+ composite.setLayout(layout);
+ }
+
+ // TODO - maybe do multi-column arrangement for simple nodes
+ return composite;
+ }
+
+ /**
+ * Creates the children under a particular header
+ *
+ * @param parent the parent composite to add the SWT items to
+ * @param header the header object that is being opened for the first time
+ */
+ protected abstract void createChildren(Composite parent, Object header);
+
+ /**
+ * Set whether a single category should be enforced or not (default=true)
+ *
+ * @param single if true, enforce a single category open at a time
+ */
+ public void setAutoClose(boolean single) {
+ mSingle = single;
+ }
+
+ /**
+ * Returns whether a single category should be enforced or not (default=true)
+ *
+ * @return true if only a single category can be open at a time
+ */
+ public boolean isAutoClose() {
+ return mSingle;
+ }
+
+ /**
+ * Returns the labels used as header categories
+ *
+ * @return list of header labels
+ */
+ public List<CLabel> getHeaderLabels() {
+ List<CLabel> headers = new ArrayList<CLabel>();
+ for (Control c : getChildren()) {
+ if (c instanceof CLabel) {
+ headers.add((CLabel) c);
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * Show all categories
+ *
+ * @param performLayout if true, call {@link #layout} and {@link #pack} when done
+ */
+ public void expandAll(boolean performLayout) {
+ for (Control c : getChildren()) {
+ if (c instanceof CLabel) {
+ if (!isOpen(c)) {
+ toggle((CLabel) c, false, false);
+ }
+ }
+ }
+ if (performLayout) {
+ pack();
+ layout();
+ }
+ }
+
+ /**
+ * Hide all categories
+ *
+ * @param performLayout if true, call {@link #layout} and {@link #pack} when done
+ */
+ public void collapseAll(boolean performLayout) {
+ for (Control c : getChildren()) {
+ if (c instanceof CLabel) {
+ if (isOpen(c)) {
+ toggle((CLabel) c, false, false);
+ }
+ }
+ }
+ if (performLayout) {
+ layout();
+ }
+ }
+
+ /**
+ * Create the composite.
+ *
+ * @param parent the parent widget to add the accordion to
+ * @param style the SWT style mask to use
+ * @param headers a list of headers, whose {@link Object#toString} method should
+ * produce the heading label
+ * @param greedy if true, grow vertically as much as possible
+ * @param wrapChildren if true, configure the child area to be horizontally laid out
+ * with wrapping
+ */
+ public AccordionControl(Composite parent, int style, List<?> headers,
+ boolean greedy, boolean wrapChildren) {
+ super(parent, style);
+ mWrap = wrapChildren;
+
+ GridLayout gridLayout = new GridLayout(1, false);
+ gridLayout.verticalSpacing = HEADER_SPACING;
+ gridLayout.horizontalSpacing = 0;
+ gridLayout.marginWidth = 0;
+ gridLayout.marginHeight = 0;
+ setLayout(gridLayout);
+
+ Font labelFont = null;
+
+ mOpen = IconFactory.getInstance().getIcon("open-folder"); //$NON-NLS-1$
+ mClosed = IconFactory.getInstance().getIcon("closed-folder"); //$NON-NLS-1$
+ CLabel first = null;
+
+ for (Object header : headers) {
+ final CLabel label = new CLabel(this, SWT.SHADOW_OUT);
+ label.setText(header.toString().replace("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$
+ updateBackground(label, false);
+ if (labelFont == null) {
+ labelFont = label.getFont();
+ FontData normal = labelFont.getFontData()[0];
+ FontData bold = new FontData(normal.getName(), normal.getHeight(), SWT.BOLD);
+ labelFont = new Font(null, bold);
+ }
+ label.setFont(labelFont);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ setHeader(header, label);
+ label.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseUp(MouseEvent e) {
+ if (e.button == 1 && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
+ toggle(label, true, mSingle);
+ }
+ }
+ });
+ label.addMouseTrackListener(new MouseTrackListener() {
+ public void mouseEnter(MouseEvent e) {
+ updateBackground(label, true);
+ }
+
+ public void mouseExit(MouseEvent e) {
+ updateBackground(label, false);
+ }
+
+ public void mouseHover(MouseEvent e) {
+ }
+ });
+
+ // Turn off border?
+ final ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL);
+
+ // Do we need the scrolled composite or can we just look at the next
+ // wizard in the hierarchy?
+
+ setContentArea(label, scrolledComposite);
+ scrolledComposite.setExpandHorizontal(true);
+ scrolledComposite.setExpandVertical(true);
+ GridData scrollGridData = new GridData(SWT.FILL,
+ greedy ? SWT.FILL : SWT.TOP, false, greedy, 1, 1);
+ scrollGridData.exclude = true;
+ scrollGridData.grabExcessHorizontalSpace = wrapChildren;
+ scrolledComposite.setLayoutData(scrollGridData);
+
+ if (wrapChildren) {
+ scrolledComposite.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = scrolledComposite.getClientArea();
+ Control content = scrolledComposite.getContent();
+ if (content != null && r != null) {
+ Point minSize = content.computeSize(r.width, SWT.DEFAULT);
+ scrolledComposite.setMinSize(minSize);
+ }
+ }
+ });
+ }
+
+ updateIcon(label);
+ if (first == null) {
+ first = label;
+ }
+ }
+
+ // Initially show the first category
+ if (headers.size() > 0) {
+ toggle(first, false, false);
+ }
+ }
+
+ /** Updates the background gradient of the given header label */
+ private void updateBackground(CLabel label, boolean mouseOver) {
+ Display display = label.getDisplay();
+ label.setBackground(new Color[] {
+ display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW),
+ display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND),
+ display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW)
+ }, new int[] {
+ mouseOver ? 60 : 40, 100
+ }, true);
+ }
+
+ /**
+ * Updates the icon for a header label to be open/close based on the {@link #isOpen}
+ * state
+ */
+ private void updateIcon(CLabel label) {
+ label.setImage(isOpen(label) ? mOpen : mClosed);
+ }
+
+ /** Returns true if the content area for the given label is open/showing */
+ private boolean isOpen(Control label) {
+ return !((GridData) getContentArea(label).getLayoutData()).exclude;
+ }
+
+ /** Toggles the visibility of the children of the given label */
+ private void toggle(CLabel label, boolean performLayout, boolean autoClose) {
+ if (autoClose) {
+ collapseAll(true);
+ }
+ ScrolledComposite scrolledComposite = getContentArea(label);
+
+ GridData scrollGridData = (GridData) scrolledComposite.getLayoutData();
+ boolean close = !scrollGridData.exclude;
+ scrollGridData.exclude = close;
+ scrolledComposite.setVisible(!close);
+ updateIcon(label);
+
+ if (!scrollGridData.exclude && scrolledComposite.getContent() == null) {
+ Composite composite = createChildContainer(scrolledComposite);
+ Object header = getHeader(label);
+ createChildren(composite, header);
+ scrolledComposite.setContent(composite);
+ scrolledComposite.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
+ }
+
+ if (performLayout) {
+ layout(true);
+ }
+ }
+
+ /** Returns the header object for the given header label */
+ private Object getHeader(Control label) {
+ return label.getData(KEY_HEADER);
+ }
+
+ /** Sets the header object for the given header label */
+ private void setHeader(Object header, final CLabel label) {
+ label.setData(KEY_HEADER, header);
+ }
+
+ /** Returns the content area for the given header label */
+ private ScrolledComposite getContentArea(Control label) {
+ return (ScrolledComposite) label.getData(KEY_CONTENT);
+ }
+
+ /** Sets the content area for the given header label */
+ private void setContentArea(final CLabel label, ScrolledComposite scrolledComposite) {
+ label.setData(KEY_CONTENT, scrolledComposite);
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+}
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
import java.util.Map.Entry;
+import java.util.TreeMap;
import java.util.regex.Pattern;
-
/**
* Helper class that is responsible for adding and managing the dynamic menu items
* contributed by the {@link IViewRule} instances, based on the current selection
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.Params;
+import com.android.ide.common.rendering.api.Params.RenderingMode;
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.Params.RenderingMode;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
-import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
-import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
+import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
+import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomButton;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
mEditedFile.getName());
}
}
+
+ reloadPalette();
}
public void onThemeChange() {
mConfigComposite.storeState();
recomputeLayout();
+
+ reloadPalette();
}
public void onCreate() {
public void onRenderingTargetPostChange(IAndroidTarget target) {
AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
updateCapabilities(targetData);
+
+ mPalette.reloadPalette(target);
}
public Map<String, Map<String, ResourceValue>> getConfiguredFrameworkResources() {
computeAndSetRealScale(false /* redraw */);
}
- IProject iProject = mEditedFile.getProject();
- renderWithBridge(iProject, model, layoutLib);
+ IProject project = mEditedFile.getProject();
+ renderWithBridge(project, model, layoutLib);
}
} finally {
// no matter the result, we are done doing the recompute based on the latest
public void reloadPalette() {
if (mPalette != null) {
- mPalette.reloadPalette(mLayoutEditor.getTargetData());
+ IAndroidTarget renderingTarget = getRenderingTarget();
+ if (renderingTarget != null) {
+ mPalette.reloadPalette(renderingTarget);
+ }
}
}
* normal background requested by the theme, and it will instead paint the
* background using a fully transparent background color
* @param logger a logger where rendering errors are reported
+ * @param renderingMode the {@link RenderingMode} to use for rendering
* @return the resulting rendered image wrapped in an {@link RenderSession}
*/
public RenderSession render(UiDocumentNode model, int width, int height,
Set<UiElementNode> explodeNodes, boolean transparentBackground,
- LayoutLog logger) {
+ LayoutLog logger, RenderingMode renderingMode) {
if (!ensureFileValid()) {
return null;
}
IProject iProject = mEditedFile.getProject();
return renderWithBridge(iProject, model, layoutLib, width, height, explodeNodes,
- transparentBackground, logger);
+ transparentBackground, logger, null /* includeWithin */, renderingMode);
}
/**
RenderLogger logger = new RenderLogger(mEditedFile.getName());
+ RenderingMode renderingMode = RenderingMode.NORMAL;
+ if (mClippingButton.getSelection() == false) {
+ renderingMode = RenderingMode.FULL_EXPAND;
+ } else {
+ // FIXME set the rendering mode using ViewRule or something.
+ List<UiElementNode> children = model.getUiChildren();
+ if (children.size() > 0 &&
+ children.get(0).getDescriptor().getXmlLocalName().equals(SCROLL_VIEW)) {
+ renderingMode = RenderingMode.V_SCROLL;
+ }
+ }
+
RenderSession session = renderWithBridge(iProject, model, layoutLib, width, height,
- explodeNodes, false, logger);
+ explodeNodes, false, logger, mIncludedWithin, renderingMode);
canvas.setSession(session, explodeNodes);
private RenderSession renderWithBridge(IProject iProject, UiDocumentNode model,
LayoutLibrary layoutLib, int width, int height, Set<UiElementNode> explodeNodes,
- boolean transparentBackground, LayoutLog logger) {
+ boolean transparentBackground, LayoutLog logger, Reference includeWithin,
+ RenderingMode renderingMode) {
ResourceManager resManager = ResourceManager.getInstance();
ProjectResources projectRes = resManager.getProjectResources(iProject);
// Code to support editing included layout
// Outer layout name:
- if (mIncludedWithin != null) {
- String contextLayoutName = mIncludedWithin.getName();
+ if (includeWithin != null) {
+ String contextLayoutName = includeWithin.getName();
// Find the layout file.
Map<String, ResourceValue> layouts = configuredProjectRes.get(
}
}
- RenderingMode renderingMode = RenderingMode.NORMAL;
- if (mClippingButton.getSelection() == false) {
- renderingMode = RenderingMode.FULL_EXPAND;
- } else {
- // FIXME set the rendering mode using ViewRule or something.
- List<UiElementNode> children = model.getUiChildren();
- if (children.size() > 0 &&
- children.get(0).getDescriptor().getXmlLocalName().equals(SCROLL_VIEW)) {
- renderingMode = RenderingMode.V_SCROLL;
- }
- }
-
// FIXME: make resource resolver persistent, and only update it when something changes.
ResourceResolver resolver = ResourceResolver.create(
configuredProjectRes, frameworkResources,
/** Reload layout. <b>Must be called on the SWT thread</b> */
private void reloadLayoutSwt(ChangeFlags flags, boolean libraryChanged) {
+ if (mConfigComposite.isDisposed()) {
+ return;
+ }
assert mConfigComposite.getDisplay().getThread() == Thread.currentThread();
boolean recompute = false;
return mConfigComposite.getCurrentConfig();
}
-
/**
* Figures out the project's minSdkVersion and targetSdkVersion and return whether the values
* have changed.
return oldMinSdkVersion != mMinSdkVersion || oldTargetSdkVersion != mTargetSdkVersion;
}
+
+ public ConfigurationComposite getConfigurationComposite() {
+ return mConfigComposite;
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2011 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 org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CLabel;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * An ImageControl which simply renders an image, with optional margins and tooltips. This
+ * is useful since a {@link CLabel}, even without text, will hide the image when there is
+ * not enough room to fully fit it.
+ * <p>
+ * The image is always rendered left and top aligned.
+ */
+public class ImageControl extends Canvas implements MouseTrackListener {
+ private Image mImage;
+ private int mLeftMargin;
+ private int mTopMargin;
+ private int mRightMargin;
+ private int mBottomMargin;
+ private boolean mShowMouseOver;
+ private boolean mMouseIn;
+ private Color mHoverColor;
+ private float mScale = 1.0f;
+
+ /**
+ * Creates an ImageControl rendering the given image, which will be dispose when this
+ * control is disposed
+ *
+ * @param parent the parent to add the image control to
+ * @param style the SWT style to use
+ * @param image the image to be rendered, which must not be null and should be unique
+ * for this image control since it will be disposed by this control when
+ * the control is disposed
+ */
+ public ImageControl(Composite parent, int style, Image image) {
+ super(parent, style | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED);
+ this.mImage = image;
+
+ addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent event) {
+ onPaint(event);
+ }
+ });
+ }
+
+ public void setScale(float scale) {
+ mScale = scale;
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public void setHoverColor(Color hoverColor) {
+ mHoverColor = hoverColor;
+ if (hoverColor != null) {
+ addMouseTrackListener(this);
+ }
+ }
+
+ public Color getHoverColor() {
+ return mHoverColor;
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ mImage.dispose();
+ mImage = null;
+ }
+
+ @Override
+ public Point computeSize(int wHint, int hHint, boolean changed) {
+ checkWidget();
+ Point e = new Point(0, 0);
+ if (mImage != null) {
+ Rectangle r = mImage.getBounds();
+ if (mScale != 1.0f) {
+ e.x += mScale * r.width;
+ e.y += mScale * r.height;
+ } else {
+ e.x += r.width;
+ e.y += r.height;
+ }
+ }
+ if (wHint == SWT.DEFAULT) {
+ e.x += mLeftMargin + mRightMargin;
+ } else {
+ e.x = wHint;
+ }
+ if (hHint == SWT.DEFAULT) {
+ e.y += mTopMargin + mBottomMargin;
+ } else {
+ e.y = hHint;
+ }
+
+ return e;
+ }
+
+ private void onPaint(PaintEvent event) {
+ Rectangle rect = getClientArea();
+ if (mImage == null || rect.width == 0 || rect.height == 0) {
+ return;
+ }
+
+ GC gc = event.gc;
+ Rectangle imageRect = mImage.getBounds();
+ int imageHeight = imageRect.height;
+ int imageWidth = imageRect.width;
+ int destWidth = imageWidth;
+ int destHeight = imageHeight;
+
+ if (mScale != 1.0f) {
+ destWidth = (int) (mScale * destWidth);
+ destHeight = (int) (mScale * destHeight);
+ }
+
+ gc.drawImage(mImage, 0, 0, imageWidth, imageHeight, rect.x + mLeftMargin, rect.y
+ + mTopMargin, destWidth, destHeight);
+
+ if (mHoverColor != null && mMouseIn) {
+ gc.setAlpha(60);
+ gc.setBackground(mHoverColor);
+ gc.setLineWidth(1);
+ gc.fillRectangle(0, 0, destWidth, destHeight);
+ }
+ }
+
+ public void setMargins(int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
+ checkWidget();
+ mLeftMargin = Math.max(0, leftMargin);
+ mTopMargin = Math.max(0, topMargin);
+ mRightMargin = Math.max(0, rightMargin);
+ mBottomMargin = Math.max(0, bottomMargin);
+ redraw();
+ }
+
+ // ---- Implements MouseTrackListener ----
+
+ public void mouseEnter(MouseEvent e) {
+ mMouseIn = true;
+ if (mHoverColor != null) {
+ redraw();
+ }
+ }
+
+ public void mouseExit(MouseEvent e) {
+ mMouseIn = false;
+ if (mHoverColor != null) {
+ redraw();
+ }
+ }
+
+ public void mouseHover(MouseEvent e) {
+ }
+}
return union;
}
+
+ /**
+ * Returns a new image which contains of the sub image given by the rectangle (x1,y1)
+ * to (x2,y2)
+ *
+ * @param source the source image
+ * @param x1 top left X coordinate
+ * @param y1 top left Y coordinate
+ * @param x2 bottom right X coordinate
+ * @param y2 bottom right Y coordinate
+ * @return a new image containing the pixels in the given range
+ */
+ public static BufferedImage subImage(BufferedImage source, int x1, int y1, int x2, int y2) {
+ int width = x2 - x1;
+ int height = y2 - y1;
+ BufferedImage sub = new BufferedImage(width, height, source.getType());
+ Graphics g = sub.getGraphics();
+ g.drawImage(source, 0, 0, width, height, x1, y1, x2, y2, null);
+ g.dispose();
+
+ return sub;
+ }
}
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH;
import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
-import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
-import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
import com.android.ide.common.api.InsertType;
import com.android.ide.common.api.Rect;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.ide.common.rendering.api.Params.RenderingMode;
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.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
-import com.android.ide.eclipse.adt.internal.editors.ui.GridDataBuilder;
-import com.android.ide.eclipse.adt.internal.editors.ui.GridLayoutBuilder;
import com.android.ide.eclipse.adt.internal.editors.ui.IDecorContent;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
+import com.android.sdklib.util.Pair;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MenuDetectEvent;
+import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseTrackListener;
+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.RGB;
import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Menu;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
* with a list of element descriptors.
* <p/>
*
- * @since GLE2
- *
* TODO list:
* - The available items should depend on the actual GLE2 Canvas selection. Selected android
* views should force filtering on what they accept can be dropped on them (e.g. TabHost,
* TableLayout). Should enable/disable them, not hide them, to avoid shuffling around.
* - Optional: a text filter
- * - Optional: have icons that depict the element and/or automatically rendered icons
- * based on a rendering of the widget.
* - Optional: have context-sensitive tools items, e.g. selection arrow tool,
* group selection tool, alignment, etc.
- * - Different view strategies: big icon, small icons, text vs no text, compact grid.
- * - This would only be useful with meaningful icons. Out current 1-letter icons are not enough
- * to get rid of text labels.
*/
public class PaletteControl extends Composite {
}
}
- /** The parent grid layout that contains all the {@link Toggle} and {@link Item} widgets. */
- private Composite mRoot;
- private ScrollBar mVBar;
- private ControlListener mControlListener;
- private Listener mScrollbarListener;
+ /**
+ * The parent grid layout that contains all the {@link Toggle} and
+ * {@link IconTextItem} widgets.
+ */
private GraphicalEditorPart mEditor;
+ private Color mBackground;
+
+ /** The palette modes control various ways to visualize and lay out the views */
+ private static enum PaletteMode {
+ /** Show rendered previews of the views */
+ PREVIEW("Show Previews", true),
+ /** Show rendered previews of the views, scaled down to 75% */
+ SMALL_PREVIEW("Show Small Previews", true),
+ /** Show rendered previews of the views, scaled down to 50% */
+ TINY_PREVIEW("Show Tiny Previews", true),
+ /** Show an icon + text label */
+ ICON_TEXT("Show Icon and Text", false),
+ /** Show only icons, packed multiple per row */
+ ICON_ONLY("Show Only Icons", true);
+
+ PaletteMode(String actionLabel, boolean wrap) {
+ mActionLabel = actionLabel;
+ mWrap = wrap;
+ }
+
+ public String getActionLabel() {
+ return mActionLabel;
+ }
+
+ public boolean getWrap() {
+ return mWrap;
+ }
+
+ public boolean isPreview() {
+ return this == PREVIEW || this == SMALL_PREVIEW || this == TINY_PREVIEW;
+ }
+
+ public boolean isScaledPreview() {
+ return this == SMALL_PREVIEW || this == TINY_PREVIEW;
+ }
+
+ private final String mActionLabel;
+ private final boolean mWrap;
+ };
+
+ /** Token used in preference string to record alphabetical sorting */
+ private static final String VALUE_ALPHABETICAL = "alpha"; //$NON-NLS-1$
+ /** Token used in preference string to record categories being turned off */
+ private static final String VALUE_NO_CATEGORIES = "nocat"; //$NON-NLS-1$
+ /** Token used in preference string to record auto close being turned off */
+ private static final String VALUE_NO_AUTOCLOSE = "noauto"; //$NON-NLS-1$
+
+ private final PreviewIconFactory mPreviewIconFactory = new PreviewIconFactory(this);
+ private PaletteMode mPaletteMode = PaletteMode.SMALL_PREVIEW;
+ /** Use alphabetical sorting instead of natural order? */
+ private boolean mAlphabetical;
+ /** Use categories instead of a single large list of views? */
+ private boolean mCategories = true;
+ /** Auto-close the previous category when new categories are opened */
+ private boolean mAutoClose = true;
+ private AccordionControl mAccordion;
+ private String mCurrentTheme;
+ private String mCurrentDevice;
+ private IAndroidTarget mCurrentTarget;
/**
* Create the composite.
* @param editor An editor associated with this palette.
*/
public PaletteControl(Composite parent, GraphicalEditorPart editor) {
- super(parent, SWT.V_SCROLL);
+ super(parent, SWT.NONE);
mEditor = editor;
- mVBar = getVerticalBar();
+ loadPaletteMode();
+ }
- mScrollbarListener = new Listener() {
- public void handleEvent(Event event) {
- scrollScrollbar();
+ /** Reads UI mode from persistent store to preserve palette mode across IDE sessions */
+ private void loadPaletteMode() {
+ String paletteModes = AdtPrefs.getPrefs().getPaletteModes();
+ if (paletteModes.length() > 0) {
+ String[] tokens = paletteModes.split(","); //$NON-NLS-1$
+ try {
+ mPaletteMode = PaletteMode.valueOf(tokens[0]);
+ } catch (Throwable t) {
+ mPaletteMode = PaletteMode.values()[0];
}
- };
-
- mVBar.addListener(SWT.Selection, mScrollbarListener);
+ mAlphabetical = paletteModes.contains(VALUE_ALPHABETICAL);
+ mCategories = !paletteModes.contains(VALUE_NO_CATEGORIES);
+ mAutoClose = !paletteModes.contains(VALUE_NO_AUTOCLOSE);
+ }
+ }
+ /**
+ * Returns the most recently stored version of auto-close-mode; this is the last
+ * user-initiated setting of the auto-close mode (we programmatically switch modes when
+ * you enter icons-only mode, and set it back to this when going to any other mode)
+ */
+ private boolean getSavedAutoCloseMode() {
+ return !AdtPrefs.getPrefs().getPaletteModes().contains(VALUE_NO_AUTOCLOSE);
+ }
- mControlListener = new ControlListener() {
- public void controlMoved(ControlEvent e) {
- // Ignore
- }
- public void controlResized(ControlEvent e) {
- if (recomputeScrollbar()) {
- redraw();
- }
- }
- };
+ /** Saves UI mode to persistent store to preserve palette mode across IDE sessions */
+ private void savePaletteMode() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mPaletteMode);
+ if (mAlphabetical) {
+ sb.append(',').append(VALUE_ALPHABETICAL);
+ }
+ if (!mCategories) {
+ sb.append(',').append(VALUE_NO_CATEGORIES);
+ }
+ if (!mAutoClose) {
+ sb.append(',').append(VALUE_NO_AUTOCLOSE);
+ }
+ AdtPrefs.getPrefs().setPaletteModes(sb.toString());
+ }
- addControlListener(mControlListener);
+ private void refreshPalette() {
+ IAndroidTarget oldTarget = mCurrentTarget;
+ mCurrentTarget = null;
+ mCurrentTheme = null;
+ mCurrentDevice = null;
+ reloadPalette(oldTarget);
}
@Override
@Override
public void dispose() {
- if (mControlListener != null) {
- removeControlListener(mControlListener);
- mControlListener = null;
- }
-
- if (mVBar != null && !mVBar.isDisposed()) {
- if (mScrollbarListener != null) {
- mVBar.removeListener(SWT.Selection, mScrollbarListener);
- }
- mVBar = null;
+ if (mBackground != null) {
+ mBackground.dispose();
+ mBackground = null;
}
super.dispose();
}
/**
+ * Returns the currently displayed target
+ *
+ * @return the current target, or null
+ */
+ public IAndroidTarget getCurrentTarget() {
+ return mCurrentTarget;
+ }
+
+ /**
+ * Returns the currently displayed theme (in palette modes that support previewing)
+ *
+ * @return the current theme, or null
+ */
+ public String getCurrentTheme() {
+ return mCurrentTheme;
+ }
+
+ /**
+ * Returns the currently displayed device (in palette modes that support previewing)
+ *
+ * @return the current device, or null
+ */
+ public String getCurrentDevice() {
+ return mCurrentDevice;
+ }
+
+ /**
* Loads or reloads the palette elements by using the layout and view descriptors from the
* given target data.
*
- * @param targetData The target data that contains the descriptors. If null or empty,
- * no groups will be created.
+ * @param target The target that has just been loaded
*/
- public void reloadPalette(AndroidTargetData targetData) {
-
+ public void reloadPalette(IAndroidTarget target) {
+ ConfigurationComposite configuration = mEditor.getConfigurationComposite();
+ String theme = configuration.getTheme();
+ String device = configuration.getDevice();
+ if (target == mCurrentTarget && mCurrentTheme != null && mCurrentTheme.equals(theme) &&
+ mCurrentDevice != null && mCurrentDevice.equals(device)) {
+ return;
+ }
+ mCurrentTheme = theme;
+ mCurrentTarget = target;
+ mCurrentDevice = device;
+ mPreviewIconFactory.reset();
+
+ // Erase old content and recreate new
for (Control c : getChildren()) {
c.dispose();
}
- GridLayoutBuilder.create(this).columns(1).columnsEqual().hSpacing(0).noMargins();
+ if (mPaletteMode.isPreview()) {
+ RGB background = mPreviewIconFactory.getBackgroundColor();
+ if (mBackground != null) {
+ mBackground.dispose();
+ }
+ mBackground = new Color(getDisplay(), background);
+ }
- mRoot = new Composite(this, SWT.NONE);
- GridLayoutBuilder.create(mRoot).columns(1).columnsEqual().spacing(0).noMargins();
- GridDataBuilder.create(mRoot).hGrab().hFill();
+ AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
+ List<Object> headers = Collections.emptyList();
+ final Map<String, List<ViewElementDescriptor>> categoryToItems;
if (targetData != null) {
- // TODO: Use metadata categories instead:
- //List<Pair<String,List<ViewElementDescriptor>>> paletteEntries =
- // ViewMetadataRepository.get().getPaletteEntries(targetData);
- //for (Pair<String,List<ViewElementDescriptor>> pair : paletteEntries) {
- // addGroup(mRoot, pair.getFirst(), pair.getSecond());
- //}
- addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors());
- addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());
+ categoryToItems = new HashMap<String, List<ViewElementDescriptor>>();
+ headers = new ArrayList<Object>();
+ List<Pair<String,List<ViewElementDescriptor>>> paletteEntries =
+ ViewMetadataRepository.get().getPaletteEntries(targetData,
+ mAlphabetical, mCategories);
+ for (Pair<String,List<ViewElementDescriptor>> pair : paletteEntries) {
+ String category = pair.getFirst();
+ List<ViewElementDescriptor> categoryItems = pair.getSecond();
+ headers.add(category);
+ categoryToItems.put(category, categoryItems);
+ }
+ } else {
+ categoryToItems = null;
}
- layout(true);
+ boolean wrap = mPaletteMode.getWrap();
- recomputeScrollbar();
- }
+ // Pack icon-only view vertically; others stretch to fill palette region
+ boolean fillVertical = mPaletteMode != PaletteMode.ICON_ONLY;
- // ----- private methods ----
-
- /** Returns true if scrollbar changed. */
- private boolean recomputeScrollbar() {
- if (mVBar != null && mRoot != null) {
-
- int sel = mVBar.getSelection();
- int max = mVBar.getMaximum();
- float current = max > 0 ? (float)sel / max : 0;
-
- int ry = mRoot.getSize().y;
-
- // The root contains composite groups
- // which in turn contain Toggle/Item CLabel instances
- Control[] children = mRoot.getChildren();
- findVisibleItem: for (int i = children.length - 1; i >= 0; i--) {
- Control ci = children[i];
- if (ci.isVisible() && ci instanceof Composite) {
- Control[] children2 = ((Composite) ci).getChildren();
- for (int j = children2.length - 1; j >= 0; j--) {
- Control cj = children2[j];
- if (cj.isVisible()) {
- // This is the bottom-most visible item
- ry = ci.getLocation().y + cj.getLocation().y + cj.getSize().y;
- break findVisibleItem;
- }
- }
+ mAccordion = new AccordionControl(this, SWT.NONE, headers, fillVertical, wrap) {
+ @Override
+ protected Composite createChildContainer(Composite parent) {
+ Composite composite = super.createChildContainer(parent);
+ if (mPaletteMode.isPreview()) {
+ composite.setBackground(mBackground);
}
+ addMenu(composite);
+ return composite;
}
-
-
- int vy = getSize().y;
- // Scrollable size is the height of the root view
- // less the current view visible height.
- int y = ry > vy ? ry - vy + 2 : 0;
- // Thumb size is the ratio between root view and visible height.
- float ft = ry > 0 ? (float)vy / ry : 1;
- int thumb = (int) Math.ceil(y * ft);
- if (thumb < 1) {
- thumb = 1;
- }
- y += thumb;
-
-
- if (y != max) {
- int minimum = mVBar.getMinimum();
- mVBar.setEnabled(y > 0);
- int maximum = y < 0 ? 1 : y;
- int selection = (int) (y * current);
- int increment = 20;
- int pageIncrement = thumb;
- mVBar.setValues(selection, minimum, maximum, thumb, increment, pageIncrement);
- scrollScrollbar();
- return true;
+ @Override
+ protected void createChildren(Composite parent, Object header) {
+ assert categoryToItems != null;
+ List<ViewElementDescriptor> list = categoryToItems.get(header);
+ for (ViewElementDescriptor desc : list) {
+ createItem(parent, desc);
+ }
}
+ };
+ addMenu(mAccordion);
+ for (CLabel headerLabel : mAccordion.getHeaderLabels()) {
+ addMenu(headerLabel);
}
+ setLayout(new FillLayout());
- return false;
- }
-
- private void scrollScrollbar() {
- if (mVBar != null && mRoot != null) {
- Point p = mRoot.getLocation();
- p.y = - mVBar.getSelection();
- mRoot.setLocation(p);
+ // Expand All for icon-only mode, but don't store it as the persistent auto-close mode;
+ // when we enter other modes it will read back whatever persistent mode.
+ if (mPaletteMode == PaletteMode.ICON_ONLY) {
+ mAccordion.expandAll(true);
+ mAccordion.setAutoClose(false);
+ } else {
+ mAccordion.setAutoClose(getSavedAutoCloseMode());
}
- }
-
- private void addGroup(Composite parent,
- String uiName,
- List<ViewElementDescriptor> descriptors) {
-
- Composite group = new Composite(parent, SWT.NONE);
- GridLayoutBuilder.create(group).columns(1).columnsEqual().spacing(0).noMargins();
- GridDataBuilder.create(group).hFill().hGrab();
-
- Toggle toggle = new Toggle(group, uiName);
- GridDataBuilder.create(toggle).hFill().hGrab();
-
- for (ViewElementDescriptor desc : descriptors) {
- // Exclude the <include> and <merge> tags from the View palette.
- // We don't have drop support for it right now, although someday we should.
- String xmlName = desc.getXmlName();
- if (VIEW_INCLUDE.equals(xmlName) || VIEW_MERGE.equals(xmlName)) {
- continue;
- }
-
- Item item = new Item(group, desc);
- toggle.addItem(item);
- GridDataBuilder.create(item).hFill().hGrab();
- }
+ layout(true);
}
/* package */ GraphicalEditorPart getEditor() {
return mEditor;
}
- /**
- * A Toggle widget is a row that is the head of a group.
- * <p/>
- * When clicked, the toggle will show/hide all the {@link Item} widgets that have been
- * added to it using {@link #addItem(Item)}.
- */
- private class Toggle extends CLabel implements MouseTrackListener, MouseListener {
- private boolean mMouseIn;
- private DragSource mSource;
- private ArrayList<Item> mItems = new ArrayList<Item>();
-
- public Toggle(Composite parent, String groupName) {
- super(parent, SWT.NONE);
- mMouseIn = false;
-
- setData(null);
-
- String s = String.format("-= %s =-", groupName);
- setText(s);
- setToolTipText(s);
- //TODO use triangle icon and swap it -- setImage(desc.getIcon());
- addMouseTrackListener(this);
- addMouseListener(this);
- }
-
- public void addItem(Item item) {
- mItems.add(item);
- }
-
- @Override
- public void dispose() {
- if (mSource != null) {
- mSource.dispose();
- mSource = null;
+ private Control createItem(Composite parent, ViewElementDescriptor desc) {
+ Control item = null;
+ switch (mPaletteMode) {
+ case SMALL_PREVIEW:
+ case TINY_PREVIEW:
+ case PREVIEW: {
+ ImageDescriptor descriptor = mPreviewIconFactory.getImageDescriptor(desc);
+ if (descriptor != null) {
+ Image image = descriptor.createImage();
+ ImageControl imageControl = new ImageControl(parent, SWT.None, image);
+ if (mPaletteMode.isScaledPreview()) {
+ // Try to preserve the overall size since rendering sizes typically
+ // vary with the dpi - so while the scaling factor for a 160 dpi
+ // rendering the scaling factor should be 0.5, for a 320 dpi one the
+ // scaling factor should be half that, 0.25.
+ float scale = 1.0f;
+ if (mPaletteMode == PaletteMode.SMALL_PREVIEW) {
+ scale = 0.75f;
+ } else if (mPaletteMode == PaletteMode.TINY_PREVIEW) {
+ scale = 0.5f;
+ }
+ int dpi = mEditor.getConfigurationComposite().getDensity().getDpiValue();
+ while (dpi > 160) {
+ scale = scale / 2;
+ dpi = dpi / 2;
+ }
+ imageControl.setScale(scale);
+ }
+ imageControl.setHoverColor(getDisplay().getSystemColor(SWT.COLOR_WHITE));
+ imageControl.setBackground(mBackground);
+ String toolTip = desc.getUiName();
+ // It appears pretty much none of the descriptors have tooltips
+ //String descToolTip = desc.getTooltip();
+ //if (descToolTip != null && descToolTip.length() > 0) {
+ // toolTip = toolTip + "\n" + descToolTip;
+ //}
+ imageControl.setToolTipText(toolTip);
+
+ item = imageControl;
+ } else {
+ // Just use an Icon+Text item for these for now
+ item = new IconTextItem(parent, desc);
+ }
+ break;
}
- super.dispose();
- }
-
- @Override
- public int getStyle() {
- int style = super.getStyle();
- if (mMouseIn) {
- style |= SWT.SHADOW_IN;
+ case ICON_TEXT: {
+ item = new IconTextItem(parent, desc);
+ break;
}
- return style;
- }
-
- // -- MouseTrackListener callbacks
-
- public void mouseEnter(MouseEvent e) {
- if (!mMouseIn) {
- mMouseIn = true;
- redraw();
+ case ICON_ONLY: {
+ item = new ImageControl(parent, SWT.None, desc.getIcon());
+ item.setToolTipText(desc.getUiName());
+ break;
}
+ default:
+ throw new IllegalArgumentException("Not yet implemented");
}
- public void mouseExit(MouseEvent e) {
- if (mMouseIn) {
- mMouseIn = false;
- redraw();
+ final DragSource source = new DragSource(item, DND.DROP_COPY);
+ source.setTransfer(new Transfer[] { SimpleXmlTransfer.getInstance() });
+ source.addDragListener(new DescDragSourceListener(desc));
+ item.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ source.dispose();
}
- }
-
- public void mouseHover(MouseEvent e) {
- // pass
- }
+ });
+ addMenu(item);
- // -- MouseListener callbacks
-
- public void mouseDoubleClick(MouseEvent arg0) {
- // pass
- }
-
- public void mouseDown(MouseEvent arg0) {
- // pass
- }
-
- public void mouseUp(MouseEvent arg0) {
- for (Item i : mItems) {
- if (i.isVisible()) {
- Object ld = i.getLayoutData();
- if (ld instanceof GridData) {
- GridData gd = (GridData) ld;
-
- i.setData(gd.heightHint != SWT.DEFAULT ?
- Integer.valueOf(gd.heightHint) :
- null);
- gd.heightHint = 0;
- }
- } else {
- Object ld = i.getLayoutData();
- if (ld instanceof GridData) {
- GridData gd = (GridData) ld;
-
- Object d = i.getData();
- if (d instanceof Integer) {
- gd.heightHint = ((Integer) d).intValue();
- } else {
- gd.heightHint = SWT.DEFAULT;
- }
- }
- }
- i.setVisible(!i.isVisible());
- }
-
- // Tell the root composite that its content changed.
- mRoot.layout(true /*changed*/);
- // Force the top composite to recompute the scrollbar and refresh it.
- mControlListener.controlResized(null /*event*/);
- }
+ return item;
}
/**
* An Item widget represents one {@link ElementDescriptor} that can be dropped on the
* GLE2 canvas using drag'n'drop.
*/
- private class Item extends CLabel implements MouseTrackListener {
+ private class IconTextItem extends CLabel implements MouseTrackListener {
private boolean mMouseIn;
- private DragSource mSource;
- private final ViewElementDescriptor mDesc;
- public Item(Composite parent, ViewElementDescriptor desc) {
+ public IconTextItem(Composite parent, ViewElementDescriptor desc) {
super(parent, SWT.NONE);
- mDesc = desc;
mMouseIn = false;
setText(desc.getUiName());
setImage(desc.getIcon());
setToolTipText(desc.getTooltip());
addMouseTrackListener(this);
-
- // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
- mSource = new DragSource(this, DND.DROP_COPY);
- mSource.setTransfer(new Transfer[] { SimpleXmlTransfer.getInstance() });
- mSource.addDragListener(new DescDragSourceListener(mDesc));
- }
-
- @Override
- public void dispose() {
- if (mSource != null) {
- mSource.dispose();
- mSource = null;
- }
- super.dispose();
}
@Override
// example the preview of an empty layout), so instead create a placeholder
// image
// Render the palette item itself as an image
- Control control = PaletteControl.this;
+ Control control = ((DragSource) event.widget).getControl();
GC gc = new GC(control);
Point size = control.getSize();
Display display = getDisplay();
int renderHeight = Math.min(screenBounds.height, MAX_RENDER_HEIGHT);
LayoutLog silentLogger = new LayoutLog();
session = editor.render(model, renderWidth, renderHeight,
- null /* explodeNodes */, hasTransparency, silentLogger);
+ null /* explodeNodes */, hasTransparency, silentLogger, RenderingMode.NORMAL);
} catch (Throwable t) {
// Previews can fail for a variety of reasons -- let's not bug
// the user with it
}
}
}
+
+ /** Action for switching view modes via radio buttons */
+ private class PaletteModeAction extends Action {
+ private final PaletteMode mMode;
+
+ PaletteModeAction(PaletteMode mode) {
+ super(mode.getActionLabel(), IAction.AS_RADIO_BUTTON);
+ mMode = mode;
+ boolean selected = mMode == mPaletteMode;
+ setChecked(selected);
+ setEnabled(!selected);
+ }
+
+ @Override
+ public void run() {
+ if (isEnabled()) {
+ mPaletteMode = mMode;
+ refreshPalette();
+ savePaletteMode();
+ }
+ }
+ }
+
+ /** Action for toggling various checkbox view modes - categories, sorting, etc */
+ private class ToggleViewOptionAction extends Action {
+ private final int mAction;
+ final static int TOGGLE_CATEGORY = 1;
+ final static int TOGGLE_ALPHABETICAL = 2;
+ final static int TOGGLE_AUTO_CLOSE = 3;
+
+ ToggleViewOptionAction(String title, int action, boolean checked) {
+ super(title, IAction.AS_CHECK_BOX);
+ mAction = action;
+ setChecked(checked);
+ }
+
+ @Override
+ public void run() {
+ switch (mAction) {
+ case TOGGLE_CATEGORY:
+ mCategories = !mCategories;
+ refreshPalette();
+ break;
+ case TOGGLE_ALPHABETICAL:
+ mAlphabetical = !mAlphabetical;
+ refreshPalette();
+ break;
+ case TOGGLE_AUTO_CLOSE:
+ mAutoClose = !mAutoClose;
+ mAccordion.setAutoClose(mAutoClose);
+ break;
+ }
+ savePaletteMode();
+ }
+ }
+
+ private void addMenu(Control control) {
+ control.addMenuDetectListener(new MenuDetectListener() {
+ public void menuDetected(MenuDetectEvent e) {
+ MenuManager manager = new MenuManager() {
+ @Override
+ public boolean isDynamic() {
+ return true;
+ }
+ };
+ for (PaletteMode mode : PaletteMode.values()) {
+ manager.add(new PaletteModeAction(mode));
+ }
+ manager.add(new Separator());
+ manager.add(new ToggleViewOptionAction("Show Categories",
+ ToggleViewOptionAction.TOGGLE_CATEGORY,
+ mCategories));
+ manager.add(new ToggleViewOptionAction("Sort Alphabetically",
+ ToggleViewOptionAction.TOGGLE_ALPHABETICAL,
+ mAlphabetical));
+ manager.add(new Separator());
+ manager.add(new ToggleViewOptionAction("Auto Close Previous",
+ ToggleViewOptionAction.TOGGLE_AUTO_CLOSE,
+ mAutoClose));
+ Menu menu = manager.createContextMenu(PaletteControl.this);
+ Point point = new Point(e.x, e.y);
+ menu.setLocation(point.x, point.y);
+ menu.setVisible(true);
+ }
+ });
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2011 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.AndroidConstants.DOT_PNG;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.ide.common.rendering.api.Params.RenderingMode;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.RGB;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Factory which can provide preview icons for android views of a particular SDK and
+ * editor's configuration chooser
+ */
+public class PreviewIconFactory {
+ private PaletteControl mPalette;
+ private RGB mBackground;
+ private File mImageDir;
+
+ private static final String PREVIEW_INFO_FILE = "preview.properties"; //$NON-NLS-1$
+
+ public PreviewIconFactory(PaletteControl palette) {
+ mPalette = palette;
+ }
+
+ /**
+ * Resets the state in the preview icon factory such that it will re-fetch information
+ * like the theme and SDK (the icons themselves are cached in a directory across IDE
+ * session though)
+ */
+ public void reset() {
+ mImageDir = null;
+ mBackground = null;
+ }
+
+ /**
+ * Returns an image descriptor for the given element descriptor, or null if no image
+ * could be computed. The rendering parameters (SDK, theme etc) correspond to those
+ * stored in the associated palette.
+ *
+ * @param desc the element descriptor to get an image for
+ * @return an image descriptor, or null if no image could be rendered
+ */
+ public ImageDescriptor getImageDescriptor(ElementDescriptor desc) {
+ File imageDir = getImageDir(false);
+ if (!imageDir.exists()) {
+ render();
+ }
+ File file = new File(imageDir, getFileName(desc));
+ if (file.exists()) {
+ try {
+ return ImageDescriptor.createFromURL(file.toURI().toURL());
+ } catch (MalformedURLException e) {
+ AdtPlugin.log(e, "Could not create image descriptor for %s", file);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Partition the elements in the document according to their rendering preferences;
+ * elements that should be skipped are removed, elements that should be rendered alone
+ * are placed in their own list, etc
+ *
+ * @param document the document containing render fragments for the various elements
+ * @return
+ */
+ private List<List<Element>> partitionRenderElements(Document document) {
+ List<List<Element>> elements = new ArrayList<List<Element>>();
+
+ List<Element> shared = new ArrayList<Element>();
+ Element root = document.getDocumentElement();
+ elements.add(shared);
+
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+
+ NodeList children = root.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ String fqn = repository.getFullClassName(element);
+ assert fqn.length() > 0 : element.getNodeName();
+ RenderMode renderMode = repository.getRenderMode(fqn);
+ if (renderMode == RenderMode.ALONE) {
+ elements.add(Collections.singletonList(element));
+ } else if (renderMode == RenderMode.NORMAL) {
+ shared.add(element);
+ } else {
+ assert renderMode == RenderMode.SKIP;
+ }
+ }
+ }
+
+ return elements;
+ }
+
+ /**
+ * Renders ALL the widgets and then extracts image data for each view and saves it
+ * on disk
+ */
+ private boolean render() {
+ LayoutEditor layoutEditor = mPalette.getEditor().getLayoutEditor();
+
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+ Document document = repository.getRenderingConfigDoc();
+
+ if (document == null) {
+ return false;
+ }
+
+ // Construct UI model from XML
+ AndroidTargetData data = layoutEditor.getTargetData();
+ DocumentDescriptor documentDescriptor;
+ if (data == null) {
+ documentDescriptor = new DocumentDescriptor("temp", null/*children*/);//$NON-NLS-1$
+ } else {
+ documentDescriptor = data.getLayoutDescriptors().getDescriptor();
+ }
+ UiDocumentNode model = (UiDocumentNode) documentDescriptor.createUiNode();
+ model.setEditor(layoutEditor);
+ GraphicalEditorPart editor = mPalette.getEditor();
+ model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider());
+
+ Element documentElement = document.getDocumentElement();
+ List<List<Element>> elements = partitionRenderElements(document);
+ for (List<Element> elementGroup : elements) {
+ // Replace the document elements with the current element group
+ while (documentElement.getFirstChild() != null) {
+ documentElement.removeChild(documentElement.getFirstChild());
+ }
+ for (Element element : elementGroup) {
+ documentElement.appendChild(element);
+ }
+
+ model.loadFromXmlNode(document);
+
+ RenderSession session = null;
+ try {
+ LayoutLog logger = new RenderLogger("palette");
+ // Important to get these sizes large enough for clients that don't support
+ // RenderMode.FULL_EXPAND such as 1.6
+ int width = 200;
+ int height = 2000;
+ Set<UiElementNode> expandNodes = Collections.<UiElementNode>emptySet();
+ RenderingMode renderingMode = RenderingMode.FULL_EXPAND;
+ session = editor.render(model, width, height, expandNodes,
+ false /*hasTransparency*/, logger,
+ renderingMode);
+
+ } catch (Throwable t) {
+ // If there are internal errors previewing the components just revert to plain
+ // icons and labels
+ continue;
+ }
+
+ if (session != null) {
+ if (session.getResult().isSuccess()) {
+ BufferedImage image = session.getImage();
+ if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
+ File imageDir = getImageDir(true);
+
+ // TODO - use resource resolution instead?
+ if (mBackground == null) {
+ int rgb = image.getRGB(image.getWidth()-1, image.getHeight()-1);
+ RGB color = new RGB((rgb & 0xFF0000) >> 16, (rgb & 0xFF00) >> 8,
+ rgb & 0xFF);
+ storeBackground(imageDir, color);
+ }
+
+ List<ViewInfo> viewInfoList = session.getRootViews();
+ if (viewInfoList != null && viewInfoList.size() > 0) {
+ // We don't render previews under a <merge> so there should
+ // only be one root.
+ ViewInfo firstRoot = viewInfoList.get(0);
+ List<ViewInfo> infos = firstRoot.getChildren();
+ for (ViewInfo info : infos) {
+ Object cookie = info.getCookie();
+ if (!(cookie instanceof UiElementNode)) {
+ continue;
+ }
+ UiElementNode node = (UiElementNode) cookie;
+ String fileName = getFileName(node);
+ File file = new File(imageDir, fileName);
+ if (file.exists()) {
+ // On Windows, perhaps we need to rename instead?
+ file.delete();
+ }
+ int x1 = info.getLeft();
+ int y1 = info.getTop();
+ int x2 = info.getRight();
+ int y2 = info.getBottom();
+ if (x1 != x2 && y1 != y2) {
+ savePreview(file, image, x1, y1, x2, y2);
+ }
+ }
+ }
+ }
+ }
+
+ session.dispose();
+ }
+ }
+
+ return true;
+ }
+
+ private String getFileName(ElementDescriptor descriptor) {
+ return descriptor.getUiName() + DOT_PNG;
+ }
+
+ private String getFileName(UiElementNode node) {
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+ String fqn = repository.getFullClassName((Element) node.getXmlNode());
+ return fqn.substring(fqn.lastIndexOf('.') + 1) + DOT_PNG;
+ }
+
+ /**
+ * Cleans up a name by removing punctuation and whitespace etc to make
+ * it a better filename
+ * @param name
+ * @return
+ */
+ private static String cleanup(String name) {
+ // Extract just the characters (no whitespace, parentheses, punctuation etc)
+ // to ensure that the filename is pretty portable
+ StringBuilder sb = new StringBuilder(name.length());
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (Character.isJavaIdentifierPart(c)) {
+ sb.append(Character.toLowerCase(c));
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /** Returns the location of a directory containing image previews (which may not exist) */
+ private File getImageDir(boolean create) {
+ if (mImageDir == null) {
+ // Location for plugin-related state data
+ IPath pluginState = AdtPlugin.getDefault().getStateLocation();
+
+ // We have multiple directories - one for each combination of SDK, theme and device
+ // (and later, possibly other qualifiers).
+ // These are created -lazily-.
+ String targetName = mPalette.getCurrentTarget().getFullName();
+ String androidTargetNamePrefix = "Android ";
+ String themeNamePrefix = "Theme.";
+ if (targetName.startsWith(androidTargetNamePrefix)) {
+ targetName = targetName.substring(androidTargetNamePrefix.length());
+ }
+ String themeName = mPalette.getCurrentTheme();
+ if (themeName == null) {
+ themeName = "Theme"; //$NON-NLS-1$
+ }
+ if (themeName.startsWith(themeNamePrefix)) {
+ themeName = themeName.substring(themeNamePrefix.length());
+ }
+ String dirName = String.format("palette-%s-%s-%s",
+ cleanup(targetName), cleanup(themeName), cleanup(mPalette.getCurrentDevice()));
+ IPath dirPath = pluginState.append(dirName);
+
+ mImageDir = new File(dirPath.toOSString());
+ }
+
+ if (create && !mImageDir.exists()) {
+ mImageDir.mkdirs();
+ }
+
+ return mImageDir;
+ }
+
+ private void savePreview(File output, BufferedImage image,
+ int left, int top, int right, int bottom) {
+ try {
+ BufferedImage im = ImageUtils.subImage(image, left, top, right, bottom);
+ ImageIO.write(im, "PNG", output); //$NON-NLS-1$
+ } catch (IOException e) {
+ AdtPlugin.log(e, "Failed writing palette file");
+ }
+ }
+
+ private void storeBackground(File imageDir, RGB color) {
+ mBackground = color;
+ File file = new File(imageDir, PREVIEW_INFO_FILE);
+ String colors = String.format("background=%02x,%02x,%02x",
+ color.red, color.green, color.blue);
+ AdtPlugin.writeFile(file, colors);
+ }
+
+ public RGB getBackgroundColor() {
+ if (mBackground == null) {
+ mBackground = null;
+
+ File imageDir = getImageDir(false);
+ if (!imageDir.exists()) {
+ render();
+ }
+
+ File file = new File(imageDir, PREVIEW_INFO_FILE);
+ if (file.exists()) {
+ Properties properties = new Properties();
+ Reader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ properties.load(reader);
+ } catch (IOException e) {
+ AdtPlugin.log(e, "Can't read preview properties");
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // Nothing useful can be done.
+ }
+ }
+ }
+
+ String colorString = (String) properties.get("background"); //$NON-NLS-1$
+ if (colorString != null) {
+ String[] colors = colorString.split(","); //$NON-NLS-1$
+ if (colors.length == 3) {
+ colorString = colorString.trim();
+ int red = Integer.parseInt(colors[0], 16);
+ int green = Integer.parseInt(colors[1], 16);
+ int blue = Integer.parseInt(colors[2], 16);
+ mBackground = new RGB(red, green, blue);
+ }
+ }
+ }
+
+ if (mBackground == null) {
+ mBackground = new RGB(0,0,0);
+ }
+ }
+
+ return mBackground;
+ }
+}
protected abstract void addMenuItems(Menu menu);
public void menuShown(MenuEvent e) {
+ // TODO: Replace this stuff with manager.setRemoveAllWhenShown(true);
MenuItem[] menuItems = mMenu.getItems();
for (int i = 0; i < menuItems.length; i++) {
menuItems[i].dispose();
package com.android.ide.eclipse.adt.internal.editors.layout.gre;
import static com.android.ide.common.api.IViewMetadata.FillPreference.NONE;
-import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
-import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
import com.android.ide.common.api.IViewMetadata.FillPreference;
import com.android.ide.eclipse.adt.AdtPlugin;
* classes
*/
public class ViewMetadataRepository {
+ private static final String PREVIEW_CONFIG_FILENAME = "rendering-configs.xml"; //$NON-NLS-1$
+ private static final String METADATA_FILENAME = "extra-view-metadata.xml"; //$NON-NLS-1$
+
/** Singleton instance */
private static ViewMetadataRepository sInstance = new ViewMetadataRepository();
return mClassToView;
}
+ /**
+ * Returns an XML document containing rendering configurations for the various Android
+ * views. The FQN of each view can be obtained via the
+ * {@link #getFullClassName(Element)} method
+ *
+ * @return an XML document containing rendering elements
+ */
+ public Document getRenderingConfigDoc() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
+ InputStream paletteStream = clz.getResourceAsStream(PREVIEW_CONFIG_FILENAME);
+ InputSource is = new InputSource(paletteStream);
+ try {
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringElementContentWhitespace(true);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(is);
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Parsing palette file failed");
+ return null;
+ }
+ }
+
+ /**
+ * Returns a fully qualified class name for an element in the rendering document
+ * returned by {@link #getRenderingConfigDoc()}
+ *
+ * @param element the element to look up the fqcn for
+ * @return the fqcn of the view the element represents a preview for
+ */
+ public String getFullClassName(Element element) {
+ // We don't use the element tag name, because in some cases we have
+ // an outer element to render some interesting inner element, such as a tab widget
+ // (which must be rendered inside a tab host).
+ //
+ // Therefore, we instead use the convention that the id is the fully qualified
+ // class name, with .'s replaced with _'s.
+
+
+ // Special case: for tab host we aren't allowed to mess with the id
+
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+
+ if ("@android:id/tabhost".equals(id)) {
+ // Special case to distinguish TabHost and TabWidget
+ NodeList children = element.getChildNodes();
+ if (children.getLength() > 1 && (children.item(1) instanceof Element)) {
+ Element child = (Element) children.item(1);
+ String childId = child.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if ("@+id/android_widget_TabWidget".equals(childId)) {
+ return "android.widget.TabWidget"; // TODO: Tab widget!
+ }
+ }
+ return "android.widget.TabHost"; // TODO: Tab widget!
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ if (id.startsWith(NEW_ID_PREFIX)) {
+ i = NEW_ID_PREFIX.length();
+ } else if (id.startsWith(ID_PREFIX)) {
+ i = ID_PREFIX.length();
+ }
+
+ for (; i < id.length(); i++) {
+ char c = id.charAt(i);
+ if (c == '_') {
+ sb.append('.');
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
/** Returns an ordered list of categories and views, parsed from a metadata file */
private List<CategoryData> getCategories() {
if (mCategories == null) {
mCategories = new ArrayList<CategoryData>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- String fileName = "extra-view-metadata.xml"; //$NON-NLS-1$
- InputStream inputStream = ViewMetadataRepository.class.getResourceAsStream(fileName);
+ Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
+ InputStream inputStream = clz.getResourceAsStream(METADATA_FILENAME);
InputSource is = new InputSource(new BufferedInputStream(inputStream));
try {
factory.setNamespaceAware(true);
factory.setValidating(false);
+ factory.setIgnoringElementContentWhitespace(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(is);
Map<String, FillPreference> fillTypes = new HashMap<String, FillPreference>();
if (fillPreference == null) {
fillPreference = NONE;
}
- ViewData view = new ViewData(category, fqcn, fillPreference);
+ String skip = child.getAttribute("skip"); //$NON-NLS-1$
+ RenderMode mode = RenderMode.NORMAL;
+ String render = child.getAttribute("render"); //$NON-NLS-1$
+ if (render.length() > 0) {
+ mode = RenderMode.get(render);
+ }
+ ViewData view = new ViewData(category, fqcn, fillPreference,
+ skip.length() == 0 ? false : Boolean.valueOf(skip),
+ mode);
category.addView(view);
}
}
* available node descriptors, categorizing and sorting them.
*
* @param targetData the target data for which to compute palette entries
+ * @param alphabetical if true, sort all items in alphabetical order
+ * @param createCategories if true, organize the items into categories
* @return a list of pairs where each pair contains of the category label and an
* ordered list of elements to be included in that category
*/
public List<Pair<String, List<ViewElementDescriptor>>> getPaletteEntries(
- AndroidTargetData targetData) {
+ AndroidTargetData targetData, boolean alphabetical, boolean createCategories) {
List<Pair<String, List<ViewElementDescriptor>>> result =
new ArrayList<Pair<String, List<ViewElementDescriptor>>>();
for (List<ViewElementDescriptor> list : lists) {
for (ViewElementDescriptor view : list) {
- String name = view.getXmlLocalName();
-
- // Exclude the <include> and <merge> tags from the View palette.
- // We don't have drop support for it right now, although someday we should.
- if (VIEW_INCLUDE.equals(name) || VIEW_MERGE.equals(name)) {
- continue;
- }
-
ViewData viewData = getClassToView().get(view.getFullClassName());
CategoryData category = other;
if (viewData != null) {
+ if (viewData.getSkip()) {
+ continue;
+ }
category = viewData.getCategory();
}
}
}
+ if (!createCategories) {
+ // Squash all categories into a single one, "Views"
+ Map<CategoryData, List<ViewElementDescriptor>> singleCategory =
+ new HashMap<CategoryData, List<ViewElementDescriptor>>();
+ List<ViewElementDescriptor> items = new ArrayList<ViewElementDescriptor>(100);
+ for (Map.Entry<CategoryData, List<ViewElementDescriptor>> entry : categories.entrySet()) {
+ items.addAll(entry.getValue());
+ }
+ singleCategory.put(new CategoryData("Views"), items);
+ categories = singleCategory;
+ }
+
for (Map.Entry<CategoryData, List<ViewElementDescriptor>> entry : categories.entrySet()) {
String name = entry.getKey().getName();
List<ViewElementDescriptor> items = entry.getValue();
}
// Natural sort of the descriptors
- Comparator<ViewElementDescriptor> comparator = new Comparator<ViewElementDescriptor>() {
- public int compare(ViewElementDescriptor v1, ViewElementDescriptor v2) {
- String fqcn1 = v1.getFullClassName();
- String fqcn2 = v2.getFullClassName();
- if (fqcn1 == null) {
- // <view> and <merge> tags etc
- fqcn1 = v1.getUiName();
- }
- if (fqcn2 == null) {
- fqcn2 = v2.getUiName();
- }
- ViewData d1 = viewMap.get(fqcn1);
- ViewData d2 = viewMap.get(fqcn2);
-
- // Use natural sorting order of the view data
- // Sort unknown views to the end (and alphabetically among themselves)
- if (d1 != null) {
- if (d2 != null) {
- return d1.getOrdinal() - d2.getOrdinal();
- } else {
- return 1;
+ if (alphabetical) {
+ Collections.sort(items);
+ } else {
+ Collections.sort(items, new Comparator<ViewElementDescriptor>() {
+ public int compare(ViewElementDescriptor v1, ViewElementDescriptor v2) {
+ String fqcn1 = v1.getFullClassName();
+ String fqcn2 = v2.getFullClassName();
+ if (fqcn1 == null) {
+ // <view> and <merge> tags etc
+ fqcn1 = v1.getUiName();
+ }
+ if (fqcn2 == null) {
+ fqcn2 = v2.getUiName();
}
- } else {
- if (d2 == null) {
- return v1.getUiName().compareTo(v2.getUiName());
+ ViewData d1 = viewMap.get(fqcn1);
+ ViewData d2 = viewMap.get(fqcn2);
+
+ // Use natural sorting order of the view data
+ // Sort unknown views to the end (and alphabetically among themselves)
+ if (d1 != null) {
+ if (d2 != null) {
+ return d1.getOrdinal() - d2.getOrdinal();
+ } else {
+ return 1;
+ }
} else {
- return -1;
+ if (d2 == null) {
+ return v1.getUiName().compareTo(v2.getUiName());
+ } else {
+ return -1;
+ }
}
}
- }
- };
- Collections.sort(items, comparator);
+ });
+ }
+
+
result.add(Pair.of(name, items));
}
private final FillPreference mFillPreference;
/** The category that the view belongs to */
private final CategoryData mCategory;
+ /** Skip this item in the palette? */
+ private final boolean mSkip;
+ /** Must this item be rendered alone? skipped? etc */
+ private final RenderMode mRenderMode;
/** The relative rank of the view for natural ordering */
private final int mOrdinal = sNextOrdinal++;
/** Constructs a new view data for the given class */
- private ViewData(CategoryData category, String fqcn, FillPreference fillPreference) {
+ private ViewData(CategoryData category, String fqcn,
+ FillPreference fillPreference, boolean skip, RenderMode renderMode) {
super();
mCategory = category;
mFqcn = fqcn;
mFillPreference = fillPreference;
+ mSkip = skip;
+ mRenderMode = renderMode;
}
/** Returns the category for views of this type */
public int compareTo(ViewData other) {
return mOrdinal - other.mOrdinal;
}
+
+ public RenderMode getRenderMode() {
+ return mRenderMode;
+ }
+
+ public boolean getSkip() {
+ return mSkip;
+ }
}
/**
return FillPreference.NONE;
}
+
+ /**
+ * Returns the {@link RenderMode} for classes with the given fully qualified class
+ * name
+ *
+ * @param fqcn the fully qualified class name
+ * @return the {@link RenderMode} to use for previews of the given view type
+ */
+ public RenderMode getRenderMode(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getRenderMode();
+ }
+
+ return RenderMode.ALONE;
+ }
+
+ /**
+ * Returns true if classes with the given fully qualified class name should be hidden
+ * or skipped from the palette
+ *
+ * @param fqcn the fully qualified class name
+ * @return true if views of the given type should be hidden from the palette
+ */
+ public boolean getSkip(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getSkip();
+ }
+
+ return false;
+ }
+
+ /** Render mode for palette preview */
+ public enum RenderMode {
+ /**
+ * Render previews, and it can be rendered as a sibling of many other views in a
+ * big linear layout
+ */
+ NORMAL,
+ /** This view needs to be rendered alone */
+ ALONE,
+ /**
+ * Skip this element; it doesn't work or does not produce any visible artifacts
+ * (such as the basic layouts)
+ */
+ SKIP;
+
+ public static RenderMode get(String render) {
+ if ("alone".equals(render)) {
+ return ALONE;
+ } else if ("skip".equals(render)) {
+ return SKIP;
+ } else {
+ return NORMAL;
+ }
+ }
+ }
}
<!ATTLIST view
class CDATA #REQUIRED
previewXml CDATA #IMPLIED
+ skip (true|false) "false"
+ render (alone|skip|normal) "normal"
fill ( none|both|width|height|opposite|
width_in_vertical|height_in_horizontal) "none"
>
<view class="android.widget.RatingBar" />
<view class="android.widget.SeekBar" fill="width_in_vertical" />
</category>
+ <category name="Layouts">
+ <view class="android.widget.LinearLayout" fill="opposite" render="skip"/>
+ <view class="android.widget.RelativeLayout" fill="opposite" render="skip"/>
+ <view class="android.widget.FrameLayout" fill="opposite" render="skip"/>
+ <view class="android.widget.AbsoluteLayout" fill="opposite" render="skip"/>
+ <view class="android.widget.TableLayout" fill="opposite" render="skip"/>
+ <view class="android.widget.TableRow" fill="opposite" render="skip"/>
+ </category>
<category name="Composite">
- <view class="android.widget.ListView" fill="width_in_vertical" />
- <view class="android.widget.ExpandableListView" fill="width_in_vertical" />
- <view class="android.widget.TwoLineListItem" />
- <view class="android.widget.GridView" fill="opposite"/>
- <view class="android.widget.ScrollView" fill="opposite"/>
- <view class="android.widget.HorizontalScrollView" />
- <view class="android.widget.SearchView" />
- <view class="android.widget.SlidingDrawer" />
- <view class="android.widget.TabHost" fill="width_in_vertical" />
- <view class="android.widget.TabWidget" />
- <view class="android.webkit.WebView" fill="opposite"/>
+ <view class="android.widget.ListView" fill="width_in_vertical" render="skip"/>
+ <view class="android.widget.ExpandableListView" fill="width_in_vertical" render="skip"/>
+ <view class="android.widget.TwoLineListItem" render="skip"/>
+ <view class="android.widget.GridView" fill="opposite" render="skip"/>
+ <view class="android.widget.ScrollView" fill="opposite" render="skip"/>
+ <view class="android.widget.HorizontalScrollView" render="skip"/>
+ <view class="android.widget.SearchView" render="skip"/>
+ <view class="android.widget.SlidingDrawer"/>
+ <view class="android.widget.TabHost" fill="width_in_vertical" render="alone" />
+ <view class="android.widget.TabWidget" render="alone" />
+ <view class="android.webkit.WebView" fill="opposite" render="skip" />
</category>
<category name="Images & Media">
<view class="android.widget.ImageView" />
<view class="android.widget.ImageButton" />
- <view class="android.widget.Gallery" fill="width_in_vertical" />
- <view class="android.widget.MediaController" />
- <view class="android.widget.VideoView" fill="opposite" />
- </category>
- <category name="Layouts">
- <view class="android.widget.LinearLayout" fill="opposite"/>
- <view class="android.widget.RelativeLayout" fill="opposite"/>
- <view class="android.widget.FrameLayout" fill="opposite"/>
- <view class="android.widget.AbsoluteLayout" fill="opposite"/>
- <view class="android.widget.TableLayout" fill="opposite"/>
- <view class="android.widget.TableRow" fill="opposite"/>
+ <view class="android.widget.Gallery" fill="width_in_vertical" render="skip"/>
+ <view class="android.widget.MediaController" render="skip"/>
+ <view class="android.widget.VideoView" fill="opposite" render="skip"/>
</category>
<category name="Time & Date">
- <view class="android.widget.TimePicker" />
- <view class="android.widget.DatePicker" />
+ <view class="android.widget.TimePicker" render="alone"/>
+ <view class="android.widget.DatePicker" render="alone"/>
<view class="android.widget.CalendarView" />
<view class="android.widget.Chronometer" />
<view class="android.widget.AnalogClock" />
<view class="android.widget.DigitalClock" />
</category>
<category name="Transitions">
- <view class="android.widget.ImageSwitcher" />
- <view class="android.widget.AdapterViewFlipper" fill="opposite"/>
- <view class="android.widget.StackView" fill="opposite"/>
- <view class="android.widget.TextSwitcher" fill="opposite"/>
- <view class="android.widget.ViewAnimator" fill="opposite"/>
- <view class="android.widget.ViewFlipper" fill="opposite"/>
- <view class="android.widget.ViewSwitcher" fill="opposite"/>
+ <view class="android.widget.ImageSwitcher" render="skip"/>
+ <view class="android.widget.AdapterViewFlipper" fill="opposite" render="skip"/>
+ <view class="android.widget.StackView" fill="opposite" render="skip"/>
+ <view class="android.widget.TextSwitcher" fill="opposite" render="skip"/>
+ <view class="android.widget.ViewAnimator" fill="opposite" render="skip"/>
+ <view class="android.widget.ViewFlipper" fill="opposite" render="skip"/>
+ <view class="android.widget.ViewSwitcher" fill="opposite" render="skip"/>
</category>
<category name="Advanced">
- <view class="android.view.View" />
- <view class="android.view.ViewStub" />
- <view class="android.gesture.GestureOverlayView" />
- <view class="android.view.SurfaceView" />
- <view class="android.widget.NumberPicker" />
- <view class="android.widget.ZoomButton" />
- <view class="android.widget.ZoomControls" />
- <view class="android.widget.DialerFilter" fill="width_in_vertical" />
+ <view class="android.view.View" render="skip"/>
+ <view class="android.view.ViewStub" render="skip"/>
+ <view class="android.gesture.GestureOverlayView" render="skip"/>
+ <view class="android.view.SurfaceView" render="skip"/>
+ <view class="android.widget.NumberPicker" render="alone"/>
+ <view class="android.widget.ZoomButton"/>
+ <view class="android.widget.ZoomControls"/>
+ <view class="include" skip="true" render="skip"/>
+ <view class="merge" skip="true" render="skip"/>
+ <view class="android.widget.DialerFilter" fill="width_in_vertical" render="skip"/>
</category>
<category name="Other">
<!-- This is the catch-all category which contains unknown views if we encounter any -->
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Default configuration for various views to be rendered
+ TODO: Remove views that don't have custom configuration
+ TODO: Parameterize the custom width (200dip) in the below?
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <AnalogClock
+ android:layout_width="wrap_content"
+ android:id="@+id/android_widget_AnalogClock"
+ android:layout_height="75dip">
+ </AnalogClock>
+ <AutoCompleteTextView
+ android:layout_height="wrap_content"
+ android:layout_width="200dip"
+ android:text="AutoComplete"
+ android:id="@+id/android_widget_AutoCompleteTextView">
+ </AutoCompleteTextView>
+ <Button
+ android:text="Button"
+ android:id="@+id/android_widget_Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </Button>
+ <CheckBox
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="CheckBox"
+ android:id="@+id/android_widget_CheckBox"
+ android:checked="true">
+ </CheckBox>
+ <CheckedTextView
+ android:text="CheckedTextView"
+ android:id="@+id/android_widget_CheckedTextView"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content">
+ </CheckedTextView>
+ <Chronometer
+ android:text="Chronometer"
+ android:id="@+id/android_widget_Chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </Chronometer>
+ <DigitalClock
+ android:text="DigitalClock"
+ android:id="@+id/android_widget_DigitalClock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </DigitalClock>
+ <EditText
+ android:id="@+id/android_widget_EditText"
+ android:layout_height="wrap_content"
+ android:text="EditText"
+ android:layout_width="200dip">
+ </EditText>
+ <ImageButton
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/android_widget_ImageButton"
+ android:src="@android:drawable/ic_menu_gallery">
+ </ImageButton>
+ <ImageView
+ android:id="@+id/android_widget_ImageView"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@android:drawable/ic_menu_gallery">
+ </ImageView>
+ <MultiAutoCompleteTextView
+ android:layout_height="wrap_content"
+ android:layout_width="200dip"
+ android:text="MultiAutoComplete"
+ android:id="@+id/android_widget_MultiAutoCompleteTextView">
+ </MultiAutoCompleteTextView>
+ <ProgressBar
+ android:id="@+id/android_widget_ProgressBar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </ProgressBar>
+ <QuickContactBadge
+ android:src="@android:drawable/ic_dialog_email"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/android_widget_QuickContactBadge">
+ </QuickContactBadge>
+ <RadioButton
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/android_widget_RadioButton"
+ android:text="RadioButton"
+ android:checked="true">
+ </RadioButton>
+ <RatingBar
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/android_widget_RatingBar"
+ android:rating="1">
+ </RatingBar>
+ <SeekBar
+ android:layout_height="wrap_content"
+ android:id="@+id/android_widget_SeekBar"
+ android:layout_width="200dip"
+ android:progress="30">
+ </SeekBar>
+ <Spinner
+ android:layout_height="wrap_content"
+ android:id="@+id/android_widget_Spinner"
+ android:layout_width="200dip">
+ </Spinner>
+ <TextView
+ android:text="TextView"
+ android:id="@+id/android_widget_TextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </TextView>
+ <ToggleButton
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:checked="false"
+ android:id="@+id/android_widget_ToggleButton"
+ android:text="ToggleButton">
+ </ToggleButton>
+ <ZoomButton
+ android:id="@+id/android_widget_ZoomButton"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@android:drawable/btn_plus">
+ </ZoomButton>
+ <ZoomControls
+ android:id="@+id/android_widget_ZoomControls"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </ZoomControls>
+ <TimePicker
+ android:id="@+id/android_widget_TimePicker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </TimePicker>
+ <DatePicker
+ android:id="@+id/android_widget_DatePicker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </DatePicker>
+ <RadioGroup
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/android_widget_RadioGroup">
+ <RadioButton
+ android:checked="true">
+ </RadioButton>
+ <RadioButton></RadioButton>
+ <RadioButton></RadioButton>
+ </RadioGroup>
+ <TabHost
+ android:id="@android:id/tabhost"
+ android:layout_width="200dip"
+ android:layout_height="100dip">
+ <LinearLayout
+ android:id="@+id/linearLayout1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TabWidget
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@android:id/tabs">
+ </TabWidget>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@android:id/tabcontent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/Tab1">
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/Tab2">
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/Tab3">
+ </LinearLayout>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+ <TabHost
+ android:id="@android:id/tabhost"
+ android:layout_width="70dip"
+ android:layout_height="100dip">
+ <LinearLayout
+ android:id="@+id/android_widget_TabWidget"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TabWidget
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@android:id/tabs">
+ </TabWidget>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@android:id/tabcontent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/Tab1">
+ </LinearLayout>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+</LinearLayout>
\ No newline at end of file
public DecorComposite(Composite parent, int style) {
super(parent, style);
- GridLayoutBuilder.create(this).noMargins().columns(2);
+ GridLayoutBuilder.create(this).noMargins().columns(2).vSpacing(1);
mTitle = new CLabel(this, SWT.NONE);
GridDataBuilder.create(mTitle).hGrab().hFill().vCenter();
public final static String PREFS_FORMAT_XML = AdtPlugin.PLUGIN_ID + ".formatXml"; //$NON-NLS-1$
+ public final static String PREFS_PALETTE_MODE = AdtPlugin.PLUGIN_ID + ".palette"; //$NON-NLS-1$
+
/** singleton instance */
private final static AdtPrefs sThis = new AdtPrefs();
private boolean mBuildForceErrorOnNativeLibInJar = true;
private boolean mFormatXml = false;
private float mMonitorDensity = 0.f;
+ private String mPalette;
public static enum BuildVerbosity {
/** Build verbosity "Always". Those messages are always displayed, even in silent mode */
if (property == null || PREFS_FORMAT_XML.equals(property)) {
mFormatXml = mStore.getBoolean(PREFS_FORMAT_XML);
}
+
+ if (property == null || PREFS_PALETTE_MODE.equals(property)) {
+ mPalette = mStore.getString(PREFS_PALETTE_MODE);
+ }
}
/**
return mBuildForceErrorOnNativeLibInJar;
}
+ public String getPaletteModes() {
+ return mPalette;
+ }
+
+ public void setPaletteModes(String palette) {
+ mPalette = palette;
+
+ // need to save this new value to the store
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ store.setValue(PREFS_PALETTE_MODE, palette);
+ }
+
public float getMonitorDensity() {
return mMonitorDensity;
}
*/
package com.android.ide.eclipse.adt;
+import java.io.File;
import java.io.StringReader;
import junit.framework.TestCase;
assertEquals(input, contents);
}
+ public void testReadWriteFile() throws Exception {
+ File temp = File.createTempFile("test", ".txt");
+ String myContent = "this is\na test";
+ AdtPlugin.writeFile(temp, myContent);
+ String readBack = AdtPlugin.readFile(temp);
+ assertEquals(myContent, readBack);
+ }
}
return dest;
}
+
+ public void testSubImage() throws Exception {
+ BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE);
+ Graphics g = image.getGraphics();
+ g.setColor(new Color(0xFF00FF00, true));
+ g.fillRect(0, 0, image.getWidth(), image.getHeight());
+ g.setColor(new Color(0xFFFF0000, true));
+ g.fillRect(25, 25, 50, 50);
+ g.dispose();
+
+ BufferedImage sub = ImageUtils.subImage(image, 25, 25, 35, 45);
+ assertEquals(10, sub.getWidth());
+ assertEquals(20, sub.getHeight());
+ assertEquals(0xFFFF0000, sub.getRGB(0, 0));
+ assertEquals(0xFFFF0000, sub.getRGB(9, 9));
+
+ sub = ImageUtils.subImage(image, 23, 23, 5, 5);
+ assertEquals(5, sub.getWidth());
+ assertEquals(5, sub.getHeight());
+ assertEquals(0xFF00FF00, sub.getRGB(0, 0));
+ assertEquals(0xFFFF0000, sub.getRGB(9, 9));
+ }
+
+
}